Skip to content

Commit

Permalink
Merge pull request #8170 from yyforyongyu/0-18-staging-missed
Browse files Browse the repository at this point in the history
Add missing commits from `0-18-staging`
  • Loading branch information
Roasbeef authored Nov 13, 2023
2 parents 9937b1d + 5168af5 commit e02fd39
Show file tree
Hide file tree
Showing 12 changed files with 2,562 additions and 2,225 deletions.
51 changes: 46 additions & 5 deletions channeldb/mp_payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,10 @@ func (m *MPPayment) Terminated() bool {
// TerminalInfo returns any HTLC settle info recorded. If no settle info is
// recorded, any payment level failure will be returned. If neither a settle
// nor a failure is recorded, both return values will be nil.
func (m *MPPayment) TerminalInfo() (*HTLCSettleInfo, *FailureReason) {
func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) {
for _, h := range m.HTLCs {
if h.Settle != nil {
return h.Settle, nil
return &h, nil
}
}

Expand Down Expand Up @@ -264,6 +264,7 @@ func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {

// GetAttempt returns the specified htlc attempt on the payment.
func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) {
// TODO(yy): iteration can be slow, make it into a tree or use BS.
for _, htlc := range m.HTLCs {
htlc := htlc
if htlc.AttemptID == id {
Expand Down Expand Up @@ -463,9 +464,49 @@ func (m *MPPayment) GetHTLCs() []HTLCAttempt {
return m.HTLCs
}

// GetFailureReason returns the failure reason.
func (m *MPPayment) GetFailureReason() *FailureReason {
return m.FailureReason
// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs
// for a given payment state. Return an error if the payment is in an
// unexpected state.
func (m *MPPayment) AllowMoreAttempts() (bool, error) {
// Now check whether the remainingAmt is zero or not. If we don't have
// any remainingAmt, no more HTLCs should be made.
if m.State.RemainingAmt == 0 {
// If the payment is newly created, yet we don't have any
// remainingAmt, return an error.
if m.Status == StatusInitiated {
return false, fmt.Errorf("%w: initiated payment has "+
"zero remainingAmt", ErrPaymentInternal)
}

// Otherwise, exit early since all other statuses with zero
// remainingAmt indicate no more HTLCs can be made.
return false, nil
}

// Otherwise, the remaining amount is not zero, we now decide whether
// to make more attempts based on the payment's current status.
//
// If at least one of the payment's attempts is settled, yet we haven't
// sent all the amount, it indicates something is wrong with the peer
// as the preimage is received. In this case, return an error state.
if m.Status == StatusSucceeded {
return false, fmt.Errorf("%w: payment already succeeded but "+
"still have remaining amount %v", ErrPaymentInternal,
m.State.RemainingAmt)
}

// Now check if we can register a new HTLC.
err := m.Registrable()
if err != nil {
log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+
"current status: %s", m.Info.PaymentIdentifier,
err, m.Status)

return false, nil
}

// Now we know we can register new HTLCs.
return true, nil
}

// serializeHTLCSettleInfo serializes the details of a settled htlc.
Expand Down
177 changes: 177 additions & 0 deletions channeldb/mp_payment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,183 @@ func TestNeedWaitAttempts(t *testing.T) {
}
}

// 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),
Expand Down
21 changes: 21 additions & 0 deletions docs/release-notes/release-notes-0.18.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
might panic due to empty witness data found in a transaction. More details
can be found [here](https://github.com/bitcoin/bitcoin/issues/28730).

* [Fixed a case](https://github.com/lightningnetwork/lnd/pull/7503) where it's
possible a failed payment might be stuck in pending.

# New Features
## Functional Enhancements

Expand All @@ -58,6 +61,13 @@
[http-header-timeout](https://github.com/lightningnetwork/lnd/pull/7715), is added so users can specify the amount of time the http server will wait for a request to complete before closing the connection. The default value is 5 seconds.

## RPC Additions

* [Deprecated](https://github.com/lightningnetwork/lnd/pull/7175)
`StatusUnknown` from the payment's rpc response in its status and added a new
status, `StatusInitiated`, to explicitly report its current state. Before
running this new version, please make sure to upgrade your client application
to include this new status so it can understand the RPC response properly.

## lncli Additions

# Improvements
Expand All @@ -81,6 +91,12 @@
hash](https://github.com/lightningnetwork/lnd/pull/8106) to the
signer.SignMessage/signer.VerifyMessage RPCs.

* `sendtoroute` will return an error when it's called using the flag
`--skip_temp_err` on a payment that's not a MPP. This is needed as a temp
error is defined as a routing error found in one of a MPP's HTLC attempts.
If, however, there's only one HTLC attempt, when it's failed, this payment is
considered failed, thus there's no such thing as temp error for a non-MPP.

## lncli Updates
## Code Health

Expand All @@ -90,6 +106,11 @@
`lnrpc.GetInfoResponse` message along with the `chain` field in the
`lnrpc.Chain` message have also been deprecated for the same reason.

* The payment lifecycle code has been refactored to improve its maintainablity.
In particular, the complexity involved in the lifecycle loop has been
decoupled into logical steps, with each step having its own responsibility,
making it easier to reason about the payment flow.

## Breaking Changes
## Performance Improvements

Expand Down
2 changes: 2 additions & 0 deletions htlcswitch/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,8 @@ func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
// We've received a fail update which means we can finalize the
// user payment and return fail response.
case *lnwire.UpdateFailHTLC:
// TODO(yy): construct deobfuscator here to avoid creating it
// in paymentLifecycle even for settled HTLCs.
paymentErr := s.parseFailedPayment(
deobfuscator, attemptID, paymentHash, n.unencrypted,
n.isResolution, htlc,
Expand Down
7 changes: 2 additions & 5 deletions itest/lnd_max_htlcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ func acceptHoldInvoice(ht *lntest.HarnessTest, idx int, sender,
invoice := receiver.RPC.AddHoldInvoice(req)

invStream := receiver.RPC.SubscribeSingleInvoice(hash[:])
inv := ht.ReceiveSingleInvoice(invStream)
require.Equal(ht, lnrpc.Invoice_OPEN, inv.State, "expect open")
ht.AssertInvoiceState(invStream, lnrpc.Invoice_OPEN)

sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
Expand All @@ -145,9 +144,7 @@ func acceptHoldInvoice(ht *lntest.HarnessTest, idx int, sender,
)
require.Len(ht, payment.Htlcs, 1)

inv = ht.ReceiveSingleInvoice(invStream)
require.Equal(ht, lnrpc.Invoice_ACCEPTED, inv.State,
"expected accepted")
ht.AssertInvoiceState(invStream, lnrpc.Invoice_ACCEPTED)

return &holdSubscription{
recipient: receiver,
Expand Down
10 changes: 8 additions & 2 deletions routing/control_tower.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@ type dbMPPayment interface {
// InFlightHTLCs returns all HTLCs that are in flight.
InFlightHTLCs() []channeldb.HTLCAttempt

// GetFailureReason returns the reason the payment failed.
GetFailureReason() *channeldb.FailureReason
// AllowMoreAttempts is used to decide whether we can safely attempt
// more HTLCs for a given payment state. Return an error if the payment
// is in an unexpected state.
AllowMoreAttempts() (bool, error)

// TerminalInfo returns the settled HTLC attempt or the payment's
// failure reason.
TerminalInfo() (*channeldb.HTLCAttempt, *channeldb.FailureReason)
}

// ControlTower tracks all outgoing payments made, whose primary purpose is to
Expand Down
14 changes: 6 additions & 8 deletions routing/control_tower_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ func TestControlTowerSubscribeSuccess(t *testing.T) {
"subscriber %v failed, want %s, got %s", i,
channeldb.StatusSucceeded, result.GetStatus())

settle, _ := result.TerminalInfo()
if settle.Preimage != preimg {
attempt, _ := result.TerminalInfo()
if attempt.Settle.Preimage != preimg {
t.Fatal("unexpected preimage")
}
if len(result.HTLCs) != 1 {
Expand Down Expand Up @@ -264,9 +264,8 @@ func TestPaymentControlSubscribeAllSuccess(t *testing.T) {
)

settle1, _ := result1.TerminalInfo()
require.Equal(
t, preimg1, settle1.Preimage, "unexpected preimage payment 1",
)
require.Equal(t, preimg1, settle1.Settle.Preimage,
"unexpected preimage payment 1")

require.Len(
t, result1.HTLCs, 1, "expect 1 htlc for payment 1, got %d",
Expand All @@ -283,9 +282,8 @@ func TestPaymentControlSubscribeAllSuccess(t *testing.T) {
)

settle2, _ := result2.TerminalInfo()
require.Equal(
t, preimg2, settle2.Preimage, "unexpected preimage payment 2",
)
require.Equal(t, preimg2, settle2.Settle.Preimage,
"unexpected preimage payment 2")
require.Len(
t, result2.HTLCs, 1, "expect 1 htlc for payment 2, got %d",
len(result2.HTLCs),
Expand Down
Loading

0 comments on commit e02fd39

Please sign in to comment.