Skip to content

Commit

Permalink
multi: Add CalcPaymentStats to DB and RPC server
Browse files Browse the repository at this point in the history
This adds a CalcPaymentStats call to the db, RPC server and dcrlncli.
This allows querying the database for a report on the total number of
payments tracked.

An upstream lnd PR (number 5635) will include a migration that goes
through every payment. While the PR has been tested upstream to not be a
problem even for nodes with large numbers of payments, there is no
actual way to know whether a node _would_ be a problem (due to no
existing way to query the total number of payments in the DB).

This PR prepares for an upstream port of an lnd version that includes
the aforementioned PR by exposing the total counts of payments, which
would be migrated.

This will allow node operators to evaluate whether the payments should
be cleared from the database before performing the migration.
  • Loading branch information
matheusd committed Feb 5, 2024
1 parent 35efe85 commit 494aad7
Show file tree
Hide file tree
Showing 9 changed files with 1,582 additions and 1,036 deletions.
94 changes: 94 additions & 0 deletions channeldb/payments_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package channeldb

import (
"github.com/decred/dcrlnd/kvdb"
)

// PaymentCountStats returns stats about the payments stored in the DB.
type PaymentCountStats struct {
Total uint64
Failed uint64
Succeeded uint64

HTLCAttempts uint64
HTLCFailed uint64
HTLCSettled uint64

OldDupePayments uint64
}

// CalcPaymentStats goes through the DB, counting up the payment stats.
func (d *DB) CalcPaymentStats() (*PaymentCountStats, error) {
res := &PaymentCountStats{}
err := kvdb.View(d, func(tx kvdb.RTx) error {
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}

// Standard payments.
return paymentsBucket.ForEach(func(k, v []byte) error {
payBucket := paymentsBucket.NestedReadBucket(k)
if payBucket == nil {
return nil
}

res.Total += 1

htlcsBucket := payBucket.NestedReadBucket(paymentHtlcsBucket)
if htlcsBucket != nil {
var inflight, settled bool
err := htlcsBucket.ForEach(func(k, _ []byte) error {
res.HTLCAttempts += 1
htlcBucket := htlcsBucket.NestedReadBucket(k)
hasFail := htlcBucket.Get(htlcFailInfoKey) != nil
hasSettle := htlcBucket.Get(htlcSettleInfoKey) != nil
if hasFail {
res.HTLCFailed += 1
} else if hasSettle {
res.HTLCSettled += 1
settled = true
} else {
inflight = true
}
return nil
})
if err != nil {
return err
}

hasFailureReason := payBucket.Get(paymentFailInfoKey) != nil

switch {
// If any of the the HTLCs did succeed and there are no HTLCs in
// flight, the payment succeeded.
case !inflight && settled:
res.Succeeded += 1

// If we have no in-flight HTLCs, and the payment failure is set, the
// payment is considered failed.
case !inflight && hasFailureReason:
res.Failed += 1
}

}

// Old duplicate payments.
dupBucket := payBucket.NestedReadBucket(duplicatePaymentsBucket)
if dupBucket == nil {
return nil
}

return dupBucket.ForEach(func(k, v []byte) error {
subBucket := dupBucket.NestedReadBucket(k)
if subBucket == nil {
return nil
}
res.OldDupePayments += 1
return nil
})
})
}, func() {})

return res, err
}
38 changes: 38 additions & 0 deletions cmd/dcrlncli/cmd_dcrlnd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"github.com/decred/dcrlnd/lnrpc"
"github.com/urfave/cli"
)

var calcPayStatsCommand = cli.Command{
Name: "calcpaystats",
Usage: "Scans the db and generates a report on total payment counts.",
ArgsUsage: "",
Category: "Payments",
Description: `
Goes through the DB and generates a report on total number of payments
made, settled and failed.
NOTE: This requires a scan through the entire set of payments in the DB,
so it may be slow on nodes that have a large number of payments.
`,
Action: actionDecorator(calcPayStats),
}

func calcPayStats(ctx *cli.Context) error {
ctxc := getContext()

client, cleanUp := getClient(ctx)
defer cleanUp()

req := &lnrpc.CalcPaymentStatsRequest{}
resp, err := client.CalcPaymentStats(ctxc, req)
if err != nil {
return err
}

printRespJSON(resp)

return nil
}
1 change: 1 addition & 0 deletions cmd/dcrlncli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ func main() {
queryRoutesCommand,
getNetworkInfoCommand,
debugLevelCommand,
calcPayStatsCommand,
decodePayReqCommand,
listChainTxnsCommand,
stopCommand,
Expand Down
Loading

0 comments on commit 494aad7

Please sign in to comment.