mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-05 17:05:50 +02:00
multi: move itest
out of lntest
This commit moves all the test cases living in `itest` out of `lntest`, further making `lntest` an independent package for general testing.
This commit is contained in:
254
itest/lnd_multi-hop-payments_test.go
Normal file
254
itest/lnd_multi-hop-payments_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntemp"
|
||||
"github.com/lightningnetwork/lnd/lntemp/node"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testMultiHopPayments(ht *lntemp.HarnessTest) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
|
||||
// As preliminary setup, we'll create two new nodes: Carol and Dave,
|
||||
// such that we now have a 4 node, 3 channel topology. Dave will make a
|
||||
// channel with Alice, and Carol with Dave. After this setup, the
|
||||
// network topology should now look like:
|
||||
// Carol -> Dave -> Alice -> Bob
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
daveArgs := []string{"--protocol.legacy.onion"}
|
||||
dave := ht.NewNode("Dave", daveArgs)
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
|
||||
// Subscribe events early so we don't miss it out.
|
||||
aliceEvents := alice.RPC.SubscribeHtlcEvents()
|
||||
bobEvents := bob.RPC.SubscribeHtlcEvents()
|
||||
carolEvents := carol.RPC.SubscribeHtlcEvents()
|
||||
daveEvents := dave.RPC.SubscribeHtlcEvents()
|
||||
|
||||
// Once subscribed, the first event will be UNKNOWN.
|
||||
ht.AssertHtlcEventType(aliceEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
ht.AssertHtlcEventType(daveEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
|
||||
// Connect the nodes.
|
||||
ht.ConnectNodes(dave, alice)
|
||||
ht.ConnectNodes(carol, dave)
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
// We'll create Dave and establish a channel to Alice. Dave will be
|
||||
// running an older node that requires the legacy onion payload.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
chanPointDave := ht.OpenChannel(
|
||||
dave, alice, lntemp.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
// Next, we'll create Carol and establish a channel to from her to
|
||||
// Dave.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
carol, dave, lntemp.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
// Create 5 invoices for Bob, which expect a payment from Carol for 1k
|
||||
// satoshis with a different preimage each time.
|
||||
const numPayments = 5
|
||||
const paymentAmt = 1000
|
||||
payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numPayments)
|
||||
|
||||
// Set the fee policies of the Alice -> Bob and the Dave -> Alice
|
||||
// channel edges to relatively large non default values. This makes it
|
||||
// possible to pick up more subtle fee calculation errors.
|
||||
maxHtlc := calculateMaxHtlc(chanAmt)
|
||||
const aliceBaseFeeSat = 1
|
||||
const aliceFeeRatePPM = 100000
|
||||
updateChannelPolicy(
|
||||
ht, alice, chanPointAlice, aliceBaseFeeSat*1000,
|
||||
aliceFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta,
|
||||
maxHtlc, carol,
|
||||
)
|
||||
|
||||
const daveBaseFeeSat = 5
|
||||
const daveFeeRatePPM = 150000
|
||||
updateChannelPolicy(
|
||||
ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM,
|
||||
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol,
|
||||
)
|
||||
|
||||
// Using Carol as the source, pay to the 5 invoices from Bob created
|
||||
// above.
|
||||
ht.CompletePaymentRequests(carol, payReqs)
|
||||
|
||||
// At this point all the channels within our proto network should be
|
||||
// shifted by 5k satoshis in the direction of Bob, the sink within the
|
||||
// payment flow generated above. The order of asserts corresponds to
|
||||
// increasing of time is needed to embed the HTLC in commitment
|
||||
// transaction, in channel Carol->David->Alice->Bob, order is Bob,
|
||||
// Alice, David, Carol.
|
||||
|
||||
// The final node bob expects to get paid five times 1000 sat.
|
||||
expectedAmountPaidAtoB := int64(numPayments * paymentAmt)
|
||||
|
||||
ht.AssertAmountPaid("Alice(local) => Bob(remote)", bob,
|
||||
chanPointAlice, int64(0), expectedAmountPaidAtoB)
|
||||
ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice,
|
||||
chanPointAlice, expectedAmountPaidAtoB, int64(0))
|
||||
|
||||
// To forward a payment of 1000 sat, Alice is charging a fee of
|
||||
// 1 sat + 10% = 101 sat.
|
||||
const aliceFeePerPayment = aliceBaseFeeSat +
|
||||
(paymentAmt * aliceFeeRatePPM / 1_000_000)
|
||||
const expectedFeeAlice = numPayments * aliceFeePerPayment
|
||||
|
||||
// Dave needs to pay what Alice pays plus Alice's fee.
|
||||
expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice
|
||||
|
||||
ht.AssertAmountPaid("Dave(local) => Alice(remote)", alice,
|
||||
chanPointDave, int64(0), expectedAmountPaidDtoA)
|
||||
ht.AssertAmountPaid("Dave(local) => Alice(remote)", dave,
|
||||
chanPointDave, expectedAmountPaidDtoA, int64(0))
|
||||
|
||||
// To forward a payment of 1101 sat, Dave is charging a fee of
|
||||
// 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170.
|
||||
const davePaymentAmt = paymentAmt + aliceFeePerPayment
|
||||
const daveFeePerPayment = daveBaseFeeSat +
|
||||
(davePaymentAmt * daveFeeRatePPM / 1_000_000)
|
||||
const expectedFeeDave = numPayments * daveFeePerPayment
|
||||
|
||||
// Carol needs to pay what Dave pays plus Dave's fee.
|
||||
expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave
|
||||
|
||||
ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave,
|
||||
chanPointCarol, int64(0), expectedAmountPaidCtoD)
|
||||
ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol,
|
||||
chanPointCarol, expectedAmountPaidCtoD, int64(0))
|
||||
|
||||
// Now that we know all the balances have been settled out properly,
|
||||
// we'll ensure that our internal record keeping for completed circuits
|
||||
// was properly updated.
|
||||
|
||||
// First, check that the FeeReport response shows the proper fees
|
||||
// accrued over each time range. Dave should've earned 170 satoshi for
|
||||
// each of the forwarded payments.
|
||||
ht.AssertFeeReport(
|
||||
dave, expectedFeeDave, expectedFeeDave, expectedFeeDave,
|
||||
)
|
||||
|
||||
// Next, ensure that if we issue the vanilla query for the forwarding
|
||||
// history, it returns 5 values, and each entry is formatted properly.
|
||||
// From David's perspective he receives a payement from Carol and
|
||||
// forwards it to Alice. So let's ensure that the forwarding history
|
||||
// returns Carol's peer alias as inbound and Alice's alias as outbound.
|
||||
info := carol.RPC.GetInfo()
|
||||
carolAlias := info.Alias
|
||||
|
||||
info = alice.RPC.GetInfo()
|
||||
aliceAlias := info.Alias
|
||||
|
||||
fwdingHistory := dave.RPC.ForwardingHistory(nil)
|
||||
require.Len(ht, fwdingHistory.ForwardingEvents, numPayments)
|
||||
|
||||
expectedForwardingFee := uint64(expectedFeeDave / numPayments)
|
||||
for _, event := range fwdingHistory.ForwardingEvents {
|
||||
// Each event should show a fee of 170 satoshi.
|
||||
require.Equal(ht, expectedForwardingFee, event.Fee)
|
||||
|
||||
// Check that peer aliases are empty since the
|
||||
// ForwardingHistoryRequest did not specify the PeerAliasLookup
|
||||
// flag.
|
||||
require.Empty(ht, event.PeerAliasIn)
|
||||
require.Empty(ht, event.PeerAliasOut)
|
||||
}
|
||||
|
||||
// Lookup the forwarding history again but this time also lookup the
|
||||
// peers' alias names.
|
||||
fwdingHistory = dave.RPC.ForwardingHistory(
|
||||
&lnrpc.ForwardingHistoryRequest{
|
||||
PeerAliasLookup: true,
|
||||
},
|
||||
)
|
||||
require.Len(ht, fwdingHistory.ForwardingEvents, numPayments)
|
||||
for _, event := range fwdingHistory.ForwardingEvents {
|
||||
// Each event should show a fee of 170 satoshi.
|
||||
require.Equal(ht, expectedForwardingFee, event.Fee)
|
||||
|
||||
// Check that peer aliases adhere to payment flow, namely
|
||||
// Carol->Dave->Alice.
|
||||
require.Equal(ht, carolAlias, event.PeerAliasIn)
|
||||
require.Equal(ht, aliceAlias, event.PeerAliasOut)
|
||||
}
|
||||
|
||||
// We expect Carol to have successful forwards and settles for
|
||||
// her sends.
|
||||
ht.AssertHtlcEvents(
|
||||
carolEvents, numPayments, 0, numPayments,
|
||||
routerrpc.HtlcEvent_SEND,
|
||||
)
|
||||
|
||||
// Dave and Alice should both have forwards and settles for
|
||||
// their role as forwarding nodes.
|
||||
ht.AssertHtlcEvents(
|
||||
daveEvents, numPayments, 0, numPayments,
|
||||
routerrpc.HtlcEvent_FORWARD,
|
||||
)
|
||||
ht.AssertHtlcEvents(
|
||||
aliceEvents, numPayments, 0, numPayments,
|
||||
routerrpc.HtlcEvent_FORWARD,
|
||||
)
|
||||
|
||||
// Bob should only have settle events for his receives.
|
||||
ht.AssertHtlcEvents(
|
||||
bobEvents, 0, 0, numPayments, routerrpc.HtlcEvent_RECEIVE,
|
||||
)
|
||||
|
||||
// Finally, close all channels.
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
ht.CloseChannel(dave, chanPointDave)
|
||||
ht.CloseChannel(carol, chanPointCarol)
|
||||
}
|
||||
|
||||
// updateChannelPolicy updates the channel policy of node to the given fees and
|
||||
// timelock delta. This function blocks until listenerNode has received the
|
||||
// policy update.
|
||||
//
|
||||
// NOTE: only used in current test.
|
||||
func updateChannelPolicy(ht *lntemp.HarnessTest, hn *node.HarnessNode,
|
||||
chanPoint *lnrpc.ChannelPoint, baseFee int64,
|
||||
feeRate int64, timeLockDelta uint32,
|
||||
maxHtlc uint64, listenerNode *node.HarnessNode) {
|
||||
|
||||
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: baseFee,
|
||||
FeeRateMilliMsat: feeRate,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
MinHtlc: 1000, // default value
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
}
|
||||
|
||||
updateFeeReq := &lnrpc.PolicyUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: float64(feeRate) / testFeeBase,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: chanPoint,
|
||||
},
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
}
|
||||
|
||||
hn.RPC.UpdateChannelPolicy(updateFeeReq)
|
||||
|
||||
// Wait for listener node to receive the channel update from node.
|
||||
ht.AssertChannelPolicyUpdate(
|
||||
listenerNode, hn, expectedPolicy, chanPoint, false,
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user