Skip to content

payments: SQL backend implementation series#10604

Merged
yyforyongyu merged 78 commits intomasterfrom
elle-payment-sql-series-new
Feb 26, 2026
Merged

payments: SQL backend implementation series#10604
yyforyongyu merged 78 commits intomasterfrom
elle-payment-sql-series-new

Conversation

@ziggie1984
Copy link
Collaborator

Summary

This PR consolidates the payment SQL backend project, which was developed as a series of smaller, independently reviewed PRs that have now all been merged into this branch.

The work introduces a full SQL-backed implementation of the payments database, allowing lnd to store payment state in a relational database (SQLite/Postgres) in addition to the existing bbolt KV store.

What's included

  • SQL schema: New payment tables with indexes and a migration harness
  • Core CRUD operations: FetchPayment, QueryPayments, InitPayment, RegisterAttempt, SettleAttempt, FailAttempt, Fail, DeletePayment, DeletePayments, DeleteFailedAttempts, FetchInFlightPayments
  • Context threading: All payment lifecycle functions now accept and propagate context.Context
  • DB-agnostic tests: Payment tests refactored to run against both the KV and SQL backends via a shared harness interface
  • Additional test coverage: Unit tests added for FetchInflightPayments, first hop data, computePaymentStatusFromResolutions, and more
  • Release notes: Included across the sub-PRs

This series was reviewed and merged incrementally; this PR brings the full implementation onto master.

@ziggie1984 ziggie1984 self-assigned this Feb 24, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @ziggie1984, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request consolidates and integrates a complete SQL backend for LND's payments database. This significant architectural change enables the use of relational databases like SQLite and Postgres for storing payment states, moving beyond the previous bbolt key-value store. The work encompasses a new SQL schema, a full suite of CRUD operations, context threading throughout payment lifecycle functions, and a refactored, database-agnostic testing framework. This PR represents the culmination of several incrementally reviewed and merged sub-PRs, bringing a more robust and flexible payment storage solution to the main branch.

