lnd/itest/lnd_multi-hop-payments_test.go
Joost Jager 0bae781785 routing: add inbound fee support to pathfinding
Add sender-side support for inbound fees in pathfinding
and route building.
2024-03-31 18:12:28 +02:00

272 lines
9.5 KiB
Go

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/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/stretchr/testify/require"
)
func testMultiHopPayments(ht *lntest.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, lntest.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, lntest.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, lntest.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 := lntest.CalculateMaxHtlc(chanAmt)
const aliceBaseFeeSat = 20
const aliceFeeRatePPM = 100000
updateChannelPolicy(
ht, alice, chanPointAlice, aliceBaseFeeSat*1000,
aliceFeeRatePPM, 0, 0, chainreg.DefaultBitcoinTimeLockDelta,
maxHtlc, carol,
)
// Define a negative inbound fee for Alice, to verify that this is
// backwards compatible with an older sender ignoring the discount.
const (
aliceInboundBaseFeeMsat = -2000
aliceInboundFeeRate = -50000 // 5%
)
updateChannelPolicy(
ht, alice, chanPointDave, 0, 0,
aliceInboundBaseFeeMsat, aliceInboundFeeRate,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc,
dave,
)
const daveBaseFeeSat = 5
const daveFeeRatePPM = 150000
updateChannelPolicy(
ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM,
0, 0,
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 20 sat +
// 10% = 120 sat, plus the inbound fee over 1120 (= 1000 + 120) sat of
// -2 sat - 5% = -58 sat. This makes a total of 62 sat per payment. For
// 5 payments, it works out to 310 sat.
const expectedFeeAlice = 310
// 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 1062 sat, Dave is charging a fee of 5 sat +
// 15% = 164.3 sat. For 5 payments this is 821.5 sat. This test works
// with sats, so we need to round down to 821.
const expectedFeeDave = 821
// 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 *lntest.HarnessTest, hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint, baseFee int64,
feeRate int64, inboundBaseFee, inboundFeeRate int32,
timeLockDelta uint32, maxHtlc uint64, listenerNode *node.HarnessNode) {
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFee,
FeeRateMilliMsat: feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: maxHtlc,
InboundFeeBaseMsat: inboundBaseFee,
InboundFeeRateMilliMsat: inboundFeeRate,
}
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate) / testFeeBase,
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
},
MaxHtlcMsat: maxHtlc,
InboundBaseFeeMsat: inboundBaseFee,
InboundFeeRatePpm: inboundFeeRate,
}
hn.RPC.UpdateChannelPolicy(updateFeeReq)
// Wait for listener node to receive the channel update from node.
ht.AssertChannelPolicyUpdate(
listenerNode, hn, expectedPolicy, chanPoint, false,
)
}