diff --git a/rfq/negotiator.go b/rfq/negotiator.go index 08f3edde7..84ad7fc9e 100644 --- a/rfq/negotiator.go +++ b/rfq/negotiator.go @@ -106,8 +106,8 @@ func NewNegotiator(cfg NegotiatorCfg) (*Negotiator, error) { // queryBidFromPriceOracle queries the price oracle for a bid price. It returns // an appropriate outgoing response message which should be sent to the peer. func (n *Negotiator) queryBidFromPriceOracle(peer route.Vertex, - assetId *asset.ID, assetGroupKey *btcec.PublicKey, - assetAmount uint64) (*rfqmath.BigIntFixedPoint, uint64, error) { + assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64, + assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) { // TODO(ffranr): Optionally accept a peer's proposed ask price as an // arg to this func and pass it to the price oracle. The price oracle @@ -119,32 +119,34 @@ func (n *Negotiator) queryBidFromPriceOracle(peer route.Vertex, ctx, cancel := n.WithCtxQuitNoTimeout() defer cancel() + counterparty := fn.Some[route.Vertex](peer) oracleResponse, err := n.cfg.PriceOracle.QueryBidPrice( - ctx, assetId, assetGroupKey, assetAmount, + ctx, counterparty, assetId, assetGroupKey, assetAmount, + assetRateHint, ) if err != nil { - return nil, 0, fmt.Errorf("failed to query price oracle for "+ + return nil, fmt.Errorf("failed to query price oracle for "+ "bid: %w", err) } // 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 oracleResponse.Err != nil { - return nil, 0, fmt.Errorf("failed to query price oracle for "+ + return nil, fmt.Errorf("failed to query price oracle for "+ "bid price: %s", oracleResponse.Err) } // By this point, the price oracle did not return an error or a bid // price. We will therefore return an error. - if oracleResponse.AssetRate.Coefficient.ToUint64() == 0 { - return nil, 0, fmt.Errorf("price oracle did not specify a " + + if oracleResponse.AssetRate.Rate.ToUint64() == 0 { + return nil, fmt.Errorf("price oracle did not specify a " + "bid price") } // TODO(ffranr): Check that the bid price is reasonable. // TODO(ffranr): Ensure that the expiry time is valid and sufficient. - return &oracleResponse.AssetRate, oracleResponse.Expiry, nil + return &oracleResponse.AssetRate, nil } // HandleOutgoingBuyOrder handles an outgoing buy order by constructing buy @@ -161,13 +163,14 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error { // We calculate a proposed bid price for our peer's // consideration. If a price oracle is not specified we will // skip this step. - var assetRateBid fn.Option[rfqmath.BigIntFixedPoint] + var assetRateHint fn.Option[rfqmsg.AssetRate] if n.cfg.PriceOracle != nil { // Query the price oracle for a bid price. - rate, _, err := n.queryBidFromPriceOracle( + assetRate, err := n.queryBidFromPriceOracle( *buyOrder.Peer, buyOrder.AssetID, buyOrder.AssetGroupKey, buyOrder.MinAssetAmount, + fn.None[rfqmsg.AssetRate](), ) if err != nil { // If we fail to query the price oracle for a @@ -178,13 +181,15 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error { "request: %v", err) } - assetRateBid = fn.Some[rfqmath.BigIntFixedPoint](*rate) + assetRateHint = fn.Some[rfqmsg.AssetRate]( + *assetRate, + ) } request, err := rfqmsg.NewBuyRequest( *buyOrder.Peer, buyOrder.AssetID, buyOrder.AssetGroupKey, buyOrder.MinAssetAmount, - assetRateBid, + assetRateHint, ) if err != nil { err := fmt.Errorf("unable to create buy request "+ @@ -213,41 +218,42 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error { // queryAskFromPriceOracle queries the price oracle for an asking price. It // returns an appropriate outgoing response message which should be sent to the // peer. -func (n *Negotiator) queryAskFromPriceOracle(peer *route.Vertex, +func (n *Negotiator) queryAskFromPriceOracle(peer route.Vertex, assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64, - suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]) ( - *rfqmath.BigIntFixedPoint, uint64, error) { + assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) { // Query the price oracle for an asking price. ctx, cancel := n.WithCtxQuitNoTimeout() defer cancel() + counterparty := fn.Some[route.Vertex](peer) oracleResponse, err := n.cfg.PriceOracle.QueryAskPrice( - ctx, assetId, assetGroupKey, assetAmount, suggestedAssetRate, + ctx, counterparty, assetId, assetGroupKey, assetAmount, + assetRateHint, ) if err != nil { - return nil, 0, fmt.Errorf("failed to query price oracle for "+ + return nil, fmt.Errorf("failed to query price oracle for "+ "ask price: %w", err) } // 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 oracleResponse.Err != nil { - return nil, 0, fmt.Errorf("failed to query price oracle for "+ + return nil, fmt.Errorf("failed to query price oracle for "+ "ask price: %s", oracleResponse.Err) } // By this point, the price oracle did not return an error or an asking // price. We will therefore return an error. - if oracleResponse.AssetRate.Coefficient.ToUint64() == 0 { - return nil, 0, fmt.Errorf("price oracle did not specify an " + + if oracleResponse.AssetRate.Rate.Coefficient.ToUint64() == 0 { + return nil, fmt.Errorf("price oracle did not specify an " + "asset to BTC rate") } // TODO(ffranr): Check that the asking price is reasonable. // TODO(ffranr): Ensure that the expiry time is valid and sufficient. - return &oracleResponse.AssetRate, oracleResponse.Expiry, nil + return &oracleResponse.AssetRate, nil } // HandleIncomingBuyRequest handles an incoming asset buy quote request. @@ -310,9 +316,9 @@ func (n *Negotiator) HandleIncomingBuyRequest( defer n.Wg.Done() // Query the price oracle for an asking price. - assetRate, rateExpiry, err := n.queryAskFromPriceOracle( - nil, request.AssetID, request.AssetGroupKey, - request.AssetAmount, request.SuggestedAssetRate, + assetRate, err := n.queryAskFromPriceOracle( + request.Peer, request.AssetID, request.AssetGroupKey, + request.AssetAmount, request.AssetRateHint, ) if err != nil { // Send a reject message to the peer. @@ -330,8 +336,9 @@ func (n *Negotiator) HandleIncomingBuyRequest( } // Construct and send a buy accept message. + expiry := uint64(assetRate.Expiry.Unix()) msg := rfqmsg.NewBuyAcceptFromRequest( - request, *assetRate, rateExpiry, + request, assetRate.Rate, expiry, ) sendOutgoingMsg(msg) }() @@ -405,9 +412,9 @@ func (n *Negotiator) HandleIncomingSellRequest( // Query the price oracle for a bid price. This is the price we // are willing to pay for the asset that our peer is trying to // sell to us. - assetRate, rateExpiry, err := n.queryBidFromPriceOracle( + assetRate, err := n.queryBidFromPriceOracle( request.Peer, request.AssetID, request.AssetGroupKey, - request.AssetAmount, + request.AssetAmount, request.AssetRateHint, ) if err != nil { // Send a reject message to the peer. @@ -425,8 +432,9 @@ func (n *Negotiator) HandleIncomingSellRequest( } // Construct and send a sell accept message. + expiry := uint64(assetRate.Expiry.Unix()) msg := rfqmsg.NewSellAcceptFromRequest( - request, *assetRate, rateExpiry, + request, assetRate.Rate, expiry, ) sendOutgoingMsg(msg) }() @@ -448,14 +456,14 @@ func (n *Negotiator) HandleOutgoingSellOrder(order SellOrder) { // We calculate a proposed ask price for our peer's // consideration. If a price oracle is not specified we will // skip this step. - var assetRate fn.Option[rfqmath.BigIntFixedPoint] + var assetRateHint fn.Option[rfqmsg.AssetRate] if n.cfg.PriceOracle != nil { // Query the price oracle for an asking price. - rate, _, err := n.queryAskFromPriceOracle( - order.Peer, order.AssetID, order.AssetGroupKey, + assetRate, err := n.queryAskFromPriceOracle( + *order.Peer, order.AssetID, order.AssetGroupKey, order.MaxAssetAmount, - fn.None[rfqmath.BigIntFixedPoint](), + fn.None[rfqmsg.AssetRate](), ) if err != nil { err := fmt.Errorf("negotiator failed to "+ @@ -464,12 +472,12 @@ func (n *Negotiator) HandleOutgoingSellOrder(order SellOrder) { return } - assetRate = fn.Some[rfqmath.BigIntFixedPoint](*rate) + assetRateHint = fn.Some[rfqmsg.AssetRate](*assetRate) } request, err := rfqmsg.NewSellRequest( *order.Peer, order.AssetID, order.AssetGroupKey, - order.MaxAssetAmount, assetRate, + order.MaxAssetAmount, assetRateHint, ) if err != nil { err := fmt.Errorf("unable to create sell request "+ @@ -566,10 +574,9 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept, // We will sanity check that price by querying our price oracle // for an ask price. We will then compare the ask price returned // by the price oracle with the ask price provided by the peer. - assetRate, _, err := n.queryAskFromPriceOracle( - &msg.Peer, msg.Request.AssetID, nil, - msg.Request.AssetAmount, - fn.None[rfqmath.BigIntFixedPoint](), + assetRate, err := n.queryAskFromPriceOracle( + msg.Peer, msg.Request.AssetID, nil, + msg.Request.AssetAmount, fn.None[rfqmsg.AssetRate](), ) if err != nil { // The price oracle returned an error. We will return @@ -601,7 +608,7 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept, big.NewInt(0).SetUint64(n.cfg.AcceptPriceDeviationPpm), ) acceptablePrice := msg.AssetRate.WithinTolerance( - *assetRate, tolerance, + assetRate.Rate, tolerance, ) if !acceptablePrice { // The price is not within the acceptable tolerance. @@ -691,9 +698,9 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept, // We will sanity check that price by querying our price oracle // for a bid price. We will then compare the bid price returned // by the price oracle with the bid price provided by the peer. - assetRate, _, err := n.queryBidFromPriceOracle( + assetRate, err := n.queryBidFromPriceOracle( msg.Peer, msg.Request.AssetID, nil, - msg.Request.AssetAmount, + msg.Request.AssetAmount, msg.Request.AssetRateHint, ) if err != nil { // The price oracle returned an error. We will return @@ -725,7 +732,7 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept, big.NewInt(0).SetUint64(n.cfg.AcceptPriceDeviationPpm), ) acceptablePrice := msg.AssetRate.WithinTolerance( - *assetRate, tolerance, + assetRate.Rate, tolerance, ) if !acceptablePrice { // The price is not within the acceptable bounds. diff --git a/rfq/oracle.go b/rfq/oracle.go index 5c32f42fc..ec022547d 100644 --- a/rfq/oracle.go +++ b/rfq/oracle.go @@ -7,29 +7,20 @@ import ( "net/url" "time" + "math" + "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rfqmsg" oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" + "github.com/lightningnetwork/lnd/routing/route" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) -const ( - // defaultAssetRateExpirySeconds is the default asset units to BTC rate - // expiry lifetime in seconds. 600s = 10 minutes. - // - // TODO(ffranr): This const is currently used in conjunction with the - // AcceptSuggestedPrices flag. It is used to set the expiry time of the - // asset units to BTC rate in the accept message. This is a temporary - // solution and should be replaced with an expiry time provided by the - // peer in the quote request message. - defaultAssetRateExpirySeconds = 600 -) - // OracleError is a struct that holds an error returned by the price oracle // service. type OracleError struct { @@ -58,11 +49,7 @@ func (o *OracleError) Error() string { type OracleResponse struct { // AssetRate is the asset to BTC rate. Other asset in the transfer is // assumed to be BTC and therefore not included in the response. - AssetRate rfqmath.BigIntFixedPoint - - // Expiry is the asset to BTC rate expiry lifetime unix timestamp. The - // rate is only valid until this time. - Expiry uint64 + AssetRate rfqmsg.AssetRate // Err is an optional error returned by the price oracle service. Err *OracleError @@ -109,17 +96,18 @@ type PriceOracle interface { // QueryAskPrice returns the ask price for a given asset amount. // The ask price is the amount the oracle suggests a peer should accept // from another peer to provide the specified asset amount. - QueryAskPrice(ctx context.Context, assetId *asset.ID, - assetGroupKey *btcec.PublicKey, assetAmount uint64, - assetRateHint fn.Option[rfqmath.BigIntFixedPoint]) ( + QueryAskPrice(ctx context.Context, peer fn.Option[route.Vertex], + assetId *asset.ID, assetGroupKey *btcec.PublicKey, + assetAmount uint64, assetRateHint fn.Option[rfqmsg.AssetRate]) ( *OracleResponse, error) // QueryBidPrice returns the bid price for a given asset amount. // The bid price is the amount the oracle suggests a peer should pay // to another peer to receive the specified asset amount. - QueryBidPrice(ctx context.Context, assetId *asset.ID, - assetGroupKey *btcec.PublicKey, - assetAmount uint64) (*OracleResponse, error) + QueryBidPrice(ctx context.Context, peer fn.Option[route.Vertex], + assetId *asset.ID, assetGroupKey *btcec.PublicKey, + assetAmount uint64, assetRateHint fn.Option[rfqmsg.AssetRate]) ( + *OracleResponse, error) } // RpcPriceOracle is a price oracle that uses an external RPC server to get @@ -203,8 +191,9 @@ func NewRpcPriceOracle(addrStr string, dialInsecure bool) (*RpcPriceOracle, // QueryAskPrice returns the ask price for the given asset amount. func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, - assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64, - assetRateHint fn.Option[rfqmath.BigIntFixedPoint]) (*OracleResponse, + peer fn.Option[route.Vertex], assetId *asset.ID, + assetGroupKey *btcec.PublicKey, assetAmount uint64, + assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { // For now, we only support querying the ask price with an asset ID. @@ -226,14 +215,13 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, rpcAssetRatesHint *oraclerpc.AssetRates err error ) - assetRateHint.WhenSome(func(rate rfqmath.BigIntFixedPoint) { + assetRateHint.WhenSome(func(assetRate rfqmsg.AssetRate) { // Compute an expiry time using the default expiry delay. - expiryTimestamp := uint64(time.Now().Unix()) + - defaultAssetRateExpirySeconds + expiryTimestamp := uint64(assetRate.Expiry.Unix()) // Marshal the subject asset rate. subjectAssetRate, err := oraclerpc.MarshalBigIntFixedPoint( - rate, + assetRate.Rate, ) if err != nil { return @@ -258,6 +246,12 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, return nil, err } + // Marshal transaction counterparty. + var counterparty []byte + peer.WhenSome(func(p route.Vertex) { + counterparty = p[:] + }) + req := &oraclerpc.QueryAssetRatesRequest{ TransactionType: oraclerpc.TransactionType_SALE, SubjectAsset: &oraclerpc.AssetSpecifier{ @@ -271,7 +265,8 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, AssetId: paymentAssetId, }, }, - AssetRatesHint: rpcAssetRatesHint, + AssetRatesHint: rpcAssetRatesHint, + TransactionCounterparty: counterparty, } // Perform query. @@ -296,9 +291,17 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, return nil, err } + // Unmarshal the expiry timestamp. + if result.Ok.AssetRates.ExpiryTimestamp > math.MaxInt64 { + return nil, fmt.Errorf("expiry timestamp exceeds " + + "int64 max") + } + expiry := time.Unix(int64( + result.Ok.AssetRates.ExpiryTimestamp, + ), 0).UTC() + return &OracleResponse{ - AssetRate: *rate, - Expiry: result.Ok.AssetRates.ExpiryTimestamp, + AssetRate: rfqmsg.NewAssetRate(*rate, expiry), }, nil case *oraclerpc.QueryAssetRatesResponse_Error: @@ -319,9 +322,10 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context, } // QueryBidPrice returns a bid price for the given asset amount. -func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, assetId *asset.ID, - assetGroupKey *btcec.PublicKey, - maxAssetAmount uint64) (*OracleResponse, error) { +func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, + peer fn.Option[route.Vertex], assetId *asset.ID, + assetGroupKey *btcec.PublicKey, maxAssetAmount uint64, + assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { // For now, we only support querying the ask price with an asset ID. if assetId == nil { @@ -337,6 +341,42 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, assetId *asset.ID, // set the subject asset ID. copy(subjectAssetId, assetId[:]) + // Construct the RPC asset rates hint. + var ( + rpcAssetRatesHint *oraclerpc.AssetRates + err error + ) + assetRateHint.WhenSome(func(assetRate rfqmsg.AssetRate) { + // Compute an expiry time using the default expiry delay. + expiryTimestamp := uint64(assetRate.Expiry.Unix()) + + // Marshal the subject asset rate. + subjectAssetRate, err := oraclerpc.MarshalBigIntFixedPoint( + assetRate.Rate, + ) + if err != nil { + return + } + + // Marshal the payment asset rate. For now, we only support BTC + // as the payment asset. + paymentAssetRate, err := oraclerpc.MarshalBigIntFixedPoint( + rfqmsg.MilliSatPerBtc, + ) + if err != nil { + return + } + + rpcAssetRatesHint = &oraclerpc.AssetRates{ + SubjectAssetRate: subjectAssetRate, + PaymentAssetRate: paymentAssetRate, + ExpiryTimestamp: expiryTimestamp, + } + }) + if err != nil { + return nil, err + } + req := &oraclerpc.QueryAssetRatesRequest{ TransactionType: oraclerpc.TransactionType_PURCHASE, SubjectAsset: &oraclerpc.AssetSpecifier{ @@ -350,7 +390,7 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, assetId *asset.ID, AssetId: paymentAssetId, }, }, - AssetRatesHint: nil, + AssetRatesHint: rpcAssetRatesHint, } // Perform query. @@ -375,9 +415,17 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, assetId *asset.ID, return nil, err } + // Unmarshal the expiry timestamp. + if result.Ok.AssetRates.ExpiryTimestamp > math.MaxInt64 { + return nil, fmt.Errorf("expiry timestamp exceeds " + + "int64 max") + } + expiry := time.Unix(int64( + result.Ok.AssetRates.ExpiryTimestamp, + ), 0).UTC() + return &OracleResponse{ - AssetRate: *rate, - Expiry: result.Ok.AssetRates.ExpiryTimestamp, + AssetRate: rfqmsg.NewAssetRate(*rate, expiry), }, nil case *oraclerpc.QueryAssetRatesResponse_Error: @@ -403,6 +451,7 @@ var _ PriceOracle = (*RpcPriceOracle)(nil) // MockPriceOracle is a mock implementation of the PriceOracle interface. // It returns the suggested rate as the exchange rate. type MockPriceOracle struct { + // expiryDelay is the lifetime of a quote in seconds. expiryDelay uint64 assetToBtcRate rfqmath.BigIntFixedPoint } @@ -436,28 +485,29 @@ func NewMockPriceOracleSatPerAsset(expiryDelay uint64, // QueryAskPrice returns the ask price for the given asset amount. func (m *MockPriceOracle) QueryAskPrice(_ context.Context, - _ *asset.ID, _ *btcec.PublicKey, _ uint64, - _ fn.Option[rfqmath.BigIntFixedPoint]) (*OracleResponse, error) { + _ fn.Option[route.Vertex], _ *asset.ID, _ *btcec.PublicKey, _ uint64, + _ fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { - // Calculate the rate expiryDelay lifetime. - expiry := uint64(time.Now().Unix()) + m.expiryDelay + // Calculate the rate expiry timestamp. + lifetime := time.Duration(m.expiryDelay) * time.Second + expiry := time.Now().Add(lifetime).UTC() return &OracleResponse{ - AssetRate: m.assetToBtcRate, - Expiry: expiry, + AssetRate: rfqmsg.NewAssetRate(m.assetToBtcRate, expiry), }, nil } // QueryBidPrice returns a bid price for the given asset amount. -func (m *MockPriceOracle) QueryBidPrice(_ context.Context, _ *asset.ID, - _ *btcec.PublicKey, _ uint64) (*OracleResponse, error) { +func (m *MockPriceOracle) QueryBidPrice(_ context.Context, + _ fn.Option[route.Vertex], _ *asset.ID, _ *btcec.PublicKey, _ uint64, + _ fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) { - // Calculate the rate expiryDelay lifetime. - expiry := uint64(time.Now().Unix()) + m.expiryDelay + // Calculate the rate expiry timestamp. + lifetime := time.Duration(m.expiryDelay) * time.Second + expiry := time.Now().Add(lifetime).UTC() return &OracleResponse{ - AssetRate: m.assetToBtcRate, - Expiry: expiry, + AssetRate: rfqmsg.NewAssetRate(m.assetToBtcRate, expiry), }, nil } diff --git a/rfq/oracle_test.go b/rfq/oracle_test.go index 2275edcc9..dd1fc3ece 100644 --- a/rfq/oracle_test.go +++ b/rfq/oracle_test.go @@ -13,8 +13,10 @@ import ( "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/internal/test" "github.com/lightninglabs/taproot-assets/rfqmath" + "github.com/lightninglabs/taproot-assets/rfqmsg" "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -158,10 +160,12 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) { inAssetRate := rfqmath.NewBigIntFixedPoint( tc.suggestedAssetRate, 3, ) + expiry := time.Now().Add(rfqmsg.DefaultQuoteLifetime).UTC() + assetRateHint := rfqmsg.NewAssetRate(inAssetRate, expiry) resp, err := client.QueryAskPrice( - ctx, tc.assetId, tc.assetGroupKey, assetAmount, - fn.Some(inAssetRate), + ctx, fn.None[route.Vertex](), tc.assetId, tc.assetGroupKey, + assetAmount, fn.Some(assetRateHint), ) // If we expect an error, ensure that it is returned. @@ -176,12 +180,11 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) { // The mock server should return the asset rates hint. require.NotNil(t, resp.AssetRate) require.Equal( - t, uint64(bidPrice), resp.AssetRate.Coefficient.ToUint64(), + t, uint64(bidPrice), resp.AssetRate.Rate.Coefficient.ToUint64(), ) // Ensure that the expiry timestamp is in the future. - responseExpiry := time.Unix(int64(resp.Expiry), 0) - require.True(t, responseExpiry.After(time.Now())) + require.True(t, resp.AssetRate.Expiry.After(time.Now())) } // TestRpcPriceOracle tests the RPC price oracle client QueryAskPrice function. @@ -258,7 +261,8 @@ func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) { assetAmount := uint64(42) resp, err := client.QueryBidPrice( - ctx, tc.assetId, tc.assetGroupKey, assetAmount, + ctx, fn.None[route.Vertex](), tc.assetId, tc.assetGroupKey, + assetAmount, fn.None[rfqmsg.AssetRate](), ) // If we expect an error, ensure that it is returned. @@ -272,11 +276,12 @@ func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) { // The mock server should return the asset rates hint. require.NotNil(t, resp.AssetRate) - require.Equal(t, testAssetRate, resp.AssetRate.Coefficient.ToUint64()) + require.Equal( + t, testAssetRate, resp.AssetRate.Rate.Coefficient.ToUint64(), + ) // Ensure that the expiry timestamp is in the future. - responseExpiry := time.Unix(int64(resp.Expiry), 0) - require.True(t, responseExpiry.After(time.Now())) + require.True(t, resp.AssetRate.Expiry.After(time.Now())) } // TestRpcPriceOracle tests the RPC price oracle client QueryBidPrice function. diff --git a/rfqmsg/buy_accept.go b/rfqmsg/buy_accept.go index 510287907..8ea39f1da 100644 --- a/rfqmsg/buy_accept.go +++ b/rfqmsg/buy_accept.go @@ -41,6 +41,8 @@ type BuyAccept struct { // NewBuyAcceptFromRequest creates a new instance of a quote accept message // given a quote request message. +// +// TODO(ffranr): Use new AssetRate type for assetRate arg. func NewBuyAcceptFromRequest(request BuyRequest, assetRate rfqmath.BigIntFixedPoint, expiry uint64) *BuyAccept { diff --git a/rfqmsg/buy_request.go b/rfqmsg/buy_request.go index 58220096a..6264e33e7 100644 --- a/rfqmsg/buy_request.go +++ b/rfqmsg/buy_request.go @@ -2,11 +2,12 @@ package rfqmsg import ( "fmt" + "math" + "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" - "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -40,18 +41,17 @@ type BuyRequest struct { // requesting a quote. AssetAmount uint64 - // SuggestedAssetRate represents a proposed conversion rate between the + // AssetRateHint represents a proposed conversion rate between the // subject asset and BTC. This rate is an initial suggestion intended to // initiate the RFQ negotiation process and may differ from the final // agreed rate. - SuggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint] + AssetRateHint fn.Option[AssetRate] } // NewBuyRequest creates a new asset buy quote request. func NewBuyRequest(peer route.Vertex, assetID *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64, - suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]) (*BuyRequest, - error) { + assetRateHint fn.Option[AssetRate]) (*BuyRequest, error) { id, err := NewID() if err != nil { @@ -60,13 +60,13 @@ func NewBuyRequest(peer route.Vertex, assetID *asset.ID, } return &BuyRequest{ - Peer: peer, - Version: latestBuyRequestVersion, - ID: id, - AssetID: assetID, - AssetGroupKey: assetGroupKey, - AssetAmount: assetAmount, - SuggestedAssetRate: suggestedAssetRate, + Peer: peer, + Version: latestBuyRequestVersion, + ID: id, + AssetID: assetID, + AssetGroupKey: assetGroupKey, + AssetAmount: assetAmount, + AssetRateHint: assetRateHint, }, nil } @@ -102,24 +102,30 @@ func NewBuyRequestFromWire(wireMsg WireMessage, "request") } + // Convert the wire message expiration time to a time.Time. + if msgData.Expiry.Val > math.MaxInt64 { + return nil, fmt.Errorf("expiry time exceeds maximum int64") + } + + expiry := time.Unix(int64(msgData.Expiry.Val), 0) + // Extract the suggested asset to BTC rate if provided. - var suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint] + var assetRateHint fn.Option[AssetRate] msgData.SuggestedAssetRate.WhenSome( func(rate tlv.RecordT[tlv.TlvType19, TlvFixedPoint]) { fp := rate.Val.IntoBigIntFixedPoint() - suggestedAssetRate = - fn.Some[rfqmath.BigIntFixedPoint](fp) + assetRateHint = fn.Some(NewAssetRate(fp, expiry)) }, ) req := BuyRequest{ - Peer: wireMsg.Peer, - Version: msgData.Version.Val, - ID: msgData.ID.Val, - AssetID: assetID, - AssetGroupKey: assetGroupKey, - AssetAmount: msgData.AssetMaxAmount.Val, - SuggestedAssetRate: suggestedAssetRate, + Peer: wireMsg.Peer, + Version: msgData.Version.Val, + ID: msgData.ID.Val, + AssetID: assetID, + AssetGroupKey: assetGroupKey, + AssetAmount: msgData.AssetMaxAmount.Val, + AssetRateHint: assetRateHint, } // Perform basic sanity checks on the quote request. @@ -148,6 +154,17 @@ func (q *BuyRequest) Validate() error { q.Version) } + // Ensure that the suggested asset rate has not expired. + var err error + q.AssetRateHint.WhenSome(func(rate AssetRate) { + if rate.Expiry.Before(time.Now()) { + err = fmt.Errorf("suggested asset rate has expired") + } + }) + if err != nil { + return err + } + return nil } @@ -196,9 +213,9 @@ func (q *BuyRequest) String() string { } return fmt.Sprintf("BuyRequest(peer=%x, id=%x, asset_id=%s, "+ - "asset_group_key=%x, asset_amount=%d, "+ - "suggested_asset_rate=%v)", q.Peer[:], q.ID[:], q.AssetID, - groupKeyBytes, q.AssetAmount, q.SuggestedAssetRate) + "asset_group_key=%x, asset_amount=%d, asset_rate_hint=%v)", + q.Peer[:], q.ID[:], q.AssetID, groupKeyBytes, q.AssetAmount, + q.AssetRateHint) } // Ensure that the message type implements the OutgoingMsg interface. diff --git a/rfqmsg/messages.go b/rfqmsg/messages.go index ec740a6fa..62990f975 100644 --- a/rfqmsg/messages.go +++ b/rfqmsg/messages.go @@ -8,6 +8,7 @@ import ( "errors" "io" "math" + "time" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightningnetwork/lnd/aliasmgr" @@ -88,6 +89,30 @@ func (id *ID) Record() tlv.Record { return tlv.MakeStaticRecord(0, id, recordSize, IdEncoder, IdDecoder) } +// AssetRate represents the exchange rate of an asset to BTC, encapsulating +// both the rate in fixed-point format and an expiration timestamp. +// +// These fields are combined in AssetRate because each rate is inherently tied +// to an expiry, ensuring that the rate's validity is clear and time-limited. +type AssetRate struct { + // Rate defines the exchange rate of asset units to BTC using a + // fixed-point representation, ensuring precision for fractional asset + // rates. + Rate rfqmath.BigIntFixedPoint + + // Expiry indicates the UTC timestamp when this rate expires and should + // no longer be considered valid. + Expiry time.Time +} + +// NewAssetRate creates a new asset rate. +func NewAssetRate(rate rfqmath.BigIntFixedPoint, expiry time.Time) AssetRate { + return AssetRate{ + Rate: rate, + Expiry: expiry, + } +} + // MaxMessageType is the maximum supported message type value. const MaxMessageType = lnwire.MessageType(math.MaxUint16) diff --git a/rfqmsg/request.go b/rfqmsg/request.go index 9eed93721..0571ec602 100644 --- a/rfqmsg/request.go +++ b/rfqmsg/request.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" - "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightningnetwork/lnd/tlv" ) @@ -107,24 +106,13 @@ func newRequestWireMsgDataFromBuy(q BuyRequest) (requestWireMsgData, error) { version := tlv.NewRecordT[tlv.TlvType0](q.Version) id := tlv.NewRecordT[tlv.TlvType2](q.ID) - // Calculate the expiration unix timestamp in seconds. - // TODO(ffranr): The expiry timestamp should be obtained from the - // request message. - expiry := tlv.NewPrimitiveRecord[tlv.TlvType6]( - uint64(time.Now().Add(DefaultQuoteLifetime).Unix()), - ) - - assetMaxAmount := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetAmount) - - // Convert the suggested asset to BTC rate to a TLV record. - var suggestedAssetRate requestSuggestedAssetRate - q.SuggestedAssetRate.WhenSome(func(rate rfqmath.BigIntFixedPoint) { - // Convert the BigIntFixedPoint to a Uint64FixedPoint. - wireRate := NewTlvFixedPointFromBigInt(rate) - suggestedAssetRate = tlv.SomeRecordT[tlv.TlvType19]( - tlv.NewRecordT[tlv.TlvType19](wireRate), - ) + // Set the expiry to the default request lifetime unless an asset rate + // hint is provided. + expiry := time.Now().Add(DefaultQuoteLifetime).Unix() + q.AssetRateHint.WhenSome(func(assetRate AssetRate) { + expiry = assetRate.Expiry.Unix() }) + expiryTlv := tlv.NewPrimitiveRecord[tlv.TlvType6](uint64(expiry)) var inAssetID requestInAssetID if q.AssetID != nil { @@ -151,11 +139,23 @@ func newRequestWireMsgDataFromBuy(q BuyRequest) (requestWireMsgData, error) { outAssetGroupKey := requestOutAssetGroupKey{} + // Convert the suggested asset to BTC rate to a TLV record. + var suggestedAssetRate requestSuggestedAssetRate + q.AssetRateHint.WhenSome(func(assetRate AssetRate) { + // Convert the BigIntFixedPoint to a TlvFixedPoint. + wireRate := NewTlvFixedPointFromBigInt(assetRate.Rate) + suggestedAssetRate = tlv.SomeRecordT[tlv.TlvType19]( + tlv.NewRecordT[tlv.TlvType19](wireRate), + ) + }) + + assetMaxAmount := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetAmount) + // Encode message data component as TLV bytes. return requestWireMsgData{ Version: version, ID: id, - Expiry: expiry, + Expiry: expiryTlv, AssetMaxAmount: assetMaxAmount, SuggestedAssetRate: suggestedAssetRate, InAssetID: inAssetID, @@ -171,22 +171,23 @@ func newRequestWireMsgDataFromSell(q SellRequest) (requestWireMsgData, error) { version := tlv.NewPrimitiveRecord[tlv.TlvType0](q.Version) id := tlv.NewRecordT[tlv.TlvType2](q.ID) - // Calculate the expiration unix timestamp in seconds. - expiry := tlv.NewPrimitiveRecord[tlv.TlvType6]( - uint64(time.Now().Add(DefaultQuoteLifetime).Unix()), - ) + // Set the expiry to the default request lifetime unless an asset rate + // hint is provided. + expiry := time.Now().Add(DefaultQuoteLifetime).Unix() + q.AssetRateHint.WhenSome(func(assetRate AssetRate) { + expiry = assetRate.Expiry.Unix() + }) + expiryTlv := tlv.NewPrimitiveRecord[tlv.TlvType6](uint64(expiry)) assetMaxAmount := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetAmount) // Convert the suggested asset rate to a TLV record. var suggestedAssetRate requestSuggestedAssetRate - q.SuggestedAssetRate.WhenSome(func(rate rfqmath.BigIntFixedPoint) { - // Convert the BigIntFixedPoint to a Uint64FixedPoint. - wireRate := NewTlvFixedPointFromBigInt(rate) + q.AssetRateHint.WhenSome(func(assetRate AssetRate) { + // Convert the BigIntFixedPoint to a TlvFixedPoint. + wireRate := NewTlvFixedPointFromBigInt(assetRate.Rate) suggestedAssetRate = tlv.SomeRecordT[tlv.TlvType19]( - tlv.NewRecordT[tlv.TlvType19]( - wireRate, - ), + tlv.NewRecordT[tlv.TlvType19](wireRate), ) }) @@ -220,7 +221,7 @@ func newRequestWireMsgDataFromSell(q SellRequest) (requestWireMsgData, error) { return requestWireMsgData{ Version: version, ID: id, - Expiry: expiry, + Expiry: expiryTlv, AssetMaxAmount: assetMaxAmount, SuggestedAssetRate: suggestedAssetRate, InAssetID: inAssetID, diff --git a/rfqmsg/sell_accept.go b/rfqmsg/sell_accept.go index 379c4f446..81c3439fa 100644 --- a/rfqmsg/sell_accept.go +++ b/rfqmsg/sell_accept.go @@ -41,6 +41,8 @@ type SellAccept struct { // NewSellAcceptFromRequest creates a new instance of an asset sell quote accept // message given an asset sell quote request message. +// +// // TODO(ffranr): Use new AssetRate type for assetRate arg. func NewSellAcceptFromRequest(request SellRequest, assetRate rfqmath.BigIntFixedPoint, expiry uint64) *SellAccept { diff --git a/rfqmsg/sell_request.go b/rfqmsg/sell_request.go index 7eda5f92a..e94836500 100644 --- a/rfqmsg/sell_request.go +++ b/rfqmsg/sell_request.go @@ -2,11 +2,11 @@ package rfqmsg import ( "fmt" + "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" - "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -40,20 +40,17 @@ type SellRequest struct { // peer intends to sell. AssetAmount uint64 - // SuggestedAssetRate represents a proposed conversion rate between the + // AssetRateHint represents a proposed conversion rate between the // subject asset and BTC. This rate is an initial suggestion intended to // initiate the RFQ negotiation process and may differ from the final // agreed rate. - SuggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint] - - // TODO(ffranr): Add expiry time for suggested ask price. + AssetRateHint fn.Option[AssetRate] } // NewSellRequest creates a new asset sell quote request. func NewSellRequest(peer route.Vertex, assetID *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64, - suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]) (*SellRequest, - error) { + assetRateHint fn.Option[AssetRate]) (*SellRequest, error) { id, err := NewID() if err != nil { @@ -61,13 +58,13 @@ func NewSellRequest(peer route.Vertex, assetID *asset.ID, } return &SellRequest{ - Peer: peer, - Version: latestSellRequestVersion, - ID: id, - AssetID: assetID, - AssetGroupKey: assetGroupKey, - AssetAmount: assetAmount, - SuggestedAssetRate: suggestedAssetRate, + Peer: peer, + Version: latestSellRequestVersion, + ID: id, + AssetID: assetID, + AssetGroupKey: assetGroupKey, + AssetAmount: assetAmount, + AssetRateHint: assetRateHint, }, nil } @@ -105,24 +102,25 @@ func NewSellRequestFromWire(wireMsg WireMessage, "request") } + expiry := time.Unix(int64(msgData.Expiry.Val), 0) + // Extract the suggested asset to BTC rate if provided. - var suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint] + var assetRateHint fn.Option[AssetRate] msgData.SuggestedAssetRate.WhenSome( func(rate tlv.RecordT[tlv.TlvType19, TlvFixedPoint]) { fp := rate.Val.IntoBigIntFixedPoint() - suggestedAssetRate = - fn.Some[rfqmath.BigIntFixedPoint](fp) + assetRateHint = fn.Some(NewAssetRate(fp, expiry)) }, ) req := SellRequest{ - Peer: wireMsg.Peer, - Version: msgData.Version.Val, - ID: msgData.ID.Val, - AssetID: assetID, - AssetGroupKey: assetGroupKey, - AssetAmount: msgData.AssetMaxAmount.Val, - SuggestedAssetRate: suggestedAssetRate, + Peer: wireMsg.Peer, + Version: msgData.Version.Val, + ID: msgData.ID.Val, + AssetID: assetID, + AssetGroupKey: assetGroupKey, + AssetAmount: msgData.AssetMaxAmount.Val, + AssetRateHint: assetRateHint, } // Perform basic sanity checks on the quote request. @@ -199,9 +197,9 @@ func (q *SellRequest) String() string { } return fmt.Sprintf("SellRequest(peer=%x, id=%x, asset_id=%s, "+ - "asset_group_key=%x, asset_amount=%d, ask_asset_rate=%v)", + "asset_group_key=%x, asset_amount=%d, asset_rate_hint=%v)", q.Peer[:], q.ID[:], q.AssetID, groupKeyBytes, q.AssetAmount, - q.SuggestedAssetRate) + q.AssetRateHint) } // Ensure that the message type implements the OutgoingMsg interface. diff --git a/taprpc/priceoraclerpc/price_oracle.pb.go b/taprpc/priceoraclerpc/price_oracle.pb.go index afcc158f4..586013e9d 100644 --- a/taprpc/priceoraclerpc/price_oracle.pb.go +++ b/taprpc/priceoraclerpc/price_oracle.pb.go @@ -371,6 +371,10 @@ type QueryAssetRatesRequest struct { // asset_rates_hint is an optional suggestion of asset rates for the // transaction, intended to provide guidance on expected pricing. AssetRatesHint *AssetRates `protobuf:"bytes,5,opt,name=asset_rates_hint,json=assetRatesHint,proto3" json:"asset_rates_hint,omitempty"` + // The counterparty is the transaction's counterparty. The price oracle + // may use this information to adjust asset rates based on the + // structure of the Lightning Network topology. + TransactionCounterparty []byte `protobuf:"bytes,6,opt,name=transaction_counterparty,json=transactionCounterparty,proto3" json:"transaction_counterparty,omitempty"` } func (x *QueryAssetRatesRequest) Reset() { @@ -440,6 +444,13 @@ func (x *QueryAssetRatesRequest) GetAssetRatesHint() *AssetRates { return nil } +func (x *QueryAssetRatesRequest) GetTransactionCounterparty() []byte { + if x != nil { + return x.TransactionCounterparty + } + return nil +} + // QueryAssetRatesOkResponse is the successful response to a // QueryAssetRates call. type QueryAssetRatesOkResponse struct { @@ -665,7 +676,7 @@ var file_priceoraclerpc_price_oracle_proto_rawDesc = []byte{ 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x42, 0x04, 0x0a, 0x02, - 0x69, 0x64, 0x22, 0xed, 0x02, 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, + 0x69, 0x64, 0x22, 0xa8, 0x03, 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4a, 0x0a, 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, @@ -688,42 +699,46 @@ var file_priceoraclerpc_price_oracle_proto_rawDesc = []byte{ 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, - 0x6e, 0x74, 0x22, 0x58, 0x0a, 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x4f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3b, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, - 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, - 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x1a, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, - 0x72, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, - 0x73, 0x4f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x02, 0x6f, - 0x6b, 0x12, 0x42, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, - 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, - 0x73, 0x45, 0x72, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, - 0x29, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x55, 0x52, 0x43, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, - 0x12, 0x08, 0x0a, 0x04, 0x53, 0x41, 0x4c, 0x45, 0x10, 0x01, 0x32, 0x71, 0x0a, 0x0b, 0x50, 0x72, - 0x69, 0x63, 0x65, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, - 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, - 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, - 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x18, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, 0x74, 0x79, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x17, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x61, 0x72, 0x74, 0x79, 0x22, 0x58, 0x0a, + 0x19, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, + 0x4f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, 0x72, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3b, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x4f, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x42, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x45, 0x72, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2a, 0x29, 0x0a, 0x0f, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, + 0x08, 0x50, 0x55, 0x52, 0x43, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, + 0x41, 0x4c, 0x45, 0x10, 0x01, 0x32, 0x71, 0x0a, 0x0b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x4f, 0x72, + 0x61, 0x63, 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x26, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, + 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x27, 0x2e, 0x70, 0x72, 0x69, 0x63, 0x65, 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x6f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/taprpc/priceoraclerpc/price_oracle.proto b/taprpc/priceoraclerpc/price_oracle.proto index 4e600adee..d4851e044 100644 --- a/taprpc/priceoraclerpc/price_oracle.proto +++ b/taprpc/priceoraclerpc/price_oracle.proto @@ -124,6 +124,11 @@ message QueryAssetRatesRequest { // asset_rates_hint is an optional suggestion of asset rates for the // transaction, intended to provide guidance on expected pricing. AssetRates asset_rates_hint = 5; + + // The counterparty is the transaction's counterparty. The price oracle + // may use this information to adjust asset rates based on the + // structure of the Lightning Network topology. + bytes transaction_counterparty = 6; } // QueryAssetRatesOkResponse is the successful response to a diff --git a/taprpc/priceoraclerpc/price_oracle.swagger.json b/taprpc/priceoraclerpc/price_oracle.swagger.json index dabd8ba11..a67bb74f5 100644 --- a/taprpc/priceoraclerpc/price_oracle.swagger.json +++ b/taprpc/priceoraclerpc/price_oracle.swagger.json @@ -152,6 +152,14 @@ "required": false, "type": "string", "format": "uint64" + }, + { + "name": "transaction_counterparty", + "description": "The counterparty is the transaction's counterparty. The price oracle\nmay use this information to adjust asset rates based on the\nstructure of the Lightning Network topology.", + "in": "query", + "required": false, + "type": "string", + "format": "byte" } ], "tags": [