Skip to content

Commit c11a292

Browse files
committed
payments/db: remap legacy zero attempt IDs
Legacy KV payments can contain HTLC attempts with attempt ID zero. The SQL payments schema requires payment_htlc_attempts.attempt_index to be globally unique, so migrating multiple such attempts can fail with a UNIQUE constraint violation. Allocate synthetic attempt indexes for legacy zero-ID attempts from the switch payment ID sequencer horizon. Keep nonzero attempt IDs unchanged and advance the switch sequence once after migration validation succeeds. This preserves the SQL uniqueness invariant and prevents future switch IDs from colliding with migrated attempts. It also wraps HTLC insert errors with the attempted index and payment hash so future migration failures identify the problematic row.
1 parent 50eb0d1 commit c11a292

3 files changed

Lines changed: 587 additions & 45 deletions

File tree

payments/db/migration1/migration_validation.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ func validateMigratedPaymentBatch(ctx context.Context,
7979
hash[:8], err)
8080
}
8181

82+
if kvPayment.Status == StatusInFlight {
83+
// Mirror the migration's legacy terminalization
84+
// before comparing KV with SQL.
85+
//
86+
//nolint:ll
87+
_, err = terminalizeUnresolvedLegacyZeroAttempts(
88+
kvPayment,
89+
)
90+
if err != nil {
91+
return fmt.Errorf("normalize KV "+
92+
"payment %x: %w", hash[:8], err)
93+
}
94+
}
95+
8296
err = structuralCompare(kvPayment, row)
8397
if err != nil {
8498
// On structural mismatch, perform a deep
@@ -191,6 +205,16 @@ func deepComparePayment(ctx context.Context, cfg *SQLStoreConfig,
191205
paymentHash[:8], err)
192206
}
193207

208+
if kvPayment.Status == StatusInFlight {
209+
// Mirror the migration's legacy terminalization before
210+
// comparing KV with SQL.
211+
_, err = terminalizeUnresolvedLegacyZeroAttempts(kvPayment)
212+
if err != nil {
213+
return fmt.Errorf("normalize KV payment %x: %w",
214+
paymentHash[:8], err)
215+
}
216+
}
217+
normalizeLegacyZeroAttemptIDsForCompare(kvPayment, sqlPayment)
194218
normalizePaymentForCompare(kvPayment)
195219
normalizePaymentForCompare(sqlPayment)
196220

@@ -221,6 +245,60 @@ func deepComparePayment(ctx context.Context, cfg *SQLStoreConfig,
221245
return nil
222246
}
223247

248+
// normalizeLegacyZeroAttemptIDsForCompare aligns expected legacy attempt ID
249+
// remaps before deep comparison.
250+
//
251+
// Legacy KV payments can use attempt ID zero to represent an unknown ID. During
252+
// migration those attempts are assigned synthetic SQL attempt indexes, while
253+
// the source KV payment is left unchanged. Match by session key and copy the
254+
// SQL attempt ID into the KV object so fallback deep comparison still reports
255+
// real data mismatches instead of this expected migration repair.
256+
func normalizeLegacyZeroAttemptIDsForCompare(kvPayment,
257+
sqlPayment *MPPayment) {
258+
259+
if kvPayment == nil || sqlPayment == nil {
260+
return
261+
}
262+
263+
sqlAttemptsBySessionKey := make(map[string]uint64)
264+
for i := range sqlPayment.HTLCs {
265+
htlc := &sqlPayment.HTLCs[i]
266+
sessionKey := htlc.SessionKey()
267+
if sessionKey == nil {
268+
// Leave normalization incomplete so the deep comparison
269+
// reports the malformed attempt instead of panicking.
270+
continue
271+
}
272+
273+
sessionKeyBytes := sessionKey.Serialize()
274+
sessionKeyStr := string(sessionKeyBytes)
275+
sqlAttemptsBySessionKey[sessionKeyStr] = htlc.AttemptID
276+
}
277+
278+
for i := range kvPayment.HTLCs {
279+
htlc := &kvPayment.HTLCs[i]
280+
if htlc.AttemptID != 0 {
281+
continue
282+
}
283+
284+
sessionKey := htlc.SessionKey()
285+
if sessionKey == nil {
286+
// Leave normalization incomplete so the deep comparison
287+
// reports the malformed attempt instead of panicking.
288+
continue
289+
}
290+
291+
sessionKeyBytes := sessionKey.Serialize()
292+
sessionKeyStr := string(sessionKeyBytes)
293+
attemptID, ok := sqlAttemptsBySessionKey[sessionKeyStr]
294+
if !ok {
295+
continue
296+
}
297+
298+
htlc.AttemptID = attemptID
299+
}
300+
}
301+
224302
// normalizePaymentForCompare normalizes fields that are expected to differ
225303
// between KV and SQL representations before deep comparison.
226304
func normalizePaymentForCompare(payment *MPPayment) {

0 commit comments

Comments
 (0)