payments/migration1: handle legacy payments with nil HTLC hash

For legacy payments, the HTLC Hash field may be nil in the bbolt
backend. Previously, the migration would fail with "HTLC attempt X
missing payment hash" when encountering such payments.

This commit fixes the migration by falling back to the parent payment
hash when the HTLC-specific hash is nil. This is consistent with how
the router handles legacy payments (see patchLegacyPaymentHash in
payment_lifecycle.go).

The validation logic is also updated to apply the same fallback when
comparing bbolt data with migrated SQL data, ensuring the comparison
succeeds.
This commit is contained in:
ziggie committed 2026-02-25 18:52:33 +01:00
1 parent f174b60b94
commit 89685de16e
2 files changed
+21 -7

No files matched your search

@@ -299,6 +299,17 @@ func normalizePaymentForCompare(payment *MPPayment) {
htlc.circuit = nil
htlc.cachedSessionKey = nil
// For legacy payments, the HTLC Hash field may be nil in the
// bbolt backend. During migration, the SQL code uses the
// parent payment hash as fallback. To ensure the comparison
// between bbolt and SQL data succeeds, we apply the same
// fallback here.
//
// See also: patchLegacyPaymentHash in payment_lifecycle.go.
if htlc.Hash == nil && payment.Info != nil {
htlc.Hash = &payment.Info.PaymentIdentifier
}
if len(htlc.Route.FirstHopWireCustomRecords) == 0 {
htlc.Route.FirstHopWireCustomRecords =
lnwire.CustomRecords{}
+10 -7
View File
@@ -364,20 +364,23 @@ func migrateHTLCAttempt(ctx context.Context, paymentID int64,
parentPaymentHash lntypes.Hash, htlc *HTLCAttempt, sqlDB SQLQueries,
stats *MigrationStats) error {
// Validate that we have a payment hash for the attempt.
// Determine the payment hash for this HTLC attempt.
//
// NOTE: We always require an attempt payment hash. A missing hash is an
// unrecoverable inconsistency. All payments should have a payment hash
// (AMP,MPP,Legacy)
// For AMP payments, each HTLC has its own unique hash. For non-AMP
// payments (MPP, Legacy), all HTLCs use the same hash as the parent
// payment. Older payment attempts may not have the hash stored
// explicitly, in which case we fall back to the parent payment hash
// which is ok since non-AMP payments have a single hash for all HTLCs.
var paymentHash []byte
switch {
case htlc.Hash != nil:
paymentHash = (*htlc.Hash)[:]
default:
return fmt.Errorf("HTLC attempt %d missing payment hash "+
"(parent payment hash=%x)", htlc.AttemptID,
parentPaymentHash[:])
// For older payments where Hash is nil, use the parent payment
// hash. This is consistent with how the router handles these
// legacy payments.
paymentHash = parentPaymentHash[:]
}
firstHopAmountMsat := int64(htlc.Route.FirstHopAmount.Val.Int())