Skip to content

Conversation

jtobin
Copy link
Member

@jtobin jtobin commented Aug 25, 2025

Adds some structure to price oracle error codes, along with some custom handling for them. In particular, a code of '1' now corresponds to an 'unsupported asset' error, which, considered a 'public' error, is forwarded in the customizable message field of the reject messages sent to peers. An error with any other code continues, at present, to be treated as an unspecified error.

Resolves #1749, #1326.

(This is a refinement over the closed #1751, which forwarded errors indiscriminately.)

ErrRejectWithCustomMsg constructs a RejectErr with error code 0 and the
specified error message.
@jtobin jtobin requested review from ffranr and GeorgeTsagk August 25, 2025 11:28
@jtobin jtobin added error handling RFQ Work relating to TAP channel Request For Quote (RFQ). labels Aug 25, 2025
jtobin added 7 commits August 25, 2025 09:04
Formalizes the 'Code' of an 'OracleError' as an 'OracleErrorCode', and
adds a couple of values corresponding to structured error cases.
Ensure the error code is part of an OracleError response, and pass the
error intact in query{Bid,Ask}FromPriceOracle, instead of formatting it
as a string.
Uses the OracleError returned by an oracle to customize the rejection
message sent to a peer. If the OracleError contains a "transparent" code
that's deemed to be suitable for passing on to a peer, then simply relay
that error. Otherwise relay an opaque "unknown reject error" message.
Avoids a blunt cast of the wire code, and also refrains from relaying
the full Error() message to the peer.
Simply passes an error code of 1 (unsupported subject asset) where
appropriate in the mock oracle. The raw code is used instead of
oraclerpc.UNSUPPORTED or similar to avoid dependency changes at present.

Also adds the mock oracle's log to gitignore.
@jtobin jtobin force-pushed the oracle-error-codes branch from 0a5e9ab to 6bdb36a Compare August 25, 2025 11:35
@coveralls
Copy link

coveralls commented Aug 25, 2025

Pull Request Test Coverage Report for Build 17268575857

Details

  • 1 of 99 (1.01%) changed or added relevant lines in 4 files are covered.
  • 84 unchanged lines in 16 files lost coverage.
  • Overall coverage decreased (-0.06%) to 56.558%

Changes Missing Coverage Covered Lines Changed/Added Lines %
rfqmsg/reject.go 0 6 0.0%
rfq/oracle.go 0 12 0.0%
taprpc/priceoraclerpc/price_oracle.pb.go 1 23 4.35%
rfq/negotiator.go 0 58 0.0%
Files with Coverage Reduction New Missed Lines %
address/mock.go 2 96.2%
asset/group_key.go 2 72.15%
mssmt/compacted_tree.go 2 79.49%
tapdb/assets_common.go 2 78.72%
tapdb/sqlc/transfers.sql.go 2 82.65%
tapdb/sqlc/universe.sql.go 2 75.78%
tapchannel/aux_leaf_signer.go 3 43.43%
rfqmsg/records.go 4 70.8%
rpcserver.go 4 61.03%
tapdb/universe.go 4 81.64%
Totals Coverage Status
Change from base Build 17140538774: -0.06%
Covered Lines: 60774
Relevant Lines: 107454

💛 - Coveralls

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.

if oracleResponse.Err != nil {
return nil, fmt.Errorf("failed to query price oracle for "+
"buy price: %s", oracleResponse.Err)
return nil, oracleResponse.Err
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 the additional context that was here previously was useful to distinguish between buy/sell price related error. Is there a good reason for removing the additional context here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree, yeah. Re-added context in a structured form in 5eaf8a9.

rfq/oracle.go Outdated
const (
// ErrUnspecifiedOracleError represents the case where the oracle has
// declined to give a more specific reason for the error.
ErrUnspecifiedOracleError OracleErrorCode = iota
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 usually start error type names with Err.... Maybe we should use names UnspecifiedOracleErrorCode and UnsupportedAssetOracleErrorCode here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Used your suggestion, fixed in 6130dac.

rfqmsg/reject.go Outdated
// 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.

Comment on lines 86 to 92
enum ErrorCode {
// ERROR_UNSPECIFIED indicates an unspecified error.
ERROR_UNSPECIFIED = 0;

// UNSUPPORTED indicates the asset is not supported.
ERROR_UNSUPPORTED = 1;
}
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 these fields should follow the enum filed names in our code so: UNSUPPORTED_ASSET_ORACLE_ERROR_CODE etc

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, fixed in 6ff7217.

jtobin added 5 commits August 25, 2025 11:45
Changes these to avoid the 'Err..' convention presently reserved for
type names.
Defines specific consts for the two RejectErr error codes, and uses
those in place of the raw uint8 values.
Changes 'ERROR_UNSPECIFIED' and 'ERROR_SUPPORTED' to more closely match
the OracleErrorCode values in rfq/oracle.go.
Introduces a structured QueryError that wraps an arbitrary error
(usually from a price oracle) with arbitrary context, and adjusts
query{Buy,Sell}FromPriceOracle to return these errors where appropriate.

Also adjusts createCustomRejectErr to handle QueryErrors, instead of
OracleErrors.
@jtobin jtobin requested a review from ffranr August 28, 2025 15:47
@levmi levmi moved this from 🆕 New to 🏗 In progress in Taproot-Assets Project Board Sep 4, 2025
@levmi levmi moved this from 🏗 In progress to 👀 In review in Taproot-Assets Project Board Sep 11, 2025

// 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


// code is the error code.
uint32 code = 2;
ErrorCode code = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Worth noting that this doesn't break backwards compatibility as both uint32 and enum are encoded as varints on the wire.

rfq/oracle.go Outdated
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)

Result: &oraclerpc.QueryAssetRatesResponse_Error{
Error: &oraclerpc.QueryAssetRatesErrResponse{
Message: "unsupported subject asset",
Code: 1,
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

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

// based on an error response from a price oracle.
func createCustomRejectErr(err error) rfqmsg.RejectErr {
var queryError *QueryError
// Check if the error we've received is the expected QueryError, and
Copy link
Member

Choose a reason for hiding this comment

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

newline before the comments

Suggested change
// Check if the error we've received is the expected QueryError, and
var queryError *QueryError
// Check if the error we've received is the expected QueryError, and

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

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

I've taken another look through this.

IMO needs commit squash and then we can review the set of commits which will be merged.

rfqmsg/reject.go Outdated
// 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.

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.

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.

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.

@levmi levmi added this to the v0.8 milestone Sep 25, 2025
@lightninglabs-deploy
Copy link

@jtobin, remember to re-request review from reviewers when ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error handling RFQ Work relating to TAP channel Request For Quote (RFQ).
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

[feature]: structured error codes for price oracles
6 participants