Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ cmd/tapd/tapd
/itest/itest.test

/docs/examples/basic-price-oracle/basic-price-oracle
/docs/examples/basic-price-oracle/basic-price-oracle-example.log

# Load test binaries and config
/loadtest
Expand Down
2 changes: 2 additions & 0 deletions docs/examples/basic-price-oracle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ func (p *RpcPriceOracleServer) QueryAssetRates(_ context.Context,
Error: &oraclerpc.QueryAssetRatesErrResponse{
Message: "unsupported payment asset, " +
"only BTC is supported",
Code: 1,
},
},
}, nil
Expand All @@ -326,6 +327,7 @@ func (p *RpcPriceOracleServer) QueryAssetRates(_ context.Context,
Result: &oraclerpc.QueryAssetRatesResponse_Error{
Error: &oraclerpc.QueryAssetRatesErrResponse{
Message: "unsupported subject asset",
Code: 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there an enum val from our lib that we can use here instead of 1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a raw '1' at present to avoid updating the mock oracle's dependencies (it looks like there have been other RPC changes since it was last updated as well). Perhaps best to avoid changing the mock oracle for now, and just update it to use the latest RPC definitions in another PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If you agree, I'll drop the commit that changes the mock oracle before merge.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that if we want to go ahead and update this price oracle it should include a version bump too.

Having said that, I believe it's okay to just leave the Code field unspecified and let it default to 0, in order to not have to populate it with magic numbers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a raw '1' at present to avoid updating the mock oracle's dependencies (it looks like there have been other RPC changes since it was last updated as well). Perhaps best to avoid changing the mock oracle for now, and just update it to use the latest RPC definitions in another PR?

I think updating the mock oracle in a separate PR makes sense to me. We can wait until 0.7 for example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's update the mock oracle in a separate PR. 👍

},
},
}, nil
Expand Down
17 changes: 13 additions & 4 deletions docs/release-notes/release-notes-0.7.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
- [An integration test flake was
fixed](https://github.com/lightninglabs/taproot-assets/pull/1651).

- Fixed two send related bugs that would lead to either a `invalid transfer
asset witness` or `unable to fund address send: error funding packet: unable
- Fixed two send related bugs that would lead to either a `invalid transfer
asset witness` or `unable to fund address send: error funding packet: unable
to list eligible coins: unable to query commitments: mismatch of managed utxo
and constructed tap commitment root` error when sending assets.
The [PR that fixed the two
Expand Down Expand Up @@ -119,17 +119,25 @@
when requesting quotes. The field can contain optional user or authentication
information that helps the price oracle to decide on the optimal price rate to
return.

- The error code returned in a response from a price oracle now has a
[structured
form](https://github.com/lightninglabs/taproot-assets/pull/1766),
allowing price oracles to either indicate that a given asset is
unsupported, or simply to return an opaque error. "Unsupported asset"
errors are forwarded in reject messages sent to peers.

- [Rename](https://github.com/lightninglabs/taproot-assets/pull/1682) the
`MintAsset` RPC message field from `universe_commitments` to
`enable_supply_commitments`.
- The `SubscribeSendEvents` RPC now supports [historical event replay of
- The `SubscribeSendEvents` RPC now supports [historical event replay of
completed sends with efficient database-level
filtering](https://github.com/lightninglabs/taproot-assets/pull/1685).
- [Add universe RPC endpoint FetchSupplyLeaves](https://github.com/lightninglabs/taproot-assets/pull/1693)
that allows users to fetch the supply leaves of a universe supply commitment.
This is useful for verification.

- A [new field `unconfirmed_transfers` was added to the response of the
- A [new field `unconfirmed_transfers` was added to the response of the
`ListBalances` RPC
method](https://github.com/lightninglabs/taproot-assets/pull/1691) to indicate
that unconfirmed asset-related transactions don't count toward the balance.
Expand Down Expand Up @@ -236,5 +244,6 @@

- ffranr
- George Tsagkarelis
- jtobin
- Olaoluwa Osuntokun
- Oliver Gugger
53 changes: 43 additions & 10 deletions rfq/negotiator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rfq

import (
"errors"
"fmt"
"sync"
"time"
Expand Down Expand Up @@ -146,10 +147,9 @@
}

// Now we will check for an error in the response from the price oracle.
// If present, we will convert it to a string and return it as an error.
// If present, we will simply relay it.
if oracleResponse.Err != nil {
return nil, fmt.Errorf("failed to query price oracle for "+
"buy price: %s", oracleResponse.Err)
return nil, oracleResponse.Err
}

// By this point, the price oracle did not return an error or a buy
Expand Down Expand Up @@ -282,10 +282,9 @@
}

// Now we will check for an error in the response from the price oracle.
// If present, we will convert it to a string and return it as an error.
// If present, we will simply relay it.
if oracleResponse.Err != nil {
return nil, fmt.Errorf("failed to query price oracle for "+
"sell price: %s", oracleResponse.Err)
return nil, oracleResponse.Err
}

// By this point, the price oracle did not return an error or a sell
Expand Down Expand Up @@ -372,10 +371,12 @@
peerID, request.PriceOracleMetadata, IntentRecvPayment,
)
if err != nil {
// Send a reject message to the peer.
// Construct an appropriate RejectErr based on
// the oracle's response, and send it to the
// peer.
msg := rfqmsg.NewReject(
request.Peer, request.ID,
rfqmsg.ErrUnknownReject,
createCustomRejectErr(err),
)
sendOutgoingMsg(msg)

Expand Down Expand Up @@ -473,10 +474,12 @@
peerID, request.PriceOracleMetadata, IntentPayInvoice,
)
if err != nil {
// Send a reject message to the peer.
// Construct an appropriate RejectErr based on
// the oracle's response, and send it to the
// peer.
msg := rfqmsg.NewReject(
request.Peer, request.ID,
rfqmsg.ErrUnknownReject,
createCustomRejectErr(err),
)
sendOutgoingMsg(msg)

Expand All @@ -495,6 +498,36 @@
return nil
}

// createCustomRejectErr creates a RejectErr with code 0 and a custom message
// based on an error response from a price oracle.
func createCustomRejectErr(err error) rfqmsg.RejectErr {
var oracleError *OracleError

if errors.As(err, &oracleError) {
// The error is of the expected type, so switch on the error
// code returned by the oracle. If the code is benign, then the
// RejectErr will simply relay the oracle's message. Otherwise,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventually we should squash all the following fix related commits to their original commit

// we'll return an opaque rejection message.
switch oracleError.Code {

Check failure on line 511 in rfq/negotiator.go

View workflow job for this annotation

GitHub Actions / Lint check

unnecessary leading newline (whitespace)

// The rejection message will state that the oracle doesn't
// support the asset.
case ErrUnsupportedOracleAsset:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the future would we append to this switch all the cases for codes that are considered public?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read the iota list of all error codes I don't really have some information source as to whether they are public or internal error codes.

We could follow a convention that all non-zero codes are public (with appropriate docs in the definitions ofcs)

Or further extend the oracle error with a direct Internal bool flag, that explicitly indicates whether this code/msg should be shared or not

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like that latter idea -- let the oracle indicate whether the error is intended to be public/private. 👍

msg := oracleError.Msg
return rfqmsg.ErrRejectWithCustomMsg(msg)

// The rejection message will be opaque, with the error
// unspecified.
default:
return rfqmsg.ErrUnknownReject
}
} else {
// The error is of an unexpected type, so just return an opaque
// error message.
return rfqmsg.ErrUnknownReject
}
}

// HandleOutgoingSellOrder handles an outgoing sell order by constructing sell
// requests and passing them to the outgoing messages channel. These requests
// are sent to peers.
Expand Down
35 changes: 32 additions & 3 deletions rfq/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,26 @@ const (
// service.
type OracleError struct {
// Code is a code which uniquely identifies the error type.
Code uint8
Code OracleErrorCode

// Msg is a human-readable error message.
Msg string
}

// OracleErrorCode uniquely identifies the kinds of error an oracle may
// return.
type OracleErrorCode uint8

const (
// ErrUnspecifiedOracleError represents the case where the oracle has
// declined to give a more specific reason for the error.
ErrUnspecifiedOracleError OracleErrorCode = iota

// ErrUnsupportedOracleAsset represents the case in which an oracle does
// not provide quotes for the requested asset.
ErrUnsupportedOracleAsset
)

// Error returns a human-readable string representation of the error.
func (o *OracleError) Error() string {
// Sanitise price oracle error message by truncating to 255 characters.
Expand Down Expand Up @@ -356,7 +370,8 @@ func (r *RpcPriceOracle) QuerySellPrice(ctx context.Context,

return &OracleResponse{
Err: &OracleError{
Msg: result.Error.Message,
Msg: result.Error.Message,
Code: marshallErrorCode(result.Error.Code),
},
}, nil

Expand All @@ -365,6 +380,19 @@ func (r *RpcPriceOracle) QuerySellPrice(ctx context.Context,
}
}

// marshallErrorCode marshalls an over-the-wire error code into an
// OracleErrorCode.
func marshallErrorCode(code oraclerpc.ErrorCode) OracleErrorCode {
switch code {
case oraclerpc.ErrorCode_ERROR_UNSPECIFIED:
return ErrUnspecifiedOracleError
case oraclerpc.ErrorCode_ERROR_UNSUPPORTED:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we add an empty line before a new switch case

Suggested change
case oraclerpc.ErrorCode_ERROR_UNSUPPORTED:
case oraclerpc.ErrorCode_ERROR_UNSUPPORTED:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except for the first case (but including the default case below)

return ErrUnsupportedOracleAsset
default:
return ErrUnspecifiedOracleError
}
}

// QueryBuyPrice returns a buy price for the given asset amount.
func (r *RpcPriceOracle) QueryBuyPrice(ctx context.Context,
assetSpecifier asset.Specifier, assetMaxAmt fn.Option[uint64],
Expand Down Expand Up @@ -467,7 +495,8 @@ func (r *RpcPriceOracle) QueryBuyPrice(ctx context.Context,

return &OracleResponse{
Err: &OracleError{
Msg: result.Error.Message,
Msg: result.Error.Message,
Code: marshallErrorCode(result.Error.Code),
},
}, nil

Expand Down
9 changes: 9 additions & 0 deletions rfqmsg/reject.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ var (
}
)

// ErrRejectWithCustomMsg produces the "unknown" error code, but pairs
// it with a custom error message.
func ErrRejectWithCustomMsg(msg string) RejectErr {
return RejectErr{
Code: 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use enum const here instead of 0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 79308a2. There were also some existing cases in that file that used the raw code, so I fixed those too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should squash or amend the commits in this PR.

Also, to stay consistent with the naming pattern used elsewhere in the code, we should probably call this NewRejectErr.

Msg: msg,
}
}

const (
// latestRejectVersion is the latest supported reject wire message data
// field version.
Expand Down
Loading
Loading