Highlights

  • SQL Backend Integration: A full SQL-backed implementation for the payments database has been integrated, allowing LND to store payment states in relational databases like SQLite and Postgres, in addition to the existing bbolt KV store.
  • Comprehensive CRUD Operations: Core CRUD operations for payments, including fetching, querying, initializing, registering, settling, failing attempts, and deleting payments, are now supported by the SQL backend.
  • Context Threading: All payment lifecycle functions have been updated to accept and propagate context.Context, improving cancellation and timeout handling across the payment flow.
  • DB-Agnostic Testing Framework: Payment tests have been refactored to run against both KV and SQL backends using a shared harness interface, ensuring consistent behavior and easier future development.
  • Enhanced Test Coverage: Additional unit tests have been added for FetchInflightPayments, first hop data, computePaymentStatusFromResolutions, and other payment-related functionalities.
  • New Database Schema: A new SQL schema has been introduced, including tables for payments, intents, HTLC attempts, resolutions, route hops, and custom TLV records, along with appropriate indexes and a migration harness.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • config_builder.go
    • Modified database builder to conditionally create SQL or KV payments DB based on build tags.
  • config_prod.go
    • Added paymentsdb and sqldb imports.
    • Introduced getPaymentsStore function for the KV backend.
  • config_test_native_sql.go
    • Added SQL-specific imports (database/sql, lncfg, paymentsdb, sqldb).
    • Implemented getPaymentsStore function for the SQL backend.
  • docs/release-notes/release-notes-0.21.0.md
    • Added a new section detailing the payment store SQL implementation and migration project, listing several sub-PRs.
    • Added Gijs van Dam and Ziggie to the list of contributors.
  • itest/lnd_payment_test.go
    • Modified testListPayments to use more robust timestamp comparison logic for start and end dates, accounting for different database precisions.
  • lnrpc/routerrpc/router_backend.go
    • Added a comment explaining the limitation of only showing the last HTLC preimage for AMP payments in MarshallPayment.
  • lnrpc/routerrpc/router_server.go
    • Modified SendToRouteSkipTempErr and SendToRoute to pass context.Context.
  • payments/db/errors.go
    • Added ErrBlindedPaymentMissingTotalAmount for blinded payments missing total amount.
    • Added errMaxPaymentsReached for internal pagination limits.
  • payments/db/interface.go
    • Updated PaymentReader and PaymentWriter interface methods to accept context.Context.
    • Added an important note to RegisterAttempt regarding serialization of calls for the same payment hash.
  • payments/db/kv_store.go
    • Removed the keepFailedPaymentAttempts field and related logic.
    • Updated all interface methods to accept context.Context.
    • Modified DeleteFailedAttempts to always delete failed HTLCs, as the configuration option was removed.
  • payments/db/kv_store_test.go
    • Added build tags to restrict tests to KV backend.
    • Refactored TestKVStoreDeleteNonInFlight to TestKVStoreDeleteDuplicatePayments, focusing on KV-specific duplicate payment handling.
    • Updated test helpers (genInfo, createTestPayments, assertPaymentInfo, assertDBPaymentstatus) to use context.Context and the new TestHarness interface.
    • Removed assertPaymentIndex and assertNoIndex from this file, moving their logic to kvTestHarness.
    • Renamed TestQueryPayments to TestKVStoreQueryPaymentsDuplicates and adjusted its focus to KV-specific duplicate handling.
  • payments/db/options.go
    • Removed KeepFailedPaymentAttempts field from StoreOptions.
    • Removed WithKeepFailedPaymentAttempts option modifier.
  • payments/db/payment.go
    • Added validation in verifyAttempt to ensure that for blinded payments, the final hop sets the total amount.
  • payments/db/payment_test.go
    • Updated test helpers (genPreimage, genSessionKey, genPaymentCreationInfo, genPreimageAndHash, genAttemptWithHash, genInfo) to be more robust and context-aware.
    • Modified createTestPayments, assertPaymentInfo, assertDBPaymentstatus to accept context.Context.
    • Removed testDeleteFailedAttempts and integrated its logic directly into TestDeleteFailedAttempts, removing the keepFailedPaymentAttempts flag.
    • Added new tests: TestQueryPayments, TestFetchInFlightPayments, TestFetchInFlightPaymentsMultipleAttempts, TestRouteFirstHopData, TestRegisterAttemptWithAMP, TestRegisterAttemptWithBlindedRoute, TestFailAttemptWithoutMessage, TestFailAttemptWithMessage, TestFailAttemptOnSucceededPayment, TestFetchPaymentWithNoAttempts.
  • payments/db/sql_converters.go
    • Added sql_converters.go to provide functions for converting SQL database data to paymentsdb domain structs, including dbPaymentToCreationInfo, dbAttemptToHTLCAttempt, and dbDataToRoute.
  • payments/db/sql_store.go
    • Added sql_store.go which implements the paymentsdb.DB interface using a SQL backend.
    • Implemented QueryPayments with support for filtering, pagination, and efficient batch loading.
    • Implemented FetchPayment to retrieve complete payment records.
    • Implemented FetchInFlightPayments to retrieve all payments with active HTLC attempts.
    • Implemented DeleteFailedAttempts to remove only failed HTLCs for a payment.
    • Implemented DeletePayment to remove entire payment records.
    • Implemented InitPayment to create new payment records, handling re-initialization of failed payments.
    • Implemented RegisterAttempt to atomically record new HTLC attempts, including route hops and custom records.
    • Implemented SettleAttempt to mark HTLC attempts as settled.
    • Implemented FailAttempt to mark HTLC attempts as failed.
    • Implemented Fail to record the ultimate reason for payment failure.
    • Implemented DeletePayments for batch deletion of payments or failed attempts.
  • payments/db/sql_store_test.go
    • Added sql_store_test.go containing SQL-specific unit tests.
    • Added TestComputePaymentStatus to test SQL to domain type conversion logic.
  • payments/db/test_harness.go
    • Added test_harness.go which defines the TestHarness interface for database-agnostic test utilities.
  • payments/db/test_kvdb.go
    • Added build tags to restrict tests to the KV backend.
    • Updated NewTestDB to return both the DB interface and a TestHarness implementation.
    • Implemented kvTestHarness with AssertPaymentIndex and AssertNoIndex for KV store-specific assertions.
  • payments/db/test_postgres.go
    • Added test_postgres.go which contains PostgreSQL-specific test setup functions.
    • Implemented noopTestHarness for SQL backends, as SQL does not use a separate payment index bucket like KV.
  • payments/db/test_sqlite.go
    • Added test_sqlite.go which contains SQLite-specific test setup functions.
    • Implemented noopTestHarness for SQL backends, as SQL does not use a separate payment index bucket like KV.
  • routing/control_tower.go
    • Updated ControlTower interface methods (InitPayment, DeleteFailedAttempts, RegisterAttempt, SettleAttempt, FailAttempt, FetchPayment, FailPayment, FetchInFlightPayments) to accept context.Context.
    • Implemented these context changes in the controlTower struct methods.
  • routing/control_tower_test.go
    • Updated test functions to pass context.Context to ControlTower methods.
    • Removed the keepFailedPaymentAttempts parameter from testKVStoreSubscribeFail.
    • Added TestKeepFailedPaymentAttempts to verify that DeleteFailedAttempts is conditionally called based on the router's KeepFailedPaymentAttempts configuration.
  • routing/mock_test.go
    • Updated mock ControlTower methods to accept context.Context.
  • routing/payment_lifecycle.go
    • Updated paymentLifecycle methods (decideNextStep, resumePayment, requestRoute, registerAttempt, sendAttempt, failPaymentAndAttempt, handleSwitchErr, failAttempt, reloadInflightAttempts, reloadPayment, handleAttemptResult, collectAndHandleResult) to accept context.Context.
    • Introduced cleanupCtx using context.WithoutCancel for database operations that need to proceed even if the main context is cancelled.
    • Added logic to conditionally call DeleteFailedAttempts based on the KeepFailedPaymentAttempts router configuration.
  • routing/payment_lifecycle_test.go
    • Updated test functions to pass context.Context to paymentLifecycle methods.
    • Added TestKeepFailedPaymentAttempts to verify the conditional deletion of failed attempts based on router configuration.
  • routing/route/route.go
    • Added a TODO comment to the LegacyPayload field, indicating it should be removed once the KV backend for payments is phased out.
  • routing/router.go
    • Added KeepFailedPaymentAttempts field to the Config struct.
    • Updated SendPayment, SendToRoute, SendToRouteSkipTempErr, PreparePayment, sendToRoute, resumePayments, and failStaleAttempt methods to accept context.Context.
  • routing/router_test.go
    • Updated test functions to pass context.Context to router methods like SendPayment and SendToRoute.
  • rpcserver.go
    • Updated paymentStream struct to include a getCtx function for retrieving the context.Context.
    • Modified SendPayment, SendToRoute, dispatchPaymentIntent, and sendPaymentSync to accept and utilize context.Context.
    • Updated DeletePayment and DeleteAllPayments to pass context.Context to the underlying paymentsDB methods.
  • server.go
    • Passed the KeepFailedPaymentAttempts configuration from the main Config to the routing.New function when initializing the channel router.
  • sqldb/migrations_dev.go
    • Added a new migration entry for 000009_payments.
  • sqldb/sqlc/db_custom.go
    • Added PaymentAndIntent interface to abstract payment and intent data.
    • Implemented PaymentAndIntent for FilterPaymentsRow, FetchPaymentRow, and FetchPaymentsByIDsRow.
  • sqldb/sqlc/migrations/000009_payments.down.sql
    • Added SQL script to drop all payment-related tables and indexes created by the 000009_payments.up.sql migration.
  • sqldb/sqlc/migrations/000009_payments.up.sql
    • Added SQL script to create the complete payment system schema, including tables for payments, payment_intents, payment_htlc_attempts, payment_htlc_attempt_resolutions, payment_route_hops, and various custom TLV record tables (payment_first_hop_custom_records, payment_attempt_first_hop_custom_records, payment_hop_custom_records), along with associated indexes and foreign key constraints.
  • sqldb/sqlc/models.go
    • Modified models.go to include new Go structs representing the payment-related database models, such as Payment, PaymentAttemptFirstHopCustomRecord, PaymentFirstHopCustomRecord, PaymentHopCustomRecord, PaymentHtlcAttempt, PaymentHtlcAttemptResolution, PaymentIntent, PaymentRouteHop, PaymentRouteHopAmp, PaymentRouteHopBlinded, and PaymentRouteHopMpp.
  • sqldb/sqlc/payments.sql.go
    • Added payments.sql.go, a Go file generated by sqlc, containing Go functions for executing payment-related SQL queries defined in sqldb/sqlc/queries/payments.sql.
  • sqldb/sqlc/querier.go
    • Modified the Querier interface to include new payment-related SQL query methods, such as CountPayments, DeleteFailedAttempts, DeletePayment, FailAttempt, FailPayment, FetchAllInflightAttempts, FetchHopLevelCustomRecords, FetchHopsForAttempts, FetchHtlcAttemptResolutionsForPayments, FetchHtlcAttemptsForPayments, FetchPayment, FetchPaymentLevelFirstHopCustomRecords, FetchPaymentsByIDs, FetchRouteLevelFirstHopCustomRecords, FilterPayments, InsertHtlcAttempt, InsertPayment, InsertPaymentAttemptFirstHopCustomRecord, InsertPaymentFirstHopCustomRecord, InsertPaymentHopCustomRecord, InsertPaymentIntent, InsertRouteHop, InsertRouteHopAmp, InsertRouteHopBlinded, InsertRouteHopMpp, and SettleAttempt.
  • sqldb/sqlc/queries/payments.sql
    • Added payments.sql, containing SQL queries for various payment-related operations, including filtering payments, fetching specific payments, counting payments, fetching HTLC attempts and their resolutions, fetching route hops and associated MPP/AMP/Blinded data, and managing custom TLV records at payment, attempt, and hop levels. Also includes queries for inserting, deleting, settling, and failing payments and attempts.
