Skip to content

Commit

Permalink
client,app: Transaction History UI (decred#2666)
Browse files Browse the repository at this point in the history
* client,app: Transaction History UI

This diff adds a UI to the wallets page displaying the transaction
history of a wallet.

Modifications are also made to the WalletTransaction type. The ID is now
a string rather than a []byte. `BalanceDelta int64` is now `Amount uint64`
as the sign is obvious from the type of the transaction and negatives are
a hassle to deal with. A `Recipient` field for `Send` transactions and a
`BondInfo` field for bond related transaction are also added.
  • Loading branch information
martonp authored Jan 15, 2024
1 parent 4f1a077 commit 1f59ceb
Show file tree
Hide file tree
Showing 28 changed files with 1,131 additions and 330 deletions.
182 changes: 96 additions & 86 deletions client/asset/btc/btc.go

Large diffs are not rendered by default.

38 changes: 20 additions & 18 deletions client/asset/btc/txdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"math"
"sync"
"time"

"decred.org/dcrdex/client/asset"
Expand All @@ -20,7 +19,6 @@ import (
)

type extendedWalletTx struct {
mtx sync.Mutex
*asset.WalletTransaction
Confirmed bool `json:"confirmed"`
// Create bond transactions are added to the store before
Expand Down Expand Up @@ -63,26 +61,26 @@ func parseBlockKey(key []byte) (blockHeight, index uint64) {
}

// txKey maps a txid to a blockKey or pendingKey.
func txKey(txid []byte) []byte {
key := make([]byte, len(txPrefix)+len(txid))
func txKey(txid string) []byte {
key := make([]byte, len(txPrefix)+len([]byte(txid)))
copy(key, txPrefix)
copy(key[len(txPrefix):], txid)
copy(key[len(txPrefix):], []byte(txid))
return key
}

type txDB interface {
storeTx(tx *extendedWalletTx) error
markTxAsSubmitted(txID dex.Bytes) error
markTxAsSubmitted(txID string) error
// getTxs retrieves n transactions from the database. refID optionally
// takes a transaction ID, and returns that transaction and the at most
// (n - 1) transactions that were made either before or after it, depending
// on the value of past. If refID is nil, the most recent n transactions
// are returned, and the value of past is ignored. If the transaction with
// ID refID is not in the database, asset.CoinNotFoundError is returned.
getTxs(n int, refID *dex.Bytes, past bool) ([]*asset.WalletTransaction, error)
getTx(txID dex.Bytes) (*asset.WalletTransaction, error)
getTxs(n int, refID *string, past bool) ([]*asset.WalletTransaction, error)
getTx(txID string) (*asset.WalletTransaction, error)
getPendingTxs() ([]*extendedWalletTx, error)
removeTx(hash dex.Bytes) error
removeTx(txID string) error
// setLastReceiveTxQuery stores the last time the wallet was queried for
// receive transactions. This is required to know how far back to query
// for incoming transactions that were received while the wallet is
Expand Down Expand Up @@ -213,7 +211,7 @@ func (db *badgerTxDB) storeTx(tx *extendedWalletTx) error {
})
}

func (db *badgerTxDB) markTxAsSubmitted(txID dex.Bytes) error {
func (db *badgerTxDB) markTxAsSubmitted(txID string) error {
return db.Update(func(txn *badger.Txn) error {
txKey := txKey(txID)
txKeyItem, err := txn.Get(txKey)
Expand All @@ -236,18 +234,18 @@ func (db *badgerTxDB) markTxAsSubmitted(txID dex.Bytes) error {
return err
}

var wt *extendedWalletTx
if err := json.Unmarshal(wtB, wt); err != nil {
var wt extendedWalletTx
if err := json.Unmarshal(wtB, &wt); err != nil {
return err
}

wt.Submitted = true
wtB, err = json.Marshal(wt)
submittedWt, err := json.Marshal(wt)
if err != nil {
return err
}

return txn.Set(blockKey, wtB)
return txn.Set(blockKey, submittedWt)
})
}

Expand All @@ -257,7 +255,8 @@ func (db *badgerTxDB) markTxAsSubmitted(txID dex.Bytes) error {
// on the value of past. If refID is nil, the most recent n transactions
// are returned, and the value of past is ignored. If the transaction with
// ID refID is not in the database, asset.CoinNotFoundError is returned.
func (db *badgerTxDB) getTxs(n int, refID *dex.Bytes, past bool) ([]*asset.WalletTransaction, error) {
// Unsubmitted transactions are not returned.
func (db *badgerTxDB) getTxs(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) {
var txs []*asset.WalletTransaction
err := db.View(func(txn *badger.Txn) error {
var startKey []byte
Expand Down Expand Up @@ -298,6 +297,9 @@ func (db *badgerTxDB) getTxs(n int, refID *dex.Bytes, past bool) ([]*asset.Walle
if err := json.Unmarshal(wtB, &wt); err != nil {
return err
}
if !wt.Submitted {
continue
}
if past {
txs = append(txs, wt.WalletTransaction)
} else {
Expand All @@ -310,7 +312,7 @@ func (db *badgerTxDB) getTxs(n int, refID *dex.Bytes, past bool) ([]*asset.Walle
return txs, err
}

func (db *badgerTxDB) getTx(txID dex.Bytes) (*asset.WalletTransaction, error) {
func (db *badgerTxDB) getTx(txID string) (*asset.WalletTransaction, error) {
txs, err := db.getTxs(1, &txID, false)
if err != nil {
return nil, err
Expand Down Expand Up @@ -352,12 +354,12 @@ func (db *badgerTxDB) getPendingTxs() ([]*extendedWalletTx, error) {
return txs, err
}

func (db *badgerTxDB) removeTx(txID dex.Bytes) error {
func (db *badgerTxDB) removeTx(txID string) error {
return db.Update(func(txn *badger.Txn) error {
txKey := txKey(txID)
txKeyItem, err := txn.Get(txKey)
if err != nil {
return err
return asset.CoinNotFoundError
}

blockKey, err := txKeyItem.ValueCopy(nil)
Expand Down
44 changes: 28 additions & 16 deletions client/asset/btc/txdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package btc

import (
"encoding/hex"
"errors"
"reflect"
"testing"
Expand Down Expand Up @@ -33,35 +34,38 @@ func TestTxDB(t *testing.T) {

tx1 := &extendedWalletTx{
WalletTransaction: &asset.WalletTransaction{
Type: asset.Send,
ID: encode.RandomBytes(32),
BalanceDelta: -1e8,
Fees: 1e5,
BlockNumber: 0,
Type: asset.Send,
ID: hex.EncodeToString(encode.RandomBytes(32)),
Amount: 1e8,
Fees: 1e5,
BlockNumber: 0,
},
Submitted: false,
}

tx2 := &extendedWalletTx{
WalletTransaction: &asset.WalletTransaction{
Type: asset.Receive,
ID: encode.RandomBytes(32),
BalanceDelta: 1e8,
Fees: 3e5,
BlockNumber: 0,
Type: asset.Receive,
ID: hex.EncodeToString(encode.RandomBytes(32)),
Amount: 1e8,
Fees: 3e5,
BlockNumber: 0,
},
Submitted: true,
}

tx3 := &extendedWalletTx{
WalletTransaction: &asset.WalletTransaction{
Type: asset.Swap,
ID: encode.RandomBytes(32),
BalanceDelta: -1e8,
Fees: 2e5,
BlockNumber: 0,
Type: asset.Swap,
ID: hex.EncodeToString(encode.RandomBytes(32)),
Amount: 1e8,
Fees: 2e5,
BlockNumber: 0,
},
Submitted: true,
}

getTxsAndCheck := func(n int, refID *dex.Bytes, past bool, expected []*asset.WalletTransaction) {
getTxsAndCheck := func(n int, refID *string, past bool, expected []*asset.WalletTransaction) {
t.Helper()

txs, err = txHistoryStore.getTxs(n, refID, past)
Expand Down Expand Up @@ -101,6 +105,14 @@ func TestTxDB(t *testing.T) {
if err != nil {
t.Fatalf("failed to store tx: %v", err)
}
getTxsAndCheck(0, nil, true, []*asset.WalletTransaction{})
getPendingTxsAndCheck([]*extendedWalletTx{tx1})

err = txHistoryStore.markTxAsSubmitted(tx1.ID)
if err != nil {
t.Fatalf("failed to mark tx as submitted: %v", err)
}
tx1.Submitted = true
getTxsAndCheck(0, nil, true, []*asset.WalletTransaction{tx1.WalletTransaction})
getPendingTxsAndCheck([]*extendedWalletTx{tx1})

Expand Down
Loading

0 comments on commit 1f59ceb

Please sign in to comment.