Merge pull request #9558 from kornpow/fix-listchaintxns-lncli-inputs

Fix input sanitation for listchaintxns lncli cmd
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-03-24 13:53:29 -06:00
commit f7b3177483
3 files changed
+151 -20

No files matched your search

+40 -20
View File
@@ -2228,9 +2228,11 @@ var listChainTxnsCommand = cli.Command{
cli.Int64Flag{ cli.Int64Flag{
Name: "end_height", Name: "end_height",
Usage: "the block height until which to list " + Usage: "the block height until which to list " +
"transactions, inclusive, to get " + "transactions, inclusive; by default this " +
"transactions until the chain tip, including " + "will return all transactions up to the " +
"unconfirmed, set this value to -1", "chain tip including unconfirmed " +
"transactions",
Value: -1,
}, },
cli.UintFlag{ cli.UintFlag{
Name: "index_offset", Name: "index_offset",
@@ -2241,7 +2243,7 @@ var listChainTxnsCommand = cli.Command{
}, },
cli.IntFlag{ cli.IntFlag{
Name: "max_transactions", Name: "max_transactions",
Usage: "(optional) the max number of transactions to " + Usage: "the max number of transactions to " +
"return; leave at default of 0 to return " + "return; leave at default of 0 to return " +
"all transactions", "all transactions",
Value: 0, Value: 0,
@@ -2251,33 +2253,51 @@ var listChainTxnsCommand = cli.Command{
List all transactions an address of the wallet was involved in. List all transactions an address of the wallet was involved in.
This call will return a list of wallet related transactions that paid 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 to an address our wallet controls, or spent utxos that we held.
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 By default, this call will get all transactions until the chain tip,
less than the start_height, transactions will be queried in reverse. including unconfirmed transactions (end_height=-1).`,
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), Action: actionDecorator(listChainTxns),
} }
func parseBlockHeightInputs(ctx *cli.Context) (int32, int32, error) {
startHeight := int32(ctx.Int64("start_height"))
endHeight := int32(ctx.Int64("end_height"))
if ctx.IsSet("start_height") && ctx.IsSet("end_height") {
if endHeight != -1 && startHeight > endHeight {
return startHeight, endHeight,
errors.New("start_height should " +
"be less than end_height if " +
"end_height is not equal to -1")
}
}
if startHeight < 0 {
return startHeight, endHeight,
errors.New("start_height should " +
"be greater than or " +
"equal to 0")
}
return startHeight, endHeight, nil
}
func listChainTxns(ctx *cli.Context) error { func listChainTxns(ctx *cli.Context) error {
ctxc := getContext() ctxc := getContext()
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
defer cleanUp() defer cleanUp()
startHeight, endHeight, err := parseBlockHeightInputs(ctx)
if err != nil {
return err
}
req := &lnrpc.GetTransactionsRequest{ req := &lnrpc.GetTransactionsRequest{
IndexOffset: uint32(ctx.Uint64("index_offset")), IndexOffset: uint32(ctx.Uint64("index_offset")),
MaxTransactions: uint32(ctx.Uint64("max_transactions")), MaxTransactions: uint32(ctx.Uint64("max_transactions")),
} StartHeight: startHeight,
EndHeight: endHeight,
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(ctxc, req) resp, err := client.GetTransactions(ctxc, req)
+106
View File
@@ -2,12 +2,14 @@ package commands
import ( import (
"encoding/hex" "encoding/hex"
"flag"
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli"
) )
// TestParseChanPoint tests parseChanPoint with various // TestParseChanPoint tests parseChanPoint with various
@@ -328,3 +330,107 @@ func TestAppendChanID(t *testing.T) {
}) })
} }
} }
// TestParseBlockHeightInputs tests the input block heights and ensure that the
// proper errors are returned when they could lead in invalid results.
func TestParseBlockHeightInputs(t *testing.T) {
t.Parallel()
app := cli.NewApp()
startDefault, endDefault := int64(0), int64(-1)
testCases := []struct {
name string
expectedStart int32
expectedEnd int32
expectedErr string
}{
{
name: "start less than end",
expectedStart: 100,
expectedEnd: 200,
expectedErr: "",
},
{
name: "start greater than end",
expectedStart: 200,
expectedEnd: 100,
expectedErr: "start_height should be " +
"less than end_height if end_height is " +
"not equal to -1",
},
{
name: "only start height set",
expectedStart: 100,
expectedEnd: -1,
expectedErr: "",
},
{
name: "start set and end height set as -1",
expectedStart: 100,
expectedEnd: -1,
expectedErr: "",
},
{
name: "neither start nor end heights defined",
expectedStart: 0,
expectedEnd: -1,
expectedErr: "",
},
{
name: "only end height defined",
expectedStart: 0,
expectedEnd: 100,
expectedErr: "",
},
{
name: "start height is a negative",
expectedStart: -1,
expectedEnd: 100,
expectedErr: "start_height should be greater " +
"than or equal to 0",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
flagSet := flag.NewFlagSet(
"listchaintxns", flag.ContinueOnError,
)
var startHeight, endHeight int64
flagSet.Int64Var(
&startHeight, "start_height", startDefault, "",
)
flagSet.Int64Var(
&endHeight, "end_height", endDefault, "",
)
err := flagSet.Set(
"start_height",
strconv.Itoa(int(tc.expectedStart)),
)
require.NoError(
t, err, "failed to set start_height flag",
)
err = flagSet.Set(
"end_height", strconv.Itoa(int(tc.expectedEnd)),
)
require.NoError(
t, err, "failed to set end_height flag",
)
ctx := cli.NewContext(app, flagSet, nil)
start, end, err := parseBlockHeightInputs(ctx)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
require.Equal(t, tc.expectedStart, start)
require.Equal(t, tc.expectedEnd, end)
})
}
}
@@ -276,6 +276,10 @@ close transaction.
access the RPC server would give an error saying the wallet is encrypted. This access the RPC server would give an error saying the wallet is encrypted. This
is now fixed to show specifically which RPC server is missing. is now fixed to show specifically which RPC server is missing.
* [Enhanced](https://github.com/lightningnetwork/lnd/pull/9558)
`listchaintxns` command with improved input validation, robust error handling,
tests, and updated documentation.
## Code Health ## Code Health
* [Add retry logic](https://github.com/lightningnetwork/lnd/pull/8381) for * [Add retry logic](https://github.com/lightningnetwork/lnd/pull/8381) for
@@ -465,6 +469,7 @@ The underlying functionality between those two options remain the same.
* Oliver Gugger * Oliver Gugger
* Olaoluwa Osuntokun * Olaoluwa Osuntokun
* Pins * Pins
* kornpow
* Viktor Tigerström * Viktor Tigerström
* Yong Yu * Yong Yu
* Ziggie * Ziggie