Skip to content

invoices: migrate legacy AMP invoice HTLCs - #11106

Merged
ziggie1984 merged 3 commits into
lightningnetwork:masterfrom
ziggie1984:invoice-sql-mig
Aug 20, 2026
Merged

invoices: migrate legacy AMP invoice HTLCs#11106
ziggie1984 merged 3 commits into
lightningnetwork:masterfrom
ziggie1984:invoice-sql-mig

Conversation

@ziggie1984

@ziggie1984 ziggie1984 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Change Description

Legacy AMP invoices created before reusable AMP invoices store their HTLCs
inline and don't contain AMPState metadata.

The KV-to-SQL migration inserted the generic HTLC rows for these invoices, but
without AMPState it did not create AMP sub-invoices or associate the HTLCs
with them. SQL read-back therefore returned an empty HTLC map, and strict
migration verification aborted startup.

Reconstruct the modern AMP sub-invoice state from each inline HTLC's set ID
before insertion. This preserves the HTLC associations, AMP metadata, custom
records, amounts, and settlement metadata while keeping the compatibility
handling isolated to the migration.

The migration fails closed if the legacy data is structurally inconsistent.
Release notes are included for v0.21.3 and v0.20.4.

Steps to Test

make lint
go vet ./invoices
go test ./invoices -run '^TestMigrateLegacyAMPInvoice$' -count=20

The regression test migrates a settled legacy AMP invoice with a canceled
competing set, AMP metadata, a large child index, and custom records, then
verifies complete SQL read-back equality.

@ziggie1984
ziggie1984 force-pushed the invoice-sql-mig branch 2 times, most recently from 838b4f3 to 86510e7 Compare August 19, 2026 20:21
@github-actions github-actions Bot added the severity-high Requires knowledgeable engineer review label Aug 19, 2026
@github-actions

Copy link
Copy Markdown

🟠 PR Severity: HIGH

gh pr view | 4 files | 253 lines changed

