Skip to content

Commit 751bd94

Browse files
authored
Merge pull request #1627 from lightninglabs/multi-rfq-receive-fix
`AddInvoice` correctly handles existing route hints
2 parents 85bbfcb + d07bfd1 commit 751bd94

File tree

4 files changed

+284
-66
lines changed

4 files changed

+284
-66
lines changed

asset/asset.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,85 @@ func (s *Specifier) AssertNotEmpty() error {
604604
return nil
605605
}
606606

607+
// Equal compares this specifier to another one, returning true if they are
608+
// equal. If strict is true, then both specifiers need to either have both equal
609+
// asset IDs and group keys set or only one of those (but matching). If strict
610+
// is false, then it's enough to have the same group key _or_ the same asset ID
611+
// (if one has no group key set). This is useful for cases where one specifier
612+
// only specifies the group key, while the other one specifies both the group
613+
// key and the asset ID. In that case, we can consider them equal if the group
614+
// keys match, even if the asset IDs are different (or one is not set).
615+
func (s *Specifier) Equal(other *Specifier, strict bool) (bool, error) {
616+
// If both specifiers are nil, they are equal.
617+
if s == nil && other == nil {
618+
return true, nil
619+
}
620+
621+
// If one of the specifiers is nil, they are not equal.
622+
if s == nil || other == nil {
623+
return false, nil
624+
}
625+
626+
// If either of them is empty while the other is not, they are not
627+
// equal.
628+
if (s.HasId() || s.HasGroupPubKey()) !=
629+
(other.HasId() || other.HasGroupPubKey()) {
630+
631+
return false, nil
632+
}
633+
634+
// If they both have distinct elements set, then they are not equal.
635+
if s.HasId() != other.HasId() &&
636+
s.HasGroupPubKey() != other.HasGroupPubKey() {
637+
638+
return false, nil
639+
}
640+
641+
// If both specifiers have a group public key, compare them.
642+
if s.HasGroupPubKey() && other.HasGroupPubKey() {
643+
groupKeyA := s.UnwrapGroupKeyToPtr()
644+
groupKeyB := other.UnwrapGroupKeyToPtr()
645+
646+
// If any unwrapped element is nil, something's wrong, and we
647+
// can't compare them.
648+
if groupKeyA == nil || groupKeyB == nil {
649+
return false, fmt.Errorf("unable to unwrap group key "+
650+
"from specifier: %v vs %v", s, other)
651+
}
652+
653+
if !groupKeyA.IsEqual(groupKeyB) {
654+
return false, nil
655+
}
656+
657+
// If we're not doing a strict comparison, then we can return
658+
// true here if the group keys match. The group key has higher
659+
// priority than the ID, so if they match and the comparison
660+
// isn't strict, we can consider the specifiers equal.
661+
if !strict {
662+
return true, nil
663+
}
664+
}
665+
666+
// If both specifiers have an ID, compare them.
667+
if s.HasId() && other.HasId() {
668+
idA := s.UnwrapIdToPtr()
669+
idB := other.UnwrapIdToPtr()
670+
671+
// If any unwrapped element is nil, something's wrong and we
672+
// can't compare them.
673+
if idA == nil || idB == nil {
674+
return false, fmt.Errorf("unable to unwrap asset ID "+
675+
"from specifier: %v vs %v", s, other)
676+
}
677+
678+
if *idA != *idB {
679+
return false, nil
680+
}
681+
}
682+
683+
return true, nil
684+
}
685+
607686
// Type denotes the asset types supported by the Taproot Asset protocol.
608687
type Type uint8
609688

