diff --git a/config_builder.go b/config_builder.go index be56f962b..af4a06ed3 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 000000000..d3a7e7671 --- /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 c5af230f7..dd984c912 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 8b4c1b1b5..7add21579 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 b45c5b823..cfbc55da7 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