multi: specify start and end height for ListTransactionDetails

Add start and end height parameters to the rpc and cli GetTransactions
endpoints. Default to returning all transactions from genesis to tip,
including unconfirmed transactions to maintain backwards compatibility.
This commit is contained in:
carla committed 2020-05-05 21:10:06 +02:00
1 parent fcc18b6815
commit 537dac3c62
10 files changed
+913 -736

No files matched your search

+40 -6
View File
@@ -2295,11 +2295,37 @@ func debugLevel(ctx *cli.Context) error {
}
var listChainTxnsCommand = cli.Command{
Name: "listchaintxns",
Category: "On-chain",
Usage: "List transactions from the wallet.",
Description: "List all transactions an address of the wallet was involved in.",
Action: actionDecorator(listChainTxns),
Name: "listchaintxns",
Category: "On-chain",
Usage: "List transactions from the wallet.",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "start_height",
Usage: "the block height from which to list " +
"transactions, inclusive",
},
cli.Int64Flag{
Name: "end_height",
Usage: "the block height until which to list " +
"transactions, inclusive, to get transactions " +
"until the chain tip, including unconfirmed, " +
"set this value to -1",
},
},
Description: `
List all transactions an address of the wallet was involved in.
This call will return a list of wallet related transactions that paid
to an address our wallet controls, or spent utxos that we held. The
start_height and end_height flags can be used to specify an inclusive
block range over which to query for transactions. If the end_height is
less than the start_height, transactions will be queried in reverse.
To get all transactions until the chain tip, including unconfirmed
transactions (identifiable with BlockHeight=0), set end_height to -1.
By default, this call will get all transactions our wallet was involved
in, including unconfirmed transactions.
`,
Action: actionDecorator(listChainTxns),
}
func listChainTxns(ctx *cli.Context) error {
@@ -2307,8 +2333,16 @@ func listChainTxns(ctx *cli.Context) error {
client, cleanUp := getClient(ctx)
defer cleanUp()
resp, err := client.GetTransactions(ctxb, &lnrpc.GetTransactionsRequest{})
req := &lnrpc.GetTransactionsRequest{}
if ctx.IsSet("start_height") {
req.StartHeight = int32(ctx.Int64("start_height"))
}
if ctx.IsSet("end_height") {
req.EndHeight = int32(ctx.Int64("end_height"))
}
resp, err := client.GetTransactions(ctxb, req)
if err != nil {
return err
}
+733 -707
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -114,10 +114,18 @@ func request_Lightning_ChannelBalance_0(ctx context.Context, marshaler runtime.M
}
var (
filter_Lightning_GetTransactions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_GetTransactions_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionsRequest
var metadata runtime.ServerMetadata
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_Lightning_GetTransactions_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetTransactions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
+15
View File
@@ -897,7 +897,22 @@ message Transaction {
string raw_tx_hex = 9;
}
message GetTransactionsRequest {
/*
The height from which to list transactions, inclusive. If this value is
greater than end_height, transactions will be read in reverse.
*/
int32 start_height = 1;
/*
The height until which to list transactions, inclusive. To include
unconfirmed transactions, this value should be set to -1, which will
return transactions from start_height until the current chain tip and
unconfirmed transactions. If no end_height is provided, the call will
default to this option.
*/
int32 end_height = 2;
}
message TransactionDetails {
/// The list of transactions relevant to the wallet.
repeated Transaction transactions = 1;
+18
View File
@@ -1377,6 +1377,24 @@
}
}
},
"parameters": [
{
"name": "start_height",
"description": "The height from which to list transactions, inclusive. If this value is\ngreater than end_height, transactions will be read in reverse.",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "end_height",
"description": "The height until which to list transactions, inclusive. To include\nunconfirmed transactions, this value should be set to -1, which will\nreturn transactions from start_height until the current chain tip and\nunconfirmed transactions. If no end_height is provided, the call will\ndefault to this option.",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
}
],
"tags": [
"Lightning"
]
+13 -7
View File
@@ -26,6 +26,10 @@ import (
const (
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
// UnconfirmedHeight is the special case end height that is used to
// obtain unconfirmed transactions from ListTransactionDetails.
UnconfirmedHeight int32 = -1
)
var (
@@ -569,20 +573,22 @@ func unminedTransactionsToDetail(
}
// ListTransactionDetails returns a list of all transactions which are
// relevant to the wallet.
// relevant to the wallet. It takes inclusive start and end height to allow
// paginated queries. Unconfirmed transactions can be included in the query
// by providing endHeight = UnconfirmedHeight (= -1).
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
func (b *BtcWallet) ListTransactionDetails(startHeight,
endHeight int32) ([]*lnwallet.TransactionDetail, error) {
// Grab the best block the wallet knows of, we'll use this to calculate
// # of confirmations shortly below.
bestBlock := b.wallet.Manager.SyncedTo()
currentHeight := bestBlock.Height
// We'll attempt to find all unconfirmed transactions (height of -1),
// as well as all transactions that are known to have confirmed at this
// height.
start := base.NewBlockIdentifierFromHeight(0)
stop := base.NewBlockIdentifierFromHeight(-1)
// We'll attempt to find all transactions from start to end height.
start := base.NewBlockIdentifierFromHeight(startHeight)
stop := base.NewBlockIdentifierFromHeight(endHeight)
txns, err := b.wallet.GetTransactions(start, stop, nil)
if err != nil {
return nil, err
+8 -2
View File
@@ -199,8 +199,14 @@ type WalletController interface {
ListUnspentWitness(minconfirms, maxconfirms int32) ([]*Utxo, error)
// ListTransactionDetails returns a list of all transactions which are
// relevant to the wallet.
ListTransactionDetails() ([]*TransactionDetail, error)
// relevant to the wallet over [startHeight;endHeight]. If start height
// is greater than end height, the transactions will be retrieved in
// reverse order. To include unconfirmed transactions, endHeight should
// be set to the special value -1. This will return transactions from
// the tip of the chain until the start height (inclusive) and
// unconfirmed transactions.
ListTransactionDetails(startHeight,
endHeight int32) ([]*TransactionDetail, error)
// LockOutpoint marks an outpoint as locked meaning it will no longer
// be deemed as eligible for coin selection. Locking outputs are
+62 -10
View File
@@ -206,7 +206,7 @@ func assertTxInWallet(t *testing.T, w *lnwallet.LightningWallet,
// We'll fetch all of our transaction and go through each one until
// finding the expected transaction with its expected confirmation
// status.
txs, err := w.ListTransactionDetails()
txs, err := w.ListTransactionDetails(0, btcwallet.UnconfirmedHeight)
if err != nil {
t.Fatalf("unable to retrieve transactions: %v", err)
}
@@ -1101,6 +1101,12 @@ func testListTransactionDetails(miner *rpctest.Harness,
txids[*txid] = struct{}{}
}
// Get the miner's current best block height before we mine blocks.
_, startHeight, err := miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("cannot get best block: %v", err)
}
// Generate 10 blocks to mine all the transactions created above.
const numBlocksMined = 10
blocks, err := miner.Node.Generate(numBlocksMined)
@@ -1108,12 +1114,22 @@ func testListTransactionDetails(miner *rpctest.Harness,
t.Fatalf("unable to mine blocks: %v", err)
}
// Next, fetch all the current transaction details.
// Our new best block height should be our start height + the number of
// blocks we just mined.
chainTip := startHeight + numBlocksMined
// Next, fetch all the current transaction details. We should find all
// of our transactions between our start height before we generated
// blocks, and our end height, which is the chain tip. This query does
// not include unconfirmed transactions, since all of our transactions
// should be confirmed.
err = waitForWalletSync(miner, alice)
if err != nil {
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
}
txDetails, err := alice.ListTransactionDetails()
txDetails, err := alice.ListTransactionDetails(
startHeight, chainTip,
)
if err != nil {
t.Fatalf("unable to fetch tx details: %v", err)
}
@@ -1219,10 +1235,13 @@ func testListTransactionDetails(miner *rpctest.Harness,
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
}
// We should be able to find the transaction above in the set of
// returned transactions, and it should have a confirmation of -1,
// indicating that it's not yet mined.
txDetails, err = alice.ListTransactionDetails()
// Query our wallet for transactions from the chain tip, including
// unconfirmed transactions. The transaction above should be included
// with a confirmation height of 0, indicating that it has not been
// mined yet.
txDetails, err = alice.ListTransactionDetails(
chainTip, btcwallet.UnconfirmedHeight,
)
if err != nil {
t.Fatalf("unable to fetch tx details: %v", err)
}
@@ -1259,18 +1278,27 @@ func testListTransactionDetails(miner *rpctest.Harness,
t.Fatalf("unable to find mempool tx in tx details!")
}
burnBlock, err := miner.Node.Generate(1)
// Generate one block for our transaction to confirm in.
var numBlocks int32 = 1
burnBlock, err := miner.Node.Generate(uint32(numBlocks))
if err != nil {
t.Fatalf("unable to mine block: %v", err)
}
// Progress our chain tip by the number of blocks we have just mined.
chainTip += numBlocks
// Fetch the transaction details again, the new transaction should be
// shown as debiting from the wallet's balance.
// shown as debiting from the wallet's balance. Start and end height
// are inclusive, so we use chainTip for both parameters to get only
// transactions from the last block.
err = waitForWalletSync(miner, alice)
if err != nil {
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
}
txDetails, err = alice.ListTransactionDetails()
txDetails, err = alice.ListTransactionDetails(
chainTip, chainTip,
)
if err != nil {
t.Fatalf("unable to fetch tx details: %v", err)
}
@@ -1302,6 +1330,30 @@ func testListTransactionDetails(miner *rpctest.Harness,
if !burnTxFound {
t.Fatal("tx burning btc not found")
}
// Generate a block which has no wallet transactions in it.
chainTip += numBlocks
_, err = miner.Node.Generate(uint32(numBlocks))
if err != nil {
t.Fatalf("unable to mine block: %v", err)
}
err = waitForWalletSync(miner, alice)
if err != nil {
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
}
// Query for transactions only in the latest block. We do not expect
// any transactions to be returned.
txDetails, err = alice.ListTransactionDetails(
chainTip, chainTip,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(txDetails) != 0 {
t.Fatalf("expected 0 transactions, got: %v", len(txDetails))
}
}
func testTransactionSubscriptions(miner *rpctest.Harness,
+1 -1
View File
@@ -313,7 +313,7 @@ func (m *mockWalletController) ListUnspentWitness(minconfirms,
ret = append(ret, utxo)
return ret, nil
}
func (*mockWalletController) ListTransactionDetails() ([]*lnwallet.TransactionDetail, error) {
func (*mockWalletController) ListTransactionDetails(_, _ int32) ([]*lnwallet.TransactionDetail, error) {
return nil, nil
}
func (*mockWalletController) LockOutpoint(o wire.OutPoint) {}
+15 -3
View File
@@ -50,6 +50,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
@@ -4548,10 +4549,21 @@ func (r *rpcServer) SubscribeTransactions(req *lnrpc.GetTransactionsRequest,
// GetTransactions returns a list of describing all the known transactions
// relevant to the wallet.
func (r *rpcServer) GetTransactions(ctx context.Context,
_ *lnrpc.GetTransactionsRequest) (*lnrpc.TransactionDetails, error) {
req *lnrpc.GetTransactionsRequest) (*lnrpc.TransactionDetails, error) {
// TODO(roasbeef): add pagination support
transactions, err := r.server.cc.wallet.ListTransactionDetails()
// To remain backwards compatible with the old api, default to the
// special case end height which will return transactions from the start
// height until the chain tip, including unconfirmed transactions.
var endHeight int32 = btcwallet.UnconfirmedHeight
// If the user has provided an end height, we overwrite our default.
if req.EndHeight != 0 {
endHeight = req.EndHeight
}
transactions, err := r.server.cc.wallet.ListTransactionDetails(
req.StartHeight, endHeight,
)
if err != nil {
return nil, err
}