Skip to content

Commit 00ebf2f

Browse files
GeorgeTsagkguggero
authored andcommitted
tapchannel: add aux invoice manager tests
1 parent ad05718 commit 00ebf2f

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
package tapchannel
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"fmt"
7+
"math/big"
8+
"testing"
9+
"time"
10+
11+
"github.com/lightninglabs/lndclient"
12+
"github.com/lightninglabs/taproot-assets/asset"
13+
"github.com/lightninglabs/taproot-assets/fn"
14+
"github.com/lightninglabs/taproot-assets/rfq"
15+
"github.com/lightninglabs/taproot-assets/rfqmath"
16+
"github.com/lightninglabs/taproot-assets/rfqmsg"
17+
"github.com/lightningnetwork/lnd/lnrpc"
18+
"github.com/lightningnetwork/lnd/lnwire"
19+
"github.com/lightningnetwork/lnd/routing/route"
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
const (
24+
// The test channel ID to use across the test cases.
25+
testChanID = 1234
26+
)
27+
28+
var (
29+
// The node ID to be used for the RFQ peer.
30+
testNodeID = route.Vertex{1, 2, 3}
31+
32+
assetRate = big.NewInt(100_000)
33+
34+
testAssetRate = rfqmath.FixedPoint[rfqmath.BigInt]{
35+
Coefficient: rfqmath.NewBigInt(assetRate),
36+
Scale: 0,
37+
}
38+
)
39+
40+
// mockRfqManager mocks the interface of the rfq manager required by the aux
41+
// invoice manager. It also holds some internal state to return the desired
42+
// quotes.
43+
type mockRfqManager struct {
44+
peerBuyQuotes rfq.BuyAcceptMap
45+
localSellQuotes rfq.SellAcceptMap
46+
}
47+
48+
func (m *mockRfqManager) PeerAcceptedBuyQuotes() rfq.BuyAcceptMap {
49+
return m.peerBuyQuotes
50+
}
51+
52+
func (m *mockRfqManager) LocalAcceptedSellQuotes() rfq.SellAcceptMap {
53+
return m.localSellQuotes
54+
}
55+
56+
// mockHtlcModifier mocks the HtlcModifier interface that is required by the
57+
// AuxInvoiceManager.
58+
type mockHtlcModifier struct {
59+
requestQue []lndclient.InvoiceHtlcModifyRequest
60+
expectedResQue []lndclient.InvoiceHtlcModifyResponse
61+
done chan bool
62+
t *testing.T
63+
}
64+
65+
// HtlcModifier handles the invoice htlc modification requests que, then checks
66+
// the returned error and response against the expected values.
67+
func (m *mockHtlcModifier) HtlcModifier(ctx context.Context,
68+
handler lndclient.InvoiceHtlcModifyHandler) error {
69+
70+
// Process the requests that are provided by the test case.
71+
for i, r := range m.requestQue {
72+
res, err := handler(ctx, r)
73+
74+
if err != nil {
75+
return err
76+
}
77+
78+
if m.expectedResQue[i].CancelSet {
79+
if !res.CancelSet {
80+
return fmt.Errorf("expected cancel set flag")
81+
}
82+
83+
continue
84+
}
85+
86+
// Check if there's a match with the expected outcome.
87+
if res.AmtPaid != m.expectedResQue[i].AmtPaid {
88+
return fmt.Errorf("invoice paid amount does not match "+
89+
"expected amount, %v != %v", res.AmtPaid,
90+
m.expectedResQue[i].AmtPaid)
91+
}
92+
}
93+
94+
// Signal that the htlc modifications are completed.
95+
close(m.done)
96+
97+
return nil
98+
}
99+
100+
// TestAuxInvoiceManager tests that the htlc modifications of the aux invoice
101+
// manager align with our expectations.
102+
func TestAuxInvoiceManager(t *testing.T) {
103+
testCases := []struct {
104+
name string
105+
buyQuotes rfq.BuyAcceptMap
106+
sellQuotes rfq.SellAcceptMap
107+
requests []lndclient.InvoiceHtlcModifyRequest
108+
responses []lndclient.InvoiceHtlcModifyResponse
109+
containedErrStr string
110+
}{
111+
{
112+
name: "non asset invoice",
113+
requests: []lndclient.InvoiceHtlcModifyRequest{
114+
{
115+
Invoice: &lnrpc.Invoice{},
116+
ExitHtlcAmt: 1234,
117+
},
118+
},
119+
responses: []lndclient.InvoiceHtlcModifyResponse{
120+
{
121+
AmtPaid: 1234,
122+
},
123+
},
124+
},
125+
{
126+
name: "non asset routing hints",
127+
requests: []lndclient.InvoiceHtlcModifyRequest{
128+
{
129+
Invoice: &lnrpc.Invoice{
130+
RouteHints: testNonAssetHints(),
131+
ValueMsat: 1_000_000,
132+
},
133+
ExitHtlcAmt: 1234,
134+
},
135+
},
136+
responses: []lndclient.InvoiceHtlcModifyResponse{
137+
{
138+
AmtPaid: 1234,
139+
},
140+
},
141+
buyQuotes: map[rfq.SerialisedScid]rfqmsg.BuyAccept{
142+
testChanID: {
143+
Peer: testNodeID,
144+
},
145+
},
146+
},
147+
{
148+
name: "asset invoice, no custom records",
149+
requests: []lndclient.InvoiceHtlcModifyRequest{
150+
{
151+
Invoice: &lnrpc.Invoice{
152+
RouteHints: testRouteHints(),
153+
PaymentAddr: []byte{1, 1, 1},
154+
},
155+
ExitHtlcAmt: 1234,
156+
},
157+
},
158+
responses: []lndclient.InvoiceHtlcModifyResponse{
159+
{
160+
CancelSet: true,
161+
},
162+
},
163+
buyQuotes: map[rfq.SerialisedScid]rfqmsg.BuyAccept{
164+
testChanID: {
165+
Peer: testNodeID,
166+
},
167+
},
168+
},
169+
{
170+
name: "asset invoice, custom records",
171+
requests: []lndclient.InvoiceHtlcModifyRequest{
172+
{
173+
Invoice: &lnrpc.Invoice{
174+
RouteHints: testRouteHints(),
175+
ValueMsat: 3_000_000,
176+
PaymentAddr: []byte{1, 1, 1},
177+
},
178+
WireCustomRecords: newWireCustomRecords(
179+
t, []*rfqmsg.AssetBalance{
180+
rfqmsg.NewAssetBalance(
181+
dummyAssetID(1), 3,
182+
),
183+
}, fn.Some(dummyRfqID(31)),
184+
),
185+
},
186+
},
187+
responses: []lndclient.InvoiceHtlcModifyResponse{
188+
{
189+
AmtPaid: 3_000_000,
190+
},
191+
},
192+
buyQuotes: rfq.BuyAcceptMap{
193+
fn.Ptr(dummyRfqID(31)).Scid(): {
194+
Peer: testNodeID,
195+
AssetRate: testAssetRate,
196+
},
197+
},
198+
},
199+
{
200+
name: "asset invoice, not enough amt",
201+
requests: []lndclient.InvoiceHtlcModifyRequest{
202+
{
203+
Invoice: &lnrpc.Invoice{
204+
RouteHints: testRouteHints(),
205+
ValueMsat: 10_000_000,
206+
PaymentAddr: []byte{1, 1, 1},
207+
},
208+
WireCustomRecords: newWireCustomRecords(
209+
t, []*rfqmsg.AssetBalance{
210+
rfqmsg.NewAssetBalance(
211+
dummyAssetID(1),
212+
4,
213+
),
214+
}, fn.Some(dummyRfqID(31)),
215+
),
216+
ExitHtlcAmt: 1234,
217+
},
218+
},
219+
responses: []lndclient.InvoiceHtlcModifyResponse{
220+
{
221+
AmtPaid: 4_000_000,
222+
},
223+
},
224+
buyQuotes: rfq.BuyAcceptMap{
225+
fn.Ptr(dummyRfqID(31)).Scid(): {
226+
Peer: testNodeID,
227+
AssetRate: testAssetRate,
228+
},
229+
},
230+
},
231+
}
232+
233+
for _, testCase := range testCases {
234+
testCase := testCase
235+
236+
t.Logf("Running AuxInvoiceManager test case: %v", testCase.name)
237+
238+
// Instantiate mock rfq manager.
239+
mockRfq := &mockRfqManager{
240+
peerBuyQuotes: testCase.buyQuotes,
241+
localSellQuotes: testCase.sellQuotes,
242+
}
243+
244+
done := make(chan bool)
245+
246+
// Instantiate mock htlc modifier.
247+
mockModifier := &mockHtlcModifier{
248+
requestQue: testCase.requests,
249+
expectedResQue: testCase.responses,
250+
done: done,
251+
t: t,
252+
}
253+
254+
// Create the manager.
255+
manager := NewAuxInvoiceManager(
256+
&InvoiceManagerConfig{
257+
ChainParams: testChainParams,
258+
InvoiceHtlcModifier: mockModifier,
259+
RfqManager: mockRfq,
260+
},
261+
)
262+
263+
err := manager.Start()
264+
require.NoError(t, err)
265+
266+
// If the manager is not done processing the htlc modification
267+
// requests within the specified timeout, assume this is a
268+
// failure.
269+
select {
270+
case <-done:
271+
case <-time.After(testTimeout):
272+
t.Fail()
273+
}
274+
}
275+
}
276+
277+
func newHash(i []byte) []byte {
278+
h := sha256.New()
279+
_, _ = h.Write(i)
280+
281+
return h.Sum(nil)
282+
}
283+
284+
func dummyAssetID(i byte) asset.ID {
285+
return asset.ID(newHash([]byte{i}))
286+
}
287+
288+
func dummyRfqID(value int) rfqmsg.ID {
289+
return rfqmsg.ID(newHash([]byte{byte(value)}))
290+
}
291+
292+
func testRouteHints() []*lnrpc.RouteHint {
293+
return []*lnrpc.RouteHint{
294+
{
295+
HopHints: []*lnrpc.HopHint{
296+
{
297+
ChanId: 1111,
298+
NodeId: route.Vertex{1, 1, 1}.String(),
299+
},
300+
{
301+
ChanId: 1111,
302+
NodeId: route.Vertex{1, 1, 1}.String(),
303+
},
304+
},
305+
},
306+
{
307+
HopHints: []*lnrpc.HopHint{
308+
{
309+
ChanId: 1233,
310+
NodeId: route.Vertex{1, 1, 1}.String(),
311+
},
312+
{
313+
ChanId: 1234,
314+
NodeId: route.Vertex{1, 2, 3}.String(),
315+
},
316+
},
317+
},
318+
}
319+
}
320+
321+
func testNonAssetHints() []*lnrpc.RouteHint {
322+
return []*lnrpc.RouteHint{
323+
{
324+
HopHints: []*lnrpc.HopHint{
325+
{
326+
ChanId: 1234,
327+
NodeId: route.Vertex{1, 1, 1}.String(),
328+
},
329+
{
330+
ChanId: 1234,
331+
NodeId: route.Vertex{1, 1, 1}.String(),
332+
},
333+
},
334+
},
335+
{
336+
HopHints: []*lnrpc.HopHint{
337+
{
338+
ChanId: 1234,
339+
NodeId: route.Vertex{2, 2, 2}.String(),
340+
},
341+
},
342+
},
343+
}
344+
}
345+
346+
func newWireCustomRecords(t *testing.T, amounts []*rfqmsg.AssetBalance,
347+
rfqID fn.Option[rfqmsg.ID]) lnwire.CustomRecords {
348+
349+
htlc := rfqmsg.NewHtlc(amounts, rfqID)
350+
351+
customRecords, err := lnwire.ParseCustomRecords(htlc.Bytes())
352+
require.NoError(t, err)
353+
354+
return customRecords
355+
}

0 commit comments

Comments
 (0)