multi: introduce interface for payment database
What changed, and why it matters
This commit is a straightforward internal code cleanup: it replaces a direct reference to a specific payments database implementation with a new Go interface. No behavior changes, bug fixes, or security fixes are visible in the diff. It simply lets different database backends satisfy the same interface in the future.
No security action required. Treat as normal refactoring and review for code quality only.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces paymentsdb.DB, PaymentReader, PaymentWriter, and PaymentControl interfaces in payments/db/interface.go. It then updates config_builder.go, routing/control_tower.go, rpcserver.go, and server.go to use the interface type (paymentsdb.DB) instead of the concrete *paymentsdb.KVPaymentsDB type. The actual runtime object assigned remains the existing kvPaymentsDB. No functional or security logic is modified.
Changed components
payments/db/interface.goconfig_builder.gorouting/control_tower.gorpcserver.goserver.goInspect captured patch +103 / −14
diff --git a/config_builder.go b/config_builder.go
index be56f96..af4a06e 100644
--- a/config_builder.go
+++ b/config_builder.go
@@ -925,9 +925,9 @@ type DatabaseInstances struct {
// InvoiceDB is the database that stores information about invoices.
InvoiceDB invoices.InvoiceDB
- // KVPaymentsDB is the database that stores all payment related
+ // PaymentsDB is the database that stores all payment related
// information.
- KVPaymentsDB *paymentsdb.KVPaymentsDB
+ PaymentsDB paymentsdb.DB
// MacaroonDB is the database that stores macaroon root keys.
MacaroonDB kvdb.Backend
@@ -1237,7 +1237,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
return nil, nil, err
}
- dbs.KVPaymentsDB = kvPaymentsDB
+ dbs.PaymentsDB = kvPaymentsDB
// Wrap the watchtower client DB and make sure we clean up.
if cfg.WtClient.Active {
diff --git a/payments/db/interface.go b/payments/db/interface.go
new file mode 100644
index 0000000..d3a7e76
--- /dev/null
+++ b/payments/db/interface.go
@@ -0,0 +1,91 @@
+package paymentsdb
+
+import (
+ "context"
+
+ "github.com/lightningnetwork/lnd/lntypes"
+)
+
+// DB represents the interface to the underlying payments database.
+type DB interface {
+ PaymentReader
+ PaymentWriter
+}
+
+// PaymentReader represents the interface to read operations from the payments
+// database.
+type PaymentReader interface {
+ // QueryPayments queries the payments database and should support
+ // pagination.
+ QueryPayments(ctx context.Context, query Query) (Response, error)
+
+ // FetchPayment fetches the payment corresponding to the given payment
+ // hash.
+ FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error)
+
+ // FetchInFlightPayments returns all payments with status InFlight.
+ FetchInFlightPayments() ([]*MPPayment, error)
+}
+
+// PaymentWriter represents the interface to write operations to the payments
+// database.
+type PaymentWriter interface {
+ // DeletePayment deletes a payment from the DB given its payment hash.
+ DeletePayment(paymentHash lntypes.Hash, failedAttemptsOnly bool) error
+
+ // DeletePayments deletes all payments from the DB given the specified
+ // flags.
+ DeletePayments(failedOnly, failedAttemptsOnly bool) (int, error)
+
+ PaymentControl
+}
+
+// PaymentControl represents the interface to control the payment lifecycle and
+// its database operations. This interface represents the control flow of how
+// a payment should be handled in the database. They are not just writing
+// operations but they inherently represent the flow of a payment. The methods
+// are called in the following order:
+//
+// 1. InitPayment
+// 2. RegisterAttempt (a payment can have multiple attempts)
+// 3. SettleAttempt or FailAttempt (attempts can also fail as long as the
+// sending amount will be eventually settled).
+// 4. Payment succeeds or "Fail" is called.
+// 5. DeleteFailedAttempts is called which will delete all failed attempts
+// for a payment to clean up the database.
+type PaymentControl interface {
+ // InitPayment checks that no other payment with the same payment hash
+ // exists in the database before creating a new payment. However, it
+ // should allow the user making a subsequent payment if the payment is
+ // in a Failed state.
+ InitPayment(lntypes.Hash, *PaymentCreationInfo) error
+
+ // RegisterAttempt atomically records the provided HTLCAttemptInfo.
+ RegisterAttempt(lntypes.Hash, *HTLCAttemptInfo) (*MPPayment, error)
+
+ // SettleAttempt marks the given attempt settled with the preimage. If
+ // this is a multi shard payment, this might implicitly mean the
+ // full payment succeeded.
+ //
+ // After invoking this method, InitPayment should always return an
+ // error to prevent us from making duplicate payments to the same
+ // payment hash. The provided preimage is atomically saved to the DB
+ // for record keeping.
+ SettleAttempt(lntypes.Hash, uint64, *HTLCSettleInfo) (*MPPayment, error)
+
+ // FailAttempt marks the given payment attempt failed.
+ FailAttempt(lntypes.Hash, uint64, *HTLCFailInfo) (*MPPayment, error)
+
+ // Fail transitions a payment into the Failed state, and records
+ // the ultimate reason the payment failed. Note that this should only
+ // be called when all active attempts are already failed. After
+ // invoking this method, InitPayment should return nil on its next call
+ // for this payment hash, allowing the user to make a subsequent
+ // payment.
+ Fail(lntypes.Hash, FailureReason) (*MPPayment, error)
+
+ // DeleteFailedAttempts removes all failed HTLCs from the db. It should
+ // be called for a given payment whenever all inflight htlcs are
+ // completed, and the payment has reached a final terminal state.
+ DeleteFailedAttempts(lntypes.Hash) error
+}
diff --git a/routing/control_tower.go b/routing/control_tower.go
index c5af230..dd984c9 100644
--- a/routing/control_tower.go
+++ b/routing/control_tower.go
@@ -151,7 +151,7 @@ func (s *controlTowerSubscriberImpl) Updates() <-chan interface{} {
// controlTower is persistent implementation of ControlTower to restrict
// double payment sending.
type controlTower struct {
- db *paymentsdb.KVPaymentsDB
+ db paymentsdb.DB
// subscriberIndex is used to provide a unique id for each subscriber
// to all payments. This is used to easily remove the subscriber when
@@ -168,7 +168,7 @@ type controlTower struct {
}
// NewControlTower creates a new instance of the controlTower.
-func NewControlTower(db *paymentsdb.KVPaymentsDB) ControlTower {
+func NewControlTower(db paymentsdb.DB) ControlTower {
return &controlTower{
db: db,
subscribersAllPayments: make(
diff --git a/rpcserver.go b/rpcserver.go
index 8b4c1b1..7add215 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -7529,7 +7529,7 @@ func (r *rpcServer) ListPayments(ctx context.Context,
query.MaxPayments = math.MaxUint64
}
- paymentsQuerySlice, err := r.server.kvPaymentsDB.QueryPayments(
+ paymentsQuerySlice, err := r.server.paymentsDB.QueryPayments(
ctx, query,
)
if err != nil {
@@ -7612,7 +7612,7 @@ func (r *rpcServer) DeletePayment(ctx context.Context,
rpcsLog.Infof("[DeletePayment] payment_identifier=%v, "+
"failed_htlcs_only=%v", hash, req.FailedHtlcsOnly)
- err = r.server.kvPaymentsDB.DeletePayment(hash, req.FailedHtlcsOnly)
+ err = r.server.paymentsDB.DeletePayment(hash, req.FailedHtlcsOnly)
if err != nil {
return nil, err
}
@@ -7652,7 +7652,7 @@ func (r *rpcServer) DeleteAllPayments(ctx context.Context,
"failed_htlcs_only=%v", req.FailedPaymentsOnly,
req.FailedHtlcsOnly)
- numDeletedPayments, err := r.server.kvPaymentsDB.DeletePayments(
+ numDeletedPayments, err := r.server.paymentsDB.DeletePayments(
req.FailedPaymentsOnly, req.FailedHtlcsOnly,
)
if err != nil {
diff --git a/server.go b/server.go
index b45c5b8..cfbc55d 100644
--- a/server.go
+++ b/server.go
@@ -336,11 +336,9 @@ type server struct {
invoicesDB invoices.InvoiceDB
- // kvPaymentsDB is the DB that contains all functions for managing
+ // paymentsDB is the DB that contains all functions for managing
// payments.
- //
- // TODO(ziggie): Replace with interface.
- kvPaymentsDB *paymentsdb.KVPaymentsDB
+ paymentsDB paymentsdb.DB
aliasMgr *aliasmgr.Manager
@@ -683,7 +681,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
addrSource: addrSource,
miscDB: dbs.ChanStateDB,
invoicesDB: dbs.InvoiceDB,
- kvPaymentsDB: dbs.KVPaymentsDB,
+ paymentsDB: dbs.PaymentsDB,
cc: cc,
sigPool: lnwallet.NewSigPool(cfg.Workers.Sig, cc.Signer),
writePool: writePool,
@@ -1007,7 +1005,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
PathFindingConfig: pathFindingConfig,
}
- s.controlTower = routing.NewControlTower(dbs.KVPaymentsDB)
+ s.controlTower = routing.NewControlTower(dbs.PaymentsDB)
strictPruning := cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.