Skip to content

Commit 4c43aed

Browse files
committed
lntest: add itest for simple payment testing upfront fees
1 parent 039f23c commit 4c43aed

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

lntest/itest/lnd_test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,8 @@ var allTestCases = []*testCase{
140140
name: "custom messaging",
141141
test: testCustomMessage,
142142
},
143+
{
144+
name: "upfront fees",
145+
test: testUpfrontFees,
146+
},
143147
}

lntest/itest/lnd_upfront_fees.go

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
package itest
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"testing"
7+
8+
"github.com/btcsuite/btcd/btcutil"
9+
"github.com/btcsuite/btcd/wire"
10+
"github.com/lightningnetwork/lnd/lnrpc"
11+
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
12+
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
13+
"github.com/lightningnetwork/lnd/lntest"
14+
"github.com/lightningnetwork/lnd/lntest/wait"
15+
"github.com/lightningnetwork/lnd/lnwire"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
// setupThreeHopNetwork creates a network with the following topology and
21+
// liquidity:
22+
// Alice (100k)----- Bob (100k) ----- Carol (100k) ----- Dave
23+
//
24+
// The funding outpoint for AB / BC / CD are returned in-order.
25+
func setupThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
26+
carol, dave *lntest.HarnessNode) []lnwire.ShortChannelID {
27+
28+
const chanAmt = btcutil.Amount(100000)
29+
var networkChans []*lnrpc.ChannelPoint
30+
31+
// Open a channel with 100k satoshis between Alice and Bob with Alice
32+
// being the sole funder of the channel.
33+
chanPointAlice := openChannelAndAssert(
34+
t, net, net.Alice, net.Bob,
35+
lntest.OpenChannelParams{
36+
Amt: chanAmt,
37+
},
38+
)
39+
networkChans = append(networkChans, chanPointAlice)
40+
41+
aliceChan, err := getChanInfo(net.Alice)
42+
require.NoError(t.t, err, "alice channel")
43+
44+
// Create a channel between bob and carol.
45+
t.lndHarness.EnsureConnected(t.t, net.Bob, carol)
46+
chanPointBob := openChannelAndAssert(
47+
t, net, net.Bob, carol,
48+
lntest.OpenChannelParams{
49+
Amt: chanAmt,
50+
},
51+
)
52+
networkChans = append(networkChans, chanPointBob)
53+
54+
// Our helper function expects one channel, so we lookup using carol.
55+
bobChan, err := getChanInfo(carol)
56+
require.NoError(t.t, err, "bob channel")
57+
58+
// Fund carol and connect her and dave so that she can create a channel
59+
// between them.
60+
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
61+
net.ConnectNodes(t.t, carol, dave)
62+
63+
t.lndHarness.EnsureConnected(t.t, carol, dave)
64+
chanPointCarol := openChannelAndAssert(
65+
t, net, carol, dave,
66+
lntest.OpenChannelParams{
67+
Amt: chanAmt,
68+
},
69+
)
70+
networkChans = append(networkChans, chanPointCarol)
71+
72+
// As above, we use the helper that only expects one channel so we
73+
// lookup on dave's end.
74+
carolChan, err := getChanInfo(dave)
75+
require.NoError(t.t, err, "carol chan")
76+
77+
// Wait for all nodes to have seen all channels.
78+
nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave}
79+
nodeNames := []string{"Alice", "Bob", "Carol", "Dave"}
80+
for _, chanPoint := range networkChans {
81+
for i, node := range nodes {
82+
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
83+
require.NoError(t.t, err, "unable to get txid")
84+
85+
point := wire.OutPoint{
86+
Hash: *txid,
87+
Index: chanPoint.OutputIndex,
88+
}
89+
90+
err = node.WaitForNetworkChannelOpen(chanPoint)
91+
require.NoError(t.t, err, "%s(%d): timeout waiting "+
92+
"for channel(%s) open", nodeNames[i],
93+
node.NodeID, point)
94+
}
95+
}
96+
97+
// Update our channel policy so that our upfront fee will hit 1 sat (1%
98+
// of our base fee). We do this so that the change in balance will show
99+
// up on our ListChannels RPC, which is expressed in satoshis.
100+
//
101+
// We only need to set outgoing edges and ensure Alice sees them
102+
// because she's the one who will be routing, and fees are charged on
103+
// the outgoing edge.
104+
setChannelPolicy(t, chanPointBob, net.Bob, net.Alice)
105+
setChannelPolicy(t, chanPointCarol, carol, net.Alice)
106+
107+
return []lnwire.ShortChannelID{
108+
lnwire.NewShortChanIDFromInt(aliceChan.ChanId),
109+
lnwire.NewShortChanIDFromInt(bobChan.ChanId),
110+
lnwire.NewShortChanIDFromInt(carolChan.ChanId),
111+
}
112+
}
113+
114+
// setChanelPolicy updates a channel's policy and asserts that a watching node
115+
// sees the update in its gossip. We set the base fee on our policy such that
116+
// a 1% upfront fee will be 1 satoshi, and reflect in our channel balances.
117+
//
118+
//nolint:gomnd
119+
func setChannelPolicy(t *harnessTest, channel *lnrpc.ChannelPoint,
120+
updatingNode, watchingNode *lntest.HarnessNode) {
121+
122+
ctxb := context.Background()
123+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
124+
defer cancel()
125+
126+
// Lookup our existing policy and copying over some existing values (so
127+
// that we have a valid update), setting only our base fee.
128+
policies := getChannelPolicies(
129+
t, updatingNode, updatingNode.PubKeyStr, channel,
130+
)
131+
require.Len(t.t, policies, 1)
132+
133+
req := &lnrpc.PolicyUpdateRequest{
134+
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
135+
ChanPoint: channel,
136+
},
137+
BaseFeeMsat: 100_000,
138+
TimeLockDelta: policies[0].TimeLockDelta,
139+
}
140+
141+
resp, err := updatingNode.UpdateChannelPolicy(ctxt, req)
142+
require.NoError(t.t, err)
143+
require.Len(t.t, resp.FailedUpdates, 0)
144+
145+
pred := func() bool {
146+
policies := getChannelPolicies(
147+
t, watchingNode, updatingNode.PubKeyStr, channel,
148+
)
149+
if len(policies) != 1 {
150+
return false
151+
}
152+
153+
return policies[0].FeeBaseMsat == req.BaseFeeMsat
154+
}
155+
156+
require.NoError(t.t, wait.Predicate(pred, defaultTimeout))
157+
}
158+
159+
func lookupChannel(ctxb context.Context, t *testing.T, node *lntest.HarnessNode,
160+
channelID uint64) *lnrpc.Channel {
161+
162+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
163+
resp, err := node.ListChannels(ctxt, &lnrpc.ListChannelsRequest{})
164+
require.NoError(t, err)
165+
cancel()
166+
167+
for _, channel := range resp.Channels {
168+
if channel.ChanId != channelID {
169+
continue
170+
}
171+
172+
return channel
173+
}
174+
175+
t.Fatalf("node: %v does not have channel: %v", node.PubKeyStr,
176+
channelID)
177+
178+
return nil
179+
}
180+
181+
//nolint:gomnd
182+
func testUpfrontFees(net *lntest.NetworkHarness, t *harnessTest) {
183+
ctxb := context.Background()
184+
carol := net.NewNode(t.t, "Carol", nil)
185+
defer shutdownAndAssert(net, t, carol)
186+
187+
dave := net.NewNode(t.t, "Dave", nil)
188+
defer shutdownAndAssert(net, t, dave)
189+
190+
// Create a hodl invoice so that we can hold the pending htlc on the
191+
// receiver while we assert that upfront fees have been pushed along
192+
// the route.
193+
preimage := [32]byte{1, 2, 3}
194+
hash := sha256.Sum256(preimage[:])
195+
req := &invoicesrpc.AddHoldInvoiceRequest{
196+
ValueMsat: 10_000,
197+
Hash: hash[:],
198+
}
199+
200+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
201+
resp, err := dave.InvoicesClient.AddHoldInvoice(ctxt, req)
202+
require.NoError(t.t, err)
203+
cancel()
204+
205+
// Setup Alice --- Bob --- Carol --- Dave channels.
206+
channels := setupThreeHopNetwork(t, net, carol, dave)
207+
208+
// Get starting balances for each channel.
209+
aliceBob0 := lookupChannel(ctxb, t.t, net.Alice, channels[0].ToUint64())
210+
bobCarol0 := lookupChannel(ctxb, t.t, net.Bob, channels[1].ToUint64())
211+
carolDave0 := lookupChannel(ctxb, t.t, carol, channels[2].ToUint64())
212+
213+
ctxt, cancel = context.WithCancel(ctxb)
214+
defer cancel()
215+
sendResp, err := net.Alice.RouterClient.SendPaymentV2(
216+
ctxt, &routerrpc.SendPaymentRequest{
217+
NoInflightUpdates: true,
218+
PaymentRequest: resp.PaymentRequest,
219+
TimeoutSeconds: 120,
220+
FeeLimitMsat: 1000000,
221+
},
222+
)
223+
require.NoError(t.t, err)
224+
225+
// Assert that the HTLC is locked in along our route.
226+
nodes := []*lntest.HarnessNode{
227+
net.Alice, net.Bob, carol, dave,
228+
}
229+
230+
pred := func() bool {
231+
return assertActiveHtlcs(nodes, hash[:]) == nil
232+
}
233+
require.NoError(t.t, wait.Predicate(pred, defaultTimeout))
234+
235+
// Wait for the invoice to be updated to an accepted state (we need
236+
// to exchange some commitments) before we settle. We also know that
237+
// the HTLC is fully locked in once the HTLC is accepted, so we can
238+
// check balances.
239+
pred = func() bool {
240+
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
241+
defer cancel()
242+
243+
req := &invoicesrpc.LookupInvoiceMsg{
244+
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentHash{
245+
PaymentHash: hash[:],
246+
},
247+
}
248+
249+
inv, err := dave.InvoicesClient.LookupInvoiceV2(ctxt, req)
250+
if err != nil {
251+
return false
252+
}
253+
254+
return inv.State == lnrpc.Invoice_ACCEPTED
255+
}
256+
require.NoError(t.t, wait.Predicate(pred, defaultTimeout))
257+
258+
// Check that nodes have pushed the upfront fee along the route. The
259+
// HTLC is pending here so we can use
260+
aliceBob1 := lookupChannel(ctxb, t.t, net.Alice, channels[0].ToUint64())
261+
bobCarol1 := lookupChannel(ctxb, t.t, net.Bob, channels[1].ToUint64())
262+
carolDave1 := lookupChannel(ctxb, t.t, carol, channels[2].ToUint64())
263+
264+
// Alice --- Bob: assert that Alice has less funds than before, and
265+
// Bob has more.
266+
require.Less(t.t, aliceBob1.LocalBalance, aliceBob0.LocalBalance)
267+
require.Greater(t.t, aliceBob1.RemoteBalance, aliceBob0.RemoteBalance)
268+
269+
// Bob --- Carol: assert that Bob has less funds than before, and Bob
270+
// has more.
271+
require.Less(t.t, bobCarol1.LocalBalance, bobCarol0.LocalBalance)
272+
require.Greater(t.t, bobCarol1.RemoteBalance, bobCarol0.RemoteBalance)
273+
274+
// Carol --- Dave: assert that Carol has less funds than before, and
275+
// Dave has more.
276+
require.Less(t.t, carolDave1.LocalBalance, carolDave0.LocalBalance)
277+
require.Greater(t.t, carolDave1.RemoteBalance, carolDave0.RemoteBalance)
278+
279+
// Finally, settle the HTLC and assert that the payment is a success.
280+
settleReq := &invoicesrpc.SettleInvoiceMsg{
281+
Preimage: preimage[:],
282+
}
283+
284+
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
285+
_, err = dave.InvoicesClient.SettleInvoice(ctxt, settleReq)
286+
cancel()
287+
require.NoError(t.t, err)
288+
289+
pmt, err := sendResp.Recv()
290+
require.NoError(t.t, err)
291+
292+
assert.Equal(t.t, lnrpc.Payment_SUCCEEDED, pmt.Status)
293+
assert.Equal(t.t, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
294+
pmt.FailureReason)
295+
}

0 commit comments

Comments
 (0)