mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-10-09 21:33:31 +02:00
Merge pull request #9703 from yyforyongyu/fix-attempt-hash
Patch htlc attempt hash for legacy payments
This commit is contained in:
@@ -111,6 +111,9 @@ keysend payment validation is stricter.
|
|||||||
process of the node won't be interrupted if a non-fatal error is returned from
|
process of the node won't be interrupted if a non-fatal error is returned from
|
||||||
the subsystems.
|
the subsystems.
|
||||||
|
|
||||||
|
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9703) a possible panic
|
||||||
|
when reloading legacy inflight payments which don't have the MPP feature.
|
||||||
|
|
||||||
# New Features
|
# New Features
|
||||||
|
|
||||||
* Add support for [archiving channel backup](https://github.com/lightningnetwork/lnd/pull/9232)
|
* Add support for [archiving channel backup](https://github.com/lightningnetwork/lnd/pull/9232)
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/lightningnetwork/lnd/routing/shards"
|
"github.com/lightningnetwork/lnd/routing/shards"
|
||||||
@@ -500,7 +501,8 @@ func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
|
|||||||
func (p *paymentLifecycle) collectResult(
|
func (p *paymentLifecycle) collectResult(
|
||||||
attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
|
attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
|
||||||
|
|
||||||
log.Tracef("Collecting result for attempt %v", spew.Sdump(attempt))
|
log.Tracef("Collecting result for attempt %v",
|
||||||
|
lnutils.SpewLogClosure(attempt))
|
||||||
|
|
||||||
result := &htlcswitch.PaymentResult{}
|
result := &htlcswitch.PaymentResult{}
|
||||||
|
|
||||||
@@ -1060,6 +1062,38 @@ func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// patchLegacyPaymentHash will make a copy of the passed attempt and sets its
|
||||||
|
// Hash field to be the payment hash if it's nil.
|
||||||
|
//
|
||||||
|
// NOTE: For legacy payments, which were created before the AMP feature was
|
||||||
|
// enabled, the `Hash` field in their HTLC attempts is nil. In that case, we use
|
||||||
|
// the payment hash as the `attempt.Hash` as they are identical.
|
||||||
|
func (p *paymentLifecycle) patchLegacyPaymentHash(
|
||||||
|
a channeldb.HTLCAttempt) channeldb.HTLCAttempt {
|
||||||
|
|
||||||
|
// Exit early if this is not a legacy attempt.
|
||||||
|
if a.Hash != nil {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log a warning if the user is still using legacy payments, which has
|
||||||
|
// weaker support.
|
||||||
|
log.Warnf("Found legacy htlc attempt %v in payment %v", a.AttemptID,
|
||||||
|
p.identifier)
|
||||||
|
|
||||||
|
// Set the attempt's hash to be the payment hash, which is the payment's
|
||||||
|
// `PaymentHash`` in the `PaymentCreationInfo`. For legacy payments
|
||||||
|
// before AMP feature, the `Hash` field was not set so we use the
|
||||||
|
// payment hash instead.
|
||||||
|
//
|
||||||
|
// NOTE: During the router's startup, we have a similar logic in
|
||||||
|
// `resumePayments`, in which we will use the payment hash instead if
|
||||||
|
// the attempt's hash is nil.
|
||||||
|
a.Hash = &p.identifier
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
// reloadInflightAttempts is called when the payment lifecycle is resumed after
|
// reloadInflightAttempts is called when the payment lifecycle is resumed after
|
||||||
// a restart. It reloads all inflight attempts from the control tower and
|
// a restart. It reloads all inflight attempts from the control tower and
|
||||||
// collects the results of the attempts that have been sent before.
|
// collects the results of the attempts that have been sent before.
|
||||||
@@ -1075,6 +1109,10 @@ func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
|
|||||||
log.Infof("Resuming HTLC attempt %v for payment %v",
|
log.Infof("Resuming HTLC attempt %v for payment %v",
|
||||||
a.AttemptID, p.identifier)
|
a.AttemptID, p.identifier)
|
||||||
|
|
||||||
|
// Potentially attach the payment hash to the `Hash` field if
|
||||||
|
// it's a legacy payment.
|
||||||
|
a = p.patchLegacyPaymentHash(a)
|
||||||
|
|
||||||
p.resultCollector(&a)
|
p.resultCollector(&a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1809,3 +1809,61 @@ func TestHandleAttemptResultSuccess(t *testing.T) {
|
|||||||
require.NoError(t, err, "expected no error")
|
require.NoError(t, err, "expected no error")
|
||||||
require.Equal(t, attempt, attemptResult.attempt)
|
require.Equal(t, attempt, attemptResult.attempt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestReloadInflightAttemptsLegacy checks that when handling a legacy HTLC
|
||||||
|
// attempt, `collectResult` behaves as expected.
|
||||||
|
func TestReloadInflightAttemptsLegacy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create a test paymentLifecycle with the initial two calls mocked.
|
||||||
|
p, m := newTestPaymentLifecycle(t)
|
||||||
|
|
||||||
|
// Mount the resultCollector to check the full call path.
|
||||||
|
p.resultCollector = p.collectResultAsync
|
||||||
|
|
||||||
|
// Create testing params.
|
||||||
|
paymentAmt := 10_000
|
||||||
|
preimage := lntypes.Preimage{1}
|
||||||
|
attempt := makeSettledAttempt(t, paymentAmt, preimage)
|
||||||
|
|
||||||
|
// Make the attempt.Hash to be nil to mock a legacy payment.
|
||||||
|
attempt.Hash = nil
|
||||||
|
|
||||||
|
// Create a mock result returned from the switch.
|
||||||
|
result := &htlcswitch.PaymentResult{
|
||||||
|
Preimage: preimage,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. calls `FetchPayment` and return the payment.
|
||||||
|
m.control.On("FetchPayment", p.identifier).Return(m.payment, nil).Once()
|
||||||
|
|
||||||
|
// 2. calls `InFlightHTLCs` and return the attempt.
|
||||||
|
attempts := []channeldb.HTLCAttempt{*attempt}
|
||||||
|
m.payment.On("InFlightHTLCs").Return(attempts).Once()
|
||||||
|
|
||||||
|
// 3. Mock the htlcswitch to return a the result chan.
|
||||||
|
resultChan := make(chan *htlcswitch.PaymentResult, 1)
|
||||||
|
m.payer.On("GetAttemptResult",
|
||||||
|
attempt.AttemptID, p.identifier, mock.Anything,
|
||||||
|
).Return(resultChan, nil).Once().Run(func(args mock.Arguments) {
|
||||||
|
// Send the preimage to the result chan.
|
||||||
|
resultChan <- result
|
||||||
|
})
|
||||||
|
|
||||||
|
// Now call the method under test.
|
||||||
|
payment, err := p.reloadInflightAttempts()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, m.payment, payment)
|
||||||
|
|
||||||
|
var r *switchResult
|
||||||
|
|
||||||
|
// Assert the result is returned within testTimeout.
|
||||||
|
waitErr := wait.NoError(func() error {
|
||||||
|
r = <-p.resultCollected
|
||||||
|
return nil
|
||||||
|
}, testTimeout)
|
||||||
|
require.NoError(t, waitErr, "timeout waiting for result")
|
||||||
|
|
||||||
|
// Assert the result is received as expected.
|
||||||
|
require.Equal(t, result, r.result)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user