Skip to content

Commit ab90081

Browse files
author
ffranr
authored
Merge pull request #1157 from lightninglabs/rfqmsg-request-fields-blip-align
rfqmsg: request fields blip align
2 parents e6b78bd + c5d356e commit ab90081

26 files changed

+1126
-678
lines changed

asset/asset.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,41 @@ type Specifier struct {
263263
groupKey fn.Option[btcec.PublicKey]
264264
}
265265

266+
// NewSpecifier creates a new Specifier instance based on the provided
267+
// parameters.
268+
//
269+
// The Specifier identifies an asset using either an asset ID, a group public
270+
// key, or a group key. At least one of these must be specified if the
271+
// `mustBeSpecified` parameter is set to true.
272+
func NewSpecifier(id *ID, groupPubKey *btcec.PublicKey, groupKey *GroupKey,
273+
mustBeSpecified bool) (Specifier, error) {
274+
275+
// Return an error if the asset ID, group public key, and group key are
276+
// all nil and at least one of them must be specified.
277+
isAnySpecified := id != nil || groupPubKey != nil || groupKey != nil
278+
if !isAnySpecified && mustBeSpecified {
279+
return Specifier{}, fmt.Errorf("at least one of the asset ID "+
280+
"or asset group key fields must be specified "+
281+
"(id=%v, groupPubKey=%v, groupKey=%v)",
282+
id, groupPubKey, groupKey)
283+
}
284+
285+
// Create an option for the asset ID.
286+
optId := fn.MaybeSome(id)
287+
288+
// Create an option for the group public key.
289+
optGroupPubKey := fn.MaybeSome(groupPubKey)
290+
291+
if groupKey != nil {
292+
optGroupPubKey = fn.Some(groupKey.GroupPubKey)
293+
}
294+
295+
return Specifier{
296+
id: optId,
297+
groupKey: optGroupPubKey,
298+
}, nil
299+
}
300+
266301
// NewSpecifierOptionalGroupPubKey creates a new specifier that specifies an
267302
// asset by its ID and an optional group public key.
268303
func NewSpecifierOptionalGroupPubKey(id ID,
@@ -308,6 +343,23 @@ func NewSpecifierFromGroupKey(groupPubKey btcec.PublicKey) Specifier {
308343
}
309344
}
310345

