mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-07-12 22:22:36 +02:00
htlcswitch: add receiver-side inbound fee support
This commit is contained in:
@ -643,6 +643,206 @@ func testChannelLinkMultiHopPayment(t *testing.T,
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelLinkInboundFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("negative", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -500,
|
||||
Rate: -100,
|
||||
}
|
||||
|
||||
// Bob is supposed to sent Carol 1000000 msats. For this, he
|
||||
// will charge an out fee of 1000 msat (the default hop network
|
||||
// policy). Bob's inbound fee is based on the sum of outgoing
|
||||
// htlc amount and the out fee that Bob charges. The value of
|
||||
// this sum is 1001000. The proportional component of the
|
||||
// inbound fee is -0.01% of the sum, which is -100 (rounded
|
||||
// up). Added to this is the base inbound fee of -500, making
|
||||
// for a total inbound fee of -600.
|
||||
const expectedBobInFee = -600
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("negative overpaid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -500,
|
||||
Rate: -100,
|
||||
}
|
||||
|
||||
// Alice is not aware of the inbound discount and pays the full
|
||||
// outbound fee.
|
||||
const expectedBobInFee = 0
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("negative total", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -5000,
|
||||
}
|
||||
|
||||
const expectedBobInFee = -5000
|
||||
|
||||
// Bob's inbound discount exceeds his outbound fee. Forwards
|
||||
// carrying a negative total fee should be rejected.
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, true,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: 1_000,
|
||||
Rate: 100_000,
|
||||
}
|
||||
|
||||
const expectedBobInFee = 101_100
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func testChannelLinkInboundFee(t *testing.T, //nolint:thelper
|
||||
bobInboundFee models.InboundFee, expectedBobInFee int64,
|
||||
expectedFail bool) {
|
||||
|
||||
channels, _, err := createClusterChannels(
|
||||
t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
|
||||
)
|
||||
require.NoError(t, err, "unable to create channel")
|
||||
|
||||
n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice,
|
||||
channels.bobToCarol, channels.carolToBob, testStartingHeight)
|
||||
|
||||
require.NoError(t, n.start())
|
||||
defer n.stop()
|
||||
|
||||
bobPolicy := n.globalPolicy
|
||||
bobPolicy.InboundFee = bobInboundFee
|
||||
n.firstBobChannelLink.UpdateForwardingPolicy(bobPolicy)
|
||||
|
||||
// Set an inbound fee for Carol. Because Carol is the payee, the fee
|
||||
// should not be applied.
|
||||
carolPolicy := n.globalPolicy
|
||||
carolPolicy.InboundFee = models.InboundFee{
|
||||
Base: -2_000,
|
||||
Rate: -200_000,
|
||||
}
|
||||
n.carolChannelLink.UpdateForwardingPolicy(carolPolicy)
|
||||
|
||||
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||
|
||||
const (
|
||||
expectedCarolInboundFee = 0
|
||||
|
||||
// Expect Bob's outbound fee to match the default hop network
|
||||
// policy.
|
||||
expectedBobOutboundFee = 1_000
|
||||
)
|
||||
|
||||
amount := lnwire.MilliSatoshi(1_000_000)
|
||||
htlcAmt := lnwire.MilliSatoshi(1_000_000 +
|
||||
expectedCarolInboundFee + expectedBobOutboundFee +
|
||||
expectedBobInFee,
|
||||
)
|
||||
totalTimelock := uint32(112)
|
||||
|
||||
hops := []*hop.Payload{
|
||||
{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
NextHop: n.carolChannelLink.
|
||||
ShortChanID(),
|
||||
AmountToForward: 1_000_000,
|
||||
OutgoingCTLV: 106,
|
||||
},
|
||||
},
|
||||
{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
AmountToForward: 1_000_000,
|
||||
OutgoingCTLV: 106,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
|
||||
if expectedFail {
|
||||
require.Error(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err, "unable to send payment")
|
||||
|
||||
// Wait for Alice and Bob's second link to receive the revocation.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Check that Carol invoice was settled and bandwidth of HTLC
|
||||
// links were changed.
|
||||
invoice, err := receiver.registry.LookupInvoice(
|
||||
context.Background(), rhash,
|
||||
)
|
||||
require.NoError(t, err, "unable to get invoice")
|
||||
require.Equal(t, invpkg.ContractSettled, invoice.State,
|
||||
"carol invoice haven't been settled")
|
||||
|
||||
expectedAliceBandwidth := aliceBandwidthBefore - htlcAmt
|
||||
require.Equalf(t,
|
||||
expectedAliceBandwidth, n.aliceChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedAliceBandwidth, n.aliceChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
expectedBobBandwidth1 := firstBobBandwidthBefore + htlcAmt
|
||||
require.Equalf(t,
|
||||
expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
bobCarolDelta := lnwire.MilliSatoshi(
|
||||
int64(amount) + expectedCarolInboundFee,
|
||||
)
|
||||
|
||||
expectedBobBandwidth2 := secondBobBandwidthBefore - bobCarolDelta
|
||||
require.Equalf(t,
|
||||
expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
expectedCarolBandwidth := carolBandwidthBefore + bobCarolDelta
|
||||
require.Equalf(t,
|
||||
expectedCarolBandwidth, n.carolChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedCarolBandwidth, n.carolChannelLink.Bandwidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// TestChannelLinkCancelFullCommitment tests the ability for links to cancel
|
||||
// forwarded HTLCs once all of their commitment slots are full.
|
||||
func TestChannelLinkCancelFullCommitment(t *testing.T) {
|
||||
@ -5994,7 +6194,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("satisfied", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
@ -6002,7 +6204,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("below minhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 100, 50,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailAmountBelowMinimum); !ok {
|
||||
t.Fatalf("expected FailAmountBelowMinimum failure code")
|
||||
}
|
||||
@ -6010,7 +6214,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("above maxhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1200,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailTemporaryChannelFailure); !ok {
|
||||
t.Fatalf("expected FailTemporaryChannelFailure failure code")
|
||||
}
|
||||
@ -6018,7 +6224,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("insufficient fee", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1005, 1000,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
@ -6031,7 +6239,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
result := link.CheckHtlcForward(
|
||||
hash, 100005, 100000, 200,
|
||||
150, 0, lnwire.ShortChannelID{},
|
||||
150, models.InboundFee{}, 0, lnwire.ShortChannelID{},
|
||||
)
|
||||
_, ok := result.WireMessage().(*lnwire.FailFeeInsufficient)
|
||||
require.True(t, ok, "expected FailFeeInsufficient failure code")
|
||||
@ -6039,7 +6247,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("expiry too soon", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 190, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 190,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooSoon); !ok {
|
||||
t.Fatalf("expected FailExpiryTooSoon failure code")
|
||||
}
|
||||
@ -6047,7 +6257,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
|
||||
t.Run("incorrect cltv expiry", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 190, 0, lnwire.ShortChannelID{})
|
||||
200, 190, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailIncorrectCltvExpiry); !ok {
|
||||
t.Fatalf("expected FailIncorrectCltvExpiry failure code")
|
||||
}
|
||||
@ -6057,11 +6269,37 @@ func TestCheckHtlcForward(t *testing.T) {
|
||||
t.Run("cltv expiry too far in the future", func(t *testing.T) {
|
||||
// Check that expiry isn't too far in the future.
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
10200, 10100, 0, lnwire.ShortChannelID{})
|
||||
10200, 10100, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooFar); !ok {
|
||||
t.Fatalf("expected FailExpiryTooFar failure code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inbound fee satisfied", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := link.CheckHtlcForward(hash, 1000+10-2-1, 1000,
|
||||
200, 150, models.InboundFee{Base: -2, Rate: -1_000},
|
||||
0, lnwire.ShortChannelID{})
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inbound fee insufficient", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := link.CheckHtlcForward(hash, 1000+10-10-101-1, 1000,
|
||||
200, 150, models.InboundFee{Base: -10, Rate: -100_000},
|
||||
0, lnwire.ShortChannelID{})
|
||||
|
||||
msg := result.WireMessage()
|
||||
if _, ok := msg.(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestChannelLinkCanceledInvoice in this test checks the interaction
|
||||
|
Reference in New Issue
Block a user