Skip to content

Commit c83d994

Browse files
committed
itest: test that RFQ can agree on asset sell quote and verify HTLC
1 parent 31d1371 commit c83d994

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

itest/rfq_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/btcsuite/btcd/btcutil"
1010
"github.com/btcsuite/btcd/wire"
11+
"github.com/lightninglabs/taproot-assets/rfq"
1112
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
1213
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
1314
"github.com/lightningnetwork/lnd/chainreg"
@@ -213,6 +214,164 @@ func testRfqAssetBuyHtlcIntercept(t *harnessTest) {
213214
require.NoError(t.t, err)
214215
}
215216

217+
// testRfqAssetSellHtlcIntercept tests RFQ negotiation, HTLC interception, and
218+
// validation between three peers. The RFQ negotiation is initiated by an asset
219+
// sell request.
220+
func testRfqAssetSellHtlcIntercept(t *harnessTest) {
221+
// Initialize a new test scenario.
222+
ts := newRfqTestScenario(t)
223+
224+
// Mint an asset with Alice's tapd node.
225+
rpcAssets := MintAssetsConfirmBatch(
226+
t.t, t.lndHarness.Miner.Client, ts.AliceTapd,
227+
[]*mintrpc.MintAssetRequest{issuableAssets[0]},
228+
)
229+
mintedAssetId := rpcAssets[0].AssetGenesis.AssetId
230+
231+
ctxb := context.Background()
232+
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
233+
defer cancel()
234+
235+
// TODO(ffranr): Add an asset buy offer to Bob's tapd node. This will
236+
// allow Alice to sell the newly minted asset to Bob.
237+
238+
// Subscribe to Alice's RFQ events stream.
239+
aliceEventNtfns, err := ts.AliceTapd.SubscribeRfqEventNtfns(
240+
ctxb, &rfqrpc.SubscribeRfqEventNtfnsRequest{},
241+
)
242+
require.NoError(t.t, err)
243+
244+
// Alice sends a sell order to Bob for some amount of the newly minted
245+
// asset.
246+
purchaseAssetAmt := uint64(200)
247+
askAmt := uint64(42000)
248+
sellOrderExpiry := uint64(time.Now().Add(24 * time.Hour).Unix())
249+
250+
_, err = ts.AliceTapd.AddAssetSellOrder(
251+
ctxt, &rfqrpc.AddAssetSellOrderRequest{
252+
AssetSpecifier: &rfqrpc.AssetSpecifier{
253+
Id: &rfqrpc.AssetSpecifier_AssetId{
254+
AssetId: mintedAssetId,
255+
},
256+
},
257+
MaxAssetAmount: purchaseAssetAmt,
258+
MinAsk: askAmt,
259+
Expiry: sellOrderExpiry,
260+
261+
// Here we explicitly specify Bob as the destination
262+
// peer for the sell order. This will prompt Alice's
263+
// tapd node to send a request for quote message to
264+
// Bob's node.
265+
PeerPubKey: ts.BobLnd.PubKey[:],
266+
},
267+
)
268+
require.NoError(t.t, err, "unable to upsert asset sell order")
269+
270+
// Wait until Alice receives an incoming sell quote accept message (sent
271+
// from Bob) RFQ event notification.
272+
waitErr := wait.NoError(func() error {
273+
event, err := aliceEventNtfns.Recv()
274+
require.NoError(t.t, err)
275+
276+
_, ok := event.Event.(*rfqrpc.RfqEvent_PeerAcceptedSellQuote)
277+
require.True(t.t, ok, "unexpected event: %v", event)
278+
279+
return nil
280+
}, defaultWaitTimeout)
281+
require.NoError(t.t, waitErr)
282+
283+
// Alice should have received an accepted quote from Bob. This accepted
284+
// quote can be used by Alice to make a payment to Bob.
285+
acceptedQuotes, err := ts.AliceTapd.QueryRfqPeerAcceptedQuotes(
286+
ctxt, &rfqrpc.QueryRfqPeerAcceptedQuotesRequest{},
287+
)
288+
require.NoError(t.t, err, "unable to query accepted quotes")
289+
require.Len(t.t, acceptedQuotes.SellQuotes, 1)
290+
291+
acceptedQuote := acceptedQuotes.SellQuotes[0]
292+
293+
// Register to receive RFQ events from Bob's tapd node. We'll use this
294+
// to wait for Bob to receive the HTLC with the asset transfer specific
295+
// scid.
296+
bobEventNtfns, err := ts.BobTapd.SubscribeRfqEventNtfns(
297+
ctxb, &rfqrpc.SubscribeRfqEventNtfnsRequest{},
298+
)
299+
require.NoError(t.t, err)
300+
301+
// Carol generates and invoice for Alice to settle.
302+
addInvoiceResp := ts.CarolLnd.RPC.AddInvoice(&lnrpc.Invoice{
303+
ValueMsat: int64(askAmt),
304+
})
305+
invoice := ts.CarolLnd.RPC.LookupInvoice(addInvoiceResp.RHash)
306+
307+
// We now need to construct a route for the payment from Alice to Carol.
308+
// The route will be Alice -> Bob -> Carol. We'll add the accepted quote
309+
// ID as a record to the custom records field of the route's first hop.
310+
// This will allow Bob to validate the payment against the accepted
311+
// quote.
312+
routeBuildRequest := routerrpc.BuildRouteRequest{
313+
AmtMsat: int64(askAmt),
314+
HopPubkeys: [][]byte{
315+
ts.BobLnd.PubKey[:],
316+
ts.CarolLnd.PubKey[:],
317+
},
318+
}
319+
routeBuildResp := ts.AliceLnd.RPC.BuildRoute(&routeBuildRequest)
320+
321+
// Add the accepted quote ID as a record to the custom records field of
322+
// the route's first hop.
323+
aliceBobHop := routeBuildResp.Route.Hops[0]
324+
if aliceBobHop.CustomRecords == nil {
325+
aliceBobHop.CustomRecords = make(map[uint64][]byte)
326+
}
327+
328+
aliceBobHop.CustomRecords[rfq.LnCustomRecordType] =
329+
acceptedQuote.Id[:]
330+
331+
// Update the route with the modified first hop.
332+
routeBuildResp.Route.Hops[0] = aliceBobHop
333+
334+
// Send the payment to the route.
335+
t.Log("Alice paying invoice")
336+
ts.AliceLnd.RPC.SendToRouteV2(&routerrpc.SendToRouteRequest{
337+
PaymentHash: invoice.RHash,
338+
Route: routeBuildResp.Route,
339+
})
340+
341+
// At this point Bob should have received a HTLC with the asset transfer
342+
// specific scid. We'll wait for Bob to publish an accept HTLC event and
343+
// then validate it against the accepted quote.
344+
waitErr = wait.NoError(func() error {
345+
t.Log("Waiting for Bob to receive HTLC")
346+
347+
event, err := bobEventNtfns.Recv()
348+
require.NoError(t.t, err)
349+
350+
acceptHtlc, ok := event.Event.(*rfqrpc.RfqEvent_AcceptHtlc)
351+
if ok {
352+
require.Equal(
353+
t.t, acceptedQuote.Scid,
354+
acceptHtlc.AcceptHtlc.Scid,
355+
)
356+
t.Log("Bob has accepted the HTLC")
357+
return nil
358+
}
359+
360+
return fmt.Errorf("unexpected event: %v", event)
361+
}, defaultWaitTimeout)
362+
require.NoError(t.t, waitErr)
363+
364+
// TODO(ffranr): Confirm that Carol receives the lightning payment from
365+
// Bob.
366+
367+
// Close event notification streams.
368+
err = aliceEventNtfns.CloseSend()
369+
require.NoError(t.t, err)
370+
371+
err = bobEventNtfns.CloseSend()
372+
require.NoError(t.t, err)
373+
}
374+
216375
// newLndNode creates a new lnd node with the given name and funds its wallet
217376
// with the specified outputs.
218377
func newLndNode(name string, outputFunds []btcutil.Amount,

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ var testCases = []*testCase{
243243
name: "rfq asset buy htlc intercept",
244244
test: testRfqAssetBuyHtlcIntercept,
245245
},
246+
{
247+
name: "rfq asset sell htlc intercept",
248+
test: testRfqAssetSellHtlcIntercept,
249+
},
246250
}
247251

248252
var optionalTestCases = []*testCase{

0 commit comments

Comments
 (0)