123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- package channeldb
- import (
- "bytes"
- "fmt"
- "testing"
- "github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
- "github.com/lightningnetwork/lnd/routing/route"
- "github.com/stretchr/testify/require"
- )
- // TestLazySessionKeyDeserialize tests that we can read htlc attempt session
- // keys that were previously serialized as a private key as raw bytes.
- func TestLazySessionKeyDeserialize(t *testing.T) {
- var b bytes.Buffer
- // Serialize as a private key.
- err := WriteElements(&b, priv)
- require.NoError(t, err)
- // Deserialize into [btcec.PrivKeyBytesLen]byte.
- attempt := HTLCAttemptInfo{}
- err = ReadElements(&b, &attempt.sessionKey)
- require.NoError(t, err)
- require.Zero(t, b.Len())
- sessionKey := attempt.SessionKey()
- require.Equal(t, priv, sessionKey)
- }
- // TestRegistrable checks the method `Registrable` behaves as expected for ALL
- // possible payment statuses.
- func TestRegistrable(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- status PaymentStatus
- registryErr error
- hasSettledHTLC bool
- paymentFailed bool
- }{
- {
- status: StatusInitiated,
- registryErr: nil,
- },
- {
- // Test inflight status with no settled HTLC and no
- // failed payment.
- status: StatusInFlight,
- registryErr: nil,
- },
- {
- // Test inflight status with settled HTLC but no failed
- // payment.
- status: StatusInFlight,
- registryErr: ErrPaymentPendingSettled,
- hasSettledHTLC: true,
- },
- {
- // Test inflight status with no settled HTLC but failed
- // payment.
- status: StatusInFlight,
- registryErr: ErrPaymentPendingFailed,
- paymentFailed: true,
- },
- {
- // Test error state with settled HTLC and failed
- // payment.
- status: 0,
- registryErr: ErrUnknownPaymentStatus,
- hasSettledHTLC: true,
- paymentFailed: true,
- },
- {
- status: StatusSucceeded,
- registryErr: ErrPaymentAlreadySucceeded,
- },
- {
- status: StatusFailed,
- registryErr: ErrPaymentAlreadyFailed,
- },
- {
- status: 0,
- registryErr: ErrUnknownPaymentStatus,
- },
- }
- for i, tc := range testCases {
- i, tc := i, tc
- p := &MPPayment{
- Status: tc.status,
- State: &MPPaymentState{
- HasSettledHTLC: tc.hasSettledHTLC,
- PaymentFailed: tc.paymentFailed,
- },
- }
- name := fmt.Sprintf("test_%d_%s", i, p.Status.String())
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- err := p.Registrable()
- require.ErrorIs(t, err, tc.registryErr,
- "registrable under state %v", tc.status)
- })
- }
- }
- // TestPaymentSetState checks that the method setState creates the
- // MPPaymentState as expected.
- func TestPaymentSetState(t *testing.T) {
- t.Parallel()
- // Create a test preimage and failure reason.
- preimage := lntypes.Preimage{1}
- failureReasonError := FailureReasonError
- testCases := []struct {
- name string
- payment *MPPayment
- totalAmt int
- expectedState *MPPaymentState
- errExpected error
- }{
- {
- // Test that when the sentAmt exceeds totalAmount, the
- // error is returned.
- name: "amount exceeded error",
- // SentAmt returns 90, 10
- // TerminalInfo returns non-nil, nil
- // InFlightHTLCs returns 0
- payment: &MPPayment{
- HTLCs: []HTLCAttempt{
- makeSettledAttempt(100, 10, preimage),
- },
- },
- totalAmt: 1,
- errExpected: ErrSentExceedsTotal,
- },
- {
- // Test that when the htlc is failed, the fee is not
- // used.
- name: "fee excluded for failed htlc",
- payment: &MPPayment{
- // SentAmt returns 90, 10
- // TerminalInfo returns nil, nil
- // InFlightHTLCs returns 1
- HTLCs: []HTLCAttempt{
- makeActiveAttempt(100, 10),
- makeFailedAttempt(100, 10),
- },
- },
- totalAmt: 1000,
- expectedState: &MPPaymentState{
- NumAttemptsInFlight: 1,
- RemainingAmt: 1000 - 90,
- FeesPaid: 10,
- HasSettledHTLC: false,
- PaymentFailed: false,
- },
- },
- {
- // Test when the payment is settled, the state should
- // be marked as terminated.
- name: "payment settled",
- // SentAmt returns 90, 10
- // TerminalInfo returns non-nil, nil
- // InFlightHTLCs returns 0
- payment: &MPPayment{
- HTLCs: []HTLCAttempt{
- makeSettledAttempt(100, 10, preimage),
- },
- },
- totalAmt: 1000,
- expectedState: &MPPaymentState{
- NumAttemptsInFlight: 0,
- RemainingAmt: 1000 - 90,
- FeesPaid: 10,
- HasSettledHTLC: true,
- PaymentFailed: false,
- },
- },
- {
- // Test when the payment is failed, the state should be
- // marked as terminated.
- name: "payment failed",
- // SentAmt returns 0, 0
- // TerminalInfo returns nil, non-nil
- // InFlightHTLCs returns 0
- payment: &MPPayment{
- FailureReason: &failureReasonError,
- },
- totalAmt: 1000,
- expectedState: &MPPaymentState{
- NumAttemptsInFlight: 0,
- RemainingAmt: 1000,
- FeesPaid: 0,
- HasSettledHTLC: false,
- PaymentFailed: true,
- },
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- // Attach the payment info.
- info := &PaymentCreationInfo{
- Value: lnwire.MilliSatoshi(tc.totalAmt),
- }
- tc.payment.Info = info
- // Call the method that updates the payment state.
- err := tc.payment.setState()
- require.ErrorIs(t, err, tc.errExpected)
- require.Equal(
- t, tc.expectedState, tc.payment.State,
- "state not updated as expected",
- )
- })
- }
- }
- // TestNeedWaitAttempts checks whether we need to wait for the results of the
- // HTLC attempts against ALL possible payment statuses.
- func TestNeedWaitAttempts(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- status PaymentStatus
- remainingAmt lnwire.MilliSatoshi
- hasSettledHTLC bool
- hasFailureReason bool
- needWait bool
- expectedErr error
- }{
- {
- // For a newly created payment we don't need to wait
- // for results.
- status: StatusInitiated,
- remainingAmt: 1000,
- needWait: false,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we don't need to wait when the
- // remainingAmt is not zero and we have no settled
- // HTLCs.
- status: StatusInFlight,
- remainingAmt: 1000,
- needWait: false,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we need to wait when the
- // remainingAmt is not zero but we have settled HTLCs.
- status: StatusInFlight,
- remainingAmt: 1000,
- hasSettledHTLC: true,
- needWait: true,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we need to wait when the
- // remainingAmt is not zero and the payment is failed.
- status: StatusInFlight,
- remainingAmt: 1000,
- needWait: true,
- hasFailureReason: true,
- expectedErr: nil,
- },
- {
- // With the payment settled, but the remainingAmt is
- // not zero, we have an error state.
- status: StatusSucceeded,
- remainingAmt: 1000,
- needWait: false,
- expectedErr: ErrPaymentInternal,
- },
- {
- // Payment is in terminal state, no need to wait.
- status: StatusFailed,
- remainingAmt: 1000,
- needWait: false,
- expectedErr: nil,
- },
- {
- // A newly created payment with zero remainingAmt
- // indicates an error.
- status: StatusInitiated,
- remainingAmt: 0,
- needWait: false,
- expectedErr: ErrPaymentInternal,
- },
- {
- // With zero remainingAmt we must wait for the results.
- status: StatusInFlight,
- remainingAmt: 0,
- needWait: true,
- expectedErr: nil,
- },
- {
- // Payment is terminated, no need to wait for results.
- status: StatusSucceeded,
- remainingAmt: 0,
- needWait: false,
- expectedErr: nil,
- },
- {
- // Payment is terminated, no need to wait for results.
- status: StatusFailed,
- remainingAmt: 0,
- needWait: false,
- expectedErr: ErrPaymentInternal,
- },
- {
- // Payment is in an unknown status, return an error.
- status: 0,
- remainingAmt: 0,
- needWait: false,
- expectedErr: ErrUnknownPaymentStatus,
- },
- {
- // Payment is in an unknown status, return an error.
- status: 0,
- remainingAmt: 1000,
- needWait: false,
- expectedErr: ErrUnknownPaymentStatus,
- },
- }
- for _, tc := range testCases {
- tc := tc
- p := &MPPayment{
- Info: &PaymentCreationInfo{
- PaymentIdentifier: [32]byte{1, 2, 3},
- },
- Status: tc.status,
- State: &MPPaymentState{
- RemainingAmt: tc.remainingAmt,
- HasSettledHTLC: tc.hasSettledHTLC,
- PaymentFailed: tc.hasFailureReason,
- },
- }
- name := fmt.Sprintf("status=%s|remainingAmt=%v|"+
- "settledHTLC=%v|failureReason=%v", tc.status,
- tc.remainingAmt, tc.hasSettledHTLC, tc.hasFailureReason)
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- result, err := p.NeedWaitAttempts()
- require.ErrorIs(t, err, tc.expectedErr)
- require.Equalf(t, tc.needWait, result, "status=%v, "+
- "remainingAmt=%v", tc.status, tc.remainingAmt)
- })
- }
- }
- // TestAllowMoreAttempts checks whether more attempts can be created against
- // ALL possible payment statuses.
- func TestAllowMoreAttempts(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- status PaymentStatus
- remainingAmt lnwire.MilliSatoshi
- hasSettledHTLC bool
- paymentFailed bool
- allowMore bool
- expectedErr error
- }{
- {
- // A newly created payment with zero remainingAmt
- // indicates an error.
- status: StatusInitiated,
- remainingAmt: 0,
- allowMore: false,
- expectedErr: ErrPaymentInternal,
- },
- {
- // With zero remainingAmt we don't allow more HTLC
- // attempts.
- status: StatusInFlight,
- remainingAmt: 0,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With zero remainingAmt we don't allow more HTLC
- // attempts.
- status: StatusSucceeded,
- remainingAmt: 0,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With zero remainingAmt we don't allow more HTLC
- // attempts.
- status: StatusFailed,
- remainingAmt: 0,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With zero remainingAmt and settled HTLCs we don't
- // allow more HTLC attempts.
- status: StatusInFlight,
- remainingAmt: 0,
- hasSettledHTLC: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With zero remainingAmt and failed payment we don't
- // allow more HTLC attempts.
- status: StatusInFlight,
- remainingAmt: 0,
- paymentFailed: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With zero remainingAmt and both settled HTLCs and
- // failed payment, we don't allow more HTLC attempts.
- status: StatusInFlight,
- remainingAmt: 0,
- hasSettledHTLC: true,
- paymentFailed: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // A newly created payment can have more attempts.
- status: StatusInitiated,
- remainingAmt: 1000,
- allowMore: true,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we can have more attempts when
- // the remainingAmt is not zero and we have neither
- // failed payment or settled HTLCs.
- status: StatusInFlight,
- remainingAmt: 1000,
- allowMore: true,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we cannot have more attempts
- // though the remainingAmt is not zero but we have
- // settled HTLCs.
- status: StatusInFlight,
- remainingAmt: 1000,
- hasSettledHTLC: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we cannot have more attempts
- // though the remainingAmt is not zero but we have
- // failed payment.
- status: StatusInFlight,
- remainingAmt: 1000,
- paymentFailed: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With HTLCs inflight we cannot have more attempts
- // though the remainingAmt is not zero but we have
- // settled HTLCs and failed payment.
- status: StatusInFlight,
- remainingAmt: 1000,
- hasSettledHTLC: true,
- paymentFailed: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With the payment settled, but the remainingAmt is
- // not zero, we have an error state.
- status: StatusSucceeded,
- remainingAmt: 1000,
- hasSettledHTLC: true,
- allowMore: false,
- expectedErr: ErrPaymentInternal,
- },
- {
- // With the payment failed with no inflight HTLCs, we
- // don't allow more attempts to be made.
- status: StatusFailed,
- remainingAmt: 1000,
- paymentFailed: true,
- allowMore: false,
- expectedErr: nil,
- },
- {
- // With the payment in an unknown state, we don't allow
- // more attempts to be made.
- status: 0,
- remainingAmt: 1000,
- allowMore: false,
- expectedErr: nil,
- },
- }
- for i, tc := range testCases {
- tc := tc
- p := &MPPayment{
- Info: &PaymentCreationInfo{
- PaymentIdentifier: [32]byte{1, 2, 3},
- },
- Status: tc.status,
- State: &MPPaymentState{
- RemainingAmt: tc.remainingAmt,
- HasSettledHTLC: tc.hasSettledHTLC,
- PaymentFailed: tc.paymentFailed,
- },
- }
- name := fmt.Sprintf("test_%d|status=%s|remainingAmt=%v", i,
- tc.status, tc.remainingAmt)
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- result, err := p.AllowMoreAttempts()
- require.ErrorIs(t, err, tc.expectedErr)
- require.Equalf(t, tc.allowMore, result, "status=%v, "+
- "remainingAmt=%v", tc.status, tc.remainingAmt)
- })
- }
- }
- func makeActiveAttempt(total, fee int) HTLCAttempt {
- return HTLCAttempt{
- HTLCAttemptInfo: makeAttemptInfo(total, total-fee),
- }
- }
- func makeSettledAttempt(total, fee int,
- preimage lntypes.Preimage) HTLCAttempt {
- return HTLCAttempt{
- HTLCAttemptInfo: makeAttemptInfo(total, total-fee),
- Settle: &HTLCSettleInfo{Preimage: preimage},
- }
- }
- func makeFailedAttempt(total, fee int) HTLCAttempt {
- return HTLCAttempt{
- HTLCAttemptInfo: makeAttemptInfo(total, total-fee),
- Failure: &HTLCFailInfo{
- Reason: HTLCFailInternal,
- },
- }
- }
- func makeAttemptInfo(total, amtForwarded int) HTLCAttemptInfo {
- hop := &route.Hop{AmtToForward: lnwire.MilliSatoshi(amtForwarded)}
- return HTLCAttemptInfo{
- Route: route.Route{
- TotalAmount: lnwire.MilliSatoshi(total),
- Hops: []*route.Hop{hop},
- },
- }
- }
|