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 4, 2023
1 parent 3c8c057 commit eb972b5
Show file tree
Hide file tree
Showing 11 changed files with 3,052 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 the tx hash: %w", err)
}
}

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
4 changes: 2 additions & 2 deletions lnrpc/walletrpc/walletkit_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -951,8 +951,8 @@ func (w *WalletKit) ListSweeps(ctx context.Context,
// can match our list of sweeps against the list of transactions that
// 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,
transactions, err := w.cfg.Wallet.ListTransactionDetails(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
215 changes: 214 additions & 1 deletion lnwallet/btcwallet/btcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,213 @@ func unminedTransactionsToDetail(
return txDetail, nil
}

// lookupInputAccount returns the input account.
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
}

// lookupOutputChain returns the output chain.
func lookupOutputChain(dbtx walletdb.ReadTx, w *base.Wallet,

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

View workflow job for this annotation

GitHub Actions / lint code

named return "account" with type "uint32" found (nonamedreturns)
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
}

// getTransactionDetails returns transaction details of a transaction given the
// transaction hash.
func getTransactionDetails(w *base.Wallet, txHash *chainhash.Hash,
currentHeight int32,
chainParams *chaincfg.Params) ([]*lnwallet.TransactionDetail, error) {

var dbTx *wtxmgr.TxDetails
var inputs []base.TransactionSummaryInput
var outputs []base.TransactionSummaryOutput

err := walletdb.View(w.Database(), func(tx walletdb.ReadTx) error {
var err error
wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)

dbTx, err = w.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,
w, 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, w, 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
}
if dbTx == nil {
return nil, nil
}

// isOurAddress is a map containing the output indices controlled by
// the wallet.
// Note: We make use of the information in `outputs` provided
// by the `wallet.TransactionSummaryOutput` 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, chainParams,
)
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 outputs {
balanceDelta += btcutil.Amount(dbTx.MsgTx.TxOut[output.Index].
Value)
}

txDetail := &lnwallet.TransactionDetail{
Hash: dbTx.Hash,
Value: balanceDelta,
BlockHash: &dbTx.Block.Hash,
BlockHeight: dbTx.Block.Height,
Timestamp: dbTx.Block.Time.Unix(),
TotalFees: int64(fee),
OutputDetails: outputDetails,
RawTx: serializedTx,
Label: dbTx.Label,
PreviousOutpoints: previousOutpoints,
}

// Check if the transaction is mined.
if dbTx.Block.Height > 0 {
txDetail.NumConfirmations = currentHeight -
dbTx.Block.Height + 1
}

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

// 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 +1591,19 @@ 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 {
return getTransactionDetails(b.wallet, txHash, currentHeight,
b.netParams)
}

// We'll attempt to find all transactions from start to end height.
start := base.NewBlockIdentifierFromHeight(startHeight)
stop := base.NewBlockIdentifierFromHeight(endHeight)
Expand Down
1 change: 1 addition & 0 deletions lnwallet/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ type WalletController interface {
// retrieve the transactions relevant to a specific account. When
// empty, transactions of all wallet accounts are returned.
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 @@ -187,7 +187,7 @@ func (w *mockWalletController) ListUnspentWitness(int32, int32,

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

return nil, nil
}
Expand Down

0 comments on commit eb972b5

Please sign in to comment.