htlcswitch: add receiver-side inbound fee support

This commit is contained in:
Joost Jager
2022-09-19 12:06:34 +02:00
parent 3e6adbf1c0
commit e8c97deaef
10 changed files with 388 additions and 19 deletions

View File

@ -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