asset/asset_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,3 +1247,125 @@ func TestDecodeAsset(t *testing.T) {
12471247

12481248
t.Logf("Decoded asset: %v", string(assetJSON))
12491249
}
1250+
1251+
// TestSpecifierEqual tests the Specifier.Equal method for all possible cases.
1252+
func TestSpecifierEqual(t *testing.T) {
1253+
id1 := ID{1, 2, 3}
1254+
id2 := ID{4, 5, 6}
1255+
pk1, _ := btcec.NewPrivateKey()
1256+
pubKey1 := pk1.PubKey()
1257+
pk2, _ := btcec.NewPrivateKey()
1258+
pubKey2 := pk2.PubKey()
1259+
1260+
cases := []struct {
1261+
name string
1262+
s, other *Specifier
1263+
strict bool
1264+
expects bool
1265+
expectErr bool
1266+
}{
1267+
{
1268+
name: "both nil",
1269+
s: nil,
1270+
other: nil,
1271+
strict: false,
1272+
expects: true,
1273+
},
1274+
{
1275+
name: "one nil (s)",
1276+
s: nil,
1277+
other: &Specifier{},
1278+
strict: false,
1279+
expects: false,
1280+
},
1281+
{
1282+
name: "one nil (other)",
1283+
s: &Specifier{},
1284+
other: nil,
1285+
strict: false,
1286+
expects: false,
1287+
},
1288+
{
1289+
name: "both empty",
1290+
s: &Specifier{},
1291+
other: &Specifier{},
1292+
strict: false,
1293+
expects: true,
1294+
},
1295+
{
1296+
name: "both with same ID",
1297+
s: &Specifier{id: fn.Some(id1)},
1298+
other: &Specifier{id: fn.Some(id1)},
1299+
strict: false,
1300+
expects: true,
1301+
},
1302+
{
1303+
name: "both with different ID",
1304+
s: &Specifier{id: fn.Some(id1)},
1305+
other: &Specifier{id: fn.Some(id2)},
1306+
strict: false,
1307+
expects: false,
1308+
},
1309+
{
1310+
name: "both with same group key",
1311+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1312+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1313+
strict: false,
1314+
expects: true,
1315+
},
1316+
{
1317+
name: "both with different group key",
1318+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1319+
other: &Specifier{groupKey: fn.Some(*pubKey2)},
1320+
strict: false,
1321+
expects: false,
1322+
},
1323+
{
1324+
name: "one with ID, one with group key",
1325+
s: &Specifier{id: fn.Some(id1)},
1326+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1327+
strict: false,
1328+
expects: false,
1329+
},
1330+
{
1331+
name: "both with ID, strict true",
1332+
s: &Specifier{id: fn.Some(id1)},
1333+
other: &Specifier{id: fn.Some(id1)},
1334+
strict: true,
1335+
expects: true,
1336+
},
1337+
{
1338+
name: "both with group key, strict true",
1339+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1340+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1341+
strict: true,
1342+
expects: true,
1343+
},
1344+
{
1345+
name: "one with ID, one with group key, strict true",
1346+
s: &Specifier{id: fn.Some(id1)},
1347+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1348+
strict: true,
1349+
expects: false,
1350+
},
1351+
{
1352+
name: "both empty, strict true",
1353+
s: &Specifier{},
1354+
other: &Specifier{},
1355+
strict: true,
1356+
expects: true,
1357+
},
1358+
}
1359+
1360+
for _, tc := range cases {
1361+
t.Run(tc.name, func(t *testing.T) {
1362+
eq, err := tc.s.Equal(tc.other, tc.strict)
1363+
if tc.expectErr {
1364+
require.Error(t, err)
1365+
} else {
1366+
require.NoError(t, err)
1367+
require.Equal(t, tc.expects, eq)
1368+
}
1369+
})
1370+
}
1371+
}

rpcserver.go

Lines changed: 51 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7228,67 +7228,48 @@ func (r *rpcServer) AddAssetBuyOffer(_ context.Context,
72287228
return &rfqrpc.AddAssetBuyOfferResponse{}, nil
72297229
}
72307230