346+
// String returns a human-readable description of the specifier.
347+
func (s *Specifier) String() string {
348+
// An unset asset ID is represented as an empty string.
349+
var assetIdStr string
350+
s.WhenId(func(id ID) {
351+
assetIdStr = id.String()
352+
})
353+
354+
var groupKeyBytes []byte
355+
s.WhenGroupPubKey(func(key btcec.PublicKey) {
356+
groupKeyBytes = key.SerializeCompressed()
357+
})
358+
359+
return fmt.Sprintf("AssetSpecifier(id=%s, group_pub_key=%x)",
360+
assetIdStr, groupKeyBytes)
361+
}
362+
311363
// AsBytes returns the asset ID and group public key as byte slices.
312364
func (s *Specifier) AsBytes() ([]byte, []byte) {
313365
var assetIDBytes, groupKeyBytes []byte
@@ -333,6 +385,11 @@ func (s *Specifier) HasGroupPubKey() bool {
333385
return s.groupKey.IsSome()
334386
}
335387

388+
// IsSome returns true if the specifier is set.
389+
func (s *Specifier) IsSome() bool {
390+
return s.HasId() || s.HasGroupPubKey()
391+
}
392+
336393
// WhenId executes the given function if the ID field is specified.
337394
func (s *Specifier) WhenId(f func(ID)) {
338395
s.id.WhenSome(f)
@@ -365,6 +422,12 @@ func (s *Specifier) UnwrapGroupKeyToPtr() *btcec.PublicKey {
365422
return s.groupKey.UnwrapToPtr()
366423
}
367424

425+
// UnwrapToPtr unwraps the asset ID and asset group public key fields,
426+
// returning them as pointers.
427+
func (s *Specifier) UnwrapToPtr() (*ID, *btcec.PublicKey) {
428+
return s.UnwrapIdToPtr(), s.UnwrapGroupKeyToPtr()
429+
}
430+
368431
// Type denotes the asset types supported by the Taproot Asset protocol.
369432
type Type uint8
370433

itest/rfq_test.go

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"time"
1010

1111
"github.com/btcsuite/btcd/btcutil"
12+
"github.com/lightninglabs/taproot-assets/asset"
13+
"github.com/lightninglabs/taproot-assets/fn"
1214
"github.com/lightninglabs/taproot-assets/rfqmsg"
1315
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
1416
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
@@ -19,6 +21,7 @@ import (
1921
"github.com/lightningnetwork/lnd/lntest/node"
2022
"github.com/lightningnetwork/lnd/lntest/wait"
2123
"github.com/lightningnetwork/lnd/lnwire"
24+
"github.com/lightningnetwork/lnd/tlv"
2225
"github.com/stretchr/testify/require"
2326
)
2427

@@ -190,8 +193,8 @@ func testRfqAssetBuyHtlcIntercept(t *harnessTest) {
190193
t.Log("Alice payment sent")
191194

192195
// At this point Bob should have received a HTLC with the asset transfer
193-
// specific scid. We'll wait for Bob to publish an accept HTLC event and
194-
// then validate it against the accepted quote.
196+
// specific scid. We'll wait for Bob to validate the HTLC against the
197+
// accepted quote and publish a HTLC accept event.
195198
BeforeTimeout(t.t, func() {
196199
t.Log("Waiting for Bob to receive HTLC")
197200

@@ -229,7 +232,11 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
229232
t.t, t.lndHarness.Miner().Client, ts.AliceTapd,
230233
[]*mintrpc.MintAssetRequest{issuableAssets[0]},
231234
)
232-
mintedAssetId := rpcAssets[0].AssetGenesis.AssetId
235+
mintedAssetIdBytes := rpcAssets[0].AssetGenesis.AssetId
236+
237+
// Type convert the asset ID bytes to an `asset.ID`.
238+
var mintedAssetId asset.ID
239+
copy(mintedAssetId[:], mintedAssetIdBytes[:])
233240

234241
ctxb := context.Background()
235242
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
@@ -241,7 +248,7 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
241248
ctxt, &rfqrpc.AddAssetBuyOfferRequest{
242249
AssetSpecifier: &rfqrpc.AssetSpecifier{
243250
Id: &rfqrpc.AssetSpecifier_AssetId{
244-
AssetId: mintedAssetId,
251+
AssetId: mintedAssetIdBytes,
245252
},
246253
},
247254
MaxUnits: 1000,
@@ -257,20 +264,18 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
257264

258265
// Alice sends a sell order to Bob for some amount of the newly minted
259266
// asset.
260-
purchaseAssetAmt := uint64(200)
261267
askAmt := uint64(42000)
262268
sellOrderExpiry := uint64(time.Now().Add(24 * time.Hour).Unix())
263269

264270
_, err = ts.AliceTapd.AddAssetSellOrder(
265271
ctxt, &rfqrpc.AddAssetSellOrderRequest{
266272
AssetSpecifier: &rfqrpc.AssetSpecifier{
267273
Id: &rfqrpc.AssetSpecifier_AssetId{
268-
AssetId: mintedAssetId,
274+
AssetId: mintedAssetIdBytes,
269275
},
270276
},
271-
MaxAssetAmount: purchaseAssetAmt,
272-
MinAsk: askAmt,
273-
Expiry: sellOrderExpiry,
277+
PaymentMaxAmt: askAmt,
278+
Expiry: sellOrderExpiry,
274279

275280
// Here we explicitly specify Bob as the destination
276281
// peer for the sell order. This will prompt Alice's
@@ -303,6 +308,10 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
303308

304309
acceptedQuote := acceptedQuotes.SellQuotes[0]
305310

311+
// Type cast the accepted quote ID bytes to an `rfqmsg.ID`.
312+
var acceptedQuoteId rfqmsg.ID
313+
copy(acceptedQuoteId[:], acceptedQuote.Id[:])
314+
306315
// Register to receive RFQ events from Bob's tapd node. We'll use this
307316
// to wait for Bob to receive the HTLC with the asset transfer specific
308317
// scid.
@@ -337,19 +346,40 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
337346

338347
// Send the payment to the route.
339348
t.Log("Alice paying invoice")
340-
var htlcRfqIDTlvType rfqmsg.HtlcRfqIDType
349+
350+
// Construct first hop custom records for payment.
351+
//
352+
// The custom records will contain the accepted quote ID and the asset
353+
// amounts that Alice will pay to Bob.
354+
//
355+
// We select an asset amount which is sufficient to cover the invoice
356+
// amount.
357+
paymentAssetAmount := uint64(42)
358+
assetAmounts := []*rfqmsg.AssetBalance{
359+
rfqmsg.NewAssetBalance(mintedAssetId, paymentAssetAmount),
360+
}
361+
362+
htlcCustomRecords := rfqmsg.NewHtlc(
363+
assetAmounts, fn.Some(acceptedQuoteId),
364+
)
365+
366+
// Convert the custom records to a TLV map for inclusion in
367+
// SendToRouteRequest.
368+
firstHopCustomRecords, err := tlv.RecordsToMap(
369+
htlcCustomRecords.Records(),
370+
)
371+
require.NoError(t.t, err)
372+
341373
routeReq := routerrpc.SendToRouteRequest{
342-
PaymentHash: invoice.RHash,
343-
Route: routeBuildResp.Route,
344-
FirstHopCustomRecords: map[uint64][]byte{
345-
uint64(htlcRfqIDTlvType.TypeVal()): acceptedQuote.Id[:],
346-
},
374+
PaymentHash: invoice.RHash,
375+
Route: routeBuildResp.Route,
376+
FirstHopCustomRecords: firstHopCustomRecords,
347377
}
348378
sendAttempt := ts.AliceLnd.RPC.SendToRouteV2(&routeReq)
349379

350-
// The payment will fail since it doesn't transport the correct amount
351-
// of the asset.
352-
require.Equal(t.t, lnrpc.HTLCAttempt_FAILED, sendAttempt.Status)
380+
// The payment will succeed since it the asset amount transport is
381+
// sufficient to cover the invoice amount.
382+
require.Equal(t.t, lnrpc.HTLCAttempt_SUCCEEDED, sendAttempt.Status)
353383

354384
// At this point Bob should have received a HTLC with the asset transfer
355385
// specific scid. We'll wait for Bob to publish an accept HTLC event and
@@ -365,7 +395,7 @@ func testRfqAssetSellHtlcIntercept(t *harnessTest) {
365395

366396
// Confirm that Carol receives the lightning payment from Alice via Bob.
367397
invoice = ts.CarolLnd.RPC.LookupInvoice(addInvoiceResp.RHash)
368-
require.Equal(t.t, invoice.State, lnrpc.Invoice_OPEN)
398+
require.Equal(t.t, lnrpc.Invoice_SETTLED, invoice.State)
369399

370400
// Close event notification streams.
371401
err = aliceEventNtfns.CloseSend()

rfq/manager.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ func (m *Manager) handleIncomingMessage(incomingMsg rfqmsg.IncomingMsg) error {
391391
// and compare it to the one in the invoice.
392392
err := m.addScidAlias(
393393
uint64(msg.ShortChannelId()),
394-
*msg.Request.AssetID, msg.Peer,
394+
msg.Request.AssetSpecifier, msg.Peer,
395395
)
396396
if err != nil {
397397
m.handleError(
@@ -483,8 +483,8 @@ func (m *Manager) handleOutgoingMessage(outgoingMsg rfqmsg.OutgoingMsg) error {
483483
// make sure we can identify the forwarded asset payment by the
484484
// outgoing SCID alias within the onion packet.
485485
err := m.addScidAlias(
486-
uint64(msg.ShortChannelId()), *msg.Request.AssetID,
487-
msg.Peer,
486+
uint64(msg.ShortChannelId()),
487+
msg.Request.AssetSpecifier, msg.Peer,
488488
)
489489
if err != nil {
490490
return fmt.Errorf("error adding local alias: %w", err)
@@ -514,7 +514,7 @@ func (m *Manager) handleOutgoingMessage(outgoingMsg rfqmsg.OutgoingMsg) error {
514514
}
515515

516516
// addScidAlias adds a SCID alias to the alias manager.
517-
func (m *Manager) addScidAlias(scidAlias uint64, assetID asset.ID,
517+
func (m *Manager) addScidAlias(scidAlias uint64, assetSpecifier asset.Specifier,
518518
peer route.Vertex) error {
519519

520520
// Retrieve all local channels.
@@ -536,6 +536,12 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetID asset.ID,
536536

537537
// Identify the correct channel to use as the base SCID for the alias
538538
// by inspecting the asset data in the custom channel data.
539+
assetID, err := assetSpecifier.UnwrapIdOrErr()
540+
if err != nil {
541+
return fmt.Errorf("asset ID must be specified when adding "+
542+
"alias: %w", err)
543+
}
544+
539545
var (
540546
assetIDStr = assetID.String()
541547
baseSCID uint64
@@ -733,14 +739,15 @@ type SellOrder struct {
733739
// AssetGroupKey is the public key of the asset group to sell.
734740
AssetGroupKey *btcec.PublicKey
735741

736-
// MaxAssetAmount is the maximum amount of the asset that can be sold as
737-
// part of the order.
738-
MaxAssetAmount uint64
739-
740-
// MinAsk is the minimum ask price that the seller is willing to accept.
741-
MinAsk lnwire.MilliSatoshi
742+
// PaymentMaxAmt is the maximum msat amount that the responding peer
743+
// must agree to pay.
744+
PaymentMaxAmt lnwire.MilliSatoshi
742745

743746
// Expiry is the unix timestamp at which the order expires.
747+
//
748+
// TODO(ffranr): This is the invoice expiry unix timestamp in seconds.
749+
// We should make use of this field to ensure quotes are valid for the
750+
// duration of the invoice.
744751
Expiry uint64
745752

746753
// Peer is the peer that the buy order is intended for. This field is

0 commit comments

Comments
 (0)