Skip to content

Commit ab07d7c

Browse files
committed
Merge branch '0-6-1-pull-requests-1627' into 0-6-1-pull-requests
2 parents 8d26508 + 049e6a4 commit ab07d7c

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
@@ -521,6 +521,85 @@ func (s *Specifier) AssertNotEmpty() error {
521521
return nil
522522
}
523523

524+
// Equal compares this specifier to another one, returning true if they are
525+
// equal. If strict is true, then both specifiers need to either have both equal
526+
// asset IDs and group keys set or only one of those (but matching). If strict
527+
// is false, then it's enough to have the same group key _or_ the same asset ID
528+
// (if one has no group key set). This is useful for cases where one specifier
529+
// only specifies the group key, while the other one specifies both the group
530+
// key and the asset ID. In that case, we can consider them equal if the group
531+
// keys match, even if the asset IDs are different (or one is not set).
532+
func (s *Specifier) Equal(other *Specifier, strict bool) (bool, error) {
533+
// If both specifiers are nil, they are equal.
534+
if s == nil && other == nil {
535+
return true, nil
536+
}
537+
538+
// If one of the specifiers is nil, they are not equal.
539+
if s == nil || other == nil {
540+
return false, nil
541+
}
542+
543+
// If either of them is empty while the other is not, they are not
544+
// equal.
545+
if (s.HasId() || s.HasGroupPubKey()) !=
546+
(other.HasId() || other.HasGroupPubKey()) {
547+
548+
return false, nil
549+
}
550+
551+
// If they both have distinct elements set, then they are not equal.
552+
if s.HasId() != other.HasId() &&
553+
s.HasGroupPubKey() != other.HasGroupPubKey() {
554+
555+
return false, nil
556+
}
557+
558+
// If both specifiers have a group public key, compare them.
559+
if s.HasGroupPubKey() && other.HasGroupPubKey() {
560+
groupKeyA := s.UnwrapGroupKeyToPtr()
561+
groupKeyB := other.UnwrapGroupKeyToPtr()
562+
563+
// If any unwrapped element is nil, something's wrong, and we
564+
// can't compare them.
565+
if groupKeyA == nil || groupKeyB == nil {
566+
return false, fmt.Errorf("unable to unwrap group key "+
567+
"from specifier: %v vs %v", s, other)
568+
}
569+
570+
if !groupKeyA.IsEqual(groupKeyB) {
571+
return false, nil
572+
}
573+
574+
// If we're not doing a strict comparison, then we can return
575+
// true here if the group keys match. The group key has higher
576+
// priority than the ID, so if they match and the comparison
577+
// isn't strict, we can consider the specifiers equal.
578+
if !strict {
579+
return true, nil
580+
}
581+
}
582+
583+
// If both specifiers have an ID, compare them.
584+
if s.HasId() && other.HasId() {
585+
idA := s.UnwrapIdToPtr()
586+
idB := other.UnwrapIdToPtr()
587+
588+
// If any unwrapped element is nil, something's wrong and we
589+
// can't compare them.
590+
if idA == nil || idB == nil {
591+
return false, fmt.Errorf("unable to unwrap asset ID "+
592+
"from specifier: %v vs %v", s, other)
593+
}
594+
595+
if *idA != *idB {
596+
return false, nil
597+
}
598+
}
599+
600+
return true, nil
601+
}
602+
524603
// Type denotes the asset types supported by the Taproot Asset protocol.
525604
type Type uint8
526605

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
@@ -7193,67 +7193,48 @@ func (r *rpcServer) AddAssetBuyOffer(_ context.Context,
71937193
return &rfqrpc.AddAssetBuyOfferResponse{}, nil
71947194
}
71957195

