mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
lntemp+itest: refactor testHtlcErrorPropagation
This commit is contained in:
parent
2a7830d442
commit
130c4e325a
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntemp/node"
|
"github.com/lightningnetwork/lnd/lntemp/node"
|
||||||
"github.com/lightningnetwork/lnd/lntemp/rpc"
|
"github.com/lightningnetwork/lnd/lntemp/rpc"
|
||||||
@ -1759,3 +1760,95 @@ func (h *HarnessTest) ReceiveTrackPayment(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReceiveHtlcEvent waits until a message is received on the subscribe
|
||||||
|
// htlc event stream or the timeout is reached.
|
||||||
|
func (h *HarnessTest) ReceiveHtlcEvent(
|
||||||
|
stream rpc.HtlcEventsClient) *routerrpc.HtlcEvent {
|
||||||
|
|
||||||
|
chanMsg := make(chan *routerrpc.HtlcEvent)
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
// Consume one message. This will block until the message is
|
||||||
|
// received.
|
||||||
|
resp, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chanMsg <- resp
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(DefaultTimeout):
|
||||||
|
require.Fail(h, "timeout", "timeout receiving htlc "+
|
||||||
|
"event update")
|
||||||
|
|
||||||
|
case err := <-errChan:
|
||||||
|
require.Failf(h, "err from stream",
|
||||||
|
"received err from stream: %v", err)
|
||||||
|
|
||||||
|
case updateMsg := <-chanMsg:
|
||||||
|
return updateMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertHtlcEventType consumes one event from a client and asserts the event
|
||||||
|
// type is matched.
|
||||||
|
func (h *HarnessTest) AssertHtlcEventType(client rpc.HtlcEventsClient,
|
||||||
|
userType routerrpc.HtlcEvent_EventType) *routerrpc.HtlcEvent {
|
||||||
|
|
||||||
|
event := h.ReceiveHtlcEvent(client)
|
||||||
|
require.Equalf(h, userType, event.EventType, "wrong event type, "+
|
||||||
|
"want %v got %v", userType, event.EventType)
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// HtlcEvent maps the series of event types used in `*routerrpc.HtlcEvent_*`.
|
||||||
|
type HtlcEvent int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HtlcEventForward HtlcEvent = iota
|
||||||
|
HtlcEventForwardFail
|
||||||
|
HtlcEventSettle
|
||||||
|
HtlcEventLinkFail
|
||||||
|
HtlcEventFinal
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssertHtlcEventType consumes one event from a client and asserts both the
|
||||||
|
// user event type the event.Event type is matched.
|
||||||
|
func (h *HarnessTest) AssertHtlcEventTypes(client rpc.HtlcEventsClient,
|
||||||
|
userType routerrpc.HtlcEvent_EventType,
|
||||||
|
eventType HtlcEvent) *routerrpc.HtlcEvent {
|
||||||
|
|
||||||
|
event := h.ReceiveHtlcEvent(client)
|
||||||
|
require.Equalf(h, userType, event.EventType, "wrong event type, "+
|
||||||
|
"want %v got %v", userType, event.EventType)
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
switch eventType {
|
||||||
|
case HtlcEventForward:
|
||||||
|
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardEvent)
|
||||||
|
|
||||||
|
case HtlcEventForwardFail:
|
||||||
|
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardFailEvent)
|
||||||
|
|
||||||
|
case HtlcEventSettle:
|
||||||
|
_, ok = event.Event.(*routerrpc.HtlcEvent_SettleEvent)
|
||||||
|
|
||||||
|
case HtlcEventLinkFail:
|
||||||
|
_, ok = event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
|
||||||
|
|
||||||
|
case HtlcEventFinal:
|
||||||
|
_, ok = event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Truef(h, ok, "wrong event type: %T, want %T", event.Event,
|
||||||
|
eventType)
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
@ -39,3 +39,56 @@ func (h *HarnessRPC) SendPayment(
|
|||||||
|
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HtlcEventsClient routerrpc.Router_SubscribeHtlcEventsClient
|
||||||
|
|
||||||
|
// SubscribeHtlcEvents makes a subscription to the HTLC events and returns a
|
||||||
|
// htlc event client.
|
||||||
|
func (h *HarnessRPC) SubscribeHtlcEvents() HtlcEventsClient {
|
||||||
|
// Use runCtx here to keep the client alive for the scope of the test.
|
||||||
|
client, err := h.Router.SubscribeHtlcEvents(
|
||||||
|
h.runCtx, &routerrpc.SubscribeHtlcEventsRequest{},
|
||||||
|
)
|
||||||
|
h.NoError(err, "SubscribeHtlcEvents")
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMissionControlConfig makes a RPC call to the node's
|
||||||
|
// GetMissionControlConfig and asserts.
|
||||||
|
//
|
||||||
|
//nolint:lll
|
||||||
|
func (h *HarnessRPC) GetMissionControlConfig() *routerrpc.GetMissionControlConfigResponse {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &routerrpc.GetMissionControlConfigRequest{}
|
||||||
|
resp, err := h.Router.GetMissionControlConfig(ctxt, req)
|
||||||
|
h.NoError(err, "GetMissionControlConfig")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMissionControlConfig makes a RPC call to the node's
|
||||||
|
// SetMissionControlConfig and asserts.
|
||||||
|
func (h *HarnessRPC) SetMissionControlConfig(
|
||||||
|
config *routerrpc.MissionControlConfig) {
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &routerrpc.SetMissionControlConfigRequest{Config: config}
|
||||||
|
_, err := h.Router.SetMissionControlConfig(ctxt, req)
|
||||||
|
h.NoError(err, "SetMissionControlConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetMissionControl makes a RPC call to the node's ResetMissionControl and
|
||||||
|
// asserts.
|
||||||
|
func (h *HarnessRPC) ResetMissionControl() {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req := &routerrpc.ResetMissionControlRequest{}
|
||||||
|
_, err := h.Router.ResetMissionControl(ctxt, req)
|
||||||
|
h.NoError(err, "ResetMissionControl")
|
||||||
|
}
|
||||||
|
@ -255,4 +255,8 @@ var allTestCasesTemp = []*lntemp.TestCase{
|
|||||||
Name: "max htlc pathfind",
|
Name: "max htlc pathfind",
|
||||||
TestFunc: testMaxHtlcPathfind,
|
TestFunc: testMaxHtlcPathfind,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "multi-hop htlc error propagation",
|
||||||
|
TestFunc: testHtlcErrorPropagation,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,65 @@
|
|||||||
package itest
|
package itest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntemp"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
|
func testHtlcErrorPropagation(ht *lntemp.HarnessTest) {
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
// In this test we wish to exercise the daemon's correct parsing,
|
// In this test we wish to exercise the daemon's correct parsing,
|
||||||
// handling, and propagation of errors that occur while processing a
|
// handling, and propagation of errors that occur while processing a
|
||||||
// multi-hop payment.
|
// multi-hop payment.
|
||||||
const chanAmt = funding.MaxBtcFundingAmount
|
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
|
// First establish a channel with a capacity of 0.5 BTC between Alice
|
||||||
// and Bob.
|
// and Bob.
|
||||||
chanPointAlice := openChannelAndAssert(
|
chanPointAlice := ht.OpenChannel(
|
||||||
t, net, net.Alice, net.Bob,
|
alice, bob,
|
||||||
lntest.OpenChannelParams{
|
lntemp.OpenChannelParams{Amt: chanAmt},
|
||||||
Amt: chanAmt,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice); err != nil {
|
|
||||||
t.Fatalf("channel not seen by alice before timeout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cType, err := channelCommitType(net.Alice, chanPointAlice)
|
// Next, we'll create a connection from Bob to Carol, and open a
|
||||||
if err != nil {
|
// channel between them so we have the topology: Alice -> Bob -> Carol.
|
||||||
t.Fatalf("unable to get channel type: %v", err)
|
// The channel created will be of lower capacity that the one created
|
||||||
}
|
// above.
|
||||||
|
const bobChanAmt = funding.MaxBtcFundingAmount
|
||||||
|
chanPointBob := ht.OpenChannel(
|
||||||
|
bob, carol, lntemp.OpenChannelParams{Amt: chanAmt},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure that Alice has Carol in her routing table before proceeding.
|
||||||
|
ht.AssertTopologyChannelOpen(alice, chanPointBob)
|
||||||
|
|
||||||
|
cType := ht.GetChannelCommitType(alice, chanPointAlice)
|
||||||
commitFee := calcStaticFee(cType, 0)
|
commitFee := calcStaticFee(cType, 0)
|
||||||
|
|
||||||
assertBaseBalance := func() {
|
assertBaseBalance := func() {
|
||||||
// Alice has opened a channel with Bob with zero push amount, so
|
// Alice has opened a channel with Bob with zero push amount,
|
||||||
// it's remote balance is zero.
|
// so it's remote balance is zero.
|
||||||
expBalanceAlice := &lnrpc.ChannelBalanceResponse{
|
expBalanceAlice := &lnrpc.ChannelBalanceResponse{
|
||||||
LocalBalance: &lnrpc.Amount{
|
LocalBalance: &lnrpc.Amount{
|
||||||
Sat: uint64(chanAmt - commitFee),
|
Sat: uint64(chanAmt - commitFee),
|
||||||
@ -57,7 +75,7 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// Deprecated fields.
|
// Deprecated fields.
|
||||||
Balance: int64(chanAmt - commitFee),
|
Balance: int64(chanAmt - commitFee),
|
||||||
}
|
}
|
||||||
assertChannelBalanceResp(t, net.Alice, expBalanceAlice)
|
ht.AssertChannelBalanceResp(alice, expBalanceAlice)
|
||||||
|
|
||||||
// Bob has a channel with Alice and another with Carol, so it's
|
// Bob has a channel with Alice and another with Carol, so it's
|
||||||
// local and remote balances are both chanAmt - commitFee.
|
// local and remote balances are both chanAmt - commitFee.
|
||||||
@ -81,52 +99,21 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// Deprecated fields.
|
// Deprecated fields.
|
||||||
Balance: int64(chanAmt - commitFee),
|
Balance: int64(chanAmt - commitFee),
|
||||||
}
|
}
|
||||||
assertChannelBalanceResp(t, net.Bob, expBalanceBob)
|
ht.AssertChannelBalanceResp(bob, expBalanceBob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we'd like to test some multi-hop failure scenarios, we'll
|
// assertLinkFailure checks that the stream provided has a single link
|
||||||
// introduce another node into our test network: Carol.
|
// failure the failure detail provided.
|
||||||
carol := net.NewNode(t.t, "Carol", nil)
|
assertLinkFailure := func(event *routerrpc.HtlcEvent,
|
||||||
|
failureDetail routerrpc.FailureDetail) {
|
||||||
|
|
||||||
// Next, we'll create a connection from Bob to Carol, and open a
|
linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
|
||||||
// channel between them so we have the topology: Alice -> Bob -> Carol.
|
require.Truef(ht, ok, "expected forwarding failure, got: %T",
|
||||||
// The channel created will be of lower capacity that the one created
|
linkFail)
|
||||||
// above.
|
|
||||||
net.ConnectNodes(t.t, net.Bob, carol)
|
|
||||||
const bobChanAmt = funding.MaxBtcFundingAmount
|
|
||||||
chanPointBob := openChannelAndAssert(
|
|
||||||
t, net, net.Bob, carol,
|
|
||||||
lntest.OpenChannelParams{
|
|
||||||
Amt: chanAmt,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that Alice has Carol in her routing table before proceeding.
|
require.Equal(ht, failureDetail,
|
||||||
nodeInfoReq := &lnrpc.NodeInfoRequest{
|
linkFail.LinkFailEvent.FailureDetail,
|
||||||
PubKey: carol.PubKeyStr,
|
"wrong link fail detail")
|
||||||
}
|
|
||||||
checkTableTimeout := time.After(time.Second * 10)
|
|
||||||
checkTableTicker := time.NewTicker(100 * time.Millisecond)
|
|
||||||
defer checkTableTicker.Stop()
|
|
||||||
|
|
||||||
out:
|
|
||||||
// TODO(roasbeef): make into async hook for node announcements
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-checkTableTicker.C:
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
_, err := net.Alice.GetNodeInfo(ctxt, nodeInfoReq)
|
|
||||||
if err != nil && strings.Contains(err.Error(),
|
|
||||||
"unable to find") {
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
break out
|
|
||||||
case <-checkTableTimeout:
|
|
||||||
t.Fatalf("carol's node announcement didn't propagate within " +
|
|
||||||
"the timeout period")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the channels, open we can now start to test our multi-hop error
|
// With the channels, open we can now start to test our multi-hop error
|
||||||
@ -137,61 +124,14 @@ out:
|
|||||||
Memo: "kek99",
|
Memo: "kek99",
|
||||||
Value: payAmt,
|
Value: payAmt,
|
||||||
}
|
}
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
carolInvoice, err := carol.AddInvoice(ctxt, invoiceReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate carol invoice: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
carolPayReq, err := carol.DecodePayReq(ctxb,
|
carolInvoice := carol.RPC.AddInvoice(invoiceReq)
|
||||||
&lnrpc.PayReqString{
|
carolPayReq := carol.RPC.DecodePayReq(carolInvoice.PaymentRequest)
|
||||||
PayReq: carolInvoice.PaymentRequest,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to decode generated payment request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before we send the payment, ensure that the announcement of the new
|
|
||||||
// channel has been processed by Alice.
|
|
||||||
if err := net.Alice.WaitForNetworkChannelOpen(chanPointBob); err != nil {
|
|
||||||
t.Fatalf("channel not seen by alice before timeout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before we start sending payments, subscribe to htlc events for each
|
|
||||||
// node.
|
|
||||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
aliceEvents, err := net.Alice.RouterClient.SubscribeHtlcEvents(
|
|
||||||
ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not subscribe events: %v", err)
|
|
||||||
}
|
|
||||||
assertSubscribed(t, aliceEvents)
|
|
||||||
|
|
||||||
bobEvents, err := net.Bob.RouterClient.SubscribeHtlcEvents(
|
|
||||||
ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not subscribe events: %v", err)
|
|
||||||
}
|
|
||||||
assertSubscribed(t, bobEvents)
|
|
||||||
|
|
||||||
carolEvents, err := carol.RouterClient.SubscribeHtlcEvents(
|
|
||||||
ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not subscribe events: %v", err)
|
|
||||||
}
|
|
||||||
assertSubscribed(t, carolEvents)
|
|
||||||
|
|
||||||
// For the first scenario, we'll test the cancellation of an HTLC with
|
// For the first scenario, we'll test the cancellation of an HTLC with
|
||||||
// an unknown payment hash.
|
// an unknown payment hash.
|
||||||
// TODO(roasbeef): return failure response rather than failing entire
|
|
||||||
// stream on payment error.
|
|
||||||
sendReq := &routerrpc.SendPaymentRequest{
|
sendReq := &routerrpc.SendPaymentRequest{
|
||||||
PaymentHash: makeFakePayHash(t),
|
PaymentHash: ht.Random32Bytes(),
|
||||||
Dest: carol.PubKey[:],
|
Dest: carol.PubKey[:],
|
||||||
Amt: payAmt,
|
Amt: payAmt,
|
||||||
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
||||||
@ -199,25 +139,56 @@ out:
|
|||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
MaxParts: 1,
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
ht.SendPaymentAssertFail(
|
||||||
t, net.Alice,
|
alice, sendReq,
|
||||||
sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS,
|
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
|
||||||
)
|
)
|
||||||
assertLastHTLCError(
|
ht.AssertLastHTLCError(
|
||||||
t, net.Alice,
|
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
||||||
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,
|
||||||
|
lntemp.HtlcEventForward,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForwardFail,
|
||||||
|
)
|
||||||
|
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
bobEvents, routerrpc.HtlcEvent_FORWARD,
|
||||||
|
lntemp.HtlcEventForward,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
bobEvents, routerrpc.HtlcEvent_FORWARD,
|
||||||
|
lntemp.HtlcEventForwardFail,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN,
|
||||||
|
lntemp.HtlcEventFinal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// We expect alice and bob to each have one forward and one forward
|
// We expect alice and bob to each have one forward and one forward
|
||||||
// fail event at this stage.
|
// fail event at this stage.
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
|
assertAliceAndBob()
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents)
|
|
||||||
|
|
||||||
// Carol should have a link failure because the htlc failed on her
|
// Carol should have a link failure because the htlc failed on her
|
||||||
// incoming link.
|
// incoming link.
|
||||||
assertLinkFailure(
|
event := ht.AssertHtlcEventType(
|
||||||
t, routerrpc.HtlcEvent_RECEIVE,
|
carolEvents, routerrpc.HtlcEvent_RECEIVE,
|
||||||
routerrpc.FailureDetail_UNKNOWN_INVOICE, carolEvents,
|
)
|
||||||
|
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, lntemp.HtlcEventFinal,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The balances of all parties should be the same as initially since
|
// The balances of all parties should be the same as initially since
|
||||||
@ -228,33 +199,36 @@ out:
|
|||||||
// value on the extended HTLC.
|
// value on the extended HTLC.
|
||||||
htlcAmt := lnwire.NewMSatFromSatoshis(1000)
|
htlcAmt := lnwire.NewMSatFromSatoshis(1000)
|
||||||
sendReq = &routerrpc.SendPaymentRequest{
|
sendReq = &routerrpc.SendPaymentRequest{
|
||||||
PaymentHash: carolInvoice.RHash,
|
PaymentHash: carolInvoice.RHash,
|
||||||
Dest: carol.PubKey[:],
|
Dest: carol.PubKey[:],
|
||||||
Amt: int64(htlcAmt.ToSatoshis()), // 10k satoshis are expected.
|
// 10k satoshis are expected.
|
||||||
|
Amt: int64(htlcAmt.ToSatoshis()),
|
||||||
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
MaxParts: 1,
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
ht.SendPaymentAssertFail(
|
||||||
t, net.Alice,
|
alice, sendReq,
|
||||||
sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS,
|
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
|
||||||
)
|
)
|
||||||
assertLastHTLCError(
|
ht.AssertLastHTLCError(
|
||||||
t, net.Alice,
|
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
||||||
lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// We expect alice and bob to each have one forward and one forward
|
// We expect alice and bob to each have one forward and one forward
|
||||||
// fail event at this stage.
|
// fail event at this stage.
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
|
assertAliceAndBob()
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents)
|
|
||||||
|
|
||||||
// Carol should have a link failure because the htlc failed on her
|
// Carol should have a link failure because the htlc failed on her
|
||||||
// incoming link.
|
// incoming link.
|
||||||
assertLinkFailure(
|
event = ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_RECEIVE)
|
||||||
t, routerrpc.HtlcEvent_RECEIVE,
|
assertLinkFailure(event, routerrpc.FailureDetail_INVOICE_UNDERPAID)
|
||||||
routerrpc.FailureDetail_INVOICE_UNDERPAID, carolEvents,
|
|
||||||
|
// There's also a final htlc event that gives the final outcome of the
|
||||||
|
// htlc.
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The balances of all parties should be the same as initially since
|
// The balances of all parties should be the same as initially since
|
||||||
@ -284,29 +258,34 @@ out:
|
|||||||
invoiceReq = &lnrpc.Invoice{
|
invoiceReq = &lnrpc.Invoice{
|
||||||
Value: toSend,
|
Value: toSend,
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
carolInvoice2 := carol.RPC.AddInvoice(invoiceReq)
|
||||||
carolInvoice2, err := carol.AddInvoice(ctxt, invoiceReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate carol invoice: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAndAssertSuccess(
|
req := &routerrpc.SendPaymentRequest{
|
||||||
t, net.Bob, &routerrpc.SendPaymentRequest{
|
PaymentRequest: carolInvoice2.PaymentRequest,
|
||||||
PaymentRequest: carolInvoice2.PaymentRequest,
|
TimeoutSeconds: 60,
|
||||||
TimeoutSeconds: 60,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
MaxParts: 1,
|
||||||
MaxParts: 1,
|
}
|
||||||
},
|
ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_SUCCEEDED)
|
||||||
)
|
|
||||||
|
|
||||||
// For each send bob makes, we need to check that bob has a
|
// For each send bob makes, we need to check that bob has a
|
||||||
// forward and settle event for his send, and carol has a
|
// forward and settle event for his send, and carol has a
|
||||||
// settle event for her receive.
|
// settle event and a final htlc event for her receive.
|
||||||
assertHtlcEvents(
|
ht.AssertHtlcEventTypes(
|
||||||
t, 1, 0, 1, routerrpc.HtlcEvent_SEND, bobEvents,
|
bobEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForward,
|
||||||
)
|
)
|
||||||
assertHtlcEvents(
|
ht.AssertHtlcEventTypes(
|
||||||
t, 0, 0, 1, routerrpc.HtlcEvent_RECEIVE, carolEvents,
|
bobEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventSettle,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
carolEvents, routerrpc.HtlcEvent_RECEIVE,
|
||||||
|
lntemp.HtlcEventSettle,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
carolEvents, routerrpc.HtlcEvent_UNKNOWN,
|
||||||
|
lntemp.HtlcEventFinal,
|
||||||
)
|
)
|
||||||
|
|
||||||
amtSent += toSend
|
amtSent += toSend
|
||||||
@ -318,11 +297,7 @@ out:
|
|||||||
invoiceReq = &lnrpc.Invoice{
|
invoiceReq = &lnrpc.Invoice{
|
||||||
Value: 100000,
|
Value: 100000,
|
||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
carolInvoice3 := carol.RPC.AddInvoice(invoiceReq)
|
||||||
carolInvoice3, err := carol.AddInvoice(ctxt, invoiceReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate carol invoice: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReq = &routerrpc.SendPaymentRequest{
|
sendReq = &routerrpc.SendPaymentRequest{
|
||||||
PaymentRequest: carolInvoice3.PaymentRequest,
|
PaymentRequest: carolInvoice3.PaymentRequest,
|
||||||
@ -330,30 +305,37 @@ out:
|
|||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
MaxParts: 1,
|
MaxParts: 1,
|
||||||
}
|
}
|
||||||
sendAndAssertFailure(
|
ht.SendPaymentAssertFail(
|
||||||
t, net.Alice,
|
alice, sendReq,
|
||||||
sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
||||||
)
|
)
|
||||||
assertLastHTLCError(
|
ht.AssertLastHTLCError(
|
||||||
t, net.Alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
|
alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alice should have a forwarding event and a forwarding failure.
|
// Alice should have a forwarding event and a forwarding failure.
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
|
ht.AssertHtlcEventTypes(
|
||||||
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForward,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForwardFail,
|
||||||
|
)
|
||||||
|
|
||||||
// Bob should have a link failure because the htlc failed on his
|
// Bob should have a link failure because the htlc failed on his
|
||||||
// outgoing link.
|
// outgoing link.
|
||||||
assertLinkFailure(
|
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
|
||||||
t, routerrpc.HtlcEvent_FORWARD,
|
assertLinkFailure(event, routerrpc.FailureDetail_INSUFFICIENT_BALANCE)
|
||||||
routerrpc.FailureDetail_INSUFFICIENT_BALANCE, bobEvents,
|
|
||||||
|
// There's also a final htlc event that gives the final outcome of the
|
||||||
|
// htlc.
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate new invoice to not pay same invoice twice.
|
// Generate new invoice to not pay same invoice twice.
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
carolInvoice = carol.RPC.AddInvoice(invoiceReq)
|
||||||
carolInvoice, err = carol.AddInvoice(ctxt, invoiceReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate carol invoice: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For our final test, we'll ensure that if a target link isn't
|
// For our final test, we'll ensure that if a target link isn't
|
||||||
// available for what ever reason then the payment fails accordingly.
|
// available for what ever reason then the payment fails accordingly.
|
||||||
@ -361,77 +343,48 @@ out:
|
|||||||
// We'll attempt to complete the original invoice we created with Carol
|
// 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
|
// above, but before we do so, Carol will go offline, resulting in a
|
||||||
// failed payment.
|
// failed payment.
|
||||||
shutdownAndAssert(net, t, carol)
|
ht.Shutdown(carol)
|
||||||
|
|
||||||
// Reset mission control to forget the temporary channel failure above.
|
// Reset mission control to forget the temporary channel failure above.
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
alice.RPC.ResetMissionControl()
|
||||||
_, err = net.Alice.RouterClient.ResetMissionControl(
|
|
||||||
ctxt, &routerrpc.ResetMissionControlRequest{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to reset mission control: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAndAssertFailure(
|
req := &routerrpc.SendPaymentRequest{
|
||||||
t, net.Alice,
|
PaymentRequest: carolInvoice.PaymentRequest,
|
||||||
&routerrpc.SendPaymentRequest{
|
TimeoutSeconds: 60,
|
||||||
PaymentRequest: carolInvoice.PaymentRequest,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
TimeoutSeconds: 60,
|
MaxParts: 1,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
}
|
||||||
MaxParts: 1,
|
ht.SendPaymentAssertFail(
|
||||||
},
|
alice, req, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
||||||
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
|
|
||||||
)
|
)
|
||||||
assertLastHTLCError(t, net.Alice, lnrpc.Failure_UNKNOWN_NEXT_PEER)
|
ht.AssertLastHTLCError(alice, lnrpc.Failure_UNKNOWN_NEXT_PEER)
|
||||||
|
|
||||||
// Alice should have a forwarding event and subsequent fail.
|
// Alice should have a forwarding event and subsequent fail.
|
||||||
assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
|
ht.AssertHtlcEventTypes(
|
||||||
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForward,
|
||||||
|
)
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
aliceEvents, routerrpc.HtlcEvent_SEND,
|
||||||
|
lntemp.HtlcEventForwardFail,
|
||||||
|
)
|
||||||
|
|
||||||
// Bob should have a link failure because he could not find the next
|
// Bob should have a link failure because he could not find the next
|
||||||
// peer.
|
// peer.
|
||||||
assertLinkFailure(
|
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
|
||||||
t, routerrpc.HtlcEvent_FORWARD,
|
assertLinkFailure(event, routerrpc.FailureDetail_NO_DETAIL)
|
||||||
routerrpc.FailureDetail_NO_DETAIL, bobEvents,
|
|
||||||
|
// There's also a final htlc event that gives the final outcome of the
|
||||||
|
// htlc.
|
||||||
|
ht.AssertHtlcEventTypes(
|
||||||
|
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finally, immediately close the channel. This function will also
|
// Finally, immediately close the channel. This function will also
|
||||||
// block until the channel is closed and will additionally assert the
|
// block until the channel is closed and will additionally assert the
|
||||||
// relevant channel closing post conditions.
|
// relevant channel closing post conditions.
|
||||||
closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false)
|
ht.CloseChannel(alice, chanPointAlice)
|
||||||
|
|
||||||
// Force close Bob's final channel.
|
// Force close Bob's final channel.
|
||||||
closeChannelAndAssert(t, net, net.Bob, chanPointBob, true)
|
ht.ForceCloseChannel(bob, chanPointBob)
|
||||||
|
|
||||||
// Cleanup by mining the force close and sweep transaction.
|
|
||||||
cleanupForceClose(t, net, net.Bob, chanPointBob)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertLinkFailure checks that the stream provided has a single link failure
|
|
||||||
// the the failure detail provided.
|
|
||||||
func assertLinkFailure(t *harnessTest,
|
|
||||||
eventType routerrpc.HtlcEvent_EventType,
|
|
||||||
failureDetail routerrpc.FailureDetail,
|
|
||||||
client routerrpc.Router_SubscribeHtlcEventsClient) {
|
|
||||||
|
|
||||||
event := assertEventAndType(t, eventType, client)
|
|
||||||
|
|
||||||
linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected forwarding failure, got: %T", linkFail)
|
|
||||||
}
|
|
||||||
|
|
||||||
if linkFail.LinkFailEvent.FailureDetail != failureDetail {
|
|
||||||
t.Fatalf("expected: %v, got: %v", failureDetail,
|
|
||||||
linkFail.LinkFailEvent.FailureDetail)
|
|
||||||
}
|
|
||||||
|
|
||||||
event = assertEventAndType(t, routerrpc.HtlcEvent_UNKNOWN, client)
|
|
||||||
finalHtlc, ok := event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected final htlc, got: %T", event.Event)
|
|
||||||
}
|
|
||||||
|
|
||||||
if finalHtlc.FinalHtlcEvent.Settled {
|
|
||||||
t.Fatalf("expected final fail")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,6 @@ var allTestCases = []*testCase{
|
|||||||
name: "multiple channel creation and update subscription",
|
name: "multiple channel creation and update subscription",
|
||||||
test: testBasicChannelCreationAndUpdates,
|
test: testBasicChannelCreationAndUpdates,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "multi-hop htlc error propagation",
|
|
||||||
test: testHtlcErrorPropagation,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "derive shared key",
|
name: "derive shared key",
|
||||||
test: testDeriveSharedKey,
|
test: testDeriveSharedKey,
|
||||||
|
Loading…
Reference in New Issue
Block a user