From 6e34a4bee7c46710c44cdbf510b8715640f0679b Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 23 Jun 2025 14:09:32 +0200 Subject: [PATCH 1/8] multi: add available rfq ids to rfqmsg.Htlc We add a new field to rfqmsg.Htlc which expresses the available quotes that may be used to send out this HTLC. This is done to allow LND to store an array of RFQ IDs to use later via the AuxTrafficShaper interface in order to query asset bandwidth and produce the correct asset related records for outgoing HTLCs. --- itest/rfq_test.go | 2 +- rfq/order.go | 4 +- rfqmsg/custom_channel_data.go | 5 +- rfqmsg/records.go | 149 +++++++++++++++++++++- rfqmsg/records_test.go | 39 +++++- rpcserver.go | 16 ++- tapchannel/aux_invoice_manager_test.go | 16 ++- tapchannelmsg/custom_channel_data_test.go | 7 +- 8 files changed, 214 insertions(+), 24 deletions(-) diff --git a/itest/rfq_test.go b/itest/rfq_test.go index 21aad600a..46cbb7839 100644 --- a/itest/rfq_test.go +++ b/itest/rfq_test.go @@ -382,7 +382,7 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) { } htlcCustomRecords := rfqmsg.NewHtlc( - assetAmounts, fn.Some(acceptedQuoteId), + assetAmounts, fn.Some(acceptedQuoteId), fn.None[[]rfqmsg.ID](), ) // Convert the custom records to a TLV map for inclusion in diff --git a/rfq/order.go b/rfq/order.go index 2b5c58f15..c3bfcf9ee 100644 --- a/rfq/order.go +++ b/rfq/order.go @@ -279,7 +279,9 @@ func (c *AssetSalePolicy) GenerateInterceptorResponse( // Include the asset balance in the HTLC record. htlcBalance := rfqmsg.NewAssetBalance(assetID, amt) htlcRecord := rfqmsg.NewHtlc( - []*rfqmsg.AssetBalance{htlcBalance}, fn.Some(c.AcceptedQuoteId), + []*rfqmsg.AssetBalance{htlcBalance}, + fn.Some(c.AcceptedQuoteId), + fn.None[[]rfqmsg.ID](), ) customRecords, err := lnwire.ParseCustomRecords(htlcRecord.Bytes()) diff --git a/rfqmsg/custom_channel_data.go b/rfqmsg/custom_channel_data.go index 19d3e2a25..1673cbb71 100644 --- a/rfqmsg/custom_channel_data.go +++ b/rfqmsg/custom_channel_data.go @@ -103,6 +103,7 @@ type JsonAssetTranche struct { // JsonHtlc is a struct that represents the asset information that can be // transferred via an HTLC. type JsonHtlc struct { - Balances []*JsonAssetTranche `json:"balances"` - RfqID string `json:"rfq_id"` + Balances []*JsonAssetTranche `json:"balances"` + RfqID string `json:"rfq_id"` + AvailableRfqIDs []string `json:"available_rfq_ids,omitempty"` } diff --git a/rfqmsg/records.go b/rfqmsg/records.go index b896f6ad6..ef683ec3e 100644 --- a/rfqmsg/records.go +++ b/rfqmsg/records.go @@ -24,6 +24,13 @@ const ( // be from the same asset group but from different tranches to be // encoded as an individual record. MaxNumOutputs = 2048 + + // MaxSendPaymentQuotes is the upper limit of quotes that may be + // acquired by the SendPayment RPC endpoint in order to carry out the + // payment. Acquiring more quotes than whatever is defined below is + // possible, but may cause performance and network I/O bottlenecks as + // a lot of RFQ messages are getting involved. + MaxSendPaymentQuotes = 50 ) var ( @@ -42,6 +49,12 @@ type ( // encode an RFQ id within the custom records of an HTLC record on the // wire. HtlcRfqIDType = tlv.TlvType65538 + + // AvailableRfqIDsType is the type alias for the TLV type that is used + // to encode the list of available RFQ IDs that can be used for an HTLC. + // This list is only meant to be handled by the complementary LND + // instance via the AuxTrafficShaper hooks. + AvailableRfqIDsType = tlv.TlvType65540 ) // SomeRfqIDRecord creates an optional record that represents an RFQ ID. @@ -49,6 +62,94 @@ func SomeRfqIDRecord(id ID) tlv.OptionalRecordT[HtlcRfqIDType, ID] { return tlv.SomeRecordT(tlv.NewPrimitiveRecord[HtlcRfqIDType, ID](id)) } +// HtlcRfqIDs is a helper wrapper around the array of IDs that can be encoded as +// records. +type HtlcRfqIDs struct { + // IDs is a list of RFQ IDs that are associated with the HTLC. + IDs []ID +} + +// Record returns the tlv record of RfqIDs. +func (r *HtlcRfqIDs) Record() tlv.Record { + size := func() uint64 { + var ( + buf bytes.Buffer + scratch [8]byte + ) + err := encodeRfqIDs(&buf, r, &scratch) + if err != nil { + panic(fmt.Sprintf("unable to encode RFQ IDs: %v", err)) + } + return uint64(buf.Len()) + } + + // Note that we set the type here as zero, as when used with a + // tlv.RecordT, the type param will be used as the type. + return tlv.MakeDynamicRecord( + 0, r, size, encodeRfqIDs, decodeRfqIDs, + ) +} + +// encodeRfqIDs encodes the RfqIDs struct on the wire. +func encodeRfqIDs(w io.Writer, val any, buf *[8]byte) error { + if rfqIDs, ok := val.(*HtlcRfqIDs); ok { + ids := rfqIDs.IDs + num := uint64(len(ids)) + if err := tlv.WriteVarInt(w, num, buf); err != nil { + return err + } + for _, id := range ids { + idCopy := id + if err := IdEncoder(w, &idCopy, buf); err != nil { + return err + } + } + + return nil + } + return tlv.NewTypeForEncodingErr(val, "*RfqIDs") +} + +// decodeRfqIDs decodes the RfqIDs from the wire. +func decodeRfqIDs(r io.Reader, val any, buf *[8]byte, _ uint64) error { + if ids, ok := val.(*HtlcRfqIDs); ok { + num, err := tlv.ReadVarInt(r, buf) + if err != nil { + return err + } + + if num > MaxSendPaymentQuotes { + return fmt.Errorf("available RFQ IDs array length too "+ + "big, max allowed is %v, got %v", + MaxSendPaymentQuotes, num) + } + + list := make([]ID, num) + for i := uint64(0); i < num; i++ { + var id ID + if err := IdDecoder(r, &id, buf, 32); err != nil { + return err + } + list[i] = id + } + + ids.IDs = list + + return nil + } + return tlv.NewTypeForDecodingErr(val, "*RfqIDs", 0, 0) +} + +// SomeRfqIDsRecord creates an optional record that represents the array of +// available RFQ IDs. +func SomeRfqIDsRecord( + ids []ID) tlv.OptionalRecordT[AvailableRfqIDsType, HtlcRfqIDs] { + + return tlv.SomeRecordT(tlv.NewRecordT[AvailableRfqIDsType, HtlcRfqIDs]( + HtlcRfqIDs{IDs: ids}, + )) +} + // Htlc is a record that represents the capacity change related to an in-flight // HTLC. This entails all the (asset_id, amount) tuples and other information // that we may need to be able to update the TAP portion of a commitment @@ -57,12 +158,23 @@ type Htlc struct { // Amounts is a list of asset balances that are changed by the HTLC. Amounts tlv.RecordT[HtlcAmountRecordType, AssetBalanceListRecord] - // RfqID is the RFQ ID that corresponds to the HTLC. + // RfqID is the RFQ ID that corresponds to the HTLC. This is the RFQ ID + // that got locked in and is being used for all the rfqmath related + // calculations. RfqID tlv.OptionalRecordT[HtlcRfqIDType, ID] + + // AvailableRfqIDs is a list of RFQ IDs that may be used for this HTLC. + // This is used by the traffic shaper when querying for available + // bandwidth. This is only the list of candidate RFQ IDs that may be + // picked when an HTLC is sent over to a peer. When one of these RFQ IDs + // gets picked it will be encoded as an Htlc.RfqID (see above field). + AvailableRfqIDs tlv.OptionalRecordT[AvailableRfqIDsType, HtlcRfqIDs] } // NewHtlc creates a new Htlc record with the given funded assets. -func NewHtlc(amounts []*AssetBalance, rfqID fn.Option[ID]) *Htlc { +func NewHtlc(amounts []*AssetBalance, rfqID fn.Option[ID], + availableRfqIDs fn.Option[[]ID]) *Htlc { + htlc := &Htlc{ Amounts: tlv.NewRecordT[HtlcAmountRecordType]( AssetBalanceListRecord{ @@ -70,10 +182,15 @@ func NewHtlc(amounts []*AssetBalance, rfqID fn.Option[ID]) *Htlc { }, ), } + rfqID.WhenSome(func(id ID) { htlc.RfqID = SomeRfqIDRecord(id) }) + availableRfqIDs.WhenSome(func(ids []ID) { + htlc.AvailableRfqIDs = SomeRfqIDsRecord(ids) + }) + return htlc } @@ -135,6 +252,12 @@ func (h *Htlc) Records() []tlv.Record { records = append(records, r.Record()) }) + h.AvailableRfqIDs.WhenSome( + func(r tlv.RecordT[AvailableRfqIDsType, HtlcRfqIDs]) { + records = append(records, r.Record()) + }, + ) + return records } @@ -154,11 +277,13 @@ func (h *Htlc) Encode(w io.Writer) error { // Decode deserializes the Htlc from the given io.Reader. func (h *Htlc) Decode(r io.Reader) error { rfqID := h.RfqID.Zero() + rfqIDs := h.AvailableRfqIDs.Zero() // Create the tlv stream. tlvStream, err := tlv.NewStream( h.Amounts.Record(), rfqID.Record(), + rfqIDs.Record(), ) if err != nil { return err @@ -173,6 +298,10 @@ func (h *Htlc) Decode(r io.Reader) error { h.RfqID = tlv.SomeRecordT(rfqID) } + if val, ok := typeMap[h.AvailableRfqIDs.TlvType()]; ok && val == nil { + h.AvailableRfqIDs = tlv.SomeRecordT(rfqIDs) + } + return nil } @@ -198,6 +327,13 @@ func (h *Htlc) AsJson() ([]byte, error) { j.RfqID = hex.EncodeToString(id[:]) }) + h.AvailableRfqIDs.ValOpt().WhenSome(func(rfqIDs HtlcRfqIDs) { + j.AvailableRfqIDs = make([]string, len(rfqIDs.IDs)) + for idx, id := range rfqIDs.IDs { + j.AvailableRfqIDs[idx] = hex.EncodeToString(id[:]) + } + }) + for idx, balance := range h.Balances() { j.Balances[idx] = &JsonAssetTranche{ AssetID: hex.EncodeToString(balance.AssetID.Val[:]), @@ -235,8 +371,9 @@ func HtlcFromCustomRecords(records lnwire.CustomRecords) (*Htlc, error) { // the custom records that we'd expect an asset HTLC to carry. func HasAssetHTLCCustomRecords(records lnwire.CustomRecords) bool { var ( - amountType HtlcAmountRecordType - rfqIDType HtlcRfqIDType + amountType HtlcAmountRecordType + rfqIDType HtlcRfqIDType + availableIDs AvailableRfqIDsType ) for key := range records { if key == uint64(amountType.TypeVal()) { @@ -246,6 +383,10 @@ func HasAssetHTLCCustomRecords(records lnwire.CustomRecords) bool { if key == uint64(rfqIDType.TypeVal()) { return true } + + if key == uint64(availableIDs.TypeVal()) { + return true + } } return false diff --git a/rfqmsg/records_test.go b/rfqmsg/records_test.go index 9898fb013..bb0ad2550 100644 --- a/rfqmsg/records_test.go +++ b/rfqmsg/records_test.go @@ -27,6 +27,11 @@ type htlcTestCase struct { type DummyChecker struct{} +var ( + dummyRFQ1 = ID(bytes.Repeat([]byte{1}, 32)) + dummyRFQ2 = ID(bytes.Repeat([]byte{2}, 32)) +) + func MockAssetMatchesSpecifier(_ context.Context, specifier asset.Specifier, id asset.ID) (bool, error) { @@ -112,7 +117,7 @@ func TestHtlc(t *testing.T) { name: "HTLC with balance asset", htlc: NewHtlc([]*AssetBalance{ NewAssetBalance([32]byte{1}, 1000), - }, fn.None[ID]()), + }, fn.None[ID](), fn.None[[]ID]()), expectRecords: true, //nolint:lll expectedJSON: `{ @@ -131,7 +136,7 @@ func TestHtlc(t *testing.T) { NewAssetBalance([32]byte{1}, 1000), NewAssetBalance([32]byte{1}, 2000), NewAssetBalance([32]byte{2}, 5000), - }, fn.None[ID]()), + }, fn.None[ID](), fn.None[[]ID]()), sumBalances: map[asset.ID]rfqmath.BigInt{ [32]byte{1}: rfqmath.NewBigIntFromUint64(3000), [32]byte{2}: rfqmath.NewBigIntFromUint64(5000), @@ -143,7 +148,29 @@ func TestHtlc(t *testing.T) { htlc: NewHtlc([]*AssetBalance{ NewAssetBalance([32]byte{1}, 1000), NewAssetBalance([32]byte{2}, 2000), - }, fn.Some(ID{0, 1, 2, 3, 4, 5, 6, 7})), + }, fn.Some(dummyRFQ1), fn.None[[]ID]()), + expectRecords: true, + //nolint:lll + expectedJSON: `{ + "balances": [ + { + "asset_id": "0100000000000000000000000000000000000000000000000000000000000000", + "amount": 1000 + }, + { + "asset_id": "0200000000000000000000000000000000000000000000000000000000000000", + "amount": 2000 + } + ], + "rfq_id": "0101010101010101010101010101010101010101010101010101010101010101" +}`, + }, + { + name: "channel with multiple balance assets", + htlc: NewHtlc([]*AssetBalance{ + NewAssetBalance([32]byte{1}, 1000), + NewAssetBalance([32]byte{2}, 2000), + }, fn.None[ID](), fn.Some([]ID{dummyRFQ1, dummyRFQ2})), expectRecords: true, //nolint:lll expectedJSON: `{ @@ -157,7 +184,11 @@ func TestHtlc(t *testing.T) { "amount": 2000 } ], - "rfq_id": "0001020304050607000000000000000000000000000000000000000000000000" + "rfq_id": "", + "available_rfq_ids": [ + "0101010101010101010101010101010101010101010101010101010101010101", + "0202020202020202020202020202020202020202020202020202020202020202" + ] }`, }, } diff --git a/rpcserver.go b/rpcserver.go index ab301069f..dbbee4fc5 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7969,7 +7969,9 @@ func (r *rpcServer) EncodeCustomRecords(_ context.Context, rfqID = fn.Some[rfqmsg.ID](id) } - htlc := rfqmsg.NewHtlc(assetAmounts, rfqID) + htlc := rfqmsg.NewHtlc( + assetAmounts, rfqID, fn.None[[]rfqmsg.ID](), + ) // We'll now map the HTLC struct into a set of TLV records, // which we can then encode into the map format expected. @@ -8111,7 +8113,9 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, "from peer %x with SCID %d", sellOrder.AssetAmount, quote.AssetRate.String(), quote.Peer, quote.ID.Scid()) - htlc := rfqmsg.NewHtlc(nil, fn.Some(quote.ID)) + htlc := rfqmsg.NewHtlc( + nil, fn.Some(quote.ID), fn.None[[]rfqmsg.ID](), + ) // We'll now map the HTLC struct into a set of TLV records, // which we can then encode into the expected map format. @@ -8246,7 +8250,9 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, var rfqID rfqmsg.ID copy(rfqID[:], acceptedQuote.Id) - htlc := rfqmsg.NewHtlc(nil, fn.Some(rfqID)) + htlc := rfqmsg.NewHtlc( + nil, fn.Some(rfqID), fn.None[[]rfqmsg.ID](), + ) // We'll now map the HTLC struct into a set of TLV records, // which we can then encode into the expected map format. @@ -8321,7 +8327,9 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, } } - htlc := rfqmsg.NewHtlc(balances, fn.None[rfqmsg.ID]()) + htlc := rfqmsg.NewHtlc( + balances, fn.None[rfqmsg.ID](), fn.None[[]rfqmsg.ID](), + ) // We'll now map the HTLC struct into a set of TLV records, // which we can then encode into the map format expected. diff --git a/tapchannel/aux_invoice_manager_test.go b/tapchannel/aux_invoice_manager_test.go index 310101dac..68e9dc7fc 100644 --- a/tapchannel/aux_invoice_manager_test.go +++ b/tapchannel/aux_invoice_manager_test.go @@ -801,18 +801,22 @@ func genHtlc(t *rapid.T, balance []*rfqmsg.AssetBalance, // Introduce a chance of no rfqID in this htlc. if rapid.Bool().Draw(t, "has_rfqid") { - return rfqmsg.NewHtlc(balance, fn.None[rfqmsg.ID]()) + return rfqmsg.NewHtlc( + balance, fn.None[rfqmsg.ID](), fn.None[[]rfqmsg.ID](), + ) } // Introduce a chance of a mismatch in the expected and actual htlc // rfqID. if rapid.Bool().Draw(t, "rfqid_match") { - return rfqmsg.NewHtlc(balance, fn.Some(dummyRfqID( - rapid.IntRange(0, 255).Draw(t, "scid"), - ))) + return rfqmsg.NewHtlc( + balance, fn.Some(dummyRfqID( + rapid.IntRange(0, 255).Draw(t, "scid"), + )), fn.None[[]rfqmsg.ID](), + ) } - return rfqmsg.NewHtlc(balance, fn.Some(rfqID)) + return rfqmsg.NewHtlc(balance, fn.Some(rfqID), fn.None[[]rfqmsg.ID]()) } // genRequest generates an InvoiceHtlcModifyRequest with random values. This @@ -1112,7 +1116,7 @@ func testNonAssetHints() []*lnrpc.RouteHint { func newWireCustomRecords(t *testing.T, amounts []*rfqmsg.AssetBalance, rfqID fn.Option[rfqmsg.ID]) lnwire.CustomRecords { - htlc := rfqmsg.NewHtlc(amounts, rfqID) + htlc := rfqmsg.NewHtlc(amounts, rfqID, fn.None[[]rfqmsg.ID]()) customRecords, err := lnwire.ParseCustomRecords(htlc.Bytes()) require.NoError(t, err) diff --git a/tapchannelmsg/custom_channel_data_test.go b/tapchannelmsg/custom_channel_data_test.go index 5a728c77a..2124504d6 100644 --- a/tapchannelmsg/custom_channel_data_test.go +++ b/tapchannelmsg/custom_channel_data_test.go @@ -282,7 +282,7 @@ func TestHtlcCustomData(t *testing.T) { rfqmsg.NewAssetBalance(assetID1, 1000), rfqmsg.NewAssetBalance(assetID2, 2000), rfqmsg.NewAssetBalance(assetID3, 5000), - }, fn.Some(rfqID)) + }, fn.Some(rfqID), fn.Some([]rfqmsg.ID{rfqID})) var customChannelData bytes.Buffer require.NoError(t, htlc.Encode(&customChannelData)) @@ -309,7 +309,10 @@ func TestHtlcCustomData(t *testing.T) { "amount": 5000 } ], - "rfq_id": "` + hexStr(rfqID[:]) + `" + "rfq_id": "` + hexStr(rfqID[:]) + `", + "available_rfq_ids": [ + "` + hexStr(rfqID[:]) + `" + ] }` require.Equal(t, expected, formattedJSON.String()) } From 6c82e107f8ebf10522ac0d01ed289afa2d8b6733 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 23 Jun 2025 11:55:52 +0200 Subject: [PATCH 2/8] tapchannel: update paymentBandwidth to handle mulitple available IDs This commit performs a small refactor to the paymentBandwidth helper. Since we now have multiple candidate RFQ IDs, we extract the main logic of calculating the bandwidth into a helper, and call it once for each of the available RFQ IDs. --- tapchannel/aux_traffic_shaper.go | 88 +++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/tapchannel/aux_traffic_shaper.go b/tapchannel/aux_traffic_shaper.go index e687ae4e8..7b725e673 100644 --- a/tapchannel/aux_traffic_shaper.go +++ b/tapchannel/aux_traffic_shaper.go @@ -277,50 +277,80 @@ func paymentBandwidthAssetUnits(htlcAssetAmount, computedLocal uint64, } // paymentBandwidth returns the available payment bandwidth of the channel based -// on the asset rate of the RFQ quote that is included in the HTLC and the asset -// units of the local balance. +// on the list of availalbe RFQ IDs. If any of those IDs matches the channel, we +// calculate the bandwidth based on its asset rate. func (s *AuxTrafficShaper) paymentBandwidth(htlc *rfqmsg.Htlc, localBalance uint64, linkBandwidth, minHtlcAmt lnwire.MilliSatoshi, fundingChan *cmsg.OpenChannel) (lnwire.MilliSatoshi, error) { - // If the HTLC doesn't have an RFQ ID, it's incomplete, and we cannot - // determine the bandwidth. - if htlc.RfqID.ValOpt().IsNone() { - log.Tracef("No RFQ ID in HTLC, cannot determine matching " + - "outgoing channel") + // If the HTLC doesn't have any available RFQ IDs, it's incomplete, and + // we cannot determine the bandwidth. The available RFQ IDs is the list + // of RFQ IDs that may be used for the HTLCs of a payment. If they are + // missing something is wrong. + if htlc.AvailableRfqIDs.IsNone() { + log.Tracef("No available RFQ IDs in HTLC, cannot determine " + + "matching outgoing channel") return 0, nil } - // For every other use case (i.e. a normal payment with a negotiated - // quote or a multi-hop keysend that also uses a quote), we need to look - // up the accepted quote and determine the outgoing bandwidth in - // satoshis based on the local asset balance. - rfqID := htlc.RfqID.ValOpt().UnsafeFromSome() + // Retrieve the available RFQ IDs. + availableIDs := htlc.AvailableRfqIDs.UnsafeFromSome().Val.IDs + acceptedSellQuotes := s.cfg.RfqManager.PeerAcceptedSellQuotes() acceptedBuyQuotes := s.cfg.RfqManager.LocalAcceptedBuyQuotes() - sellQuote, isSellQuote := acceptedSellQuotes[rfqID.Scid()] - buyQuote, isBuyQuote := acceptedBuyQuotes[rfqID.Scid()] + // Now we'll go over our available RFQ IDs and try to find one that can + // produce bandwidth over the channel. + for _, rfqID := range availableIDs { + // For this rfqID we'll fetch the corresponding quote and rate. + sellQuote, isSellQuote := acceptedSellQuotes[rfqID.Scid()] + buyQuote, isBuyQuote := acceptedBuyQuotes[rfqID.Scid()] - var ( - rate rfqmsg.AssetRate - specifier asset.Specifier - ) - switch { - case isSellQuote: - rate = sellQuote.AssetRate - specifier = sellQuote.Request.AssetSpecifier + var ( + rate rfqmsg.AssetRate + specifier asset.Specifier + ) + switch { + case isSellQuote: + rate = sellQuote.AssetRate + specifier = sellQuote.Request.AssetSpecifier + + case isBuyQuote: + rate = buyQuote.AssetRate + specifier = buyQuote.Request.AssetSpecifier + + default: + return 0, fmt.Errorf("no accepted quote found for RFQ "+ + "ID %x (SCID %d)", rfqID[:], rfqID.Scid()) + } - case isBuyQuote: - rate = buyQuote.AssetRate - specifier = buyQuote.Request.AssetSpecifier + bandwidth, err := s.paymentBandwidthRFQ( + rfqID, rate, specifier, localBalance, linkBandwidth, + minHtlcAmt, fundingChan, + ) + if err != nil { + return 0, err + } - default: - return 0, fmt.Errorf("no accepted quote found for RFQ ID "+ - "%x (SCID %d)", rfqID[:], rfqID.Scid()) + // We know that we establish 1 quote per peer in the scope of + // each payment. This means that the first quote that produces + // bandwidth is the only quote that can produce bandwidth, so + // we immediately return it. + if bandwidth > 0 { + return bandwidth, nil + } } - // Now that we found the quote, we can determine if this quote is even + return 0, nil +} + +// paymentBandwidthRFQ retrieves the bandwidth for a specific channel and quote. +func (s *AuxTrafficShaper) paymentBandwidthRFQ(rfqID rfqmsg.ID, + rate rfqmsg.AssetRate, specifier asset.Specifier, localBalance uint64, + linkBandwidth, minHtlcAmt lnwire.MilliSatoshi, + fundingChan *cmsg.OpenChannel) (lnwire.MilliSatoshi, error) { + + // Now that we have the quote, we can determine if this quote is even // compatible with this channel. If not, we cannot forward the HTLC // and should return 0 bandwidth. for _, b := range fundingChan.FundedAssets.Val.Outputs { From 967e5f335eb781e5392413a47a996400c403c2b2 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Tue, 22 Jul 2025 11:27:55 +0200 Subject: [PATCH 3/8] tapchannel: AuxTrafficShaper finds matching available quote When LND queries the PaymentBandwidth there's no way to signal back which RFQ ID ended up being used for that bandwidth calculation. We rely on the assumption that one quote is established per peer within the scope of a payment. This way, the AuxTrafficShaper methods spot the quote that it needs to use by matching the peer of the quote with the peer that LND is going to send this HTLC to. --- server.go | 4 +- tapchannel/aux_traffic_shaper.go | 69 ++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/server.go b/server.go index e7ecbb504..8d958062c 100644 --- a/server.go +++ b/server.go @@ -1118,7 +1118,7 @@ func (s *Server) PaymentBandwidth(fundingBlob, htlcBlob, return s.cfg.AuxTrafficShaper.PaymentBandwidth( fundingBlob, htlcBlob, commitmentBlob, linkBandwidth, htlcAmt, - htlcView, + htlcView, peer, ) } @@ -1140,7 +1140,7 @@ func (s *Server) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, } return s.cfg.AuxTrafficShaper.ProduceHtlcExtraData( - totalAmount, htlcCustomRecords, + totalAmount, htlcCustomRecords, peer, ) } diff --git a/tapchannel/aux_traffic_shaper.go b/tapchannel/aux_traffic_shaper.go index 7b725e673..1e0bea4fc 100644 --- a/tapchannel/aux_traffic_shaper.go +++ b/tapchannel/aux_traffic_shaper.go @@ -19,6 +19,7 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/tlv" ) @@ -116,8 +117,8 @@ func (s *AuxTrafficShaper) ShouldHandleTraffic(cid lnwire.ShortChannelID, // called first. func (s *AuxTrafficShaper) PaymentBandwidth(fundingBlob, htlcBlob, commitmentBlob lfn.Option[tlv.Blob], linkBandwidth, - htlcAmt lnwire.MilliSatoshi, - htlcView lnwallet.AuxHtlcView) (lnwire.MilliSatoshi, error) { + htlcAmt lnwire.MilliSatoshi, htlcView lnwallet.AuxHtlcView, + peer route.Vertex) (lnwire.MilliSatoshi, error) { fundingBlobBytes := fundingBlob.UnwrapOr(nil) htlcBytes := htlcBlob.UnwrapOr(nil) @@ -230,6 +231,7 @@ func (s *AuxTrafficShaper) PaymentBandwidth(fundingBlob, htlcBlob, // the asset units in our local balance. return s.paymentBandwidth( htlc, computedLocal, linkBandwidth, minHtlcAmt, fundingChan, + peer, ) } @@ -277,11 +279,12 @@ func paymentBandwidthAssetUnits(htlcAssetAmount, computedLocal uint64, } // paymentBandwidth returns the available payment bandwidth of the channel based -// on the list of availalbe RFQ IDs. If any of those IDs matches the channel, we +// on the list of available RFQ IDs. If any of those IDs matches the channel, we // calculate the bandwidth based on its asset rate. func (s *AuxTrafficShaper) paymentBandwidth(htlc *rfqmsg.Htlc, localBalance uint64, linkBandwidth, minHtlcAmt lnwire.MilliSatoshi, - fundingChan *cmsg.OpenChannel) (lnwire.MilliSatoshi, error) { + fundingChan *cmsg.OpenChannel, + peer route.Vertex) (lnwire.MilliSatoshi, error) { // If the HTLC doesn't have any available RFQ IDs, it's incomplete, and // we cannot determine the bandwidth. The available RFQ IDs is the list @@ -309,13 +312,16 @@ func (s *AuxTrafficShaper) paymentBandwidth(htlc *rfqmsg.Htlc, var ( rate rfqmsg.AssetRate specifier asset.Specifier + quotePeer route.Vertex ) switch { case isSellQuote: + quotePeer = sellQuote.Peer rate = sellQuote.AssetRate specifier = sellQuote.Request.AssetSpecifier case isBuyQuote: + quotePeer = buyQuote.Peer rate = buyQuote.AssetRate specifier = buyQuote.Request.AssetSpecifier @@ -324,6 +330,12 @@ func (s *AuxTrafficShaper) paymentBandwidth(htlc *rfqmsg.Htlc, "ID %x (SCID %d)", rfqID[:], rfqID.Scid()) } + // If the channel peer does not match the quote peer, continue + // to the next available quote. + if peer != quotePeer { + continue + } + bandwidth, err := s.paymentBandwidthRFQ( rfqID, rate, specifier, localBalance, linkBandwidth, minHtlcAmt, fundingChan, @@ -439,8 +451,8 @@ func ComputeLocalBalance(commitment cmsg.Commitment, // blob of an HTLC, may produce a different blob or modify the amount of bitcoin // this HTLC should carry. func (s *AuxTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, - htlcCustomRecords lnwire.CustomRecords) (lnwire.MilliSatoshi, - lnwire.CustomRecords, error) { + htlcCustomRecords lnwire.CustomRecords, + peer route.Vertex) (lnwire.MilliSatoshi, lnwire.CustomRecords, error) { if !rfqmsg.HasAssetHTLCCustomRecords(htlcCustomRecords) { log.Tracef("No asset HTLC custom records, not producing " + @@ -464,16 +476,47 @@ func (s *AuxTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, return totalAmount, htlcCustomRecords, nil } - if htlc.RfqID.ValOpt().IsNone() { - return 0, nil, fmt.Errorf("no RFQ ID present in HTLC blob") + // Within the context of a payment we may negotiate multiple quotes. All + // of the quotes that may be used for a payment are encoded in the + // following field. If we don't have any available quotes then we can't + // proceed. + if htlc.AvailableRfqIDs.IsNone() { + return 0, nil, fmt.Errorf("no available RFQ IDs present in " + + "HTLC blob") } - rfqID := htlc.RfqID.ValOpt().UnsafeFromSome() acceptedQuotes := s.cfg.RfqManager.PeerAcceptedSellQuotes() - quote, ok := acceptedQuotes[rfqID.Scid()] - if !ok { - return 0, nil, fmt.Errorf("no accepted quote found for RFQ ID "+ - "%x (SCID %d)", rfqID[:], rfqID.Scid()) + availableIDs := htlc.AvailableRfqIDs.UnsafeFromSome().Val.IDs + + var ( + rfqID rfqmsg.ID + quote rfqmsg.SellAccept + ) + + // Let's find the quote that matches this peer. This will be the quote + // that we'll use to calculate the asset units. Given that we may only + // establish a maximum of 1 quote per peer per payment, this check is + // safe to perform as there are no competing quotes for a certain peer. + for _, id := range availableIDs { + q, ok := acceptedQuotes[id.Scid()] + if !ok { + continue + } + + // We found the quote to use, now let's set the related fields. + if q.Peer == peer { + // This is the actual RFQ ID that our peer will use to + // perform checks on their end. + rfqID = id + htlc.RfqID = rfqmsg.SomeRfqIDRecord(rfqID) + quote = q + break + } + } + + if htlc.RfqID.IsNone() { + return 0, nil, fmt.Errorf("none of the available RFQ IDs "+ + "match our peer=%s", peer) } // Now that we've queried the accepted quote, we know how many asset From 9bb47136f8a51fd05e0e391df2aa540da58e1bd2 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Fri, 4 Jul 2025 10:48:03 +0200 Subject: [PATCH 4/8] fn: add SendOrDone --- fn/send.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fn/send.go b/fn/send.go index 6721362de..865bc2d14 100644 --- a/fn/send.go +++ b/fn/send.go @@ -1,5 +1,7 @@ package fn +import "context" + // SendOrQuit attempts to and a message through channel c. If this succeeds, // then bool is returned. Otherwise if a quit signal is received first, then // false is returned. @@ -12,6 +14,19 @@ func SendOrQuit[T any, Q any](c chan<- T, msg T, quit chan Q) bool { } } +// SendOrDone attempts to and a message through channel c. If this succeeds, +// then bool is returned. Otherwise if a ctx done signal is received first, then +// false is returned. +func SendOrDone[T any](ctx context.Context, c chan<- T, msg T) bool { + select { + case c <- msg: + return true + + case <-ctx.Done(): + return false + } +} + // SendAll attempts to send all messages through channel c. // // TODO(roasbeef): add non-blocking variant? From ab1cdd6768c8d4252d21cbcdff3e6ba6fe30585d Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 23 Jun 2025 12:45:32 +0200 Subject: [PATCH 5/8] rpcserver: SendPayment uses multiple RFQ quotes The RPC method now uses all of the introduced features. Instead of acquiring just one quote we now extract that logic into a helper and call it once for each valid peer. We then encode the array of available RFQ IDs into the first hop records and hand it over to LND. --- rpcserver.go | 291 +++++++++--- taprpc/tapchannelrpc/tapchannel.pb.go | 447 +++++++++++-------- taprpc/tapchannelrpc/tapchannel.proto | 22 +- taprpc/tapchannelrpc/tapchannel.swagger.json | 21 +- 4 files changed, 529 insertions(+), 252 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index dbbee4fc5..1d44dc0b0 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -109,6 +109,13 @@ const ( // maxRfqHopHints is the maximum number of RFQ quotes that may be // encoded as hop hints in a bolt11 invoice. maxRfqHopHints = 20 + + // multiRfqNegotiationTimeout is the timeout within which we expect our + // peer and our node to reach an agreement over a quote. This timeout + // is a bit shorter in the context of multi-RFQ in order to not block + // the whole payment if only one of the peers is taking too long to + // respond. + multiRfqNegotiationTimeout = time.Second * 8 ) type ( @@ -8114,7 +8121,8 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, quote.AssetRate.String(), quote.Peer, quote.ID.Scid()) htlc := rfqmsg.NewHtlc( - nil, fn.Some(quote.ID), fn.None[[]rfqmsg.ID](), + nil, fn.None[rfqmsg.ID](), + fn.Some([]rfqmsg.ID{quote.ID}), ) // We'll now map the HTLC struct into a set of TLV records, @@ -8155,24 +8163,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, "use: %w", err) } - // TODO(george): temporary as multi-rfq send is not supported - // yet - if peerPubKey == nil && len(chanMap) > 1 { - return fmt.Errorf("multiple valid peers found, need " + - "specify peer pub key") - } - - // Even if the user didn't specify the peer public key before, - // we definitely know it now. So let's make sure it's always - // set. - // - // TODO(george): we just grab the first value, this is temporary - // until multi-rfq send is implemented. - for _, v := range chanMap { - peerPubKey = &v[0].ChannelInfo.PubKeyBytes - break - } - // paymentMaxAmt is the maximum amount that the counterparty is // expected to pay. This is the amount that the invoice is // asking for plus the fee limit in milli-satoshis. @@ -8181,77 +8171,53 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, return err } - resp, err := r.AddAssetSellOrder( - ctx, &rfqrpc.AddAssetSellOrderRequest{ - AssetSpecifier: &rpcSpecifier, - PaymentMaxAmt: uint64(paymentMaxAmt), - Expiry: uint64(expiry.Unix()), - PeerPubKey: peerPubKey[:], - TimeoutSeconds: uint32( - rfq.DefaultTimeout.Seconds(), - ), + rpcCtx, _, client := r.cfg.Lnd.Client.RawClientWithMacAuth(ctx) + payReqInfo, err := client.DecodePayReq( + rpcCtx, &lnrpc.PayReqString{ + PayReq: req.PaymentRequest.PaymentRequest, }, ) if err != nil { - return fmt.Errorf("error adding sell order: %w", err) + return err } - var acceptedQuote *rfqrpc.PeerAcceptedSellQuote - switch r := resp.Response.(type) { - case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote: - acceptedQuote = r.AcceptedQuote - - case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote: - return fmt.Errorf("peer %v sent back an invalid "+ - "quote, status: %v", r.InvalidQuote.Peer, - r.InvalidQuote.Status.String()) - - case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote: - return fmt.Errorf("peer %v rejected the quote, code: "+ - "%v, error message: %v", r.RejectedQuote.Peer, - r.RejectedQuote.ErrorCode, - r.RejectedQuote.ErrorMessage) + payHash := payReqInfo.PaymentHash - default: - return fmt.Errorf("unexpected response type: %T", r) - } + // Since we don't have any pre-negotiated rfq IDs available, we + // need to initiate the negotiation procedure for this payment. + // We'll store here all the quotes we acquired successfully. + acquiredQuotes := r.acquirePaymentQuotes( + ctx, &rpcSpecifier, paymentMaxAmt, expiry, chanMap, + req.AllowOverpay, payHash, + ) - // Check if the payment requires overpayment based on the quote. - err = checkOverpayment( - acceptedQuote, paymentMaxAmt, req.AllowOverpay, + // Send out the information about the acquired quotes on the + // stream. + err = notifyUserForPaymentQuotes( + stream, acquiredQuotes, payHash, peerPubKey, ) if err != nil { return err } - // Send out the information about the quote on the stream. - err = stream.Send(&tchrpc.SendPaymentResponse{ - Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{ - AcceptedSellOrder: acceptedQuote, - }, - }) - if err != nil { - return err + if len(acquiredQuotes) == 0 { + return fmt.Errorf("payment %v (amt=%v) failed to "+ + "acquire any quotes", payHash, + btcutil.Amount(req.PaymentRequest.Amt)) } - // Unmarshall the accepted quote's asset rate. - assetRate, err := rpcutils.UnmarshalRfqFixedPoint( - acceptedQuote.BidAssetRate, + quoteIDs := fn.Map( + acquiredQuotes, + func(quote *rfqrpc.PeerAcceptedSellQuote) rfqmsg.ID { + return rfqmsg.ID(quote.Id) + }, ) - if err != nil { - return fmt.Errorf("error unmarshalling asset rate: %w", - err) - } - - rpcsLog.Infof("Got quote for %v asset units at %v asset/BTC "+ - "from peer %x with SCID %d", acceptedQuote.AssetAmount, - assetRate, peerPubKey, acceptedQuote.Scid) - - var rfqID rfqmsg.ID - copy(rfqID[:], acceptedQuote.Id) + // We now create the HTLC with all the available rfq IDs. This + // will be used by LND later to query the bandwidth of various + // channels based on the established quotes. htlc := rfqmsg.NewHtlc( - nil, fn.Some(rfqID), fn.None[[]rfqmsg.ID](), + nil, fn.None[rfqmsg.ID](), fn.Some(quoteIDs), ) // We'll now map the HTLC struct into a set of TLV records, @@ -8821,6 +8787,67 @@ func calculateAssetMaxAmount(ctx context.Context, priceOracle rfq.PriceOracle, return maxMathUnits.ToUint64(), nil } +// notifyUserForPaymentQuotes sends the accepted quotes to the RPC user of +// SendPayment via the response stream. It also logs info on each accepted +// quote. +func notifyUserForPaymentQuotes( + stream tchrpc.TaprootAssetChannels_SendPaymentServer, + quotes []*rfqrpc.PeerAcceptedSellQuote, payHash string, + peerPubKey *route.Vertex) error { + + var ( + err error + firstQuote *rfqrpc.PeerAcceptedSellQuote + ) + + for _, quote := range quotes { + if firstQuote == nil { + firstQuote = quote + } + + // Unmarshall the accepted quote's asset rate. + assetRate, err := rpcutils.UnmarshalRfqFixedPoint( + quote.BidAssetRate, + ) + if err != nil { + rpcsLog.Errorf("error unmarshalling asset rate for "+ + "payment %v, peer %v, scid %v: %v", payHash, + quote.Peer, quote.Scid, err) + + continue + } + + // Log the negotiated quote for debugging purposes. + rpcsLog.Infof("Payment %v got quote for %v asset units at %v "+ + " asset/BTC from peer %x with SCID %d", payHash, + quote.AssetAmount, assetRate, peerPubKey, quote.Scid) + } + + // Let's send the first quote as a single quote stream response. This is + // done to maintain compatibility with old behavior, where a single + // quote was returned in the response stream. + if firstQuote != nil { + err = stream.Send(&tchrpc.SendPaymentResponse{ + Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{ + AcceptedSellOrder: firstQuote, + }, + }) + if err != nil { + return err + } + } + + // We now stream the full array of negotiated quotes to the response + // stream. + return stream.Send(&tchrpc.SendPaymentResponse{ + Result: &tchrpc.SendPaymentResponse_AcceptedSellOrders{ + AcceptedSellOrders: &tchrpc.AcceptedSellQuotes{ + AcceptedSellOrders: quotes, + }, + }, + }) +} + // validateInvoiceAmount validates the quote against the invoice we're trying to // add. It returns the value in msat that should be included in the invoice. func validateInvoiceAmount(acceptedQuote *rfqrpc.PeerAcceptedBuyQuote, @@ -8951,6 +8978,124 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context, return quote, nil } +// acquirePaymentQuotes attempts to asynchronously negotiate quotes for this +// payment with all available peers. The list of successfully negotiated quotes +// is returned. +func (r *rpcServer) acquirePaymentQuotes(ctx context.Context, + rpcSpecifier *rfqrpc.AssetSpecifier, paymentMaxAmt lnwire.MilliSatoshi, + expiry time.Time, chanMap rfq.PeerChanMap, + overpay bool, payHash string) []*rfqrpc.PeerAcceptedSellQuote { + + var acquiredQuotes []*rfqrpc.PeerAcceptedSellQuote + + collectorCtx, cancel := context.WithCancel(ctx) + defer cancel() + + quoteCollector := make(chan *rfqrpc.PeerAcceptedSellQuote) + errorCollector := make(chan error) + + // For each candidate peer in our list let's try to establish a + // sufficient quote for this payment. Any result that we collect will be + // reported back to the collector via the quote/err channels. + for peer := range chanMap { + // We make sure to always write the result to a channel before + // returning. This way the collector can expect a certain number + // of items via the channels. + go func(peer route.Vertex) { + rpcCtx, cancel := context.WithTimeout( + collectorCtx, multiRfqNegotiationTimeout, + ) + defer cancel() + + quote, err := r.acquireSellQuote( + rpcCtx, rpcSpecifier, paymentMaxAmt, + expiry, &peer, + ) + if err != nil { + fn.SendOrDone(collectorCtx, errorCollector, err) + return + } + + err = checkOverpayment(quote, paymentMaxAmt, overpay) + if err != nil { + fn.SendOrDone(collectorCtx, errorCollector, err) + return + } + + fn.SendOrDone(collectorCtx, quoteCollector, quote) + }(peer) + } + + resCounter := 0 + for { + select { + case quote := <-quoteCollector: + acquiredQuotes = append(acquiredQuotes, quote) + resCounter++ + + case err := <-errorCollector: + rpcsLog.Errorf("Payment %v got error while trying to "+ + "acquire quote: %v", payHash, err) + resCounter++ + + case <-ctx.Done(): + return nil + } + + // If all routines produced a result, or the maximum allowed + // number of quotes was acquired, return the list of quotes. + // This will also cancel the collector context in the defer + // function, causing any dangling routines to immediately exit. + if resCounter >= len(chanMap) || + len(acquiredQuotes) >= rfqmsg.MaxSendPaymentQuotes { + + return acquiredQuotes + } + } +} + +// acquireSellQuote performs an RFQ negotiation with the target peer and quote +// parameters and returns the quote if the negotiation was successful. +func (r *rpcServer) acquireSellQuote(ctx context.Context, + rpcSpecifier *rfqrpc.AssetSpecifier, paymentMaxAmt lnwire.MilliSatoshi, + expiry time.Time, + peerPubKey *route.Vertex) (*rfqrpc.PeerAcceptedSellQuote, error) { + + resp, err := r.AddAssetSellOrder( + ctx, &rfqrpc.AddAssetSellOrderRequest{ + AssetSpecifier: rpcSpecifier, + PaymentMaxAmt: uint64(paymentMaxAmt), + Expiry: uint64(expiry.Unix()), + PeerPubKey: peerPubKey[:], + TimeoutSeconds: uint32( + rfq.DefaultTimeout.Seconds(), + ), + }, + ) + if err != nil { + return nil, fmt.Errorf("error adding sell order: %w", err) + } + + switch r := resp.Response.(type) { + case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote: + return r.AcceptedQuote, nil + + case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote: + return nil, fmt.Errorf("peer %v sent back an invalid "+ + "quote, status: %v", r.InvalidQuote.Peer, + r.InvalidQuote.Status.String()) + + case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote: + return nil, fmt.Errorf("peer %v rejected the quote, code: "+ + "%v, error message: %v", r.RejectedQuote.Peer, + r.RejectedQuote.ErrorCode, + r.RejectedQuote.ErrorMessage) + + default: + return nil, fmt.Errorf("unexpected response type: %T", r) + } +} + // DeclareScriptKey declares a new script key to the wallet. This is useful // when the script key contains scripts, which would mean it wouldn't be // recognized by the wallet automatically. Declaring a script key will make any diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index b2ced05c8..d9f1b0498 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -482,6 +482,56 @@ func (x *SendPaymentRequest) GetGroupKey() []byte { return nil } +type AcceptedSellQuotes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // If swapping of assets is necessary to carry out the payment, a number of + // RFQ quotes may be negotiated for that purpose. The following field + // contains all the sell orders that were negotiated with our peers. + AcceptedSellOrders []*rfqrpc.PeerAcceptedSellQuote `protobuf:"bytes,1,rep,name=accepted_sell_orders,json=acceptedSellOrders,proto3" json:"accepted_sell_orders,omitempty"` +} + +func (x *AcceptedSellQuotes) Reset() { + *x = AcceptedSellQuotes{} + if protoimpl.UnsafeEnabled { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AcceptedSellQuotes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AcceptedSellQuotes) ProtoMessage() {} + +func (x *AcceptedSellQuotes) ProtoReflect() protoreflect.Message { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AcceptedSellQuotes.ProtoReflect.Descriptor instead. +func (*AcceptedSellQuotes) Descriptor() ([]byte, []int) { + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{6} +} + +func (x *AcceptedSellQuotes) GetAcceptedSellOrders() []*rfqrpc.PeerAcceptedSellQuote { + if x != nil { + return x.AcceptedSellOrders + } + return nil +} + type SendPaymentResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -490,6 +540,7 @@ type SendPaymentResponse struct { // Types that are assignable to Result: // // *SendPaymentResponse_AcceptedSellOrder + // *SendPaymentResponse_AcceptedSellOrders // *SendPaymentResponse_PaymentResult Result isSendPaymentResponse_Result `protobuf_oneof:"result"` } @@ -497,7 +548,7 @@ type SendPaymentResponse struct { func (x *SendPaymentResponse) Reset() { *x = SendPaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -510,7 +561,7 @@ func (x *SendPaymentResponse) String() string { func (*SendPaymentResponse) ProtoMessage() {} func (x *SendPaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -523,7 +574,7 @@ func (x *SendPaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendPaymentResponse.ProtoReflect.Descriptor instead. func (*SendPaymentResponse) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{6} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{7} } func (m *SendPaymentResponse) GetResult() isSendPaymentResponse_Result { @@ -540,6 +591,13 @@ func (x *SendPaymentResponse) GetAcceptedSellOrder() *rfqrpc.PeerAcceptedSellQuo return nil } +func (x *SendPaymentResponse) GetAcceptedSellOrders() *AcceptedSellQuotes { + if x, ok := x.GetResult().(*SendPaymentResponse_AcceptedSellOrders); ok { + return x.AcceptedSellOrders + } + return nil +} + func (x *SendPaymentResponse) GetPaymentResult() *lnrpc.Payment { if x, ok := x.GetResult().(*SendPaymentResponse_PaymentResult); ok { return x.PaymentResult @@ -556,18 +614,31 @@ type SendPaymentResponse_AcceptedSellOrder struct { // sell order is negotiated with the channel peer. The result will be // the first message in the response stream. If no swap is needed, the // payment results will be streamed directly. + // Deprecated. This will now only contain the first quote that was + // negotiated. Since the introduction of multi-rfq we now negotiate + // multiple quotes in the context of a payment. Use the new field named + // "accepted_sell_orders" to retrieve all of them. AcceptedSellOrder *rfqrpc.PeerAcceptedSellQuote `protobuf:"bytes,1,opt,name=accepted_sell_order,json=acceptedSellOrder,proto3,oneof"` } +type SendPaymentResponse_AcceptedSellOrders struct { + // If swapping of assets is necessary to carry out the payment, a number + // of RFQ quotes may be negotiated for that purpose. The following field + // contains all the sell orders that were negotiated with our peers. + AcceptedSellOrders *AcceptedSellQuotes `protobuf:"bytes,3,opt,name=accepted_sell_orders,json=acceptedSellOrders,proto3,oneof"` +} + type SendPaymentResponse_PaymentResult struct { - // The payment result of a single payment attempt. Multiple attempts may - // be returned per payment request until either the payment succeeds or - // the payment times out. + // The payment result of a single payment attempt. Multiple attempts + // may be returned per payment request until either the payment + // succeeds or the payment times out. PaymentResult *lnrpc.Payment `protobuf:"bytes,2,opt,name=payment_result,json=paymentResult,proto3,oneof"` } func (*SendPaymentResponse_AcceptedSellOrder) isSendPaymentResponse_Result() {} +func (*SendPaymentResponse_AcceptedSellOrders) isSendPaymentResponse_Result() {} + func (*SendPaymentResponse_PaymentResult) isSendPaymentResponse_Result() {} type HodlInvoice struct { @@ -581,7 +652,7 @@ type HodlInvoice struct { func (x *HodlInvoice) Reset() { *x = HodlInvoice{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -594,7 +665,7 @@ func (x *HodlInvoice) String() string { func (*HodlInvoice) ProtoMessage() {} func (x *HodlInvoice) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -607,7 +678,7 @@ func (x *HodlInvoice) ProtoReflect() protoreflect.Message { // Deprecated: Use HodlInvoice.ProtoReflect.Descriptor instead. func (*HodlInvoice) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{7} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{8} } func (x *HodlInvoice) GetPaymentHash() []byte { @@ -657,7 +728,7 @@ type AddInvoiceRequest struct { func (x *AddInvoiceRequest) Reset() { *x = AddInvoiceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -670,7 +741,7 @@ func (x *AddInvoiceRequest) String() string { func (*AddInvoiceRequest) ProtoMessage() {} func (x *AddInvoiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -683,7 +754,7 @@ func (x *AddInvoiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddInvoiceRequest.ProtoReflect.Descriptor instead. func (*AddInvoiceRequest) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{8} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{9} } func (x *AddInvoiceRequest) GetAssetId() []byte { @@ -742,7 +813,7 @@ type AddInvoiceResponse struct { func (x *AddInvoiceResponse) Reset() { *x = AddInvoiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[9] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -755,7 +826,7 @@ func (x *AddInvoiceResponse) String() string { func (*AddInvoiceResponse) ProtoMessage() {} func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[9] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -768,7 +839,7 @@ func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead. func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{9} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{10} } func (x *AddInvoiceResponse) GetAcceptedBuyQuote() *rfqrpc.PeerAcceptedBuyQuote { @@ -804,7 +875,7 @@ type AssetPayReq struct { func (x *AssetPayReq) Reset() { *x = AssetPayReq{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[10] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -817,7 +888,7 @@ func (x *AssetPayReq) String() string { func (*AssetPayReq) ProtoMessage() {} func (x *AssetPayReq) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[10] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -830,7 +901,7 @@ func (x *AssetPayReq) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetPayReq.ProtoReflect.Descriptor instead. func (*AssetPayReq) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{10} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{11} } func (x *AssetPayReq) GetAssetId() []byte { @@ -877,7 +948,7 @@ type AssetPayReqResponse struct { func (x *AssetPayReqResponse) Reset() { *x = AssetPayReqResponse{} if protoimpl.UnsafeEnabled { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[11] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +961,7 @@ func (x *AssetPayReqResponse) String() string { func (*AssetPayReqResponse) ProtoMessage() {} func (x *AssetPayReqResponse) ProtoReflect() protoreflect.Message { - mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[11] + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +974,7 @@ func (x *AssetPayReqResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetPayReqResponse.ProtoReflect.Descriptor instead. func (*AssetPayReqResponse) Descriptor() ([]byte, []int) { - return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{11} + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{12} } func (x *AssetPayReqResponse) GetAssetAmount() uint64 { @@ -1021,106 +1092,118 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x6c, 0x6f, 0x77, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x70, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4f, 0x76, 0x65, 0x72, 0x70, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x22, 0xa9, 0x01, 0x0a, - 0x13, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, - 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x08, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x30, 0x0a, 0x0b, 0x48, 0x6f, 0x64, 0x6c, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x87, 0x02, 0x0a, 0x11, 0x41, - 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, - 0x37, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x64, 0x6c, - 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x48, - 0x6f, 0x64, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x64, 0x6c, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x4b, 0x65, 0x79, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x79, 0x5f, 0x71, 0x75, 0x6f, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, - 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, - 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x73, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, - 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x22, 0x8e, 0x02, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x61, 0x70, - 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x52, 0x0e, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x12, 0x33, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0a, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, - 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x26, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, - 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xda, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, - 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, - 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, - 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0a, - 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, 0x74, 0x61, 0x70, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, - 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x53, 0x0a, 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, - 0x79, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, + 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x22, 0x65, 0x0a, 0x12, + 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, + 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, + 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, + 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x22, 0x80, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x13, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, + 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x55, 0x0a, 0x14, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x61, 0x70, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x73, 0x48, 0x00, 0x52, + 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x12, 0x37, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x08, 0x0a, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x30, 0x0a, 0x0b, 0x48, 0x6f, 0x64, 0x6c, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x87, 0x02, 0x0a, 0x11, 0x41, 0x64, 0x64, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, + 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x68, 0x6f, 0x64, 0x6c, 0x5f, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, + 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x64, + 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0b, 0x68, 0x6f, 0x64, 0x6c, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4b, + 0x65, 0x79, 0x22, 0xa2, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x79, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, + 0x6f, 0x74, 0x65, 0x52, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, + 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x52, 0x65, + 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x4b, 0x65, 0x79, 0x22, 0x8e, 0x02, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, + 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x3f, 0x0a, 0x0f, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x52, 0x0e, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x12, 0x33, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x36, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, + 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x61, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, + 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x06, 0x70, + 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xda, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, + 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, + 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, + 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, + 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 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, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, + 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, + 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0a, 0x41, 0x64, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x61, 0x70, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, + 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, + 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x22, + 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 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, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1135,7 +1218,7 @@ func file_tapchannelrpc_tapchannel_proto_rawDescGZIP() []byte { return file_tapchannelrpc_tapchannel_proto_rawDescData } -var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_tapchannelrpc_tapchannel_proto_goTypes = []any{ (*FundChannelRequest)(nil), // 0: tapchannelrpc.FundChannelRequest (*FundChannelResponse)(nil), // 1: tapchannelrpc.FundChannelResponse @@ -1143,55 +1226,58 @@ var file_tapchannelrpc_tapchannel_proto_goTypes = []any{ (*EncodeCustomRecordsRequest)(nil), // 3: tapchannelrpc.EncodeCustomRecordsRequest (*EncodeCustomRecordsResponse)(nil), // 4: tapchannelrpc.EncodeCustomRecordsResponse (*SendPaymentRequest)(nil), // 5: tapchannelrpc.SendPaymentRequest - (*SendPaymentResponse)(nil), // 6: tapchannelrpc.SendPaymentResponse - (*HodlInvoice)(nil), // 7: tapchannelrpc.HodlInvoice - (*AddInvoiceRequest)(nil), // 8: tapchannelrpc.AddInvoiceRequest - (*AddInvoiceResponse)(nil), // 9: tapchannelrpc.AddInvoiceResponse - (*AssetPayReq)(nil), // 10: tapchannelrpc.AssetPayReq - (*AssetPayReqResponse)(nil), // 11: tapchannelrpc.AssetPayReqResponse - nil, // 12: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry - nil, // 13: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry - (*routerrpc.SendPaymentRequest)(nil), // 14: routerrpc.SendPaymentRequest - (*rfqrpc.PeerAcceptedSellQuote)(nil), // 15: rfqrpc.PeerAcceptedSellQuote - (*lnrpc.Payment)(nil), // 16: lnrpc.Payment - (*lnrpc.Invoice)(nil), // 17: lnrpc.Invoice - (*rfqrpc.PeerAcceptedBuyQuote)(nil), // 18: rfqrpc.PeerAcceptedBuyQuote - (*lnrpc.AddInvoiceResponse)(nil), // 19: lnrpc.AddInvoiceResponse - (*taprpc.DecimalDisplay)(nil), // 20: taprpc.DecimalDisplay - (*taprpc.AssetGroup)(nil), // 21: taprpc.AssetGroup - (*taprpc.GenesisInfo)(nil), // 22: taprpc.GenesisInfo - (*lnrpc.PayReq)(nil), // 23: lnrpc.PayReq + (*AcceptedSellQuotes)(nil), // 6: tapchannelrpc.AcceptedSellQuotes + (*SendPaymentResponse)(nil), // 7: tapchannelrpc.SendPaymentResponse + (*HodlInvoice)(nil), // 8: tapchannelrpc.HodlInvoice + (*AddInvoiceRequest)(nil), // 9: tapchannelrpc.AddInvoiceRequest + (*AddInvoiceResponse)(nil), // 10: tapchannelrpc.AddInvoiceResponse + (*AssetPayReq)(nil), // 11: tapchannelrpc.AssetPayReq + (*AssetPayReqResponse)(nil), // 12: tapchannelrpc.AssetPayReqResponse + nil, // 13: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + nil, // 14: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + (*routerrpc.SendPaymentRequest)(nil), // 15: routerrpc.SendPaymentRequest + (*rfqrpc.PeerAcceptedSellQuote)(nil), // 16: rfqrpc.PeerAcceptedSellQuote + (*lnrpc.Payment)(nil), // 17: lnrpc.Payment + (*lnrpc.Invoice)(nil), // 18: lnrpc.Invoice + (*rfqrpc.PeerAcceptedBuyQuote)(nil), // 19: rfqrpc.PeerAcceptedBuyQuote + (*lnrpc.AddInvoiceResponse)(nil), // 20: lnrpc.AddInvoiceResponse + (*taprpc.DecimalDisplay)(nil), // 21: taprpc.DecimalDisplay + (*taprpc.AssetGroup)(nil), // 22: taprpc.AssetGroup + (*taprpc.GenesisInfo)(nil), // 23: taprpc.GenesisInfo + (*lnrpc.PayReq)(nil), // 24: lnrpc.PayReq } var file_tapchannelrpc_tapchannel_proto_depIdxs = []int32{ - 12, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + 13, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry 2, // 1: tapchannelrpc.EncodeCustomRecordsRequest.router_send_payment:type_name -> tapchannelrpc.RouterSendPaymentData - 13, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry - 14, // 3: tapchannelrpc.SendPaymentRequest.payment_request:type_name -> routerrpc.SendPaymentRequest - 15, // 4: tapchannelrpc.SendPaymentResponse.accepted_sell_order:type_name -> rfqrpc.PeerAcceptedSellQuote - 16, // 5: tapchannelrpc.SendPaymentResponse.payment_result:type_name -> lnrpc.Payment - 17, // 6: tapchannelrpc.AddInvoiceRequest.invoice_request:type_name -> lnrpc.Invoice - 7, // 7: tapchannelrpc.AddInvoiceRequest.hodl_invoice:type_name -> tapchannelrpc.HodlInvoice - 18, // 8: tapchannelrpc.AddInvoiceResponse.accepted_buy_quote:type_name -> rfqrpc.PeerAcceptedBuyQuote - 19, // 9: tapchannelrpc.AddInvoiceResponse.invoice_result:type_name -> lnrpc.AddInvoiceResponse - 20, // 10: tapchannelrpc.AssetPayReqResponse.decimal_display:type_name -> taprpc.DecimalDisplay - 21, // 11: tapchannelrpc.AssetPayReqResponse.asset_group:type_name -> taprpc.AssetGroup - 22, // 12: tapchannelrpc.AssetPayReqResponse.genesis_info:type_name -> taprpc.GenesisInfo - 23, // 13: tapchannelrpc.AssetPayReqResponse.pay_req:type_name -> lnrpc.PayReq - 0, // 14: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest - 3, // 15: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest - 5, // 16: tapchannelrpc.TaprootAssetChannels.SendPayment:input_type -> tapchannelrpc.SendPaymentRequest - 8, // 17: tapchannelrpc.TaprootAssetChannels.AddInvoice:input_type -> tapchannelrpc.AddInvoiceRequest - 10, // 18: tapchannelrpc.TaprootAssetChannels.DecodeAssetPayReq:input_type -> tapchannelrpc.AssetPayReq - 1, // 19: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse - 4, // 20: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse - 6, // 21: tapchannelrpc.TaprootAssetChannels.SendPayment:output_type -> tapchannelrpc.SendPaymentResponse - 9, // 22: tapchannelrpc.TaprootAssetChannels.AddInvoice:output_type -> tapchannelrpc.AddInvoiceResponse - 11, // 23: tapchannelrpc.TaprootAssetChannels.DecodeAssetPayReq:output_type -> tapchannelrpc.AssetPayReqResponse - 19, // [19:24] is the sub-list for method output_type - 14, // [14:19] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 14, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + 15, // 3: tapchannelrpc.SendPaymentRequest.payment_request:type_name -> routerrpc.SendPaymentRequest + 16, // 4: tapchannelrpc.AcceptedSellQuotes.accepted_sell_orders:type_name -> rfqrpc.PeerAcceptedSellQuote + 16, // 5: tapchannelrpc.SendPaymentResponse.accepted_sell_order:type_name -> rfqrpc.PeerAcceptedSellQuote + 6, // 6: tapchannelrpc.SendPaymentResponse.accepted_sell_orders:type_name -> tapchannelrpc.AcceptedSellQuotes + 17, // 7: tapchannelrpc.SendPaymentResponse.payment_result:type_name -> lnrpc.Payment + 18, // 8: tapchannelrpc.AddInvoiceRequest.invoice_request:type_name -> lnrpc.Invoice + 8, // 9: tapchannelrpc.AddInvoiceRequest.hodl_invoice:type_name -> tapchannelrpc.HodlInvoice + 19, // 10: tapchannelrpc.AddInvoiceResponse.accepted_buy_quote:type_name -> rfqrpc.PeerAcceptedBuyQuote + 20, // 11: tapchannelrpc.AddInvoiceResponse.invoice_result:type_name -> lnrpc.AddInvoiceResponse + 21, // 12: tapchannelrpc.AssetPayReqResponse.decimal_display:type_name -> taprpc.DecimalDisplay + 22, // 13: tapchannelrpc.AssetPayReqResponse.asset_group:type_name -> taprpc.AssetGroup + 23, // 14: tapchannelrpc.AssetPayReqResponse.genesis_info:type_name -> taprpc.GenesisInfo + 24, // 15: tapchannelrpc.AssetPayReqResponse.pay_req:type_name -> lnrpc.PayReq + 0, // 16: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest + 3, // 17: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest + 5, // 18: tapchannelrpc.TaprootAssetChannels.SendPayment:input_type -> tapchannelrpc.SendPaymentRequest + 9, // 19: tapchannelrpc.TaprootAssetChannels.AddInvoice:input_type -> tapchannelrpc.AddInvoiceRequest + 11, // 20: tapchannelrpc.TaprootAssetChannels.DecodeAssetPayReq:input_type -> tapchannelrpc.AssetPayReq + 1, // 21: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse + 4, // 22: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse + 7, // 23: tapchannelrpc.TaprootAssetChannels.SendPayment:output_type -> tapchannelrpc.SendPaymentResponse + 10, // 24: tapchannelrpc.TaprootAssetChannels.AddInvoice:output_type -> tapchannelrpc.AddInvoiceResponse + 12, // 25: tapchannelrpc.TaprootAssetChannels.DecodeAssetPayReq:output_type -> tapchannelrpc.AssetPayReqResponse + 21, // [21:26] is the sub-list for method output_type + 16, // [16:21] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_tapchannelrpc_tapchannel_proto_init() } @@ -1273,7 +1359,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*SendPaymentResponse); i { + switch v := v.(*AcceptedSellQuotes); i { case 0: return &v.state case 1: @@ -1285,7 +1371,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*HodlInvoice); i { + switch v := v.(*SendPaymentResponse); i { case 0: return &v.state case 1: @@ -1297,7 +1383,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*AddInvoiceRequest); i { + switch v := v.(*HodlInvoice); i { case 0: return &v.state case 1: @@ -1309,7 +1395,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*AddInvoiceResponse); i { + switch v := v.(*AddInvoiceRequest); i { case 0: return &v.state case 1: @@ -1321,7 +1407,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*AssetPayReq); i { + switch v := v.(*AddInvoiceResponse); i { case 0: return &v.state case 1: @@ -1333,6 +1419,18 @@ func file_tapchannelrpc_tapchannel_proto_init() { } } file_tapchannelrpc_tapchannel_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*AssetPayReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tapchannelrpc_tapchannel_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*AssetPayReqResponse); i { case 0: return &v.state @@ -1348,8 +1446,9 @@ func file_tapchannelrpc_tapchannel_proto_init() { file_tapchannelrpc_tapchannel_proto_msgTypes[3].OneofWrappers = []any{ (*EncodeCustomRecordsRequest_RouterSendPayment)(nil), } - file_tapchannelrpc_tapchannel_proto_msgTypes[6].OneofWrappers = []any{ + file_tapchannelrpc_tapchannel_proto_msgTypes[7].OneofWrappers = []any{ (*SendPaymentResponse_AcceptedSellOrder)(nil), + (*SendPaymentResponse_AcceptedSellOrders)(nil), (*SendPaymentResponse_PaymentResult)(nil), } type x struct{} @@ -1358,7 +1457,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tapchannelrpc_tapchannel_proto_rawDesc, NumEnums: 0, - NumMessages: 14, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index cbb3ef860..81a7e6882 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -155,17 +155,33 @@ message SendPaymentRequest { bytes group_key = 7; } +message AcceptedSellQuotes { + // If swapping of assets is necessary to carry out the payment, a number of + // RFQ quotes may be negotiated for that purpose. The following field + // contains all the sell orders that were negotiated with our peers. + repeated rfqrpc.PeerAcceptedSellQuote accepted_sell_orders = 1; +} + message SendPaymentResponse { oneof result { // In case channel assets need to be swapped to another asset, an asset // sell order is negotiated with the channel peer. The result will be // the first message in the response stream. If no swap is needed, the // payment results will be streamed directly. + // Deprecated. This will now only contain the first quote that was + // negotiated. Since the introduction of multi-rfq we now negotiate + // multiple quotes in the context of a payment. Use the new field named + // "accepted_sell_orders" to retrieve all of them. rfqrpc.PeerAcceptedSellQuote accepted_sell_order = 1; - // The payment result of a single payment attempt. Multiple attempts may - // be returned per payment request until either the payment succeeds or - // the payment times out. + // If swapping of assets is necessary to carry out the payment, a number + // of RFQ quotes may be negotiated for that purpose. The following field + // contains all the sell orders that were negotiated with our peers. + AcceptedSellQuotes accepted_sell_orders = 3; + + // The payment result of a single payment attempt. Multiple attempts + // may be returned per payment request until either the payment + // succeeds or the payment times out. lnrpc.Payment payment_result = 2; } } diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index 3e4b6bcd3..4993696dc 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -1487,6 +1487,19 @@ } } }, + "tapchannelrpcAcceptedSellQuotes": { + "type": "object", + "properties": { + "accepted_sell_orders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/rfqrpcPeerAcceptedSellQuote" + }, + "description": "If swapping of assets is necessary to carry out the payment, a number of\nRFQ quotes may be negotiated for that purpose. The following field\ncontains all the sell orders that were negotiated with our peers." + } + } + }, "tapchannelrpcAddInvoiceRequest": { "type": "object", "properties": { @@ -1718,11 +1731,15 @@ "properties": { "accepted_sell_order": { "$ref": "#/definitions/rfqrpcPeerAcceptedSellQuote", - "description": "In case channel assets need to be swapped to another asset, an asset\nsell order is negotiated with the channel peer. The result will be\nthe first message in the response stream. If no swap is needed, the\npayment results will be streamed directly." + "description": "In case channel assets need to be swapped to another asset, an asset\nsell order is negotiated with the channel peer. The result will be\nthe first message in the response stream. If no swap is needed, the\npayment results will be streamed directly.\nDeprecated. This will now only contain the first quote that was\nnegotiated. Since the introduction of multi-rfq we now negotiate\nmultiple quotes in the context of a payment. Use the new field named\n\"accepted_sell_orders\" to retrieve all of them." + }, + "accepted_sell_orders": { + "$ref": "#/definitions/tapchannelrpcAcceptedSellQuotes", + "description": "If swapping of assets is necessary to carry out the payment, a number\nof RFQ quotes may be negotiated for that purpose. The following field\ncontains all the sell orders that were negotiated with our peers." }, "payment_result": { "$ref": "#/definitions/lnrpcPayment", - "description": "The payment result of a single payment attempt. Multiple attempts may\nbe returned per payment request until either the payment succeeds or\nthe payment times out." + "description": "The payment result of a single payment attempt. Multiple attempts\nmay be returned per payment request until either the payment\nsucceeds or the payment times out." } } }, From 53871ae72d6f15f85fda1c7e8d54c2540a5244bd Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 30 Jun 2025 11:41:09 +0200 Subject: [PATCH 6/8] taprpc: update SendPayment docs --- taprpc/tapchannelrpc/tapchannel.pb.go | 4 ++-- taprpc/tapchannelrpc/tapchannel.proto | 4 ++-- taprpc/tapchannelrpc/tapchannel.swagger.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index d9f1b0498..7b795bf7b 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -374,8 +374,8 @@ type SendPaymentRequest struct { // payment_request.first_hop_custom_records already contains valid RFQ data. AssetAmount uint64 `protobuf:"varint,2,opt,name=asset_amount,json=assetAmount,proto3" json:"asset_amount,omitempty"` // The node identity public key of the peer to ask for a quote for sending - // out the assets and converting them to satoshis. This must be specified if - // there are multiple channels with the given asset ID. + // out the assets and converting them to satoshis. If set, only a quote with + // this peer may be negotiated to carry out the payment. PeerPubkey []byte `protobuf:"bytes,3,opt,name=peer_pubkey,json=peerPubkey,proto3" json:"peer_pubkey,omitempty"` // The full lnd payment request to send. All fields behave the same way as // they do for lnd's routerrpc.SendPaymentV2 RPC method (see the API docs diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index 81a7e6882..df4002cf8 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -124,8 +124,8 @@ message SendPaymentRequest { uint64 asset_amount = 2; // The node identity public key of the peer to ask for a quote for sending - // out the assets and converting them to satoshis. This must be specified if - // there are multiple channels with the given asset ID. + // out the assets and converting them to satoshis. If set, only a quote with + // this peer may be negotiated to carry out the payment. bytes peer_pubkey = 3; // The full lnd payment request to send. All fields behave the same way as diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index 4993696dc..5ccef11e2 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -1704,7 +1704,7 @@ "peer_pubkey": { "type": "string", "format": "byte", - "description": "The node identity public key of the peer to ask for a quote for sending\nout the assets and converting them to satoshis. This must be specified if\nthere are multiple channels with the given asset ID." + "description": "The node identity public key of the peer to ask for a quote for sending\nout the assets and converting them to satoshis. If set, only a quote with\nthis peer may be negotiated to carry out the payment." }, "payment_request": { "$ref": "#/definitions/routerrpcSendPaymentRequest", From a39ef31728a43443df4e89cae20b39cc3ae8dceb Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 2 Jul 2025 11:49:43 +0200 Subject: [PATCH 7/8] taprpc: deprecate EncodeCustomRecords rpc --- taprpc/tapchannelrpc/tapchannel.pb.go | 46 ++++++++++---------- taprpc/tapchannelrpc/tapchannel.proto | 5 ++- taprpc/tapchannelrpc/tapchannel.swagger.json | 2 +- taprpc/tapchannelrpc/tapchannel_grpc.pb.go | 7 +++ 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index 7b795bf7b..0c2a2788c 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -1169,41 +1169,41 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x06, 0x70, - 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xda, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, + 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xdf, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0a, 0x41, 0x64, - 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x61, 0x70, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, - 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, - 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x22, - 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 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, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, - 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, + 0x51, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, + 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x53, 0x0a, 0x11, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, + 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 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, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index df4002cf8..6934fa1e6 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -17,6 +17,7 @@ service TaprootAssetChannels { rpc FundChannel (FundChannelRequest) returns (FundChannelResponse); /* + Deprecated. EncodeCustomRecords allows RPC users to encode Taproot Asset channel related data into the TLV format that is used in the custom records of the lnd payment or other channel related RPCs. This RPC is completely stateless and @@ -24,7 +25,9 @@ service TaprootAssetChannels { validation. */ rpc EncodeCustomRecords (EncodeCustomRecordsRequest) - returns (EncodeCustomRecordsResponse); + returns (EncodeCustomRecordsResponse) { + option deprecated = true; + }; /* litcli: `ln sendpayment` SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index 5ccef11e2..2dab3e88c 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -18,7 +18,7 @@ "paths": { "/v1/taproot-assets/channels/encode-custom-data": { "post": { - "summary": "EncodeCustomRecords allows RPC users to encode Taproot Asset channel related\ndata into the TLV format that is used in the custom records of the lnd\npayment or other channel related RPCs. This RPC is completely stateless and\ndoes not perform any checks on the data provided, other than pure format\nvalidation.", + "summary": "Deprecated.\nEncodeCustomRecords allows RPC users to encode Taproot Asset channel related\ndata into the TLV format that is used in the custom records of the lnd\npayment or other channel related RPCs. This RPC is completely stateless and\ndoes not perform any checks on the data provided, other than pure format\nvalidation.", "operationId": "TaprootAssetChannels_EncodeCustomRecords", "responses": { "200": { diff --git a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go index 4d1302d89..7b0e24e20 100644 --- a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go +++ b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go @@ -22,6 +22,9 @@ type TaprootAssetChannelsClient interface { // FundChannel initiates the channel funding negotiation with a peer for the // creation of a channel that contains a specified amount of a given asset. FundChannel(ctx context.Context, in *FundChannelRequest, opts ...grpc.CallOption) (*FundChannelResponse, error) + // Deprecated: Do not use. + // + // Deprecated. // EncodeCustomRecords allows RPC users to encode Taproot Asset channel related // data into the TLV format that is used in the custom records of the lnd // payment or other channel related RPCs. This RPC is completely stateless and @@ -65,6 +68,7 @@ func (c *taprootAssetChannelsClient) FundChannel(ctx context.Context, in *FundCh return out, nil } +// Deprecated: Do not use. func (c *taprootAssetChannelsClient) EncodeCustomRecords(ctx context.Context, in *EncodeCustomRecordsRequest, opts ...grpc.CallOption) (*EncodeCustomRecordsResponse, error) { out := new(EncodeCustomRecordsResponse) err := c.cc.Invoke(ctx, "/tapchannelrpc.TaprootAssetChannels/EncodeCustomRecords", in, out, opts...) @@ -132,6 +136,9 @@ type TaprootAssetChannelsServer interface { // FundChannel initiates the channel funding negotiation with a peer for the // creation of a channel that contains a specified amount of a given asset. FundChannel(context.Context, *FundChannelRequest) (*FundChannelResponse, error) + // Deprecated: Do not use. + // + // Deprecated. // EncodeCustomRecords allows RPC users to encode Taproot Asset channel related // data into the TLV format that is used in the custom records of the lnd // payment or other channel related RPCs. This RPC is completely stateless and From 028af112cbdb68832f1df9984ff32435c05784e8 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 28 Jul 2025 12:32:03 +0200 Subject: [PATCH 8/8] docs: add release notes for multi-rfq send --- docs/release-notes/release-notes-0.7.0.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release-notes/release-notes-0.7.0.md b/docs/release-notes/release-notes-0.7.0.md index 50e8d15e3..2e542fc4a 100644 --- a/docs/release-notes/release-notes-0.7.0.md +++ b/docs/release-notes/release-notes-0.7.0.md @@ -47,6 +47,13 @@ migration](https://github.com/lightninglabs/taproot-assets/pull/1612) was added that retroactively inserts all burned assets into that table. +- Sending a payment now supports multi-rfq. This new feature allows for multiple + quotes to be used in order to carry out a payment. With multiple quotes, we + can use liquidity that is spread across different channels and also use + multiple rates. See + [related PR](https://github.com/lightninglabs/taproot-assets/pull/1613) for + more info. + ## RPC Additions - The [price oracle RPC calls now have an intent, optional peer ID and metadata