7196-
// marshalPeerAcceptedBuyQuotes marshals a map of peer accepted asset buy quotes
7197-
// into the RPC form. These are quotes that were requested by our node and have
7198-
// been accepted by our peers.
7199-
func marshalPeerAcceptedBuyQuotes(
7200-
quotes map[rfq.SerialisedScid]rfqmsg.BuyAccept) (
7201-
[]*rfqrpc.PeerAcceptedBuyQuote, error) {
7196+
// marshalPeerAcceptedBuyQuote marshals a peer accepted asset buy quote into
7197+
// the RPC form. This is a quote that was requested by our node and has been
7198+
// accepted by our peer.
7199+
func marshalPeerAcceptedBuyQuote(
7200+
quote rfqmsg.BuyAccept) *rfqrpc.PeerAcceptedBuyQuote {
72027201

7203-
// Marshal the accepted quotes into the RPC form.
7204-
rpcQuotes := make(
7205-
[]*rfqrpc.PeerAcceptedBuyQuote, 0, len(quotes),
7206-
)
7207-
for scid, quote := range quotes {
7208-
coefficient := quote.AssetRate.Rate.Coefficient.String()
7209-
rpcAskAssetRate := &rfqrpc.FixedPoint{
7210-
Coefficient: coefficient,
7211-
Scale: uint32(quote.AssetRate.Rate.Scale),
7212-
}
7213-
7214-
rpcQuote := &rfqrpc.PeerAcceptedBuyQuote{
7215-
Peer: quote.Peer.String(),
7216-
Id: quote.ID[:],
7217-
Scid: uint64(scid),
7218-
AssetMaxAmount: quote.Request.AssetMaxAmt,
7219-
AskAssetRate: rpcAskAssetRate,
7220-
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7221-
}
7222-
rpcQuotes = append(rpcQuotes, rpcQuote)
7202+
coefficient := quote.AssetRate.Rate.Coefficient.String()
7203+
rpcAskAssetRate := &rfqrpc.FixedPoint{
7204+
Coefficient: coefficient,
7205+
Scale: uint32(quote.AssetRate.Rate.Scale),
72237206
}
72247207

7225-
return rpcQuotes, nil
7208+
return &rfqrpc.PeerAcceptedBuyQuote{
7209+
Peer: quote.Peer.String(),
7210+
Id: quote.ID[:],
7211+
Scid: uint64(quote.ShortChannelId()),
7212+
AssetMaxAmount: quote.Request.AssetMaxAmt,
7213+
AskAssetRate: rpcAskAssetRate,
7214+
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7215+
}
72267216
}
72277217

7228-
// marshalPeerAcceptedSellQuotes marshals a map of peer accepted asset sell
7229-
// quotes into the RPC form. These are quotes that were requested by our node
7230-
// and have been accepted by our peers.
7231-
//
7232-
// nolint: lll
7233-
func marshalPeerAcceptedSellQuotes(quotes map[rfq.SerialisedScid]rfqmsg.SellAccept) (
7234-
[]*rfqrpc.PeerAcceptedSellQuote, error) {
7218+
// marshalPeerAcceptedSellQuote marshals peer accepted asset sell quote into the
7219+
// RPC form. This is a quote that was requested by our node and has been
7220+
// accepted by our peers.
7221+
func marshalPeerAcceptedSellQuote(
7222+
quote rfqmsg.SellAccept) *rfqrpc.PeerAcceptedSellQuote {
72357223

7236-
// Marshal the accepted quotes into the RPC form.
7237-
rpcQuotes := make([]*rfqrpc.PeerAcceptedSellQuote, 0, len(quotes))
7238-
for scid, quote := range quotes {
7239-
rpcAssetRate := &rfqrpc.FixedPoint{
7240-
Coefficient: quote.AssetRate.Rate.Coefficient.String(),
7241-
Scale: uint32(quote.AssetRate.Rate.Scale),
7242-
}
7243-
7244-
// TODO(ffranr): Add SellRequest payment max amount to
7245-
// PeerAcceptedSellQuote.
7246-
rpcQuote := &rfqrpc.PeerAcceptedSellQuote{
7247-
Peer: quote.Peer.String(),
7248-
Id: quote.ID[:],
7249-
Scid: uint64(scid),
7250-
BidAssetRate: rpcAssetRate,
7251-
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7252-
}
7253-
rpcQuotes = append(rpcQuotes, rpcQuote)
7224+
rpcAssetRate := &rfqrpc.FixedPoint{
7225+
Coefficient: quote.AssetRate.Rate.Coefficient.String(),
7226+
Scale: uint32(quote.AssetRate.Rate.Scale),
72547227
}
72557228

7256-
return rpcQuotes, nil
7229+
// TODO(ffranr): Add SellRequest payment max amount to
7230+
// PeerAcceptedSellQuote.
7231+
return &rfqrpc.PeerAcceptedSellQuote{
7232+
Peer: quote.Peer.String(),
7233+
Id: quote.ID[:],
7234+
Scid: uint64(quote.ShortChannelId()),
7235+
BidAssetRate: rpcAssetRate,
7236+
Expiry: uint64(quote.AssetRate.Expiry.Unix()),
7237+
}
72577238
}
72587239

72597240
// QueryPeerAcceptedQuotes is used to query for quotes that were requested by
@@ -7267,19 +7248,13 @@ func (r *rpcServer) QueryPeerAcceptedQuotes(_ context.Context,
72677248
peerAcceptedBuyQuotes := r.cfg.RfqManager.PeerAcceptedBuyQuotes()
72687249
peerAcceptedSellQuotes := r.cfg.RfqManager.PeerAcceptedSellQuotes()
72697250

7270-
rpcBuyQuotes, err := marshalPeerAcceptedBuyQuotes(peerAcceptedBuyQuotes)
7271-
if err != nil {
7272-
return nil, fmt.Errorf("error marshalling peer accepted buy "+
7273-
"quotes: %w", err)
7274-
}
7275-
7276-
rpcSellQuotes, err := marshalPeerAcceptedSellQuotes(
7277-
peerAcceptedSellQuotes,
7251+
rpcBuyQuotes := fn.Map(
7252+
maps.Values(peerAcceptedBuyQuotes), marshalPeerAcceptedBuyQuote,
7253+
)
7254+
rpcSellQuotes := fn.Map(
7255+
maps.Values(peerAcceptedSellQuotes),
7256+
marshalPeerAcceptedSellQuote,
72787257
)
7279-
if err != nil {
7280-
return nil, fmt.Errorf("error marshalling peer accepted sell "+
7281-
"quotes: %w", err)
7282-
}
72837258

72847259
return &rfqrpc.QueryPeerAcceptedQuotesResponse{
72857260
BuyQuotes: rpcBuyQuotes,
@@ -8153,9 +8128,19 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
81538128
var expensiveQuote *rfqrpc.PeerAcceptedBuyQuote
81548129
if !existingQuotes {
81558130
expensiveQuote = acquiredQuotes[0].quote
8131+
} else {
8132+
mgr := r.cfg.AuxInvoiceManager
8133+
buyQuote, err := mgr.GetBuyQuoteFromRouteHints(
8134+
iReq, specifier,
8135+
)
8136+
if err != nil {
8137+
return nil, fmt.Errorf("failed to find matching buy "+
8138+
"quote in accepted quotes: %w", err)
8139+
}
8140+
8141+
expensiveQuote = marshalPeerAcceptedBuyQuote(*buyQuote)
81568142
}
81578143

8158-
// replace with above
81598144
// Now that we have the accepted quote, we know the amount in (milli)
81608145
// Satoshi that we need to pay. We can now update the invoice with this
81618146
// amount.

tapchannel/aux_invoice_manager.go

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

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

0 commit comments

Comments
 (0)