Activity
  • This pull request consolidates a series of smaller, independently reviewed and merged PRs that collectively implemented the SQL backend for the payments database. The primary activity for this feature occurred in those preceding PRs, with this PR serving as a final integration step onto the master branch.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request introduces a comprehensive SQL backend for the payments database in lnd, supporting both SQLite and Postgres. The implementation is well-structured, follows the established patterns in the codebase, and includes thorough documentation for the new SQL-specific functions. The transition to context-aware database operations is a significant improvement for resource management. The implementation generally adheres to the LND Style Guide, particularly regarding function documentation and code segmentation. However, there are a few areas where performance and consistency can be improved, particularly regarding timestamp handling, non-deterministic result ordering in some queries, and the use of context.TODO() in the routing layer.

Value: lnwire.MilliSatoshi(amountMsat),
// The creation time is stored in the database as UTC but here
// we convert it to local time.
CreationTime: createdAt.Local(),

Choose a reason for hiding this comment

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

high

The use of .Local() here converts the timestamp to the node's local timezone. Since lnd generally uses UTC internally for all database operations and logic (e.g., expiry calculations), this could lead to inconsistent behavior if the node's local timezone is not UTC. It's better to keep the time in UTC as it is stored in the database.

Suggested change
CreationTime: createdAt.Local(),
CreationTime: createdAt,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the review! However, I think this is intentional and the suggested change would actually break tests.

The .Local() call is needed for consistency with the KV backend. In codec.go, deserializeTime returns time.Unix(0, int64(unixNano)), and Go's time.Unix sets the Location to time.Local by default. The SQL converter's .Local() makes the two backends return time.Time values with the same Location field.

More concretely, in payment_test.go the test constructs CreationTime as:

CreationTime: time.Unix(time.Now().Unix(), 0),

which also returns local time. The test then compares via reflect.DeepEqual(payment.Info, c) (line 247), which checks the Location field of time.Time. If we removed .Local(), the SQL backend would return UTC while the test expects local time, causing this DeepEqual check to fail.

As for the actual usage of CreationTime in the codebase — it's only ever consumed via .Unix() and .UnixNano() in router_backend.go, both of which are timezone-agnostic. So this is purely about test consistency.


