mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
a1678fa9b7
Because we need to account for an addtional fee buffer we need to increase channel capacities for the multi payment tests.
375 lines
12 KiB
Go
375 lines
12 KiB
Go
package itest
|
|
|
|
import (
|
|
"github.com/lightningnetwork/lnd/funding"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func testHtlcErrorPropagation(ht *lntest.HarnessTest) {
|
|
// In this test we wish to exercise the daemon's correct parsing,
|
|
// handling, and propagation of errors that occur while processing a
|
|
// multi-hop payment.
|
|
const chanAmt = funding.MaxBtcFundingAmount
|
|
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
// Since we'd like to test some multi-hop failure scenarios, we'll
|
|
// introduce another node into our test network: Carol.
|
|
carol := ht.NewNode("Carol", nil)
|
|
ht.ConnectNodes(bob, carol)
|
|
|
|
// Before we start sending payments, subscribe to htlc events for each
|
|
// node.
|
|
aliceEvents := alice.RPC.SubscribeHtlcEvents()
|
|
bobEvents := bob.RPC.SubscribeHtlcEvents()
|
|
carolEvents := carol.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)
|
|
|
|
// First establish a channel with a capacity of 0.5 BTC between Alice
|
|
// and Bob.
|
|
chanPointAlice := ht.OpenChannel(
|
|
alice, bob,
|
|
lntest.OpenChannelParams{Amt: chanAmt},
|
|
)
|
|
|
|
// Next, we'll create a connection from Bob to Carol, and open a
|
|
// channel between them so we have the topology: Alice -> Bob -> Carol.
|
|
// The channel created will be of lower capacity that the one created
|
|
// above.
|
|
chanPointBob := ht.OpenChannel(
|
|
bob, carol, lntest.OpenChannelParams{Amt: chanAmt},
|
|
)
|
|
|
|
// Ensure that Alice has Carol in her routing table before proceeding.
|
|
ht.AssertTopologyChannelOpen(alice, chanPointBob)
|
|
|
|
cType := ht.GetChannelCommitType(alice, chanPointAlice)
|
|
commitFee := lntest.CalcStaticFee(cType, 0)
|
|
|
|
assertBaseBalance := func() {
|
|
// Alice has opened a channel with Bob with zero push amount,
|
|
// so it's remote balance is zero.
|
|
expBalanceAlice := &lnrpc.ChannelBalanceResponse{
|
|
LocalBalance: &lnrpc.Amount{
|
|
Sat: uint64(chanAmt - commitFee),
|
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
chanAmt - commitFee,
|
|
)),
|
|
},
|
|
RemoteBalance: &lnrpc.Amount{},
|
|
UnsettledLocalBalance: &lnrpc.Amount{},
|
|
UnsettledRemoteBalance: &lnrpc.Amount{},
|
|
PendingOpenLocalBalance: &lnrpc.Amount{},
|
|
PendingOpenRemoteBalance: &lnrpc.Amount{},
|
|
// Deprecated fields.
|
|
Balance: int64(chanAmt - commitFee),
|
|
}
|
|
ht.AssertChannelBalanceResp(alice, expBalanceAlice)
|
|
|
|
// Bob has a channel with Alice and another with Carol, so it's
|
|
// local and remote balances are both chanAmt - commitFee.
|
|
expBalanceBob := &lnrpc.ChannelBalanceResponse{
|
|
LocalBalance: &lnrpc.Amount{
|
|
Sat: uint64(chanAmt - commitFee),
|
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
chanAmt - commitFee,
|
|
)),
|
|
},
|
|
RemoteBalance: &lnrpc.Amount{
|
|
Sat: uint64(chanAmt - commitFee),
|
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
chanAmt - commitFee,
|
|
)),
|
|
},
|
|
UnsettledLocalBalance: &lnrpc.Amount{},
|
|
UnsettledRemoteBalance: &lnrpc.Amount{},
|
|
PendingOpenLocalBalance: &lnrpc.Amount{},
|
|
PendingOpenRemoteBalance: &lnrpc.Amount{},
|
|
// Deprecated fields.
|
|
Balance: int64(chanAmt - commitFee),
|
|
}
|
|
ht.AssertChannelBalanceResp(bob, expBalanceBob)
|
|
}
|
|
|
|
// assertLinkFailure checks that the stream provided has a single link
|
|
// failure the failure detail provided.
|
|
assertLinkFailure := func(event *routerrpc.HtlcEvent,
|
|
failureDetail routerrpc.FailureDetail) {
|
|
|
|
linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
|
|
require.Truef(ht, ok, "expected forwarding failure, got: %T",
|
|
linkFail)
|
|
|
|
require.Equal(ht, failureDetail,
|
|
linkFail.LinkFailEvent.FailureDetail,
|
|
"wrong link fail detail")
|
|
}
|
|
|
|
// With the channels, open we can now start to test our multi-hop error
|
|
// scenarios. First, we'll generate an invoice from carol that we'll
|
|
// use to test some error cases.
|
|
const payAmt = 10000
|
|
invoiceReq := &lnrpc.Invoice{
|
|
Memo: "kek99",
|
|
Value: payAmt,
|
|
}
|
|
|
|
carolInvoice := carol.RPC.AddInvoice(invoiceReq)
|
|
carolPayReq := carol.RPC.DecodePayReq(carolInvoice.PaymentRequest)
|
|
|
|
// For the first scenario, we'll test the cancellation of an HTLC with
|
|
// an unknown payment hash.
|
|
sendReq := &routerrpc.SendPaymentRequest{
|
|
PaymentHash: ht.Random32Bytes(),
|
|
Dest: carol.PubKey[:],
|
|
Amt: payAmt,
|
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
MaxParts: 1,
|
|
}
|
|
ht.SendPaymentAssertFail(
|
|
alice, sendReq,
|
|
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
|
|
)
|
|
ht.AssertLastHTLCError(
|
|
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
|
)
|
|
|
|
// assertAliceAndBob is a helper closure that asserts Alice and Bob
|
|
// each has one forward and one forward fail event, and Bob has the
|
|
// final htlc fail event.
|
|
assertAliceAndBob := func() {
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForward,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForwardFail,
|
|
)
|
|
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_FORWARD,
|
|
lntest.HtlcEventForward,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_FORWARD,
|
|
lntest.HtlcEventForwardFail,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN,
|
|
lntest.HtlcEventFinal,
|
|
)
|
|
}
|
|
|
|
// We expect alice and bob to each have one forward and one forward
|
|
// fail event at this stage.
|
|
assertAliceAndBob()
|
|
|
|
// Carol should have a link failure because the htlc failed on her
|
|
// incoming link.
|
|
event := ht.AssertHtlcEventType(
|
|
carolEvents, routerrpc.HtlcEvent_RECEIVE,
|
|
)
|
|
assertLinkFailure(event, routerrpc.FailureDetail_UNKNOWN_INVOICE)
|
|
|
|
// There's also a final htlc event that gives the final outcome of the
|
|
// htlc.
|
|
ht.AssertHtlcEventTypes(
|
|
carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntest.HtlcEventFinal,
|
|
)
|
|
|
|
// The balances of all parties should be the same as initially since
|
|
// the HTLC was canceled.
|
|
assertBaseBalance()
|
|
|
|
// Next, we'll test the case of a recognized payHash but, an incorrect
|
|
// value on the extended HTLC.
|
|
htlcAmt := lnwire.NewMSatFromSatoshis(1000)
|
|
sendReq = &routerrpc.SendPaymentRequest{
|
|
PaymentHash: carolInvoice.RHash,
|
|
Dest: carol.PubKey[:],
|
|
// 10k satoshis are expected.
|
|
Amt: int64(htlcAmt.ToSatoshis()),
|
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
MaxParts: 1,
|
|
}
|
|
ht.SendPaymentAssertFail(
|
|
alice, sendReq,
|
|
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
|
|
)
|
|
ht.AssertLastHTLCError(
|
|
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
|
)
|
|
|
|
// We expect alice and bob to each have one forward and one forward
|
|
// fail event at this stage.
|
|
assertAliceAndBob()
|
|
|
|
// Carol should have a link failure because the htlc failed on her
|
|
// incoming link.
|
|
event = ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_RECEIVE)
|
|
assertLinkFailure(event, routerrpc.FailureDetail_INVOICE_UNDERPAID)
|
|
|
|
// There's also a final htlc event that gives the final outcome of the
|
|
// htlc.
|
|
ht.AssertHtlcEventTypes(
|
|
carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntest.HtlcEventFinal,
|
|
)
|
|
|
|
// The balances of all parties should be the same as initially since
|
|
// the HTLC was canceled.
|
|
assertBaseBalance()
|
|
|
|
// Next we'll test an error that occurs mid-route due to an outgoing
|
|
// link having insufficient capacity. In order to do so, we'll first
|
|
// need to unbalance the link connecting Bob<->Carol.
|
|
//
|
|
// To do so, we'll push most of the funds in the channel over to
|
|
// Alice's side, leaving on 10k satoshis of available balance for bob.
|
|
chanReserve := int64(chanAmt / 100)
|
|
feeBuffer := lntest.CalcStaticFeeBuffer(cType, 0)
|
|
amtToSend := int64(chanAmt) - chanReserve - int64(feeBuffer) - 10000
|
|
|
|
invoiceReq = &lnrpc.Invoice{
|
|
Value: amtToSend,
|
|
}
|
|
carolInvoice2 := carol.RPC.AddInvoice(invoiceReq)
|
|
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice2.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
MaxParts: 1,
|
|
}
|
|
ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_SUCCEEDED)
|
|
|
|
// We need to check that bob has a forward and settle event for his
|
|
// send, and carol has a settle event and a final htlc event for her
|
|
// receive.
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForward,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventSettle,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
carolEvents, routerrpc.HtlcEvent_RECEIVE,
|
|
lntest.HtlcEventSettle,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
carolEvents, routerrpc.HtlcEvent_UNKNOWN,
|
|
lntest.HtlcEventFinal,
|
|
)
|
|
|
|
// At this point, Alice has 50mil satoshis on her side of the channel,
|
|
// but Bob only has 10k available on his side of the channel. So a
|
|
// payment from Alice to Carol worth 100k satoshis should fail.
|
|
invoiceReq = &lnrpc.Invoice{
|
|
Value: 100000,
|
|
}
|
|
carolInvoice3 := carol.RPC.AddInvoice(invoiceReq)
|
|
|
|
sendReq = &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice3.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
MaxParts: 1,
|
|
}
|
|
ht.SendPaymentAssertFail(
|
|
alice, sendReq,
|
|
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
|
)
|
|
ht.AssertLastHTLCError(
|
|
alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
|
|
)
|
|
|
|
// Alice should have a forwarding event and a forwarding failure.
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForward,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForwardFail,
|
|
)
|
|
|
|
// Bob should have a link failure because the htlc failed on his
|
|
// outgoing link.
|
|
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
|
|
assertLinkFailure(event, routerrpc.FailureDetail_INSUFFICIENT_BALANCE)
|
|
|
|
// There's also a final htlc event that gives the final outcome of the
|
|
// htlc.
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntest.HtlcEventFinal,
|
|
)
|
|
|
|
// Generate new invoice to not pay same invoice twice.
|
|
carolInvoice = carol.RPC.AddInvoice(invoiceReq)
|
|
|
|
// For our final test, we'll ensure that if a target link isn't
|
|
// available for what ever reason then the payment fails accordingly.
|
|
//
|
|
// We'll attempt to complete the original invoice we created with Carol
|
|
// above, but before we do so, Carol will go offline, resulting in a
|
|
// failed payment.
|
|
ht.Shutdown(carol)
|
|
|
|
// Reset mission control to forget the temporary channel failure above.
|
|
alice.RPC.ResetMissionControl()
|
|
|
|
req = &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
MaxParts: 1,
|
|
}
|
|
ht.SendPaymentAssertFail(
|
|
alice, req, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
|
)
|
|
ht.AssertLastHTLCError(alice, lnrpc.Failure_UNKNOWN_NEXT_PEER)
|
|
|
|
// Alice should have a forwarding event and subsequent fail.
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForward,
|
|
)
|
|
ht.AssertHtlcEventTypes(
|
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
|
lntest.HtlcEventForwardFail,
|
|
)
|
|
|
|
// Bob should have a link failure because he could not find the next
|
|
// peer.
|
|
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
|
|
assertLinkFailure(event, routerrpc.FailureDetail_NO_DETAIL)
|
|
|
|
// There's also a final htlc event that gives the final outcome of the
|
|
// htlc.
|
|
ht.AssertHtlcEventTypes(
|
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntest.HtlcEventFinal,
|
|
)
|
|
|
|
// Finally, immediately close the channel. This function will also
|
|
// block until the channel is closed and will additionally assert the
|
|
// relevant channel closing post conditions.
|
|
ht.CloseChannel(alice, chanPointAlice)
|
|
|
|
// Force close Bob's final channel.
|
|
ht.ForceCloseChannel(bob, chanPointBob)
|
|
}
|