routing tests: move TestRouterPaymentStateMachine to own file

(almost) PURE CODE MOVE
The only code change is to change a few select cases from

case _ <- channel:
to
case <- channel:

to please the linter.

The test is testing the payment lifecycle, so move it to
payment_lifecycle_test.go
This commit is contained in:
Johan T. Halseth 2020-04-01 00:13:26 +02:00
parent 3610824abd
commit 4d343bbb46
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 653 additions and 632 deletions

@ -0,0 +1,653 @@
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"
)
// 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()
hop1 := testGraph.aliasMap["b"]
hop2 := testGraph.aliasMap["c"]
hops := []*route.Hop{
{
ChannelID: 1,
PubKeyBytes: hop1,
LegacyPayload: true,
},
{
ChannelID: 2,
PubKeyBytes: hop2,
LegacyPayload: true,
},
}
// We create a simple route that we will supply every time the router
// requests one.
rt, err := route.NewRouteFromHops(
lnwire.MilliSatoshi(10000), 100, testGraph.aliasMap["a"], hops,
)
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()
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
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)
}
}
}
}

@ -2,7 +2,6 @@ package routing
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"image/color" "image/color"
"math" "math"
@ -2616,637 +2615,6 @@ func assertChannelsPruned(t *testing.T, graph *channeldb.ChannelGraph,
} }
} }
// 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()
hop1 := testGraph.aliasMap["b"]
hop2 := testGraph.aliasMap["c"]
hops := []*route.Hop{
{
ChannelID: 1,
PubKeyBytes: hop1,
LegacyPayload: true,
},
{
ChannelID: 2,
PubKeyBytes: hop2,
LegacyPayload: true,
},
}
// We create a simple route that we will supply every time the router
// requests one.
rt, err := route.NewRouteFromHops(
lnwire.MilliSatoshi(10000), 100, testGraph.aliasMap["a"], hops,
)
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 alreayd 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 router.Stop()
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()
paymentAmt := lnwire.NewMSatFromSatoshis(1000)
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 alreayd 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)
}
}
}
}
// TestSendToRouteStructuredError asserts that SendToRoute returns a structured // TestSendToRouteStructuredError asserts that SendToRoute returns a structured
// error. // error.
func TestSendToRouteStructuredError(t *testing.T) { func TestSendToRouteStructuredError(t *testing.T) {