err = db.SettleAttempt(ctx, sqlc.SettleAttemptParams{
AttemptIndex: int64(attemptID),
ResolutionTime: time.Now(),

Choose a reason for hiding this comment

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

high

Using time.Now() here ignores the SettleTime provided in the settleInfo parameter. This timestamp is often generated using a mockable clock in tests or represents the actual time the event occurred in the payment lifecycle. Hardcoding time.Now() makes the database record inconsistent with the event time and can break time-sensitive tests.

Suggested change
ResolutionTime: time.Now(),
ResolutionTime: settleInfo.SettleTime,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — ResolutionTime now uses settleInfo.SettleTime instead of time.Now(). A test assertion was also added to assertPaymentInfo to verify the settle time is correctly stored and retrieved, with explicit timestamps passed at the call sites.


err = db.FailAttempt(ctx, sqlc.FailAttemptParams{
AttemptIndex: int64(attemptID),
ResolutionTime: time.Now(),

Choose a reason for hiding this comment

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

high

Using time.Now() here ignores the FailTime provided in the failInfo parameter. Similar to the settlement case, this timestamp should reflect the actual time the failure occurred as determined by the caller, ensuring consistency in the database and supporting deterministic testing.

Suggested change
ResolutionTime: time.Now(),
ResolutionTime: failInfo.FailTime,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — ResolutionTime now uses failInfo.FailTime instead of time.Now(). Same as the settle case, a test assertion was added to verify the fail time is correctly stored and retrieved.

Comment on lines 1064 to 1068
mpPayments = make([]*MPPayment, 0, len(processedPayments))
for _, payment := range processedPayments {
mpPayments = append(mpPayments, payment)
}

Choose a reason for hiding this comment

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

medium

The returned slice of payments is constructed by iterating over a map, which results in a non-deterministic order. The KV backend implementation returns payments ordered by their hash (due to bbolt's ForEach order). To maintain consistency and provide a predictable API, the results should be sorted, for example by their sequence number.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Acknowledged — the map iteration order is non-deterministic. A sort.Slice by SequenceNum has been added after the map-to-slice conversion to produce a deterministic, insertion-ordered result.

Comment on lines +784 to +789
if mpPayment.Status != StatusSucceeded &&
!query.IncludeIncomplete {

return nil
}

Choose a reason for hiding this comment

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

medium

When IncludeIncomplete is false, the code still fetches the complete payment data (including all HTLCs and hops) for every payment in the range, only to skip it in Go code if it hasn't succeeded. This is inefficient for large databases. This filtering should be performed in the SQL query itself, for example by checking for the existence of a settled HTLC resolution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! However, filtering at the SQL level is not straightforward here by design. The payment status is not stored as a column in the payments table — it is computed in Go by examining the HTLC attempt resolutions. There is no status field the SQL query can filter on directly.

To push this filtering into SQL would require either adding a denormalised status column (and keeping it in sync on every state transition), or a subquery joining across htlc_attempts and htlc_attempt_resolutions to infer status. Both options are significant complexity for a performance optimisation. We'll keep the current approach and defer any optimisation to a follow-up if it becomes a bottleneck.

func (r *ChannelRouter) PreparePayment(payment *LightningPayment) (
PaymentSession, shards.ShardTracker, error) {

ctx := context.TODO()

Choose a reason for hiding this comment

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

medium

The use of context.TODO() here and in other methods within ChannelRouter prevents proper cancellation and resource management. Since the payment lifecycle functions have been updated to accept a context, it's an improvement opportunity to thread the context from the caller (e.g., from the RPC layer) down to these methods.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is out of scope for this PR. PreparePayment does not accept a context parameter, so threading one through would require changing the interface and all callers — a separate refactor. We already thread context through all the payment DB methods introduced in this PR; propagating it further up the call stack can be addressed in a follow-up.

Comment on lines +1366 to +1368
Scid: strconv.FormatUint(
hop.ChannelID, 10,
),

Choose a reason for hiding this comment

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

medium

Storing the Short Channel ID (SCID) as a TEXT field and performing string conversions (strconv.FormatUint) on every write is less efficient than using a BIGINT (uint64). Since SCIDs are fundamentally 64-bit integers, using the appropriate numeric type in the schema would improve performance and reduce storage requirements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The TEXT type for SCIDs is intentional and consistent with the existing invoice table design, which also stores chan_id as TEXT (see 000001_invoices.up.sql). Changing to BIGINT here would introduce a third convention alongside the invoice (TEXT) and graph (BLOB) tables, without providing a consistency benefit. We'll keep it aligned with the invoice table.

@ziggie1984 ziggie1984 added no-changelog payments Related to invoices/payments sql labels Feb 24, 2026
@ziggie1984
Copy link
Collaborator Author

ziggie1984 commented Feb 24, 2026

Included PRs

This PR is a consolidation of the following series of PRs that were merged individually:


#9147 - [Part 1|3] Introduce SQL Payment schema into LND

This implements the SQL backend for the payments subsystem of LND.

The following parts will focus on:

  1. Refactoring the attemptID out of the switch package
  2. Migration of old payment data (including duplicate payments which will be separate compared to the kv implementation. This is ok because there shouldn't be many nodes with this old behaviour plus we reduce complexity.
  3. Add performance tests

#10287 - [Part 2|*] Implement First Part for SQL Backend functions

This PR implements the QueryPayments and FetchPayment SQL Payments DB backend functions.

It does not yet add testing for these functions which will be added in followup PRs once we have the ability to add payments and attempts to the database which we then can fetch.


#10291 - [Part 3|*] Implement Second Part for SQL Backend functions

This PR implements most of the Insert and Delete methods we need for the unit tests in the following PR.


#10368 - [Part 3b|*] Implement Third Part for SQL Backend functions

Implements the Third and Final Part of Payments SQL backend functions. Follows after #10291.


#10292 - [Part 4|*] Add unit tests for the previous introduced SQL Backend methods

This PR adds the DB related unit tests for backend functions which were introduced in #10287, #10291 and #10368 and finalizes the implementation for the SQL backend.

This PR now lets the unit tests and the itest pass for the SQL payments backend.


#10307 - [Part 5|*] Thread Contexts through payment methods Part 1

Builds on top of the latest payment SQL changes and threads the context through payment functions.

This is part 1 threads the context up to the Router Level. We can even further thread the context up to the rpc level, however we need to be careful which functions we allow to be canceled by the RPC level. But this is tackled in Part 2.


#10308 - [Part 6|*] Thread Contexts through payment methods Part 2

Part 2 of the Context Threading refactor.


#10373 - [Part 7|*] Addressing TODOs left from the SQL payments implementation

This PR cleans up some TODOs of the payment sql series, before tackling the dispatcher and the migration.

@saubyk saubyk added this to the v0.21.0 milestone Feb 24, 2026
@saubyk saubyk added this to v0.21 Feb 24, 2026
@saubyk saubyk moved this to In progress in v0.21 Feb 24, 2026
@ziggie1984
Copy link
Collaborator Author

ziggie1984 commented Feb 24, 2026


PR #9147 — [Part 1/3] Introduce SQL Payment schema into LND

This implements the SQL backend for the payments subsystem of LND.

PR  0c1691e67724  →  branch  93626822f757  [subject]
  subj  ✓  lnd+paymentsdb: introduce harness for the payment sql backend
  body  ✓  (identical)
  stat  ✓  +144/-22

PR  07c350da2c15  →  branch  dd585a821b6f  [subject]
  subj  ✓  sqldb: add payment sql tables
  body  ✓  (identical)
  stat  ✓  +578/-0

PR  8e9f45702e12  →  branch  8c7b2367e5ba  [subject]
  subj  ✓  lnd: make the payment schema migration available for testing
  body  ✓  (identical)
  stat  ✓  +7/-1

PR  75b3a35182b1  →  branch  b6a05f795116  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +9/-0

✅ All 4 commits accounted for


PR #10287 — [Part 2] Implement First Part for SQL Backend functions

Implements QueryPayments and FetchPayment. Depends on #9147.

PR  6f8d3a5eb564  →  branch  e7a30966218c  [subject]
  subj  ✓  sqldb: add index and comment to payment tables
  body  ✓  (identical)
  stat  ✓  +7/-2

PR  0c9ed7f057fb  →  branch  18e776883725  [subject]
  subj  ✓  multi: add relevant queries for QueryPayments implemenation
  body  ✓  (identical)
  stat  ✓  +776/-0

PR  a163776ef00e  →  branch  eeca189b87d6  [subject]
  subj  ✓  paymentsdb: add new internal error
  body  ✓  (identical)
  stat  ✓  +4/-0

PR  4b5c2eb15077  →  branch  e8fe45fe6575  [subject]
  subj  ✓  paymentsdb: implement QueryPayments for sql backend
  body  ✓  (identical)
  stat  ✓  +917/-0

PR  8101c353ef7d  →  branch  8aae9ffc2ce1  [subject]
  subj  ✓  paymentsdb: implement FetchPayment for sql backend
  body  ✓  (identical)
  stat  ✓  +43/-0

PR  93f3a3804cc1  →  branch  4c5eaad60635  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +2/-0

✅ All 6 commits accounted for


PR #10291 — [Part 3] Implement Second Part for SQL Backend functions

Implements most Insert and Delete methods. Depends on #10287.

PR  560a0df4aa7b  →  branch  f8241748a135  [subject]
  subj  ✓  paymentsdb: enhance some godoc function descriptions
  body  ✓  (identical)
  stat  ✓  +10/-4

PR  6efe904ac52c  →  branch  27071acb6bff  [subject]
  subj  ✓  sqldb: Change payment_intent relationship to payment table
  body  ✓  (identical)
  stat  ✓  +53/-52

PR  1f9017c36dba  →  branch  bdea68bebfdf  [subject]
  subj  ✓  sqldb: add queries for deleting a payment and attempts
  body  ✓  (identical)
  stat  ✓  +42/-0

PR  179506a2545d  →  branch  6faf68c5fae1  [subject]
  subj  ✓  paymentsdb: add query to only fetch resolution type for HTLCs
  body  ✓  (identical)
  stat  ✓  +45/-0

PR  be9ac2840f50  →  branch  3012eb2ca096  [subject]
  subj  ✓  paymentsdb: implement DeleteFailedAttempts for sql backend
  body  ✓  (identical)
  stat  ✓  +126/-0

PR  a6610ad9019e  →  branch  c3e25ff8efcb  [subject]
  subj  ✓  paymentsdb: implement DeletePayment for sql backend
  body  ✓  (identical)
  stat  ✓  +71/-1

PR  ce121fbefb7d  →  branch  0c96a2d7228f  [subject]
  subj  ✓  sqldb+paymentsdb: add queries to insert all relavant data
  body  ✓  (identical)
  stat  ✓  +598/-0

PR  07467e93a1b6  →  branch  510f6fb532bf  [subject]
  subj  ✓  paymentsdb: implement InitPayment for sql backend
  body  ✓  (identical)
  stat  ✓  +133/-1

PR  56de565edd01  →  branch  d2cdd9d86f3c  [subject]
  subj  ✓  paymentsdb: add note to RegisterAttempt
  body  ✓  (identical)
  stat  ✓  +11/-0

PR  da2a8a051f9b  →  branch  40c8502fd325  [subject]
  subj  ✓  paymentsdb: implement RegisterAttempt for sql backend
  body  ✓  (identical)
  stat  ✓  +246/-0

PR  005031da217c  →  branch  c0e55b5855e9  [subject]
  subj  ✓  paymentsdb: verify total amount for last hop in the blinded path
  body  ✓  (identical)
  stat  ✓  +52/-0

PR  6422190232a5  →  branch  620dacc432ae  [subject]
  subj  ✓  paymentsdb: implement SettleAttempt for sql backend
  body  ✓  (identical)
  stat  ✓  +63/-0

PR  1101cc3edba9  →  branch  4c867c1b8861  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +2/-0

✅ All 13 commits accounted for


PR #10368 — [Part 3b] Implement Third Part for SQL Backend functions

Implements FailAttempt, DeletePayments, FetchInFlightPayments. Depends on #10291.

PR  07700492cf34  →  branch  7d87c0ce2ff5  [subject]
  subj  ✓  paymentsdb: fix formatting for sql QueryPayments
  body  ✓  (identical)
  stat  ✓  +2/-6

PR  842c36f2dfb8  →  branch  5d328c976e43  [subject]
  subj  ✓  paymentsdb: remove pointer receiver dependecy to make it more robust
  body  ✓  (identical)
  stat  ✓  +50/-42

PR  ff3708fed5b4  →  branch  caf73c59da2e  [subject]
  subj  ✓  paymentsdb: rename paymentsBatchData
  body  ✓  (identical)
  stat  ✓  +15/-12

PR  b921a054082e  →  branch  fd6796e5613a  [subject]
  subj  ✓  multi: implement Fail method for sql backend
  body  ✓  (identical)
  stat  ✓  +83/-0

PR  3bdf5d3ee5ef  →  branch  53a7877af0ca  [subject]
  subj  ✓  paymentsdb: implement FailAttempt for sql backend
  body  ✓  (identical)
  stat  ✓  +93/-0

PR  43402f903e10  →  branch  ff12082452c0  [subject]
  subj  ✓  paymentsdb: implement DeletePayments for sql backend
  body  ✓  (identical)
  stat  ✓  +296/-55

PR  08fc64f01f83  →  branch  218a30d16e6f  [subject]
  subj  ✓  paymentsdb: add a wrapper to the fetchpayment method
  body  ✓  (identical)
  stat  ✓  +34/-20

PR  54b43434ae38  →  branch  b02531cc859b  [subject]
  subj  ✓  paymentsdb: rename functions and variables
  body  ✓  (identical)
  stat  ✓  +36/-30

PR  1b3361570849  →  branch  82ee78c5ef59  [subject]
  subj  ✓  paymentsdb: use batch function when querying for resolutions
  body  ✓  (identical)
  stat  ✓  +42/-28

PR  84952f6968e4  →  branch  969b00e4a549  [subject]
  subj  ✓  paymentsdb: implement FetchInFlightPayments for sql backend
  body  ✓  (identical)
  stat  ✓  +288/-40

PR  856d8556cf23  →  branch  4e52896cc118  [subject]
  subj  ✓  paymentsdb: added unit test for computePaymentStatusFromResolutions
  body  ✓  (identical)
  stat  ✓  +229/-0

PR  56e24800f3cd  →  branch  c919d2561971  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +3/-0

✅ All 12 commits accounted for


PR #10292 — [Part 4] Add unit tests for SQL Backend methods

Adds DB-level unit tests for #10287, #10291, #10368. Finalises SQL backend implementation.

PR  5a3044672121  →  branch  9ad3f2195b0b  [subject]
  subj  ✓  paymentsdb: remove kvstore from sql db implementation
  body  ✓  (identical)
  stat  ✓  +0/-4

PR  7b504ca35d35  →  branch  fdfa1430f866  [subject*]
  subj  ✓  paymentsdb: refactor test helpers
  body  ✓  (identical)
  stat  ✓  +203/-74

PR  967f028376f2  →  branch  de50f471e818  [subject]
  subj  ✓  paymentsdb: make QueryPayments test db agnostic
  body  ✓  (identical)
  stat  ✓  +498/-184

PR  fe0b4ac1333a  →  branch  2eadbfb54df9  [subject]
  subj  ✓  paymentsdb: fix test case before testing sql backend
  body  ✓  (identical)
  stat  ✓  +8/-2

PR  edb2f0851960  →  branch  570863ca269c  [subject]
  subj  ✓  paymentsdb: add harness to run payment db agnostic tests
  body  ✓  (identical)
  stat  ✓  +135/-0

PR  e754aac3deb6  →  branch  080fd78fb4a8  [subject]
  subj  ✓  paymentsdb: introduce a harness interface
  body  ✓  (identical)
  stat  ✓  +162/-101

PR  2700dce5b897  →  branch  5f1f41359664  [subject]
  subj  ✓  paymentsdb: make specific kv store tests only available via build tag
  body  ✓  (identical)
  stat  ✓  +2/-0

PR  1fb7c606e78c  →  branch  f518931cf0ca  [subject]
  subj  ✓  itest: fix list_payments accuracy edge case
  body  ✓  (identical)
  stat  ✓  +43/-16

PR  59edd24367a7  →  branch  ea4e183c3981  [subject]
  subj  ✓  lnrpc: fix linter
  body  ✓  (identical)
  stat  ✓  +0/-1

PR  339c0017d1c8  →  branch  bfd59aac224e  [subject]
  subj  ✓  paymentsdb: add more comments
  body  ✓  (identical)
  stat  ✓  +10/-3

PR  5c5856ba384c  →  branch  e40c8df16b6f  [subject]
  subj  ✓  paymentsdb: add firstcustom records to unit tests
  body  ✓  (identical)
  stat  ✓  +15/-4

PR  577990cf888f  →  branch  dfacb9ec00ac  [subject]
  subj  ✓  paymentsdb: add unit test for FetchInflightPayments method
  body  ✓  (identical)
  stat  ✓  +124/-0

PR  cfc0dc0470d3  →  branch  fcb8929ea5ab  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +2/-1

✅ All 13 commits accounted for


PR #10307 — [Part 5] Thread Contexts through payment methods (Part 1)

Threads context up to the Router level. Depends on #10292.

PR  67cac5bf86b3  →  branch  bc15d4bb506c  [subject]
  subj  ✓  multi: thread context through DeletePayment
  body  ✓  (identical)
  stat  ✓  +32/-17

PR  f2b5035ea7cb  →  branch  563a2ac9d301  [subject]
  subj  ✓  multi: thread context through DeletePayments
  body  ✓  (identical)
  stat  ✓  +19/-14

PR  36c19f2597a6  →  branch  25d05be03843  [subject]
  subj  ✓  multi: thread context through FetchPayment
  body  ✓  (identical)
  stat  ✓  +55/-28

PR  2f79e8d299a3  →  branch  10062ca9ac04  [subject]
  subj  ✓  multi: thread context through FetchInflightPayments
  body  ✓  (identical)
  stat  ✓  +25/-16

PR  a8d35602d53e  →  branch  60026494b1e0  [subject]
  subj  ✓  multi: thread context through InitPayment
  body  ✓  (identical)
  stat  ✓  +46/-39

PR  60ed5b385a97  →  branch  8bb7150dc291  [subject]
  subj  ✓  multi: thread context through RegisterAttempt method
  body  ✓  (identical)
  stat  ✓  +70/-42

PR  15c5a2450370  →  branch  3b7fdabaecf7  [subject]
  subj  ✓  multi: thread context through SettleAttempt
  body  ✓  (identical)
  stat  ✓  +37/-28

PR  814285f8ea9e  →  branch  446300984ec4  [subject]
  subj  ✓  multi: thread context through FailAttempt
  body  ✓  (identical)
  stat  ✓  +33/-26

PR  937d7127fae3  →  branch  635a67c94a18  [subject]
  subj  ✓  multi: thread context through Fail payment functions
  body  ✓  (identical)
  stat  ✓  +41/-23

PR  08db1c462400  →  branch  7f2df4b0889d  [subject]
  subj  ✓  multi: thread context through DeleteFailedAttempts
  body  ✓  (identical)
  stat  ✓  +44/-18

PR  736f6452e270  →  branch  33597ad35d0f  [subject]
  subj  ✓  docs: add release notes
  body  ✓  (identical)
  stat  ✓  +3/-0

✅ All 11 commits accounted for


PR #10308 — [Part 6] Thread Contexts through payment methods (Part 2)

Threads context through the routing/lifecycle layer. Depends on #10307.

PR  ae9c31f6e3e4  →  branch  dfe0c43c7d93  [subject]
  subj  ✓  routing: Add context to requestRoute
  body  ✓  (identical)
  stat  ✓  +6/-8

PR  d11b7621948e  →  branch  8038dc0054fb  [subject]
  subj  ✓  multi: thread context through payment lifecyle functions
  body  ✓  (identical)
  stat  ✓  +91/-54

PR  91fd00a8ebf8  →  branch  e130ccc494bd  [subject]
  subj  ✓  routing: Thread context through failPaymentAndAttempt
  body  ✓  (identical)
  stat  ✓  +11/-12

PR  ea337268f138  →  branch  5b06854850a7  [subject]
  subj  ✓  routing: add context to failAttempt
  body  ✓  (identical)
  stat  ✓  +5/-7

PR  abcd83447a11  →  branch  03d891d52dbc  [subject]
  subj  ✓  routing: add context to reloadInflightAttempts
  body  ✓  (identical)
  stat  ✓  +4/-6

PR  6aefe5bae745  →  branch  4cc542808746  [subject]
  subj  ✓  routing: add context to reloadPayment method
  body  ✓  (identical)
  stat  ✓  +3/-4

PR  b60e6988744e  →  branch  d8d5d3f990c4  [subject]
  subj  ✓  multi: thread context through SendPayment
  body  ✓  (identical)
  stat  ✓  +34/-16

PR  aae5a33411a9  →  branch  9da39e565528  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +2/-0

✅ All 8 commits accounted for


PR #10373 — [Part 7] Addressing TODOs from SQL payments implementation

Cleans up TODOs from the payment SQL series. Depends on #10308.

PR  e3d8d23e03b8  →  branch  3ae0ccc6f5c1  [subject]
  subj  ✓  paymentsdb: make delete payments test db agnostic
  body  ✓  (identical)
  stat  ✓  +220/-199

PR  f4a7dae90ccf  →  branch  253e4fd832c5  [subject]
  subj  ✓  routing: add TODO to also delete payments without HTLCs
  body  ✓  (identical)
  stat  ✓  +5/-0

PR  4f2d1bc72ed1  →  branch  995ad7e1b53b  [subject]
  subj  ✓  multi: move failed attempt cfg option to the router subsytem
  body  ✓  (identical)
  stat  ✓  +224/-175

PR  967977ab12ac  →  branch  a861ac8d46ae  [subject*]
  subj  ✓  paymentsdb: refactor test helpers
  body  ✓  (identical)
  stat  ✓  +61/-107

PR  7d89e217032a  →  branch  a0a22b7ff914  [subject]
  subj  ✓  paymentsdb: add additional test for first hop data
  body  ✓  (identical)
  stat  ✓  +62/-0

PR  3c6f16bbe9b5  →  branch  372fbbd70b92  [subject]
  subj  ✓  paymentsdb: add more unit tests to increase coverage
  body  ✓  (identical)
  stat  ✓  +294/-9

PR  fb828146c574  →  branch  2548c1516738  [subject*]
  subj  ✓  docs: add release-notes
  body  ✓  (identical)
  stat  ✓  +2/-0

✅ All 7 commits accounted for


Consolidation-branch-only commits

4 commits have no counterpart in any constituent PR — original fixes applied
directly on the consolidation branch.

a2cb753428df2191672c317367b9f9094e4c8446  ✓  sqldb: rename 000009_payments to 000010_payments                                +3/-3
0c2951aa05e450e58cd66ee300258e5ed4f60560 ✓  paymentsdb: fix SettleAttempt and FailAttempt to use caller-provided timestamps  +78/-57
216de55dff8de52a686f65c1245e3f9acbc426b7  ✓  paymentsdb: sort FetchInFlightPayments result by sequence number                 +7/-1
e9a88267ff52c3f1beca97b9d449bfdb6350ab5a ✓  paymentsdb: fix duplicate interface check and down migration drop order          +6/-9

@ziggie1984 ziggie1984 added the severity-override-medium Manual override to medium label Feb 24, 2026
@ziggie1984
Copy link
Collaborator Author

Classified as severity-override-medium because all of the PRs already saw review. And this PR just merges the sidebranch into master

Copy link
Collaborator

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

Very useful comment explaining the PR's merged in here 🙏 great work! 🚢

LGTM after merge conflict resolution

We prepare the code for the sql payment backend. However no
payment db interface method for the sql backend is implemented
yet. This will be done in the following commits. They currently
use the embedded KVStore to satify the build environment.
This does not include duplicate payments yet. They will be added
when the migration code is introduced for payments.
We allow the migration of the payment schema to be applied when
the test_native_sql is active to make sure the tables are properly
contructed.
Previously a one(intent)-to-many(payment) relationship it is now
changed to a one-to-one relationship because a payment request
only can have 1 payment related to it. Looking into the future
with BOLT12 offers, the fetched invoice from the offer could be
stored here as well and the relationship would still hold.
A context is added to failPaymentAndAttempt and its dependant
function calls.
We make the TestDeleteNonInFlight and separate all the logic out
for the duplicate payment test case. The deletion of duplicate
payments is now tested in isolation only for the kv backend.
Previously we had db and application logic mixed on the db level.
We now move the config option KeepFailedPaymentAttempts to the
ChannelRouter level and move it out of the db level.
We add a test which tests the retrieval of first hop data like
the first hop amount or the custom records on the route level.
We add a couple of additional tests to increase the unit test
coverage of the sql store but also the kv store. We only create
db agnostic unit tests so both backends are tested effectively.
The 000009 schema version slot is now taken by 000009_graph_v2_columns
which was merged ahead of the payments schema. Bump the payments schema
file number to 000010 to avoid the collision.

Moreover update the migration_dev.go file to reflect this change and
update the order of migration.
@ziggie1984 ziggie1984 force-pushed the elle-payment-sql-series-new branch from 2589333 to 190893d Compare February 25, 2026 07:21
@lightninglabs-deploy lightninglabs-deploy added the severity-medium Focused review required label Feb 25, 2026
@ziggie1984 ziggie1984 force-pushed the elle-payment-sql-series-new branch from 190893d to 147db52 Compare February 25, 2026 09:08
@lightninglabs-deploy lightninglabs-deploy added severity-medium Focused review required and removed severity-medium Focused review required labels Feb 25, 2026
@lightninglabs-deploy
Copy link
Collaborator

🟡 PR Severity: MEDIUM

⚠️ Override active: severity-override-medium | 39 files total | ~4,159 non-test/non-generated lines changed

🔴 Critical (4 files) — overridden by label
  • rpcserver.go — Core server coordination (always CRITICAL)
  • server.go — Core server coordination (always CRITICAL)
  • sqldb/sqlc/migrations/000010_payments.up.sql — Database migration (new payments schema; migrations are always CRITICAL)
  • sqldb/sqlc/migrations/000010_payments.down.sql — Database migration rollback
🟠 High (11 files) — overridden by label
  • lnrpc/routerrpc/router_backend.go — RPC/API definitions
  • lnrpc/routerrpc/router_server.go — RPC/API definitions
  • routing/control_tower.go — Payment routing state machine
  • routing/payment_lifecycle.go — Payment routing state machine
  • routing/route/route.go — Payment routing
  • routing/router.go — Payment pathfinding/routing
  • sqldb/migrations_dev.go — SQL database infrastructure
  • sqldb/sqlc/db_custom.go — SQL database infrastructure
  • sqldb/sqlc/models.go — SQL database models
  • sqldb/sqlc/querier.go — SQL database query interface
  • sqldb/sqlc/queries/payments.sql — SQL query definitions
🟡 Medium (14 files)
  • payments/db/errors.go — Payments management
  • payments/db/interface.go — Payments management
  • payments/db/kv_store.go — Payments management
  • payments/db/options.go — Payments management
  • payments/db/payment.go — Payments management
  • payments/db/sql_converters.go — Payments management
  • payments/db/sql_store.go — Payments management
  • payments/db/test_harness.go — Payments test harness
  • payments/db/test_kvdb.go — Payments test support
  • payments/db/test_postgres.go — Payments test support
  • payments/db/test_sqlite.go — Payments test support
  • config_builder.go — Server configuration
  • config_prod.go — Server configuration
  • config_test_native_sql.go — Test/native SQL configuration
🟢 Low (1 file)
  • docs/release-notes/release-notes-0.21.0.md — Release notes
Excluded from counting (tests & generated)

Test files: itest/lnd_payment_test.go, payments/db/kv_store_test.go, payments/db/payment_test.go, payments/db/sql_store_test.go, routing/control_tower_test.go, routing/mock_test.go, routing/payment_lifecycle_test.go, routing/router_test.go

Auto-generated: sqldb/sqlc/payments.sql.go

Analysis

Override in effect: A severity-override-medium label is present, forcing the severity to MEDIUM regardless of natural classification.

Natural classification would be CRITICAL due to:

  • rpcserver.go and server.go — Core server coordination files (always CRITICAL per policy)
  • sqldb/sqlc/migrations/000010_payments.up.sql / .down.sql — Database migrations are always CRITICAL per policy

Both severity-bump thresholds are exceeded (for informational purposes):

  • 30 non-test, non-generated files changed (threshold: >20)
  • ~4,159 non-test, non-generated lines changed (threshold: >500)

This PR consolidates the full SQL-backed payments database implementation, bringing together a series of smaller, individually-reviewed PRs. The severity-override-medium label reflects that this work was reviewed incrementally. Reviewers should nonetheless be aware that the consolidated diff touches core server coordination (server.go, rpcserver.go), introduces new database migrations, and modifies payment routing logic in routing/*.


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

@ziggie1984 ziggie1984 force-pushed the elle-payment-sql-series-new branch 2 times, most recently from bba3e6c to 78ba5e4 Compare February 25, 2026 17:19
…timestamps

The SQL backend introduced in this PR was ignoring the SettleTime and
FailTime fields provided in HTLCSettleInfo and HTLCFailInfo, instead
always recording time.Now() as the resolution timestamp. The KV backend
correctly serializes and deserializes these fields.

The timestamps are set by the caller using a mockable clock
(p.router.cfg.Clock.Now() in payment_lifecycle.go), so ignoring them
means the stored timestamp reflects when the DB write happened rather
than when the event occurred, breaking deterministic testing.

This commit also extends the test assertions in assertPaymentInfo to
verify that SettleTime and FailTime are correctly stored and retrieved
by the SQL backend, and updates the relevant call sites to pass explicit
timestamps so regressions are caught.
The SQL implementation collects payments into a map before converting
to a slice, resulting in non-deterministic iteration order due to Go's
intentional map randomisation. Sort the result by SequenceNum to produce
a deterministic, insertion-ordered output.

Note that the current sole caller (resumePayments in router.go) processes
each payment independently, so this ordering does not affect any existing
behaviour.
- Remove duplicate compile-time interface assertion for SQLStore.

- Fix the down migration to drop payment_intents before payments to
respect the foreign key dependency order. This was not a bug in the
first place bc we have the CASCADE when deleting payments.
@ziggie1984 ziggie1984 force-pushed the elle-payment-sql-series-new branch from 78ba5e4 to e9a8826 Compare February 25, 2026 17:36
Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

LGTM🛡️

err = db.SettleAttempt(ctx, sqlc.SettleAttemptParams{
AttemptIndex: int64(attemptID),
ResolutionTime: time.Now(),
ResolutionTime: settleInfo.SettleTime.UTC(),
Copy link
Member

Choose a reason for hiding this comment

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

Q: what happens if this field is not set? @gemini-code-assist

Choose a reason for hiding this comment

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

That's a good question! In Go, time.Time is a struct, so it's always initialized to a value, even if it's the zero value (January 1, year 1, 00:00:00 UTC). The .UTC() method will convert this value to UTC.

Furthermore, the resolution_time column in the payment_htlc_attempt_resolutions table is defined as TIMESTAMP NOT NULL in the SQL schema. This means the database itself enforces that a value must always be provided for this field.

In the payment_lifecycle (where SettleAttempt is called), the SettleTime field of settleInfo is explicitly set using p.router.cfg.Clock.Now(). This function is designed to return the current time, ensuring that SettleTime is always a valid, non-zero timestamp in practice. Therefore, this field should always have a meaningful value when an attempt is settled.

@yyforyongyu yyforyongyu merged commit 9bef04a into master Feb 26, 2026
41 checks passed
@github-project-automation github-project-automation bot moved this from In progress to Done in v0.21 Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog payments Related to invoices/payments severity-medium Focused review required severity-override-medium Manual override to medium sql

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants