Skip to content

Commit

Permalink
Merge pull request #890 from ErikEk/add-gettransaction
Browse files Browse the repository at this point in the history
wallet: add gettransaction
  • Loading branch information
Roasbeef authored Oct 30, 2023
2 parents 2795f3d + 4453270 commit 9c13542
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 0 deletions.
53 changes: 53 additions & 0 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ var (
// to true.
ErrTxLabelExists = errors.New("transaction already labelled")

// ErrNoTx is returned when a transaction can not be found.
ErrNoTx = errors.New("can not find transaction")

// ErrTxUnsigned is returned when a transaction is created in the
// watch-only mode where we can select coins but not sign any inputs.
ErrTxUnsigned = errors.New("watch-only wallet, transaction not signed")
Expand Down Expand Up @@ -2464,6 +2467,56 @@ func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier,
return &res, err
}

// GetTransactionResult returns a summary of the transaction along with
// other block properties.
type GetTransactionResult struct {
Summary TransactionSummary
Height int32
BlockHash *chainhash.Hash
Confirmations int32
Timestamp int64
}

// GetTransaction returns detailed data of a transaction given its id. In addition it
// returns properties about its block.
func (w *Wallet) GetTransaction(txHash chainhash.Hash) (*GetTransactionResult,
error) {

var res GetTransactionResult
err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)

txDetail, err := w.TxStore.TxDetails(txmgrNs, &txHash)
if err != nil {
return err
}

// If the transaction was not found we return an error.
if txDetail == nil {
return fmt.Errorf("%w: txid %v", ErrNoTx, txHash)
}

res = GetTransactionResult{
Summary: makeTxSummary(dbtx, w, txDetail),
Timestamp: txDetail.Block.Time.Unix(),
Confirmations: txDetail.Block.Height,
}

// If it is a confirmed transaction we set the corresponding
// block height and hash.
if txDetail.Block.Height != -1 {
res.Height = txDetail.Block.Height
res.BlockHash = &txDetail.Block.Hash
}

return nil
})
if err != nil {
return nil, err
}
return &res, nil
}

// AccountResult is a single account result for the AccountsResult type.
type AccountResult struct {
waddrmgr.AccountProperties
Expand Down
100 changes: 100 additions & 0 deletions wallet/wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ import (

"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/stretchr/testify/require"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)

var (
TstSerializedTx, _ = hex.DecodeString("010000000114d9ff358894c486b4ae11c2a8cf7851b1df64c53d2e511278eff17c22fb7373000000008c493046022100995447baec31ee9f6d4ec0e05cb2a44f6b817a99d5f6de167d1c75354a946410022100c9ffc23b64d770b0e01e7ff4d25fbc2f1ca8091053078a247905c39fce3760b601410458b8e267add3c1e374cf40f1de02b59213a82e1d84c2b94096e22e2f09387009c96debe1d0bcb2356ffdcf65d2a83d4b34e72c62eccd8490dbf2110167783b2bffffffff0280969800000000001976a914479ed307831d0ac19ebc5f63de7d5f1a430ddb9d88ac38bfaa00000000001976a914dadf9e3484f28b385ddeaa6c575c0c0d18e9788a88ac00000000")
TstTx, _ = btcutil.NewTxFromBytes(TstSerializedTx)
TstTxHash = TstTx.Hash()

TstMinedTxBlockHeight = int32(279143)
TstMinedSignedTxBlockDetails = &wtxmgr.BlockMeta{
Block: wtxmgr.Block{
Hash: *TstTxHash,
Height: TstMinedTxBlockHeight,
},
Time: time.Now(),
}
)

// TestLocateBirthdayBlock ensures we can properly map a block in the chain to a
Expand Down Expand Up @@ -205,3 +216,92 @@ func TestLabelTransaction(t *testing.T) {
})
}
}

// TestGetTransaction tests if we can fetch a mined, an existing
// and a non-existing transaction from the wallet like we expect.
func TestGetTransaction(t *testing.T) {
t.Parallel()
rec, err := wtxmgr.NewTxRecord(TstSerializedTx, time.Now())
require.NoError(t, err)

tests := []struct {
name string

// Transaction id.
txid chainhash.Hash

// Expected height.
expectedHeight int32

// Store function.
f func(*wtxmgr.Store, walletdb.ReadWriteBucket) (*wtxmgr.Store, error)

// The error we expect to be returned.
expectedErr error
}{
{
name: "existing unmined transaction",
txid: *TstTxHash,
// We write txdetail for the tx to disk.
f: func(s *wtxmgr.Store, ns walletdb.ReadWriteBucket) (
*wtxmgr.Store, error) {

err = s.InsertTx(ns, rec, nil)
return s, err
},
expectedErr: nil,
},
{
name: "existing mined transaction",
txid: *TstTxHash,
// We write txdetail for the tx to disk.
f: func(s *wtxmgr.Store, ns walletdb.ReadWriteBucket) (
*wtxmgr.Store, error) {

err = s.InsertTx(ns, rec, TstMinedSignedTxBlockDetails)
return s, err
},
expectedHeight: TstMinedTxBlockHeight,
expectedErr: nil,
},
{
name: "non-existing transaction",
txid: *TstTxHash,
// Write no txdetail to disk.
f: func(s *wtxmgr.Store, ns walletdb.ReadWriteBucket) (
*wtxmgr.Store, error) {

return s, nil
},
expectedErr: ErrNoTx,
},
}
for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
w, cleanup := testWallet(t)
defer cleanup()

err := walletdb.Update(w.db, func(rw walletdb.ReadWriteTx) error {
ns := rw.ReadWriteBucket(wtxmgrNamespaceKey)
_, err := test.f(w.TxStore, ns)
return err
})
require.NoError(t, err)
tx, err := w.GetTransaction(test.txid)
require.ErrorIs(t, err, test.expectedErr)

// Discontinue if no transaction were found.
if err != nil {
return
}

// Check if we get the expected hash.
require.Equal(t, &test.txid, tx.Summary.Hash)

// Check the block height.
require.Equal(t, test.expectedHeight, tx.Height)
})
}
}

0 comments on commit 9c13542

Please sign in to comment.