mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-10-11 15:13:31 +02:00
routing/router_test: add TestRouterPaymentStateMachine
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.
This commit is contained in:
@@ -1,8 +1,15 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockPaymentAttemptDispatcher struct {
|
type mockPaymentAttemptDispatcher struct {
|
||||||
@@ -62,3 +69,217 @@ func (m *mockPaymentAttemptDispatcher) setPaymentResult(
|
|||||||
|
|
||||||
m.onPayment = f
|
m.onPayment = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockPaymentSessionSource struct {
|
||||||
|
routes []*route.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PaymentSessionSource = (*mockPaymentSessionSource)(nil)
|
||||||
|
|
||||||
|
func (m *mockPaymentSessionSource) NewPaymentSession(routeHints [][]zpay32.HopHint,
|
||||||
|
target route.Vertex) (PaymentSession, error) {
|
||||||
|
|
||||||
|
return &mockPaymentSession{m.routes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentSessionSource) NewPaymentSessionForRoute(
|
||||||
|
preBuiltRoute *route.Route) PaymentSession {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentSessionSource) NewPaymentSessionEmpty() PaymentSession {
|
||||||
|
return &mockPaymentSession{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPaymentSession struct {
|
||||||
|
routes []*route.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PaymentSession = (*mockPaymentSession)(nil)
|
||||||
|
|
||||||
|
func (m *mockPaymentSession) RequestRoute(payment *LightningPayment,
|
||||||
|
height uint32, finalCltvDelta uint16) (*route.Route, error) {
|
||||||
|
|
||||||
|
if len(m.routes) == 0 {
|
||||||
|
return nil, fmt.Errorf("no routes")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := m.routes[0]
|
||||||
|
m.routes = m.routes[1:]
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentSession) ReportVertexFailure(v route.Vertex) {}
|
||||||
|
|
||||||
|
func (m *mockPaymentSession) ReportEdgeFailure(e *EdgeLocator) {}
|
||||||
|
|
||||||
|
func (m *mockPaymentSession) ReportEdgePolicyFailure(errSource route.Vertex, failedEdge *EdgeLocator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPayer struct {
|
||||||
|
sendResult chan error
|
||||||
|
paymentResultErr chan error
|
||||||
|
paymentResult chan *htlcswitch.PaymentResult
|
||||||
|
quit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PaymentAttemptDispatcher = (*mockPayer)(nil)
|
||||||
|
|
||||||
|
func (m *mockPayer) SendHTLC(_ lnwire.ShortChannelID,
|
||||||
|
paymentID uint64,
|
||||||
|
_ *lnwire.UpdateAddHTLC) error {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case res := <-m.sendResult:
|
||||||
|
return res
|
||||||
|
case <-m.quit:
|
||||||
|
return fmt.Errorf("test quitting")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPayer) GetPaymentResult(paymentID uint64, _ htlcswitch.ErrorDecrypter) (
|
||||||
|
<-chan *htlcswitch.PaymentResult, error) {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case res := <-m.paymentResult:
|
||||||
|
resChan := make(chan *htlcswitch.PaymentResult, 1)
|
||||||
|
resChan <- res
|
||||||
|
return resChan, nil
|
||||||
|
case err := <-m.paymentResultErr:
|
||||||
|
return nil, err
|
||||||
|
case <-m.quit:
|
||||||
|
return nil, fmt.Errorf("test quitting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type initArgs struct {
|
||||||
|
c *channeldb.PaymentCreationInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type registerArgs struct {
|
||||||
|
a *channeldb.PaymentAttemptInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type successArgs struct {
|
||||||
|
preimg lntypes.Preimage
|
||||||
|
}
|
||||||
|
|
||||||
|
type failArgs struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockControlTower struct {
|
||||||
|
inflights map[lntypes.Hash]channeldb.InFlightPayment
|
||||||
|
successful map[lntypes.Hash]struct{}
|
||||||
|
|
||||||
|
init chan initArgs
|
||||||
|
register chan registerArgs
|
||||||
|
success chan successArgs
|
||||||
|
fail chan failArgs
|
||||||
|
fetchInFlight chan struct{}
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ channeldb.ControlTower = (*mockControlTower)(nil)
|
||||||
|
|
||||||
|
func makeMockControlTower() *mockControlTower {
|
||||||
|
return &mockControlTower{
|
||||||
|
inflights: make(map[lntypes.Hash]channeldb.InFlightPayment),
|
||||||
|
successful: make(map[lntypes.Hash]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) InitPayment(phash lntypes.Hash,
|
||||||
|
c *channeldb.PaymentCreationInfo) error {
|
||||||
|
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if m.init != nil {
|
||||||
|
m.init <- initArgs{c}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := m.successful[phash]; ok {
|
||||||
|
return fmt.Errorf("already successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := m.inflights[phash]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("in flight")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.inflights[phash] = channeldb.InFlightPayment{
|
||||||
|
Info: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
|
||||||
|
a *channeldb.PaymentAttemptInfo) error {
|
||||||
|
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if m.register != nil {
|
||||||
|
m.register <- registerArgs{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ok := m.inflights[phash]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("not in flight")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Attempt = a
|
||||||
|
m.inflights[phash] = p
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) Success(phash lntypes.Hash,
|
||||||
|
preimg lntypes.Preimage) error {
|
||||||
|
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if m.success != nil {
|
||||||
|
m.success <- successArgs{preimg}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m.inflights, phash)
|
||||||
|
m.successful[phash] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) Fail(phash lntypes.Hash) error {
|
||||||
|
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if m.fail != nil {
|
||||||
|
m.fail <- failArgs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m.inflights, phash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockControlTower) FetchInFlightPayments() (
|
||||||
|
[]*channeldb.InFlightPayment, error) {
|
||||||
|
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
if m.fetchInFlight != nil {
|
||||||
|
m.fetchInFlight <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fl []*channeldb.InFlightPayment
|
||||||
|
for _, ifl := range m.inflights {
|
||||||
|
fl = append(fl, &ifl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fl, nil
|
||||||
|
}
|
||||||
|
@@ -731,7 +731,9 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Once again, Roasbeef should route around Goku since they disagree
|
// Once again, Roasbeef should route around Goku since they disagree
|
||||||
// w.r.t to the block height, and instead go through Pham Nuwen.
|
// w.r.t to the block height, and instead go through Pham Nuwen. We
|
||||||
|
// flip a bit in the payment hash to allow resending this payment.
|
||||||
|
payment.PaymentHash[1] ^= 1
|
||||||
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
@@ -898,6 +900,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
return preImage, nil
|
return preImage, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// We flip a bit in the payment hash to allow resending this payment.
|
||||||
|
payment.PaymentHash[1] ^= 1
|
||||||
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
@@ -2507,35 +2511,588 @@ func assertChannelsPruned(t *testing.T, graph *channeldb.ChannelGraph,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockControlTower struct{}
|
// 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()
|
||||||
|
|
||||||
var _ channeldb.ControlTower = (*mockControlTower)(nil)
|
const startingBlockHeight = 101
|
||||||
|
|
||||||
func makeMockControlTower() *mockControlTower {
|
// Setup two simple channels such that we can mock sending along this
|
||||||
return &mockControlTower{}
|
// 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),
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) InitPayment(lntypes.Hash,
|
testGraph, err := createTestGraphFromChannels(testChannels)
|
||||||
*channeldb.PaymentCreationInfo) error {
|
if err != nil {
|
||||||
return 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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ChannelID: 2,
|
||||||
|
PubKeyBytes: hop2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) RegisterAttempt(lntypes.Hash,
|
// We create a simple route that we will supply every time the router
|
||||||
*channeldb.PaymentAttemptInfo) error {
|
// requests one.
|
||||||
return nil
|
rt, err := route.NewRouteFromHops(
|
||||||
|
lnwire.MilliSatoshi(10000), 100, testGraph.aliasMap["a"], hops,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create route: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) Success(paymentHash lntypes.Hash,
|
// A payment state machine test case consists of several ordered steps,
|
||||||
preimg lntypes.Preimage) error {
|
// that we use for driving the scenario.
|
||||||
return nil
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) Fail(paymentHash lntypes.Hash) error {
|
const (
|
||||||
return nil
|
// 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"
|
||||||
|
|
||||||
|
// 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 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},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockControlTower) FetchInFlightPayments() (
|
// Create a mock control tower with channels set up, that we use to
|
||||||
[]*channeldb.InFlightPayment, error) {
|
// 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{})
|
||||||
|
|
||||||
return nil, nil
|
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,
|
||||||
|
MissionControl: &mockPaymentSessionSource{},
|
||||||
|
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
|
||||||
|
},
|
||||||
|
})
|
||||||
|
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.
|
||||||
|
didFetch := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-control.fetchInFlight:
|
||||||
|
close(didFetch)
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatalf("router did not fetch in flight " +
|
||||||
|
"payments")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := router.Start(); err != nil {
|
||||||
|
t.Fatalf("unable to start router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-didFetch:
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
errSource, err := btcec.ParsePubKey(hop1[:], btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to fetch source node pub: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(preImage[:], bytes.Repeat([]byte{9}, 32))
|
||||||
|
|
||||||
|
router.cfg.MissionControl = &mockPaymentSessionSource{
|
||||||
|
routes: test.routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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:
|
||||||
|
select {
|
||||||
|
case getPaymentResult <- &htlcswitch.PaymentResult{
|
||||||
|
Error: &htlcswitch.ForwardingError{
|
||||||
|
ErrorSource: errSource,
|
||||||
|
FailureMessage: &lnwire.FailTemporaryChannelFailure{},
|
||||||
|
},
|
||||||
|
}:
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user