This also fixes a test bug that the manually created route didn't match the actual payment amount in the test cases, and adds some fees to the route.
667 lines
18 KiB
Go
667 lines
18 KiB
Go
package routing
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/clock"
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
)
|
|
|
|
// createTestRoute builds a route a->b->c paying the given amt to c.
|
|
func createTestRoute(amt lnwire.MilliSatoshi,
|
|
aliasMap map[string]route.Vertex) (*route.Route, error) {
|
|
|
|
hopFee := lnwire.NewMSatFromSatoshis(3)
|
|
hop1 := aliasMap["b"]
|
|
hop2 := aliasMap["c"]
|
|
hops := []*route.Hop{
|
|
{
|
|
ChannelID: 1,
|
|
PubKeyBytes: hop1,
|
|
LegacyPayload: true,
|
|
AmtToForward: amt + hopFee,
|
|
},
|
|
{
|
|
ChannelID: 2,
|
|
PubKeyBytes: hop2,
|
|
LegacyPayload: true,
|
|
AmtToForward: amt,
|
|
},
|
|
}
|
|
|
|
// We create a simple route that we will supply every time the router
|
|
// requests one.
|
|
return route.NewRouteFromHops(
|
|
amt+2*hopFee, 100, aliasMap["a"], hops,
|
|
)
|
|
}
|
|
|
|
// TestRouterPaymentStateMachine tests that the router interacts as expected
|
|
// with the ControlTower during a payment lifecycle, such that it payment
|
|
// attempts are not sent twice to the switch, and results are handled after a
|
|
// restart.
|
|
func TestRouterPaymentStateMachine(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const startingBlockHeight = 101
|
|
|
|
// Setup two simple channels such that we can mock sending along this
|
|
// route.
|
|
chanCapSat := btcutil.Amount(100000)
|
|
testChannels := []*testChannel{
|
|
symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{
|
|
Expiry: 144,
|
|
FeeRate: 400,
|
|
MinHTLC: 1,
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
}, 1),
|
|
symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{
|
|
Expiry: 144,
|
|
FeeRate: 400,
|
|
MinHTLC: 1,
|
|
MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat),
|
|
}, 2),
|
|
}
|
|
|
|
testGraph, err := createTestGraphFromChannels(testChannels, "a")
|
|
if err != nil {
|
|
t.Fatalf("unable to create graph: %v", err)
|
|
}
|
|
defer testGraph.cleanUp()
|
|
|
|
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
|
|
|
|
// We create a simple route that we will supply every time the router
|
|
// requests one.
|
|
rt, err := createTestRoute(paymentAmt, testGraph.aliasMap)
|
|
if err != nil {
|
|
t.Fatalf("unable to create route: %v", err)
|
|
}
|
|
|
|
// A payment state machine test case consists of several ordered steps,
|
|
// that we use for driving the scenario.
|
|
type testCase struct {
|
|
// steps is a list of steps to perform during the testcase.
|
|
steps []string
|
|
|
|
// routes is the sequence of routes we will provide to the
|
|
// router when it requests a new route.
|
|
routes []*route.Route
|
|
}
|
|
|
|
const (
|
|
// routerInitPayment is a test step where we expect the router
|
|
// to call the InitPayment method on the control tower.
|
|
routerInitPayment = "Router:init-payment"
|
|
|
|
// routerRegisterAttempt is a test step where we expect the
|
|
// router to call the RegisterAttempt method on the control
|
|
// tower.
|
|
routerRegisterAttempt = "Router:register-attempt"
|
|
|
|
// routerSuccess is a test step where we expect the router to
|
|
// call the Success method on the control tower.
|
|
routerSuccess = "Router:success"
|
|
|
|
// routerFail is a test step where we expect the router to call
|
|
// the Fail method on the control tower.
|
|
routerFail = "Router:fail"
|
|
|
|
// sendToSwitchSuccess is a step where we expect the router to
|
|
// call send the payment attempt to the switch, and we will
|
|
// respond with a non-error, indicating that the payment
|
|
// attempt was successfully forwarded.
|
|
sendToSwitchSuccess = "SendToSwitch:success"
|
|
|
|
// sendToSwitchResultFailure is a step where we expect the
|
|
// router to send the payment attempt to the switch, and we
|
|
// will respond with a forwarding error. This can happen when
|
|
// forwarding fail on our local links.
|
|
sendToSwitchResultFailure = "SendToSwitch:failure"
|
|
|
|
// getPaymentResultSuccess is a test step where we expect the
|
|
// router to call the GetPaymentResult method, and we will
|
|
// respond with a successful payment result.
|
|
getPaymentResultSuccess = "GetPaymentResult:success"
|
|
|
|
// getPaymentResultFailure is a test step where we expect the
|
|
// router to call the GetPaymentResult method, and we will
|
|
// respond with a forwarding error.
|
|
getPaymentResultFailure = "GetPaymentResult:failure"
|
|
|
|
// resendPayment is a test step where we manually try to resend
|
|
// the same payment, making sure the router responds with an
|
|
// error indicating that it is already in flight.
|
|
resendPayment = "ResendPayment"
|
|
|
|
// startRouter is a step where we manually start the router,
|
|
// used to test that it automatically will resume payments at
|
|
// startup.
|
|
startRouter = "StartRouter"
|
|
|
|
// stopRouter is a test step where we manually make the router
|
|
// shut down.
|
|
stopRouter = "StopRouter"
|
|
|
|
// paymentSuccess is a step where assert that we receive a
|
|
// successful result for the original payment made.
|
|
paymentSuccess = "PaymentSuccess"
|
|
|
|
// paymentError is a step where assert that we receive an error
|
|
// for the original payment made.
|
|
paymentError = "PaymentError"
|
|
|
|
// resentPaymentSuccess is a step where assert that we receive
|
|
// a successful result for a payment that was resent.
|
|
resentPaymentSuccess = "ResentPaymentSuccess"
|
|
|
|
// resentPaymentError is a step where assert that we receive an
|
|
// error for a payment that was resent.
|
|
resentPaymentError = "ResentPaymentError"
|
|
)
|
|
|
|
tests := []testCase{
|
|
{
|
|
// Tests a normal payment flow that succeeds.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
paymentSuccess,
|
|
},
|
|
routes: []*route.Route{rt},
|
|
},
|
|
{
|
|
// A payment flow with a failure on the first attempt,
|
|
// but that succeeds on the second attempt.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Make the first sent attempt fail.
|
|
getPaymentResultFailure,
|
|
|
|
// The router should retry.
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Make the second sent attempt succeed.
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
paymentSuccess,
|
|
},
|
|
routes: []*route.Route{rt, rt},
|
|
},
|
|
{
|
|
// A payment flow with a forwarding failure first time
|
|
// sending to the switch, but that succeeds on the
|
|
// second attempt.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
|
|
// Make the first sent attempt fail.
|
|
sendToSwitchResultFailure,
|
|
|
|
// The router should retry.
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Make the second sent attempt succeed.
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
paymentSuccess,
|
|
},
|
|
routes: []*route.Route{rt, rt},
|
|
},
|
|
{
|
|
// A payment that fails on the first attempt, and has
|
|
// only one route available to try. It will therefore
|
|
// fail permanently.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Make the first sent attempt fail.
|
|
getPaymentResultFailure,
|
|
|
|
// Since there are no more routes to try, the
|
|
// payment should fail.
|
|
routerFail,
|
|
paymentError,
|
|
},
|
|
routes: []*route.Route{rt},
|
|
},
|
|
{
|
|
// We expect the payment to fail immediately if we have
|
|
// no routes to try.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerFail,
|
|
paymentError,
|
|
},
|
|
routes: []*route.Route{},
|
|
},
|
|
{
|
|
// A normal payment flow, where we attempt to resend
|
|
// the same payment after each step. This ensures that
|
|
// the router don't attempt to resend a payment already
|
|
// in flight.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
|
|
// Manually resend the payment, the router
|
|
// should attempt to init with the control
|
|
// tower, but fail since it is already in
|
|
// flight.
|
|
resendPayment,
|
|
routerInitPayment,
|
|
resentPaymentError,
|
|
|
|
// The original payment should proceed as
|
|
// normal.
|
|
sendToSwitchSuccess,
|
|
|
|
// Again resend the payment and assert it's not
|
|
// allowed.
|
|
resendPayment,
|
|
routerInitPayment,
|
|
resentPaymentError,
|
|
|
|
// Notify about a success for the original
|
|
// payment.
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
|
|
// Now that the original payment finished,
|
|
// resend it again to ensure this is not
|
|
// allowed.
|
|
resendPayment,
|
|
routerInitPayment,
|
|
resentPaymentError,
|
|
paymentSuccess,
|
|
},
|
|
routes: []*route.Route{rt},
|
|
},
|
|
{
|
|
// Tests that the router is able to handle the
|
|
// receieved payment result after a restart.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Shut down the router. The original caller
|
|
// should get notified about this.
|
|
stopRouter,
|
|
paymentError,
|
|
|
|
// Start the router again, and ensure the
|
|
// router registers the success with the
|
|
// control tower.
|
|
startRouter,
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
},
|
|
routes: []*route.Route{rt},
|
|
},
|
|
{
|
|
// Tests that we are allowed to resend a payment after
|
|
// it has permanently failed.
|
|
steps: []string{
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
|
|
// Resending the payment at this stage should
|
|
// not be allowed.
|
|
resendPayment,
|
|
routerInitPayment,
|
|
resentPaymentError,
|
|
|
|
// Make the first attempt fail.
|
|
getPaymentResultFailure,
|
|
routerFail,
|
|
|
|
// Since we have no more routes to try, the
|
|
// original payment should fail.
|
|
paymentError,
|
|
|
|
// Now resend the payment again. This should be
|
|
// allowed, since the payment has failed.
|
|
resendPayment,
|
|
routerInitPayment,
|
|
routerRegisterAttempt,
|
|
sendToSwitchSuccess,
|
|
getPaymentResultSuccess,
|
|
routerSuccess,
|
|
resentPaymentSuccess,
|
|
},
|
|
routes: []*route.Route{rt},
|
|
},
|
|
}
|
|
|
|
// Create a mock control tower with channels set up, that we use to
|
|
// synchronize and listen for events.
|
|
control := makeMockControlTower()
|
|
control.init = make(chan initArgs)
|
|
control.register = make(chan registerArgs)
|
|
control.success = make(chan successArgs)
|
|
control.fail = make(chan failArgs)
|
|
control.fetchInFlight = make(chan struct{})
|
|
|
|
quit := make(chan struct{})
|
|
defer close(quit)
|
|
|
|
// setupRouter is a helper method that creates and starts the router in
|
|
// the desired configuration for this test.
|
|
setupRouter := func() (*ChannelRouter, chan error,
|
|
chan *htlcswitch.PaymentResult, chan error) {
|
|
|
|
chain := newMockChain(startingBlockHeight)
|
|
chainView := newMockChainView(chain)
|
|
|
|
// We set uo the use the following channels and a mock Payer to
|
|
// synchonize with the interaction to the Switch.
|
|
sendResult := make(chan error)
|
|
paymentResultErr := make(chan error)
|
|
paymentResult := make(chan *htlcswitch.PaymentResult)
|
|
|
|
payer := &mockPayer{
|
|
sendResult: sendResult,
|
|
paymentResult: paymentResult,
|
|
paymentResultErr: paymentResultErr,
|
|
}
|
|
|
|
router, err := New(Config{
|
|
Graph: testGraph.graph,
|
|
Chain: chain,
|
|
ChainView: chainView,
|
|
Control: control,
|
|
SessionSource: &mockPaymentSessionSource{},
|
|
MissionControl: &mockMissionControl{},
|
|
Payer: payer,
|
|
ChannelPruneExpiry: time.Hour * 24,
|
|
GraphPruneInterval: time.Hour * 2,
|
|
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
|
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
|
},
|
|
NextPaymentID: func() (uint64, error) {
|
|
next := atomic.AddUint64(&uniquePaymentID, 1)
|
|
return next, nil
|
|
},
|
|
Clock: clock.NewTestClock(time.Unix(1, 0)),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unable to create router %v", err)
|
|
}
|
|
|
|
// On startup, the router should fetch all pending payments
|
|
// from the ControlTower, so assert that here.
|
|
errCh := make(chan error)
|
|
go func() {
|
|
close(errCh)
|
|
select {
|
|
case <-control.fetchInFlight:
|
|
return
|
|
case <-time.After(1 * time.Second):
|
|
errCh <- errors.New("router did not fetch in flight " +
|
|
"payments")
|
|
}
|
|
}()
|
|
|
|
if err := router.Start(); err != nil {
|
|
t.Fatalf("unable to start router: %v", err)
|
|
}
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
t.Fatalf("error in anonymous goroutine: %s", err)
|
|
}
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("did not fetch in flight payments at startup")
|
|
}
|
|
|
|
return router, sendResult, paymentResult, paymentResultErr
|
|
}
|
|
|
|
router, sendResult, getPaymentResult, getPaymentResultErr := setupRouter()
|
|
defer func() {
|
|
if err := router.Stop(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
for _, test := range tests {
|
|
// Craft a LightningPayment struct.
|
|
var preImage lntypes.Preimage
|
|
if _, err := rand.Read(preImage[:]); err != nil {
|
|
t.Fatalf("unable to generate preimage")
|
|
}
|
|
|
|
payHash := preImage.Hash()
|
|
|
|
payment := LightningPayment{
|
|
Target: testGraph.aliasMap["c"],
|
|
Amount: paymentAmt,
|
|
FeeLimit: noFeeLimit,
|
|
PaymentHash: payHash,
|
|
}
|
|
|
|
router.cfg.SessionSource = &mockPaymentSessionSource{
|
|
routes: test.routes,
|
|
}
|
|
|
|
router.cfg.MissionControl = &mockMissionControl{}
|
|
|
|
// Send the payment. Since this is new payment hash, the
|
|
// information should be registered with the ControlTower.
|
|
paymentResult := make(chan error)
|
|
go func() {
|
|
_, _, err := router.SendPayment(&payment)
|
|
paymentResult <- err
|
|
}()
|
|
|
|
var resendResult chan error
|
|
for _, step := range test.steps {
|
|
switch step {
|
|
|
|
case routerInitPayment:
|
|
var args initArgs
|
|
select {
|
|
case args = <-control.init:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("no init payment with control")
|
|
}
|
|
|
|
if args.c == nil {
|
|
t.Fatalf("expected non-nil CreationInfo")
|
|
}
|
|
|
|
// In this step we expect the router to make a call to
|
|
// register a new attempt with the ControlTower.
|
|
case routerRegisterAttempt:
|
|
var args registerArgs
|
|
select {
|
|
case args = <-control.register:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("not registered with control")
|
|
}
|
|
|
|
if args.a == nil {
|
|
t.Fatalf("expected non-nil AttemptInfo")
|
|
}
|
|
|
|
// In this step we expect the router to call the
|
|
// ControlTower's Succcess method with the preimage.
|
|
case routerSuccess:
|
|
select {
|
|
case <-control.success:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("not registered with control")
|
|
}
|
|
|
|
// In this step we expect the router to call the
|
|
// ControlTower's Fail method, to indicate that the
|
|
// payment failed.
|
|
case routerFail:
|
|
select {
|
|
case <-control.fail:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("not registered with control")
|
|
}
|
|
|
|
// In this step we expect the SendToSwitch method to be
|
|
// called, and we respond with a nil-error.
|
|
case sendToSwitchSuccess:
|
|
select {
|
|
case sendResult <- nil:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to send result")
|
|
}
|
|
|
|
// In this step we expect the SendToSwitch method to be
|
|
// called, and we respond with a forwarding error
|
|
case sendToSwitchResultFailure:
|
|
select {
|
|
case sendResult <- htlcswitch.NewForwardingError(
|
|
&lnwire.FailTemporaryChannelFailure{},
|
|
1,
|
|
):
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to send result")
|
|
}
|
|
|
|
// In this step we expect the GetPaymentResult method
|
|
// to be called, and we respond with the preimage to
|
|
// complete the payment.
|
|
case getPaymentResultSuccess:
|
|
select {
|
|
case getPaymentResult <- &htlcswitch.PaymentResult{
|
|
Preimage: preImage,
|
|
}:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to send result")
|
|
}
|
|
|
|
// In this state we expect the GetPaymentResult method
|
|
// to be called, and we respond with a forwarding
|
|
// error, indicating that the router should retry.
|
|
case getPaymentResultFailure:
|
|
failure := htlcswitch.NewForwardingError(
|
|
&lnwire.FailTemporaryChannelFailure{},
|
|
1,
|
|
)
|
|
|
|
select {
|
|
case getPaymentResult <- &htlcswitch.PaymentResult{
|
|
Error: failure,
|
|
}:
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to get result")
|
|
}
|
|
|
|
// In this step we manually try to resend the same
|
|
// payment, making sure the router responds with an
|
|
// error indicating that it is already in flight.
|
|
case resendPayment:
|
|
resendResult = make(chan error)
|
|
go func() {
|
|
_, _, err := router.SendPayment(&payment)
|
|
resendResult <- err
|
|
}()
|
|
|
|
// In this step we manually stop the router.
|
|
case stopRouter:
|
|
select {
|
|
case getPaymentResultErr <- fmt.Errorf(
|
|
"shutting down"):
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to send payment " +
|
|
"result error")
|
|
}
|
|
|
|
if err := router.Stop(); err != nil {
|
|
t.Fatalf("unable to restart: %v", err)
|
|
}
|
|
|
|
// In this step we manually start the router.
|
|
case startRouter:
|
|
router, sendResult, getPaymentResult,
|
|
getPaymentResultErr = setupRouter()
|
|
|
|
// In this state we expect to receive an error for the
|
|
// original payment made.
|
|
case paymentError:
|
|
select {
|
|
case err := <-paymentResult:
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("got no payment result")
|
|
}
|
|
|
|
// In this state we expect the original payment to
|
|
// succeed.
|
|
case paymentSuccess:
|
|
select {
|
|
case err := <-paymentResult:
|
|
if err != nil {
|
|
t.Fatalf("did not expecte error %v", err)
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("got no payment result")
|
|
}
|
|
|
|
// In this state we expect to receive an error for the
|
|
// resent payment made.
|
|
case resentPaymentError:
|
|
select {
|
|
case err := <-resendResult:
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("got no payment result")
|
|
}
|
|
|
|
// In this state we expect the resent payment to
|
|
// succeed.
|
|
case resentPaymentSuccess:
|
|
select {
|
|
case err := <-resendResult:
|
|
if err != nil {
|
|
t.Fatalf("did not expect error %v", err)
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("got no payment result")
|
|
}
|
|
|
|
default:
|
|
t.Fatalf("unknown step %v", step)
|
|
}
|
|
}
|
|
}
|
|
}
|