mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
Merge pull request #3960 from bitromortac/listpayments-pagination
channeldb+lnrpc+lncli: listpayments pagination support
This commit is contained in:
commit
7e6f3ece23
@ -162,7 +162,7 @@ func fetchDuplicatePayment(bucket kvdb.ReadBucket) (*MPPayment, error) {
|
||||
}
|
||||
|
||||
payment := &MPPayment{
|
||||
sequenceNum: sequenceNum,
|
||||
SequenceNum: sequenceNum,
|
||||
Info: creationInfo,
|
||||
FailureReason: failureReason,
|
||||
Status: paymentStatus,
|
||||
|
@ -108,9 +108,9 @@ type HTLCFailInfo struct {
|
||||
// have the associated Settle or Fail struct populated if the HTLC is no longer
|
||||
// in-flight.
|
||||
type MPPayment struct {
|
||||
// sequenceNum is a unique identifier used to sort the payments in
|
||||
// SequenceNum is a unique identifier used to sort the payments in
|
||||
// order of creation.
|
||||
sequenceNum uint64
|
||||
SequenceNum uint64
|
||||
|
||||
// Info holds all static information about this payment, and is
|
||||
// populated when the payment is initiated.
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@ -246,7 +247,7 @@ func (db *DB) FetchPayments() ([]*MPPayment, error) {
|
||||
|
||||
// Before returning, sort the payments by their sequence number.
|
||||
sort.Slice(payments, func(i, j int) bool {
|
||||
return payments[i].sequenceNum < payments[j].sequenceNum
|
||||
return payments[i].SequenceNum < payments[j].SequenceNum
|
||||
})
|
||||
|
||||
return payments, nil
|
||||
@ -334,7 +335,7 @@ func fetchPayment(bucket kvdb.ReadBucket) (*MPPayment, error) {
|
||||
}
|
||||
|
||||
return &MPPayment{
|
||||
sequenceNum: sequenceNum,
|
||||
SequenceNum: sequenceNum,
|
||||
Info: creationInfo,
|
||||
HTLCs: htlcs,
|
||||
FailureReason: failureReason,
|
||||
@ -423,6 +424,140 @@ func fetchHtlcFailInfo(bucket kvdb.ReadBucket) (*HTLCFailInfo, error) {
|
||||
return deserializeHTLCFailInfo(r)
|
||||
}
|
||||
|
||||
// PaymentsQuery represents a query to the payments database starting or ending
|
||||
// at a certain offset index. The number of retrieved records can be limited.
|
||||
type PaymentsQuery struct {
|
||||
// IndexOffset determines the starting point of the payments query and
|
||||
// is always exclusive. In normal order, the query starts at the next
|
||||
// higher (available) index compared to IndexOffset. In reversed order,
|
||||
// the query ends at the next lower (available) index compared to the
|
||||
// IndexOffset. In the case of a zero index_offset, the query will start
|
||||
// with the oldest payment when paginating forwards, or will end with
|
||||
// the most recent payment when paginating backwards.
|
||||
IndexOffset uint64
|
||||
|
||||
// MaxPayments is the maximal number of payments returned in the
|
||||
// payments query.
|
||||
MaxPayments uint64
|
||||
|
||||
// Reversed gives a meaning to the IndexOffset. If reversed is set to
|
||||
// true, the query will fetch payments with indices lower than the
|
||||
// IndexOffset, otherwise, it will return payments with indices greater
|
||||
// than the IndexOffset.
|
||||
Reversed bool
|
||||
|
||||
// If IncludeIncomplete is true, then return payments that have not yet
|
||||
// fully completed. This means that pending payments, as well as failed
|
||||
// payments will show up if this field is set to true.
|
||||
IncludeIncomplete bool
|
||||
}
|
||||
|
||||
// PaymentsResponse contains the result of a query to the payments database.
|
||||
// It includes the set of payments that match the query and integers which
|
||||
// represent the index of the first and last item returned in the series of
|
||||
// payments. These integers allow callers to resume their query in the event
|
||||
// that the query's response exceeds the max number of returnable events.
|
||||
type PaymentsResponse struct {
|
||||
// Payments is the set of payments returned from the database for the
|
||||
// PaymentsQuery.
|
||||
Payments []MPPayment
|
||||
|
||||
// FirstIndexOffset is the index of the first element in the set of
|
||||
// returned MPPayments. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response. The offset can be used to continue reverse pagination.
|
||||
FirstIndexOffset uint64
|
||||
|
||||
// LastIndexOffset is the index of the last element in the set of
|
||||
// returned MPPayments. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response. The offset can be used to continue forward pagination.
|
||||
LastIndexOffset uint64
|
||||
}
|
||||
|
||||
// QueryPayments is a query to the payments database which is restricted
|
||||
// to a subset of payments by the payments query, containing an offset
|
||||
// index and a maximum number of returned payments.
|
||||
func (db *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) {
|
||||
var resp PaymentsResponse
|
||||
|
||||
allPayments, err := db.FetchPayments()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(allPayments) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
indexExclusiveLimit := query.IndexOffset
|
||||
// In backward pagination, if the index limit is the default 0 value,
|
||||
// we set our limit to maxint to include all payments from the highest
|
||||
// sequence number on.
|
||||
if query.Reversed && indexExclusiveLimit == 0 {
|
||||
indexExclusiveLimit = math.MaxInt64
|
||||
}
|
||||
|
||||
for i := range allPayments {
|
||||
var payment *MPPayment
|
||||
|
||||
// If we have the max number of payments we want, exit.
|
||||
if uint64(len(resp.Payments)) == query.MaxPayments {
|
||||
break
|
||||
}
|
||||
|
||||
if query.Reversed {
|
||||
payment = allPayments[len(allPayments)-1-i]
|
||||
|
||||
// In the reversed direction, skip over all payments
|
||||
// that have sequence numbers greater than or equal to
|
||||
// the index offset. We skip payments with equal index
|
||||
// because the offset is exclusive.
|
||||
if payment.SequenceNum >= indexExclusiveLimit {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
payment = allPayments[i]
|
||||
|
||||
// In the forward direction, skip over all payments that
|
||||
// have sequence numbers less than or equal to the index
|
||||
// offset. We skip payments with equal indexes because
|
||||
// the index offset is exclusive.
|
||||
if payment.SequenceNum <= indexExclusiveLimit {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// To keep compatibility with the old API, we only return
|
||||
// non-succeeded payments if requested.
|
||||
if payment.Status != StatusSucceeded &&
|
||||
!query.IncludeIncomplete {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resp.Payments = append(resp.Payments, *payment)
|
||||
}
|
||||
|
||||
// Need to swap the payments slice order if reversed order.
|
||||
if query.Reversed {
|
||||
for l, r := 0, len(resp.Payments)-1; l < r; l, r = l+1, r-1 {
|
||||
resp.Payments[l], resp.Payments[r] =
|
||||
resp.Payments[r], resp.Payments[l]
|
||||
}
|
||||
}
|
||||
|
||||
// Set the first and last index of the returned payments so that the
|
||||
// caller can resume from this point later on.
|
||||
if len(resp.Payments) > 0 {
|
||||
resp.FirstIndexOffset = resp.Payments[0].SequenceNum
|
||||
resp.LastIndexOffset =
|
||||
resp.Payments[len(resp.Payments)-1].SequenceNum
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DeletePayments deletes all completed and failed payments from the DB.
|
||||
func (db *DB) DeletePayments() error {
|
||||
return kvdb.Update(db, func(tx kvdb.RwTx) error {
|
||||
|
@ -3,6 +3,7 @@ package channeldb
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
@ -175,3 +177,264 @@ func TestRouteSerialization(t *testing.T) {
|
||||
spew.Sdump(testRoute), spew.Sdump(route2))
|
||||
}
|
||||
}
|
||||
|
||||
// deletePayment removes a payment with paymentHash from the payments database.
|
||||
func deletePayment(t *testing.T, db *DB, paymentHash lntypes.Hash) {
|
||||
t.Helper()
|
||||
|
||||
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
|
||||
payments := tx.ReadWriteBucket(paymentsRootBucket)
|
||||
|
||||
err := payments.DeleteNestedBucket(paymentHash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("could not delete "+
|
||||
"payment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestQueryPayments tests retrieval of payments with forwards and reversed
|
||||
// queries.
|
||||
func TestQueryPayments(t *testing.T) {
|
||||
// Define table driven test for QueryPayments.
|
||||
// Test payments have sequence indices [1, 3, 4, 5, 6, 7].
|
||||
tests := []struct {
|
||||
name string
|
||||
query PaymentsQuery
|
||||
firstIndex uint64
|
||||
lastIndex uint64
|
||||
|
||||
// expectedSeqNrs contains the set of sequence numbers we expect
|
||||
// our query to return.
|
||||
expectedSeqNrs []uint64
|
||||
}{
|
||||
{
|
||||
name: "IndexOffset at the end of the payments range",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 7,
|
||||
MaxPayments: 7,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 0,
|
||||
lastIndex: 0,
|
||||
expectedSeqNrs: nil,
|
||||
},
|
||||
{
|
||||
name: "query in forwards order, start at beginning",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 0,
|
||||
MaxPayments: 2,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 1,
|
||||
lastIndex: 3,
|
||||
expectedSeqNrs: []uint64{1, 3},
|
||||
},
|
||||
{
|
||||
name: "query in forwards order, start at end, overflow",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 6,
|
||||
MaxPayments: 2,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 7,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{7},
|
||||
},
|
||||
{
|
||||
name: "start at offset index outside of payments",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 20,
|
||||
MaxPayments: 2,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 0,
|
||||
lastIndex: 0,
|
||||
expectedSeqNrs: nil,
|
||||
},
|
||||
{
|
||||
name: "overflow in forwards order",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 4,
|
||||
MaxPayments: math.MaxUint64,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 5,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{5, 6, 7},
|
||||
},
|
||||
{
|
||||
name: "start at offset index outside of payments, " +
|
||||
"reversed order",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 9,
|
||||
MaxPayments: 2,
|
||||
Reversed: true,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 6,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{6, 7},
|
||||
},
|
||||
{
|
||||
name: "query in reverse order, start at end",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 0,
|
||||
MaxPayments: 2,
|
||||
Reversed: true,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 6,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{6, 7},
|
||||
},
|
||||
{
|
||||
name: "query in reverse order, starting in middle",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 4,
|
||||
MaxPayments: 2,
|
||||
Reversed: true,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 1,
|
||||
lastIndex: 3,
|
||||
expectedSeqNrs: []uint64{1, 3},
|
||||
},
|
||||
{
|
||||
name: "query in reverse order, starting in middle, " +
|
||||
"with underflow",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 4,
|
||||
MaxPayments: 5,
|
||||
Reversed: true,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 1,
|
||||
lastIndex: 3,
|
||||
expectedSeqNrs: []uint64{1, 3},
|
||||
},
|
||||
{
|
||||
name: "all payments in reverse, order maintained",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 0,
|
||||
MaxPayments: 7,
|
||||
Reversed: true,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 1,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{1, 3, 4, 5, 6, 7},
|
||||
},
|
||||
{
|
||||
name: "exclude incomplete payments",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 0,
|
||||
MaxPayments: 7,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: false,
|
||||
},
|
||||
firstIndex: 0,
|
||||
lastIndex: 0,
|
||||
expectedSeqNrs: nil,
|
||||
},
|
||||
{
|
||||
name: "query payments at index gap",
|
||||
query: PaymentsQuery{
|
||||
IndexOffset: 1,
|
||||
MaxPayments: 7,
|
||||
Reversed: false,
|
||||
IncludeIncomplete: true,
|
||||
},
|
||||
firstIndex: 3,
|
||||
lastIndex: 7,
|
||||
expectedSeqNrs: []uint64{3, 4, 5, 6, 7},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := initDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to init db: %v", err)
|
||||
}
|
||||
|
||||
// Populate the database with a set of test payments.
|
||||
numberOfPayments := 7
|
||||
pControl := NewPaymentControl(db)
|
||||
|
||||
for i := 0; i < numberOfPayments; i++ {
|
||||
// Generate a test payment.
|
||||
info, _, _, err := genInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test "+
|
||||
"payment: %v", err)
|
||||
}
|
||||
|
||||
// Create a new payment entry in the database.
|
||||
err = pControl.InitPayment(info.PaymentHash, info)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to initialize "+
|
||||
"payment in database: %v", err)
|
||||
}
|
||||
|
||||
// Immediately delete the payment with index 2.
|
||||
if i == 1 {
|
||||
deletePayment(t, db, info.PaymentHash)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all payments in the database.
|
||||
allPayments, err := db.FetchPayments()
|
||||
if err != nil {
|
||||
t.Fatalf("payments could not be fetched from "+
|
||||
"database: %v", err)
|
||||
}
|
||||
|
||||
if len(allPayments) != 6 {
|
||||
t.Fatalf("Number of payments received does not "+
|
||||
"match expected one. Got %v, want %v.",
|
||||
len(allPayments), 6)
|
||||
}
|
||||
|
||||
querySlice, err := db.QueryPayments(tt.query)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if tt.firstIndex != querySlice.FirstIndexOffset ||
|
||||
tt.lastIndex != querySlice.LastIndexOffset {
|
||||
t.Errorf("First or last index does not match "+
|
||||
"expected index. Want (%d, %d), got (%d, %d).",
|
||||
tt.firstIndex, tt.lastIndex,
|
||||
querySlice.FirstIndexOffset,
|
||||
querySlice.LastIndexOffset)
|
||||
}
|
||||
|
||||
if len(querySlice.Payments) != len(tt.expectedSeqNrs) {
|
||||
t.Errorf("expected: %v payments, got: %v",
|
||||
len(allPayments), len(querySlice.Payments))
|
||||
}
|
||||
|
||||
for i, seqNr := range tt.expectedSeqNrs {
|
||||
q := querySlice.Payments[i]
|
||||
if seqNr != q.SequenceNum {
|
||||
t.Errorf("sequence numbers do not match, "+
|
||||
"got %v, want %v", q.SequenceNum, seqNr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1936,10 +1936,42 @@ var listPaymentsCommand = cli.Command{
|
||||
Name: "listpayments",
|
||||
Category: "Payments",
|
||||
Usage: "List all outgoing payments.",
|
||||
Description: "This command enables the retrieval of payments stored " +
|
||||
"in the database. Pagination is supported by the usage of " +
|
||||
"index_offset in combination with the paginate_forwards flag. " +
|
||||
"Reversed pagination is enabled by default to receive " +
|
||||
"current payments first. Pagination can be resumed by using " +
|
||||
"the returned last_index_offset (for forwards order), or " +
|
||||
"first_index_offset (for reversed order) as the offset_index. ",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "include_incomplete",
|
||||
Usage: "if set to true, payments still in flight (or failed) will be returned as well",
|
||||
Usage: "if set to true, payments still in flight (or " +
|
||||
"failed) will be returned as well, keeping" +
|
||||
"indices for payments the same as without " +
|
||||
"the flag",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "index_offset",
|
||||
Usage: "The index of a payment that will be used as " +
|
||||
"either the start (in forwards mode) or end " +
|
||||
"(in reverse mode) of a query to determine " +
|
||||
"which payments should be returned in the " +
|
||||
"response, where the index_offset is " +
|
||||
"excluded. If index_offset is set to zero in " +
|
||||
"reversed mode, the query will end with the " +
|
||||
"last payment made.",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "max_payments",
|
||||
Usage: "the max number of payments to return, by " +
|
||||
"default, all completed payments are returned",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "paginate_forwards",
|
||||
Usage: "if set, payments succeeding the " +
|
||||
"index_offset will be returned, allowing " +
|
||||
"forwards pagination",
|
||||
},
|
||||
},
|
||||
Action: actionDecorator(listPayments),
|
||||
@ -1951,6 +1983,9 @@ func listPayments(ctx *cli.Context) error {
|
||||
|
||||
req := &lnrpc.ListPaymentsRequest{
|
||||
IncludeIncomplete: ctx.Bool("include_incomplete"),
|
||||
IndexOffset: uint64(ctx.Uint("index_offset")),
|
||||
MaxPayments: uint64(ctx.Uint("max_payments")),
|
||||
Reversed: !ctx.Bool("paginate_forwards"),
|
||||
}
|
||||
|
||||
payments, err := client.ListPayments(context.Background(), req)
|
||||
|
1564
lnrpc/rpc.pb.go
1564
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -3102,6 +3102,13 @@ message Payment {
|
||||
|
||||
/// The HTLCs made in attempt to settle the payment [EXPERIMENTAL].
|
||||
repeated HTLCAttempt htlcs = 14;
|
||||
|
||||
/**
|
||||
The creation index of this payment. Each payment can be uniquely identified
|
||||
by this index, which may not strictly increment by 1 for payments made in
|
||||
older versions of lnd.
|
||||
*/
|
||||
uint64 payment_index = 15;
|
||||
}
|
||||
|
||||
message HTLCAttempt {
|
||||
@ -3134,14 +3141,46 @@ message ListPaymentsRequest {
|
||||
/**
|
||||
If true, then return payments that have not yet fully completed. This means
|
||||
that pending payments, as well as failed payments will show up if this
|
||||
field is set to True.
|
||||
field is set to true. This flag doesn't change the meaning of the indices,
|
||||
which are tied to individual payments.
|
||||
*/
|
||||
bool include_incomplete = 1;
|
||||
|
||||
/**
|
||||
The index of a payment that will be used as either the start or end of a
|
||||
query to determine which payments should be returned in the response. The
|
||||
index_offset is exclusive. In the case of a zero index_offset, the query
|
||||
will start with the oldest payment when paginating forwards, or will end
|
||||
with the most recent payment when paginating backwards.
|
||||
*/
|
||||
uint64 index_offset = 2;
|
||||
|
||||
/// The maximal number of payments returned in the response to this query.
|
||||
uint64 max_payments = 3;
|
||||
|
||||
/**
|
||||
If set, the payments returned will result from seeking backwards from the
|
||||
specified index offset. This can be used to paginate backwards. The order
|
||||
of the returned payments is always oldest first (ascending index order).
|
||||
*/
|
||||
bool reversed = 4;
|
||||
}
|
||||
|
||||
message ListPaymentsResponse {
|
||||
/// The list of payments
|
||||
repeated Payment payments = 1;
|
||||
|
||||
/**
|
||||
The index of the first item in the set of returned payments. This can be
|
||||
used as the index_offset to continue seeking backwards in the next request.
|
||||
*/
|
||||
uint64 first_index_offset = 2;
|
||||
|
||||
/**
|
||||
The index of the last item in the set of returned payments. This can be used
|
||||
as the index_offset to continue seeking forwards in the next request.
|
||||
*/
|
||||
uint64 last_index_offset = 3;
|
||||
}
|
||||
|
||||
message DeleteAllPaymentsRequest {
|
||||
|
@ -1155,7 +1155,31 @@
|
||||
"parameters": [
|
||||
{
|
||||
"name": "include_incomplete",
|
||||
"description": "*\nIf true, then return payments that have not yet fully completed. This means\nthat pending payments, as well as failed payments will show up if this\nfield is set to True.",
|
||||
"description": "*\nIf true, then return payments that have not yet fully completed. This means\nthat pending payments, as well as failed payments will show up if this\nfield is set to true. This flag doesn't change the meaning of the indices,\nwhich are tied to individual payments.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "index_offset",
|
||||
"description": "*\nThe index of a payment that will be used as either the start or end of a\nquery to determine which payments should be returned in the response. The\nindex_offset is exclusive. In the case of a zero index_offset, the query\nwill start with the oldest payment when paginating forwards, or will end\nwith the most recent payment when paginating backwards.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "max_payments",
|
||||
"description": "/ The maximal number of payments returned in the response to this query.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "reversed",
|
||||
"description": "*\nIf set, the payments returned will result from seeking backwards from the\nspecified index offset. This can be used to paginate backwards. The order\nof the returned payments is always oldest first (ascending index order).",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
@ -3494,6 +3518,16 @@
|
||||
"$ref": "#/definitions/lnrpcPayment"
|
||||
},
|
||||
"title": "/ The list of payments"
|
||||
},
|
||||
"first_index_offset": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "*\nThe index of the first item in the set of returned payments. This can be\nused as the index_offset to continue seeking backwards in the next request."
|
||||
},
|
||||
"last_index_offset": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "*\nThe index of the last item in the set of returned payments. This can be used\nas the index_offset to continue seeking forwards in the next request."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3957,6 +3991,11 @@
|
||||
"$ref": "#/definitions/lnrpcHTLCAttempt"
|
||||
},
|
||||
"description": "/ The HTLCs made in attempt to settle the payment [EXPERIMENTAL]."
|
||||
},
|
||||
"payment_index": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "*\nThe creation index of this payment. Each payment can be uniquely identified\nby this index, which may not strictly increment by 1 for payments made in\nolder versions of lnd."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4380,7 +4380,7 @@ func testListPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
t.Fatalf("Can't delete payments at the end: %v", err)
|
||||
}
|
||||
|
||||
// Check that there are no payments before test.
|
||||
// Check that there are no payments after test.
|
||||
listReq := &lnrpc.ListPaymentsRequest{}
|
||||
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
|
||||
paymentsResp, err = net.Alice.ListPayments(ctxt, listReq)
|
||||
|
30
rpcserver.go
30
rpcserver.go
@ -5153,26 +5153,37 @@ func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopol
|
||||
}
|
||||
}
|
||||
|
||||
// ListPayments returns a list of all outgoing payments.
|
||||
// ListPayments returns a list of outgoing payments determined by a paginated
|
||||
// database query.
|
||||
func (r *rpcServer) ListPayments(ctx context.Context,
|
||||
req *lnrpc.ListPaymentsRequest) (*lnrpc.ListPaymentsResponse, error) {
|
||||
|
||||
rpcsLog.Debugf("[ListPayments]")
|
||||
|
||||
payments, err := r.server.chanDB.FetchPayments()
|
||||
query := channeldb.PaymentsQuery{
|
||||
IndexOffset: req.IndexOffset,
|
||||
MaxPayments: req.MaxPayments,
|
||||
Reversed: req.Reversed,
|
||||
IncludeIncomplete: req.IncludeIncomplete,
|
||||
}
|
||||
|
||||
// If the maximum number of payments wasn't specified, then we'll
|
||||
// default to return the maximal number of payments representable.
|
||||
if req.MaxPayments == 0 {
|
||||
query.MaxPayments = math.MaxUint64
|
||||
}
|
||||
|
||||
paymentsQuerySlice, err := r.server.chanDB.QueryPayments(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paymentsResp := &lnrpc.ListPaymentsResponse{}
|
||||
for _, payment := range payments {
|
||||
// To keep compatibility with the old API, we only return
|
||||
// non-suceeded payments if requested.
|
||||
if payment.Status != channeldb.StatusSucceeded &&
|
||||
!req.IncludeIncomplete {
|
||||
continue
|
||||
paymentsResp := &lnrpc.ListPaymentsResponse{
|
||||
LastIndexOffset: paymentsQuerySlice.LastIndexOffset,
|
||||
FirstIndexOffset: paymentsQuerySlice.FirstIndexOffset,
|
||||
}
|
||||
|
||||
for _, payment := range paymentsQuerySlice.Payments {
|
||||
// Fetch the payment's route and preimage. If no HTLC was
|
||||
// successful, an empty route and preimage will be used.
|
||||
var (
|
||||
@ -5231,6 +5242,7 @@ func (r *rpcServer) ListPayments(ctx context.Context,
|
||||
PaymentRequest: string(payment.Info.PaymentRequest),
|
||||
Status: status,
|
||||
Htlcs: htlcs,
|
||||
PaymentIndex: payment.SequenceNum,
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user