paymentsdb: normalize orphaned blinded total#10982
Conversation
🟠 PR Severity: HIGH
🟠 High (1 file)
🟡 Medium (2 files)
🟢 Low (1 file, plus 2 excluded test files)
AnalysisThe highest-severity file touched is The No severity bump applies: only 4 non-test/non-doc files changed (well under the 20-file threshold) and ~49 lines changed excluding tests (well under the 500-line threshold), and no multiple distinct critical packages are touched. Given the RPC-facing change to router backend logic alongside a payments DB migration adjustment, a knowledgeable engineer should review both the RPC behavior change and the migration validation/backfill logic for correctness. To override, add a |
dc8939a to
1276e61
Compare
1276e61 to
4ef261b
Compare
35e5c9b to
37e3fe9
Compare
|
/gateway review |
There was a problem hiding this comment.
Gateway review — 5 findings
🔴 0 Blocker · 🟠 1 Major · 🟡 4 Minor · 🔵 0 Nit
Summary
This PR fixes a real, well-understood node-startup failure: historical routes where SendToRouteV2 copied a blinded total_amt_msat without encrypted recipient data caused the KV→SQL payment migration to attempt a NULL encrypted_data insert against a NOT NULL column, aborting the migration and preventing LND from starting. The RPC-side hardening, the orphaned-total normalization, and the matching compare-time normalization are the right shape, and the added tests cover the primary paths. The paymentHash → htlcHash rewrite in migrateHTLCAttempt is a behavior-preserving refactor.
The central concern is that the fix treats the two malformed-hop cases asymmetrically. An orphaned total is normalized so the node starts, but a hop with a blinding point and no encrypted data now returns a hard error that aborts the whole migration transaction — reintroducing the exact startup-blocking failure class this PR exists to eliminate, for any node whose historical data contains that combination.
The remaining findings are lower-severity: a breaking change on a deprecated RPC field, an undocumented cross-function normalization invariant, a misleading diagnostic label, and a test-coverage gap.
Bot commands
/gateway re-review— re-run after pushing changes/gateway dismiss <id>— silence a finding (maintainers)/gateway explain <id>— elaborate on a finding (anyone)
|
🤖 gateway audit metadata for this PR — auto-generated, please don't edit. |
37e3fe9 to
9a90f07
Compare
|
/gateway re-review |
SendToRouteV2 accepts caller-provided routes. It already required recipient-encrypted data when a blinding point was present. However, it copied a blinded total amount independently. This allowed a total-only hop to enter the payment database even though LND did not classify it as blinded. Require encrypted data when either blinded field is supplied. Cover the rejected combination, a valid blinded total and a regular hop.
9a90f07 to
7336b98
Compare
There was a problem hiding this comment.
Gateway review — 3 findings
🔴 0 Blocker · 🟠 1 Major · 🟡 2 Minor · 🔵 0 Nit
Summary
The author addressed most of the prior review. The SendToRouteV2 release-note callout (F2), the attempt_index relabeling (F5), and the reverted hash-selection refactor (F4) are all resolved; the BOLT-4 rationale added to the RPC guards checks out under spec review. F3's requested documentation was added on the compare side but only in one direction, leaving the coupling half-documented.
The central concern (F1) remains unresolved. The blinding-point-only migration branch still aborts the entire at-startup, single-transaction migration rather than normalizing like its sibling orphaned-total case. The author added the justifying comment F1 asked for, but two independent reviews this round judge the underlying "this shape cannot exist historically" claim to be an unprovable assertion, and the code still bricks node startup with no recovery path if it is wrong — mitigated only by the fact that the migration itself has not yet shipped in a release.
One genuinely-new minor finding: the orphanedTotalAmt variable implies a control-flow role it does not have.
Status of prior findings
- F2 addressed: Fixed in docs/release-notes/release-notes-0.21.2.md — the note now states the fix "also affects callers replaying an affected historical route returned by
ListPaymentsorTrackPayment," and the BOLT-4 justification for the new guards is spec-correct. - F4 addressed: Fixed in payments/db/migration1/sql_migration.go — the
htlcHashrewrite was reverted;migrateHTLCAttemptkeeps the originalpaymentHashselection and threadsparentPaymentHashonly for diagnostics, so the untested-refactor concern no longer applies. - F5 addressed: Fixed in payments/db/migration1/sql_migration.go — the parameter is now
attemptIndexand both new diagnostics label the valueattempt_index=%d, no longer implying it is the original on-disk attempt ID.
Bot commands
/gateway re-review— re-run after pushing changes/gateway dismiss <id>— silence a finding (maintainers)/gateway explain <id>— elaborate on a finding (anyone)
|
/gateway dismiss F1, as mentioned we want to abort this case because this cannot happen and signals some deeper issue with the underlying dataset |
|
🚫 Dismissed F1 (major) by @ziggie1984 — as mentioned we want to abort this case because this cannot happen and signals some deeper issue with the underlying dataset Open findings on this PR: 🟡 F3 (minor) · 🟡 F6 (minor) |
The KV route format stores blinded fields independently. Routes accepted through SendToRouteV2 could therefore contain a blinded total amount without encrypted recipient data. The SQL migration treated the total as proof of a blinded hop and bound nil to the required encrypted-data column, preventing LND from starting. Use encrypted recipient data as the blinded-hop discriminator and normalize only the known total-only case. Reject blinding-point-only records with payment, attempt and hop context instead of exposing an opaque SQL constraint error. Log normalized totals, account for them during migration validation, and cover both cases with regression tests.
Document the handling of historical total-only blinded route data and the new SendToRouteV2 validation that prevents creating such records.
7336b98 to
17ee638
Compare
|
/gateway approve |
|
❌ Cannot approve: the PR has new commits since the last review (last reviewed Run |
|
/gateway re-review |
There was a problem hiding this comment.
Gateway review
No findings.
Status of prior findings
- F2 addressed: Fixed in docs/release-notes/release-notes-0.21.2.md — the bug-fix bullet now states "This also affects callers replaying an affected historical route returned by
ListPaymentsorTrackPayment." - F3 addressed: Fixed in payments/db/migration1/migration_validation.go:407 and payments/db/migration1/sql_migration.go:840 — both sites now document the invariant and cross-reference each other ("A blinding point without data is rejected during migration, so it cannot reach this comparison." / "Keep this rejection in sync with normalizePaymentForCompare, which only normalizes hops without a blinding point."), making the previously-implicit coupling explicit.
- F4 addressed: Fixed in payments/db/migration1/sql_migration.go:745 — the paymentHash/htlcHash rewrite was reverted to the pre-existing behavior-preserving hash-selection switch;
parentPaymentHashis now threaded only for the new diagnostics, so the untested-AMP-branch concern no longer applies. - F5 addressed: Fixed in payments/db/migration1/sql_migration.go:843 — the parameter is renamed
attemptIndexand both the error and warning now label the valueattempt_index=%d, no longer implying it is the original on-disk attempt ID. - F6 addressed: Fixed in payments/db/migration1/sql_migration.go:848 — the
orphanedTotalAmtvariable was removed; the warning is now gated inline on!hasEncryptedData && hop.TotalAmtMsat != 0, and the row omission is driven by theif hasEncryptedDatainsert guard with a comment explaining the length-based check, so no variable implies a control-flow role it lacks.
Bot commands
/gateway re-review— re-run after pushing changes/gateway dismiss <id>— silence a finding (maintainers)/gateway explain <id>— elaborate on a finding (anyone)
|
/gateway approve |
There was a problem hiding this comment.
✅ Approved — all prior findings addressed.
Findings recap
Findings:
F1(major) — unresolved: A single historical route hop withhop.BlindingPoint != nilandlen(hop.EncryptedData) == 0now causesmigrateRouteHopto return `fmt.…F2(minor) — addressed: The newTotalAmtMsat != 0 && len(EncryptedData) == 0rejection inUnmarshallHopWithPubkeychanges the behavior of the public `SendToRout…F3(minor) — addressed: The migration round-trip holds only because of an undocumented coupling between two textually different predicates. `normalizePaymentForComp…F4(minor) — addressed: ThemigrateHTLCAttemptrewrite toPaymentHash: htlcHash[:]with the newswitch(htlc.Hash != nil→*htlc.Hash, else `parentPayment…F5(minor) — addressed: The new error and warning strings label the value asattempt=%d, butmigrateRouteHopreceivesattemptIndex(namedattemptIDin the s…F6(minor) — addressed:orphanedTotalAmtand its comment ("Omit such an orphaned total rather than creating a blinded-hop row") read as if the variable controls w…
Dismissed:
F1by @ziggie1984 — as mentioned we want to abort this case because this cannot happen and signals some deeper issue with the underlying dataset
Approved by @ziggie1984 via /gateway approve. Last reviewed at 17ee638. Skill v0.3.0, model claude-opus-4-8.
Change Description
Fix a KV-to-SQL payment migration failure for historical routes containing a
non-zero blinded
total_amt_msatwithout encrypted recipient data.SendToRouteV2historically copied the blinded total amount independently ofthe encrypted data. LND did not classify such a hop as blinded at runtime, but
the migration treated the total amount as proof of a blinded hop and attempted
to insert a row with a NULL
encrypted_datavalue. This violated the existingSQL NOT NULL constraint and prevented LND from starting.
This PR:
SendToRouteV2supplies either ablinding point or blinded total amount;
representation;
attempt and hop context;
PYDBwarning identifying the affected payment, attempt, hop anddiscarded total amount;
Steps to Test
Pull Request Checklist
Testing
Code Style and Documentation
PYDBsubsystem at warning level.