🟠 High (1 file)
  • invoices/sql_migration.go - invoice SQL migration logic (invoices/*)
🟢 Low (3 files)
  • invoices/sql_migration_test.go - test-only change
  • docs/release-notes/release-notes-0.20.4.md - release notes
  • docs/release-notes/release-notes-0.21.3.md - release notes

Analysis

The substantive change is in invoices/sql_migration.go, which falls under invoices/* (invoice management/settlement), classified as HIGH severity. It is not a channeldb/migration*, sqldb/*, or wtdb/* path, so the automatic CRITICAL database-migration rule does not apply. No bump conditions are met: only one non-test file changed with ~110 non-test lines modified, well under the 20-file/500-line thresholds, and only a single package is touched. The remaining files are a test file and release notes, both LOW.


To override, add a severity-override-{critical,high,medium,low} label.

@ziggie1984

Copy link
Copy Markdown
Collaborator Author

Legacy AMP invoice migration: before and after

Legacy AMP invoices store their HTLCs inline. Each HTLC contains its AMP set
ID and metadata, but the invoice predates the AMPState structure used by
reusable AMP invoices.

flowchart TB
    legacy["Legacy KV AMP invoice<br/><br/>IsAMP = true<br/>AMPState = empty"]
    htlcs["Inline HTLCs<br/><br/>Set ID<br/>AMP metadata<br/>Custom records"]

    legacy --> htlcs
    htlcs --> oldMigration
    htlcs --> normalize

    subgraph before["Before the fix"]
        direction TB

        oldMigration["Original SQL migration"]
        genericRows["Insert invoice and generic HTLC rows<br/>inside the migration transaction"]
        emptyState["Iterate AMPState<br/>0 entries"]
        missingSubInvoice["No AMP sub-invoice"]
        missingAssociation["No AMP-to-HTLC association"]
        oldReader["SQL AMP reader follows<br/>AMP associations"]
        emptyResult["Read-back result<br/><br/>Htlcs = empty<br/>AMPState = empty"]
        mismatch["Strict comparison fails"]
        rollback["Entire migration transaction rolls back"]

        oldMigration --> genericRows
        oldMigration --> emptyState
        emptyState --> missingSubInvoice
        missingSubInvoice --> missingAssociation
        genericRows --> oldReader
        missingAssociation --> oldReader
        oldReader --> emptyResult
        emptyResult --> mismatch
        mismatch --> rollback
    end

    subgraph after["With the fix"]
        direction TB

        normalize["Normalize the legacy representation<br/>in memory"]
        group["Group existing HTLCs by<br/>their embedded AMP set ID"]
        ampState["Reconstruct AMPState<br/><br/>InvoiceKeys<br/>State and AmtPaid<br/>Settle index and date"]
        existingWriter["Existing SQL migration writer"]
        invoiceHtlcs["invoice_htlcs<br/><br/>Original HTLC data<br/>and custom records"]
        subInvoices["amp_sub_invoices<br/><br/>One row per AMP set"]
        associations["amp_sub_invoice_htlcs<br/><br/>AMP-to-HTLC associations<br/>and AMP metadata"]
        newReader["SQL AMP reader"]
        restored["Read-back result<br/><br/>Htlcs restored<br/>AMPState reconstructed"]
        match["Strict comparison passes"]
        commit["Migration transaction commits"]

        normalize --> group
        group --> ampState
        ampState --> existingWriter
        existingWriter --> invoiceHtlcs
        existingWriter --> subInvoices
        existingWriter --> associations
        invoiceHtlcs --> newReader
        subInvoices --> newReader
        associations --> newReader
        newReader --> restored
        restored --> match
        match --> commit
    end
Loading

The HTLC data itself was already available in the legacy record. The missing
piece was the SQL relationship connecting each HTLC to its AMP sub-invoice.

The fix reconstructs only that derivable AMPState metadata in memory and then
uses the existing SQL writer. It does not modify the source KV database or
reinterpret the payment. Structurally inconsistent legacy records still fail
the migration, and the transaction is rolled back safely.

@ziggie1984
ziggie1984 marked this pull request as ready for review August 20, 2026 01:12
@ziggie1984
ziggie1984 requested a review from bhandras August 20, 2026 01:12
@ziggie1984
ziggie1984 force-pushed the invoice-sql-mig branch 2 times, most recently from 1752cbe to 15f1063 Compare August 20, 2026 01:38
@ziggie1984 ziggie1984 self-assigned this Aug 20, 2026
@ziggie1984 ziggie1984 added this to v0.21 Aug 20, 2026
@ziggie1984 ziggie1984 added this to the v0.21.3 milestone Aug 20, 2026
@ziggie1984 ziggie1984 moved this to In review in v0.21 Aug 20, 2026
@ziggie1984

Copy link
Copy Markdown
Collaborator Author

Legacy AMP settlement representation in SQL

The migrated legacy invoice intentionally differs from a reusable AMP invoice:

Representation Parent invoices row amp_sub_invoices row
Legacy AMP is_amp=true, state=ContractSettled, with the original settle index/date Reconstructed set state, amount, and HTLC associations; no separate settle index/date
Reusable AMP Remains ContractOpen, without parent-level settlement metadata Every settled set has its own settle index/date

Before reusable AMP invoices existed, settling one AMP set made the parent
invoice terminal. The KV settle index therefore recorded a single event against
the parent invoice, not against a set ID. The parent combination
is_amp=true + ContractSettled + parent settle metadata is consequently the
indicator that this is the migrated legacy representation.

Migration reconstructs the missing AMP sub-invoice rows because SQL needs them
to associate each HTLC with its set ID. It marks the successful set as settled
and preserves canceled/competing sets, but it does not manufacture a second
settlement event on the successful sub-invoice. This keeps the invoice
non-reusable and ensures InvoicesSettledSince emits the historical payment
exactly once.

@bhandras bhandras left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

@bitromortac
bitromortac self-requested a review August 20, 2026 14:20
AMP invoices created before reusable invoices store their HTLCs inline
and have no AMPState metadata. Reconstruct the sub-invoice state from
each HTLC set ID before inserting it into SQL. This preserves the AMP
associations during read-back verification.

Add regression coverage for settled and canceled legacy sets, AMP
metadata, and custom records.
@ziggie1984
ziggie1984 requested review from ellemouton and removed request for bitromortac August 20, 2026 17:05
@ziggie1984
ziggie1984 enabled auto-merge August 20, 2026 17:05

@ellemouton ellemouton left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏

@ziggie1984
ziggie1984 merged commit 90ea05d into lightningnetwork:master Aug 20, 2026
74 of 77 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in v0.21 Aug 20, 2026
@ziggie1984 ziggie1984 added backport-v0.21.x-branch This label triggers a backport to branch `v0.21.x-branch ` backport-v0.20.x-branch This label is used to trigger the creation of a backport PR to the branch `v0.20.x-branch`. labels Aug 20, 2026
@ziggie1984
ziggie1984 deleted the invoice-sql-mig branch August 20, 2026 19:57
@github-actions

Copy link
Copy Markdown

Created backport PR for v0.20.x-branch:

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-11106-to-v0.20.x-branch
git worktree add --checkout .worktree/backport-11106-to-v0.20.x-branch backport-11106-to-v0.20.x-branch
cd .worktree/backport-11106-to-v0.20.x-branch
git reset --hard HEAD^
git cherry-pick -x cb9c86195251422dc217c35b915e54a55d8f8580 b7a2f153962c34b3920533da0ae70f05e01d7a4f
git push --force-with-lease

@github-actions

Copy link
Copy Markdown

Created backport PR for v0.21.x-branch:

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-11106-to-v0.21.x-branch
git worktree add --checkout .worktree/backport-11106-to-v0.21.x-branch backport-11106-to-v0.21.x-branch
cd .worktree/backport-11106-to-v0.21.x-branch
git reset --hard HEAD^
git cherry-pick -x b7a2f153962c34b3920533da0ae70f05e01d7a4f
git push --force-with-lease

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-v0.20.x-branch This label is used to trigger the creation of a backport PR to the branch `v0.20.x-branch`. backport-v0.21.x-branch This label triggers a backport to branch `v0.21.x-branch ` migration severity-high Requires knowledgeable engineer review sql

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[bug]: kv_invoice_migration fails on legacy AMP invoices with empty AMPState ("migrated record does not match original record")

3 participants