7231-
// marshalPeerAcceptedBuyQuotes marshals a map of peer accepted asset buy quotes
7232-
// into the RPC form. These are quotes that were requested by our node and have
7233-
// been accepted by our peers.
7234-
func marshalPeerAcceptedBuyQuotes(
7235-
quotes map[rfq.SerialisedScid]rfqmsg.BuyAccept) (
7236-
[]*rfqrpc.PeerAcceptedBuyQuote, error) {
7231+
// marshalPeerAcceptedBuyQuote marshals a peer accepted asset buy quote into
7232+
// the RPC form. This is a quote that was requested by our node and has been
7233+
// accepted by our peer.
7234+
func marshalPeerAcceptedBuyQuote(
7235+
quote rfqmsg.BuyAccept) *rfqrpc.PeerAcceptedBuyQuote {
72377236

7238-
// Marshal the accepted quotes into the RPC form.
7239-
rpcQuotes := make(
7240-
[]*rfqrpc.PeerAcceptedBuyQuote, 0, len(quotes),
7241-
)
7242-
for scid, quote := range quotes {
7243-
coefficient := quote.AssetRate.Rate.Coefficient.String()
7244-
rpcAskAssetRate := &rfqrpc.FixedPoint{
7245-
Coefficient: coefficient,
7246-
Scale: uint32(quote.AssetRate.Rate.Scale),
7247-
}
7248-
7249-
rpcQuote := &rfqrpc.PeerAcceptedBuyQuote{
7250-
Peer: quote.Peer.String(),
7251-
Id: quote.ID[:],
7252-
Scid: uint64(scid),
7253-
AssetMaxAmount: quote.Request.AssetMaxAmt,
7254-
AskAssetRate: rpcAskAssetRate,
7255-
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7256-
}
7257-
rpcQuotes = append(rpcQuotes, rpcQuote)
7237+
coefficient := quote.AssetRate.Rate.Coefficient.String()
7238+
rpcAskAssetRate := &rfqrpc.FixedPoint{
7239+
Coefficient: coefficient,
7240+
Scale: uint32(quote.AssetRate.Rate.Scale),
72587241
}
72597242

7260-
return rpcQuotes, nil
7243+
return &rfqrpc.PeerAcceptedBuyQuote{
7244+
Peer: quote.Peer.String(),
7245+
Id: quote.ID[:],
7246+
Scid: uint64(quote.ShortChannelId()),
7247+
AssetMaxAmount: quote.Request.AssetMaxAmt,
7248+
AskAssetRate: rpcAskAssetRate,
7249+
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7250+
}
72617251
}
72627252

7263-
// marshalPeerAcceptedSellQuotes marshals a map of peer accepted asset sell
7264-
// quotes into the RPC form. These are quotes that were requested by our node
7265-
// and have been accepted by our peers.
7266-
//
7267-
// nolint: lll
7268-
func marshalPeerAcceptedSellQuotes(quotes map[rfq.SerialisedScid]rfqmsg.SellAccept) (
7269-
[]*rfqrpc.PeerAcceptedSellQuote, error) {
7253+
// marshalPeerAcceptedSellQuote marshals peer accepted asset sell quote into the
7254+
// RPC form. This is a quote that was requested by our node and has been
7255+
// accepted by our peers.
7256+
func marshalPeerAcceptedSellQuote(
7257+
quote rfqmsg.SellAccept) *rfqrpc.PeerAcceptedSellQuote {
72707258

7271-
// Marshal the accepted quotes into the RPC form.
7272-
rpcQuotes := make([]*rfqrpc.PeerAcceptedSellQuote, 0, len(quotes))
7273-
for scid, quote := range quotes {
7274-
rpcAssetRate := &rfqrpc.FixedPoint{
7275-
Coefficient: quote.AssetRate.Rate.Coefficient.String(),
7276-
Scale: uint32(quote.AssetRate.Rate.Scale),
7277-
}
7278-
7279-
// TODO(ffranr): Add SellRequest payment max amount to
7280-
// PeerAcceptedSellQuote.
7281-
rpcQuote := &rfqrpc.PeerAcceptedSellQuote{
7282-
Peer: quote.Peer.String(),
7283-
Id: quote.ID[:],
7284-
Scid: uint64(scid),
7285-
BidAssetRate: rpcAssetRate,
7286-
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7287-
}
7288-
rpcQuotes = append(rpcQuotes, rpcQuote)
7259+
rpcAssetRate := &rfqrpc.FixedPoint{
7260+
Coefficient: quote.AssetRate.Rate.Coefficient.String(),
7261+
Scale: uint32(quote.AssetRate.Rate.Scale),
72897262
}
72907263

7291-
return rpcQuotes, nil
7264+
// TODO(ffranr): Add SellRequest payment max amount to
7265+
// PeerAcceptedSellQuote.
7266+
return &rfqrpc.PeerAcceptedSellQuote{
7267+
Peer: quote.Peer.String(),
7268+
Id: quote.ID[:],
7269+
Scid: uint64(quote.ShortChannelId()),
7270+
BidAssetRate: rpcAssetRate,
7271+
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7272+
}
72927273
}
72937274

72947275
// QueryPeerAcceptedQuotes is used to query for quotes that were requested by
@@ -7302,19 +7283,13 @@ func (r *rpcServer) QueryPeerAcceptedQuotes(_ context.Context,
73027283
peerAcceptedBuyQuotes := r.cfg.RfqManager.PeerAcceptedBuyQuotes()
73037284
peerAcceptedSellQuotes := r.cfg.RfqManager.PeerAcceptedSellQuotes()
73047285

7305-
rpcBuyQuotes, err := marshalPeerAcceptedBuyQuotes(peerAcceptedBuyQuotes)
7306-
if err != nil {
7307-
return nil, fmt.Errorf("error marshalling peer accepted buy "+
7308-
"quotes: %w", err)
7309-
}
7310-
7311-
rpcSellQuotes, err := marshalPeerAcceptedSellQuotes(
7312-
peerAcceptedSellQuotes,
7286+
rpcBuyQuotes := fn.Map(
7287+
maps.Values(peerAcceptedBuyQuotes), marshalPeerAcceptedBuyQuote,
7288+
)
7289+
rpcSellQuotes := fn.Map(
7290+
maps.Values(peerAcceptedSellQuotes),
7291+
marshalPeerAcceptedSellQuote,
73137292
)
7314-
if err != nil {
7315-
return nil, fmt.Errorf("error marshalling peer accepted sell "+
7316-
"quotes: %w", err)
7317-
}
73187293

73197294
return &rfqrpc.QueryPeerAcceptedQuotesResponse{
73207295
BuyQuotes: rpcBuyQuotes,
@@ -8188,9 +8163,19 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
81888163
var expensiveQuote *rfqrpc.PeerAcceptedBuyQuote
81898164
if !existingQuotes {
81908165
expensiveQuote = acquiredQuotes[0].quote
8166+
} else {
8167+
mgr := r.cfg.AuxInvoiceManager
8168+
buyQuote, err := mgr.GetBuyQuoteFromRouteHints(
8169+
iReq, specifier,
8170+
)
8171+
if err != nil {
8172+
return nil, fmt.Errorf("failed to find matching buy "+
8173+
"quote in accepted quotes: %w", err)
8174+
}
8175+
8176+
expensiveQuote = marshalPeerAcceptedBuyQuote(*buyQuote)
81918177
}
81928178

8193-
// replace with above
81948179
// Now that we have the accepted quote, we know the amount in (milli)
81958180
// Satoshi that we need to pay. We can now update the invoice with this
81968181
// amount.

tapchannel/aux_invoice_manager.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,38 @@ func IsAssetInvoice(invoice *lnrpc.Invoice, rfqLookup RfqLookup) bool {
430430
return false
431431
}
432432

433+
// GetBuyQuoteFromRouteHints is a helper method that extracts a buy quote from
434+
// the route hints of an invoice, given that the quote is accepted and has a
435+
// matching specifier. If no matching quote is found, it returns an error.
436+
func (s *AuxInvoiceManager) GetBuyQuoteFromRouteHints(invoice *lnrpc.Invoice,
437+
specifier asset.Specifier) (*rfqmsg.BuyAccept, error) {
438+
439+
buyQuotes := s.cfg.RfqManager.PeerAcceptedBuyQuotes()
440+
for _, hint := range invoice.RouteHints {
441+
for _, h := range hint.HopHints {
442+
scid := rfqmsg.SerialisedScid(h.ChanId)
443+
buyQuote, ok := buyQuotes[scid]
444+
if !ok {
445+
continue
446+
}
447+
448+
quoteSpecifier := buyQuote.Request.AssetSpecifier
449+
areEqual, err := quoteSpecifier.Equal(&specifier, false)
450+
if err != nil {
451+
return nil, fmt.Errorf("error comparing "+
452+
"specifiers: %w", err)
453+
}
454+
455+
if areEqual {
456+
return &buyQuote, nil
457+
}
458+
}
459+
}
460+
461+
return nil, fmt.Errorf("no buy quote found for specifier %s",
462+
specifier.String())
463+
}
464+
433465
// validateAssetHTLC runs a couple of checks on the provided asset HTLC.
434466
func (s *AuxInvoiceManager) validateAssetHTLC(ctx context.Context,
435467
htlc *rfqmsg.Htlc, circuitKey invoices.CircuitKey) error {

0 commit comments

Comments
 (0)