Skip to content

Commit

Permalink
wallet: add GetTransaction returning data for any transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
bjarnemagnussen committed Nov 19, 2021
1 parent 9043c19 commit 03046ed
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,175 @@ func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier,
return &res, err
}

// GetTransactionResult returns a transaction in the UnminedTransaction
// field if it is unmined, or in the MinedTransaction field as a Block
// structure for a mined transaction.
type GetTransactionResult struct {
MinedTransaction *Block
UnminedTransaction *TransactionSummary
}

// GetTransaction returns data of any transaction given its id. A mined
// transaction is returned in a Block structure which records properties about
// the block. A bool is also returned to denote if the transaction previously
// existed in the database or not.
func (w *Wallet) GetTransaction(txHash *chainhash.Hash) (*GetTransactionResult,
bool, error) {

var res GetTransactionResult
chainClient := w.chainClient
if chainClient == nil {
return nil, false, errors.New("no chain server client")
}

// In case the transaction is unconfirmed it could due to race condition
// confirm during the time we receive it from the backend and check it
// in the database.
// We therefore store the best block height and will use it for quering the
// database.
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return nil, false, errors.New("cannot get best block")
}

// Get the transaction information from directly calling the backend
// endpoint.
var txResult *btcjson.TxRawResult
switch client := chainClient.(type) {
case *chain.RPCClient:
txResult, err = client.GetRawTransactionVerbose(txHash)
if err != nil {
return nil, false, err
}
case *chain.BitcoindClient:
txResult, err = client.GetRawTransactionVerbose(txHash)
if err != nil {
return nil, false, err
}
case *chain.NeutrinoClient:
return nil, false, errors.New("not supported with neutrino client")
}

// Set the block hash from the provided string and get the block height
// from it.
//
// TODO: Fetching block heights by their hashes is inherently racy
// because not all block headers are saved but when they are for SPV the
// db can be queried directly without this.
var blockHash *chainhash.Hash
var height int32 = -1
if txResult.BlockHash != "" {
blockHashBytes, err := hex.DecodeString(txResult.BlockHash)
if err != nil {
return nil, false, err
}

// The byte is reversed due to different endianness in response
// and call.
reversed := make([]byte, len(blockHashBytes))
for i, n := range blockHashBytes {
j := len(blockHashBytes) - i - 1
reversed[j] = n
}
blockHash, err = chainhash.NewHash(reversed)
if err != nil {
return nil, false, err
}

// Obtain the block height from the backend.
switch client := chainClient.(type) {
case *chain.RPCClient:
header, err := client.GetBlockHeaderVerbose(blockHash)
if err != nil {
return nil, false, err
}
height = header.Height
case *chain.BitcoindClient:
height, err = client.GetBlockHeight(blockHash)
if err != nil {
return nil, false, err
}
case *chain.NeutrinoClient:
height, err = client.GetBlockHeight(blockHash)
if err != nil {
return nil, false, err
}
}

// We know that the transaction definitively is in a specific
// block height and set both start and end height to it.
bestHeight = height
}

// Populate with additional data if this transaction exists in the
// database.
var summary *TransactionSummary
var inDB bool
err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)

rangeFn := func(details []wtxmgr.TxDetails) (bool, error) {
// TODO: probably should make RangeTransactions not
// reuse the details backing array memory.
dets := make([]wtxmgr.TxDetails, len(details))
copy(dets, details)
details = dets

for i := range details {
dbTx := makeTxSummary(dbtx, w, &details[i])

// We are only interested in the specific
// transaction and can return early.
if txHash.IsEqual(dbTx.Hash) {
summary, inDB = &dbTx, true
height = details[i].Block.Height
blockHash = &details[i].Block.Hash
return true, nil
}
}
return false, nil
}

return w.TxStore.RangeTransactions(txmgrNs, bestHeight,
height, rangeFn)
})
if err != nil {
return nil, false, err
}

// We need to build the transaction to return as the response if it was
// not found in the database.
if summary == nil {
txRaw, err := hex.DecodeString(txResult.Hex)
if err != nil {
return nil, false, err
}
summary = &TransactionSummary{
Hash: txHash,
Transaction: txRaw,
MyInputs: make([]TransactionSummaryInput, 0),
MyOutputs: make([]TransactionSummaryOutput, 0),
Timestamp: txResult.Time,
}
}

// Add the transaction either as confirmed or unconfirmed to the
// response.
switch blockHash {
case nil:
res.UnminedTransaction = summary
default:
res.MinedTransaction = &Block{
Hash: blockHash,
Height: height,
Timestamp: summary.Timestamp,
Transactions: []TransactionSummary{*summary},
}
}

return &res, inDB, nil
}

// AccountResult is a single account result for the AccountsResult type.
type AccountResult struct {
waddrmgr.AccountProperties
Expand Down

0 comments on commit 03046ed

Please sign in to comment.