Skip to content

Commit

Permalink
rpc: add txhash param to listchaintxns
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikEk committed May 1, 2023
1 parent 3c8c057 commit 020621b
Show file tree
Hide file tree
Showing 11 changed files with 3,000 additions and 2,769 deletions.
11 changes: 11 additions & 0 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,10 @@ var listChainTxnsCommand = cli.Command{
"until the chain tip, including unconfirmed, " +
"set this value to -1",
},
cli.StringFlag{
Name: "tx_hash",
Usage: "return the transaction with this hash",
},
},
Description: `
List all transactions an address of the wallet was involved in.
Expand All @@ -1817,6 +1821,7 @@ var listChainTxnsCommand = cli.Command{
}

func listChainTxns(ctx *cli.Context) error {
var err error
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
Expand All @@ -1829,6 +1834,12 @@ func listChainTxns(ctx *cli.Context) error {
if ctx.IsSet("end_height") {
req.EndHeight = int32(ctx.Int64("end_height"))
}
if ctx.IsSet("tx_hash") {
req.TxHash, err = hex.DecodeString(ctx.String("tx_hash"))
if err != nil {
return fmt.Errorf("error decoding tx_hash: %v", err)

Check failure on line 1840 in cmd/lncli/commands.go

View workflow job for this annotation

GitHub Actions / lint code

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}
}

resp, err := client.GetTransactions(ctxc, req)
if err != nil {
Expand Down
5,526 changes: 2,769 additions & 2,757 deletions lnrpc/lightning.pb.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions lnrpc/lightning.proto
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,10 @@ message GetTransactionsRequest {

// An optional filter to only include transactions relevant to an account.
string account = 3;

// The transaction hash. When using REST, this field must be encoded as
// base64.
bytes tx_hash = 4;
}

message TransactionDetails {
Expand Down
16 changes: 16 additions & 0 deletions lnrpc/lightning.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,14 @@
"in": "query",
"required": false,
"type": "string"
},
{
"name": "tx_hash",
"description": "The transaction hash. When using REST, this field must be encoded as\nbase64.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
}
],
"tags": [
Expand Down Expand Up @@ -2682,6 +2690,14 @@
"in": "query",
"required": false,
"type": "string"
},
{
"name": "tx_hash",
"description": "The transaction hash. When using REST, this field must be encoded as\nbase64.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
}
],
"tags": [
Expand Down
2 changes: 1 addition & 1 deletion lnrpc/walletrpc/walletkit_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ func (w *WalletKit) ListSweeps(ctx context.Context,
// the wallet is still tracking. Sweeps are currently always swept to
// the default wallet account.
transactions, err := w.cfg.Wallet.ListTransactionDetails(
0, btcwallet.UnconfirmedHeight, lnwallet.DefaultAccountName,
0, btcwallet.UnconfirmedHeight, nil, lnwallet.DefaultAccountName,
)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion lntest/mock/walletcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func (w *WalletController) ListUnspentWitness(int32, int32,
}

// ListTransactionDetails currently returns dummy values.
func (w *WalletController) ListTransactionDetails(int32, int32,
func (w *WalletController) ListTransactionDetails(int32, int32, *chainhash.Hash,
string) ([]*lnwallet.TransactionDetail, error) {

return nil, nil
Expand Down
182 changes: 181 additions & 1 deletion lnwallet/btcwallet/btcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,55 @@ func unminedTransactionsToDetail(
return txDetail, nil
}

func lookupInputAccount(dbtx walletdb.ReadTx, w *base.Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 {
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)

// TODO: Debits should record which account(s?) they
// debit from so this doesn't need to be looked up.
prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
prev, err := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash)
if err != nil {
log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err)
return 0
}
if prev == nil {
log.Errorf("Missing previous transaction %v", prevOP.Hash)
return 0
}
prevOut := prev.MsgTx.TxOut[prevOP.Index]
_, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.ChainParams())
var inputAcct uint32
if err == nil && len(addrs) > 0 {
_, inputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0])
}
if err != nil {
log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err)
inputAcct = 0
}
return inputAcct

Check failure on line 1402 in lnwallet/btcwallet/btcwallet.go

View workflow job for this annotation

GitHub Actions / lint code

return with no blank line before (nlreturn)
}

func lookupOutputChain(dbtx walletdb.ReadTx, w *base.Wallet, details *wtxmgr.TxDetails,
cred wtxmgr.CreditRecord) (account uint32, internal bool) {

addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)

output := details.MsgTx.TxOut[cred.Index]
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.ChainParams())
var ma waddrmgr.ManagedAddress
if err == nil && len(addrs) > 0 {
ma, err = w.Manager.Address(addrmgrNs, addrs[0])
}
if err != nil {
log.Errorf("Cannot fetch account for wallet output: %v", err)
} else {
account = ma.InternalAccount()
internal = ma.Internal()
}
return

Check failure on line 1422 in lnwallet/btcwallet/btcwallet.go

View workflow job for this annotation

GitHub Actions / lint code

return with no blank line before (nlreturn)
}

// ListTransactionDetails returns a list of all transactions which are 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
Expand All @@ -1384,13 +1433,144 @@ func unminedTransactionsToDetail(
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
accountFilter string) ([]*lnwallet.TransactionDetail, error) {
txHash *chainhash.Hash, accountFilter string,
) ([]*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

if txHash != nil { // Fixa

Check failure on line 1444 in lnwallet/btcwallet/btcwallet.go

View workflow job for this annotation

GitHub Actions / lint code

`if txHash != nil` has complex nested blocks (complexity: 10) (nestif)
var dbTx *wtxmgr.TxDetails
var err error
var inputs []base.TransactionSummaryInput
var outputs []base.TransactionSummaryOutput

err = walletdb.View(b.wallet.Database(), func(tx walletdb.ReadTx) error {

wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)

dbTx, err = b.wallet.TxStore.TxDetails(wtxmgrNs, txHash)
if err != nil {
return err
}
if dbTx == nil {
return nil
}

if len(dbTx.Debits) != 0 {
inputs = make([]base.TransactionSummaryInput, len(dbTx.Debits))
for i, d := range dbTx.Debits {
inputs[i] = base.TransactionSummaryInput{
Index: d.Index,
PreviousAccount: lookupInputAccount(tx, b.wallet, dbTx, d),
PreviousAmount: d.Amount,
}
}
}

outputs = make([]base.TransactionSummaryOutput, 0, len(dbTx.MsgTx.TxOut))
for i := range dbTx.MsgTx.TxOut {
credIndex := len(outputs)
mine := len(dbTx.Credits) > credIndex && dbTx.Credits[credIndex].Index == uint32(i)
if !mine {
continue
}
acct, internal := lookupOutputChain(tx, b.wallet, dbTx, dbTx.Credits[credIndex])
output := base.TransactionSummaryOutput{
Index: uint32(i),
Account: acct,
Internal: internal,
}
outputs = append(outputs, output)
}

return err
})

if err != nil {
return nil, err
}

// isOurAddress is a map containing the output indices controlled by
// the wallet.
// Note: We make use of the information in `output` provided
// by the `wallet.TransactionSummary` structure that holds information
// only if the output is controlled by the wallet.
isOurAddress := make(map[int]bool, len(outputs))
for _, o := range outputs {
isOurAddress[int(o.Index)] = true
}

previousOutpoints := getPreviousOutpoints(&dbTx.MsgTx, inputs)

var outputDetails []lnwallet.OutputDetail
for i, txOut := range dbTx.MsgTx.TxOut {
var addresses []btcutil.Address
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, b.netParams,
)
if err == nil {
// Add supported addresses.
addresses = outAddresses
}

outputDetails = append(outputDetails, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addresses,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[i],
})
}

var fee btcutil.Amount
if len(dbTx.Debits) == len(dbTx.MsgTx.TxIn) {
for _, deb := range dbTx.Debits {
fee += deb.Amount
}
for _, txOut := range dbTx.MsgTx.TxOut {
fee -= btcutil.Amount(txOut.Value)
}
}

serializedTx := dbTx.SerializedTx
if serializedTx == nil {
var buf bytes.Buffer
err := dbTx.MsgTx.Serialize(&buf)
if err != nil {
return nil, err
}
serializedTx = buf.Bytes()
}

var balanceDelta btcutil.Amount
for _, input := range inputs {
balanceDelta -= input.PreviousAmount
}
for _, output := range dbTx.MsgTx.TxOut {
balanceDelta += btcutil.Amount(output.Value)
}

txDetail := &lnwallet.TransactionDetail{
Hash: dbTx.Hash,
Value: balanceDelta,
NumConfirmations: currentHeight - dbTx.Block.Height + 1, // check this
BlockHash: &dbTx.Block.Hash,
BlockHeight: dbTx.Block.Height,
Timestamp: dbTx.Block.Time.Unix(),
TotalFees: int64(fee), // Check
OutputDetails: outputDetails,
RawTx: serializedTx,
Label: dbTx.Label,
PreviousOutpoints: previousOutpoints,
}

return []*lnwallet.TransactionDetail{txDetail}, err
}

// We'll attempt to find all transactions from start to end height.
start := base.NewBlockIdentifierFromHeight(startHeight)
stop := base.NewBlockIdentifierFromHeight(endHeight)
Expand Down
2 changes: 1 addition & 1 deletion lnwallet/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ type WalletController interface {
// unconfirmed transactions. The account parameter serves as a filter to
// retrieve the transactions relevant to a specific account. When
// empty, transactions of all wallet accounts are returned.
ListTransactionDetails(startHeight, endHeight int32,
ListTransactionDetails(startHeight, endHeight int32, txHash *chainhash.Hash,
accountFilter string) ([]*TransactionDetail, error)

// LockOutpoint marks an outpoint as locked meaning it will no longer
Expand Down
2 changes: 1 addition & 1 deletion lnwallet/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (w *mockWalletController) ListUnspentWitness(int32, int32,
}

// ListTransactionDetails currently returns dummy values.
func (w *mockWalletController) ListTransactionDetails(int32, int32,
func (w *mockWalletController) ListTransactionDetails(int32, int32, *chainhash.Hash,
string) ([]*TransactionDetail, error) {

return nil, nil
Expand Down
10 changes: 5 additions & 5 deletions lnwallet/test/test_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,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(0, btcwallet.UnconfirmedHeight, "")
txs, err := w.ListTransactionDetails(0, btcwallet.UnconfirmedHeight, nil, "")
require.NoError(t, err, "unable to retrieve transactions")
for _, tx := range txs {
if tx.Hash != txHash {
Expand Down Expand Up @@ -1129,7 +1129,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
txDetails, err := alice.ListTransactionDetails(
startHeight, chainTip, "",
startHeight, chainTip, nil, "",
)
require.NoError(t, err, "unable to fetch tx details")

Expand Down Expand Up @@ -1238,7 +1238,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
// with a confirmation height of 0, indicating that it has not been
// mined yet.
txDetails, err = alice.ListTransactionDetails(
chainTip, btcwallet.UnconfirmedHeight, "",
chainTip, btcwallet.UnconfirmedHeight, nil, "",
)
require.NoError(t, err, "unable to fetch tx details")
var mempoolTxFound bool
Expand Down Expand Up @@ -1290,7 +1290,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
// transactions from the last block.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
txDetails, err = alice.ListTransactionDetails(chainTip, chainTip, "")
txDetails, err = alice.ListTransactionDetails(chainTip, chainTip, nil, "")
require.NoError(t, err, "unable to fetch tx details")
var burnTxFound bool
for _, txDetail := range txDetails {
Expand Down Expand Up @@ -1331,7 +1331,7 @@ func testListTransactionDetails(miner *rpctest.Harness,

// Query for transactions only in the latest block. We do not expect
// any transactions to be returned.
txDetails, err = alice.ListTransactionDetails(chainTip, chainTip, "")
txDetails, err = alice.ListTransactionDetails(chainTip, chainTip, nil, "")
require.NoError(t, err, "unexpected error")
if len(txDetails) != 0 {
t.Fatalf("expected 0 transactions, got: %v", len(txDetails))
Expand Down
12 changes: 10 additions & 2 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5781,14 +5781,22 @@ func (r *rpcServer) GetTransactions(ctx context.Context,
// special case end height which will return transactions from the start
// height until the chain tip, including unconfirmed transactions.
var endHeight = btcwallet.UnconfirmedHeight

var hash *chainhash.Hash
var err error
// If the user has provided an end height, we overwrite our default.
if req.EndHeight != 0 {
endHeight = req.EndHeight
}

if req.TxHash != nil {
strHash := hex.EncodeToString(req.TxHash)
hash, err = chainhash.NewHashFromStr(strHash)
if err != nil {
return nil, err
}
}
transactions, err := r.server.cc.Wallet.ListTransactionDetails(
req.StartHeight, endHeight, req.Account,
req.StartHeight, endHeight, hash, req.Account,
)
if err != nil {
return nil, err
Expand Down

0 comments on commit 020621b

Please sign in to comment.