Skip to content

Commit

Permalink
client,app: Transaction History UI
Browse files Browse the repository at this point in the history
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 committed Jan 10, 2024
1 parent 71de25d commit fb145fa
Show file tree
Hide file tree
Showing 28 changed files with 1,081 additions and 302 deletions.
132 changes: 67 additions & 65 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2272,7 +2272,6 @@ func (btc *baseWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, ui
}
return nil, nil, 0, err
} else if split {
fmt.Printf("original coins: %s, split coins %s\n", coins, splitCoins)
return splitCoins, []dex.Bytes{nil}, splitFees, nil // no redeem script required for split tx output
}
return coins, redeemScripts, 0, nil // splitCoins == coins
Expand Down Expand Up @@ -2419,7 +2418,7 @@ func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Ou
for _, txOut := range tx.TxOut {
totalOut += uint64(txOut.Value)
}
btc.addTxToHistory(asset.Split, txHash[:], 0, totalIn-totalOut, true)
btc.addTxToHistory(asset.Split, txHash, 0, totalIn-totalOut, nil, nil, true)

success = true
return coins, totalIn - totalOut, nil
Expand Down Expand Up @@ -2675,7 +2674,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, input
totalOut += uint64(msgTx.TxOut[i].Value)
}

btc.addTxToHistory(asset.Split, txHash[:], 0, coinSum-totalOut, true)
btc.addTxToHistory(asset.Split, txHash, 0, coinSum-totalOut, nil, nil, true)

fundingCoins = map[OutPoint]*UTxO{op.Pt: {
TxHash: op.txHash(),
Expand Down Expand Up @@ -3110,7 +3109,7 @@ func accelerateOrder(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes,
}

txHash := btc.hashTx(signedTx)
btc.addTxToHistory(asset.Acceleration, txHash[:], 0, fees, true)
btc.addTxToHistory(asset.Acceleration, txHash, 0, fees, nil, nil, true)

// Delete the old change from the cache
btc.cm.ReturnOutPoint(NewOutPoint(changeTxHash, changeVout))
Expand Down Expand Up @@ -3414,14 +3413,14 @@ func (btc *baseWallet) txDB() txDB {
return dbi.(txDB)
}

func (btc *baseWallet) markTxAsSubmitted(id dex.Bytes) {
func (btc *baseWallet) markTxAsSubmitted(txID string) {
txHistoryDB := btc.txDB()
if txHistoryDB == nil {
return
}

var txHash chainhash.Hash
copy(txHash[:], id)
copy(txHash[:], txID)

btc.pendingTxsMtx.Lock()
wt, found := btc.pendingTxs[txHash]
Expand All @@ -3432,51 +3431,57 @@ func (btc *baseWallet) markTxAsSubmitted(id dex.Bytes) {
}
btc.pendingTxsMtx.Unlock()

err := txHistoryDB.markTxAsSubmitted(id)
err := txHistoryDB.markTxAsSubmitted(txID)
if err != nil {
btc.log.Errorf("failed to mark tx as submitted in tx history db: %v", err)
}

btc.emit.TransactionNote(wt.WalletTransaction, true)
}

func (btc *baseWallet) removeTxFromHistory(id dex.Bytes) {
func (btc *baseWallet) removeTxFromHistory(txID string) {
txHistoryDB := btc.txDB()
if txHistoryDB == nil {
return
}

err := txHistoryDB.removeTx(id)
err := txHistoryDB.removeTx(txID)
if err != nil {
btc.log.Errorf("failed to remove tx from tx history db: %v", err)
}
}

func (btc *baseWallet) addTxToHistory(txType asset.TransactionType, id dex.Bytes, balanceDelta int64, fees uint64, submitted bool) {
func (btc *baseWallet) addTxToHistory(txType asset.TransactionType, txHash *chainhash.Hash, amount uint64, fees uint64,
bondInfo *asset.BondTxInfo, recipient *string, submitted bool) {
txHistoryDB := btc.txDB()
if txHistoryDB == nil {
return
}

wt := &extendedWalletTx{
WalletTransaction: &asset.WalletTransaction{
Type: txType,
ID: id,
BalanceDelta: balanceDelta,
Fees: fees,
Type: txType,
ID: txHash.String(),
Amount: amount,
Fees: fees,
BondInfo: bondInfo,
Recipient: recipient,
},
Submitted: submitted,
}

var txHash chainhash.Hash
copy(txHash[:], wt.ID)

btc.pendingTxsMtx.Lock()
btc.pendingTxs[txHash] = wt
btc.pendingTxs[*txHash] = wt
btc.pendingTxsMtx.Unlock()

err := txHistoryDB.storeTx(wt)
if err != nil {
btc.log.Errorf("failed to store tx in tx history db: %v", err)
}

if submitted {
btc.emit.TransactionNote(wt.WalletTransaction, true)
}
}

// Swap sends the swaps in a single transaction and prepares the receipts. The
Expand Down Expand Up @@ -3607,7 +3612,7 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui
return nil, nil, 0, err
}

btc.addTxToHistory(asset.Swap, txHash[:], -int64(totalOut), fees, true)
btc.addTxToHistory(asset.Swap, txHash, totalOut, fees, nil, nil, true)

// If change is nil, return a nil asset.Coin.
var changeCoin asset.Coin
Expand Down Expand Up @@ -3771,7 +3776,7 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin,
return nil, nil, 0, err
}

btc.addTxToHistory(asset.Redeem, txHash[:], int64(totalIn), fee, true)
btc.addTxToHistory(asset.Redeem, txHash, totalIn, fee, nil, nil, true)

// Log the change output.
coinIDs := make([]dex.Bytes, 0, len(form.Redemptions))
Expand Down Expand Up @@ -4028,7 +4033,7 @@ func (btc *baseWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.B
if len(msgTx.TxOut) > 0 { // something went very wrong if not true
fee = uint64(utxo.Value - msgTx.TxOut[0].Value)
}
btc.addTxToHistory(asset.Refund, txHash[:], utxo.Value, fee, true)
btc.addTxToHistory(asset.Refund, txHash, uint64(utxo.Value), fee, nil, nil, true)

return ToCoinID(refundHash, 0), nil
}
Expand Down Expand Up @@ -4240,7 +4245,7 @@ func (btc *baseWallet) SendTransaction(rawTx []byte) ([]byte, error) {
return nil, err
}

btc.markTxAsSubmitted(txHash[:])
btc.markTxAsSubmitted(txHash.String())

return ToCoinID(txHash, 0), nil
}
Expand All @@ -4264,11 +4269,6 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract
return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err)
}

selfSend, err := btc.OwnsDepositAddress(address)
if err != nil {
return nil, 0, 0, fmt.Errorf("error checking address ownership: %w", err)
}

baseSize := dexbtc.MinimumTxOverhead
if btc.segwit {
baseSize += dexbtc.P2WPKHOutputSize * 2
Expand Down Expand Up @@ -4314,12 +4314,16 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract
totalOut += uint64(txOut.Value)
}

var amtSent int64
if !selfSend {
amtSent = -int64(toSend)
selfSend, err := btc.OwnsDepositAddress(address)
if err != nil {
return nil, 0, 0, fmt.Errorf("error checking address ownership: %w", err)
}
txType := asset.Send
if selfSend {
txType = asset.SelfSend
}

btc.addTxToHistory(asset.Send, txHash[:], amtSent, totalIn-totalOut, true)
btc.addTxToHistory(txType, txHash, toSend, totalIn-totalOut, nil, &address, true)

return txHash, 0, toSend, nil
}
Expand Down Expand Up @@ -4853,7 +4857,7 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time
btc.log.Errorf("error returning coins for unused bond tx: %v", coins)
}
if txIDToRemoveFromHistory != nil {
btc.removeTxFromHistory(txIDToRemoveFromHistory[:])
btc.removeTxFromHistory(txIDToRemoveFromHistory.String())
}
}

Expand Down Expand Up @@ -4911,7 +4915,12 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time
}
success = true

btc.addTxToHistory(asset.CreateBond, txid[:], -int64(amt), fee, false)
bondInfo := &asset.BondTxInfo{
AccountID: acctID,
LockTime: uint64(lockTimeSec),
BondID: pkh,
}
btc.addTxToHistory(asset.CreateBond, txid, amt, fee, bondInfo, nil, false)
txIDToRemoveFromHistory = txid

return bond, abandon, nil
Expand Down Expand Up @@ -5000,6 +5009,10 @@ func (btc *baseWallet) RefundBond(ctx context.Context, ver uint16, coinID, scrip
if ver != 0 {
return nil, errors.New("only version 0 bonds supported")
}
lockTime, pkhPush, err := dexbtc.ExtractBondDetailsV0(0, script)
if err != nil {
return nil, err
}
txHash, vout, err := decodeCoinID(coinID)
if err != nil {
return nil, err
Expand All @@ -5021,8 +5034,11 @@ func (btc *baseWallet) RefundBond(ctx context.Context, ver uint16, coinID, scrip
if len(msgTx.TxOut) == 1 {
fees = amt - uint64(msgTx.TxOut[0].Value)
}
btc.addTxToHistory(asset.RedeemBond, txID[:], int64(amt), fees, true)

bondInfo := &asset.BondTxInfo{
LockTime: uint64(lockTime),
BondID: pkhPush,
}
btc.addTxToHistory(asset.RedeemBond, txID, amt, fees, bondInfo, nil, true)
return NewOutput(txHash, 0, uint64(msgTx.TxOut[0].Value)), nil
}

Expand Down Expand Up @@ -5179,8 +5195,7 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
btc.log.Errorf("Error decoding txid %s: %v", tx.TxID, err)
continue
}
txID := dex.Bytes(txHash[:])
_, err = txHistoryDB.getTx(txID)
_, err = txHistoryDB.getTx(txHash.String())
if err == nil {
continue
}
Expand All @@ -5199,25 +5214,8 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
fee = toSatoshi(*tx.Fee)
}
}
wt := &extendedWalletTx{
WalletTransaction: &asset.WalletTransaction{
Type: asset.Receive,
ID: txID,
BalanceDelta: int64(toSatoshi(tx.Amount)),
Fees: fee,
},
Submitted: true,
}

err = txHistoryDB.storeTx(wt)
if err != nil {
btc.log.Errorf("Error storing tx %s: %v", tx.TxID, err)
}
if !wt.Confirmed || err != nil {
btc.pendingTxsMtx.Lock()
btc.pendingTxs[*txHash] = wt
btc.pendingTxsMtx.Unlock()
}
btc.addTxToHistory(asset.Receive, txHash, toSatoshi(tx.Amount), fee, nil, nil, true)
}
}
}
Expand All @@ -5229,29 +5227,30 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
}
btc.pendingTxsMtx.RUnlock()

handlePendingTx := func(hash chainhash.Hash, tx *extendedWalletTx) {
handlePendingTx := func(txHash chainhash.Hash, tx *extendedWalletTx) {
tx.mtx.Lock()
defer tx.mtx.Unlock()

if !tx.Submitted {
return
}
gtr, err := btc.node.getWalletTransaction(&hash)

gtr, err := btc.node.getWalletTransaction(&txHash)
if errors.Is(err, asset.CoinNotFoundError) {
err = txHistoryDB.removeTx(hash[:])
err = txHistoryDB.removeTx(txHash.String())
if err == nil {
btc.pendingTxsMtx.Lock()
delete(btc.pendingTxs, hash)
delete(btc.pendingTxs, txHash)
btc.pendingTxsMtx.Unlock()
} else {
// Leave it in the pendingPendingTxs and attempt to remove it
// again next time.
btc.log.Errorf("Error removing tx %s from the history store: %v", hash, err)
btc.log.Errorf("Error removing tx %s from the history store: %v", txHash, err)
}
return
}
if err != nil {
btc.log.Errorf("Error getting transaction %s: %v", hash, err)
btc.log.Errorf("Error getting transaction %s: %v", txHash, err)
return
}

Expand All @@ -5267,12 +5266,14 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
btc.log.Errorf("Error getting block height for %s: %v", blockHash, err)
return
}
if tx.BlockNumber != uint64(blockHeight) {
if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != gtr.BlockTime {
tx.BlockNumber = uint64(blockHeight)
tx.Timestamp = gtr.BlockTime
updated = true
}
} else if gtr.BlockHash == "" && tx.BlockNumber != 0 {
tx.BlockNumber = 0
tx.Timestamp = 0
updated = true
}

Expand All @@ -5288,14 +5289,15 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
if updated {
err = txHistoryDB.storeTx(tx)
if err != nil {
btc.log.Errorf("Error updating tx %s: %v", hash, err)
btc.log.Errorf("Error updating tx %s: %v", txHash, err)
return
}
if tx.Confirmed {
btc.pendingTxsMtx.Lock()
delete(btc.pendingTxs, hash)
delete(btc.pendingTxs, txHash)
btc.pendingTxsMtx.Unlock()
}
btc.emit.TransactionNote(tx.WalletTransaction, false)
}
}

Expand All @@ -5309,7 +5311,7 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
// If past is true, the transactions prior to the refID are returned, otherwise
// the transactions after the refID are returned. n is the number of
// transactions to return. If n is <= 0, all the transactions will be returned.
func (btc *ExchangeWalletSPV) TxHistory(n int, refID *dex.Bytes, past bool) ([]*asset.WalletTransaction, error) {
func (btc *ExchangeWalletSPV) TxHistory(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) {
txHistoryDB := btc.txDB()
if txHistoryDB == nil {
return nil, fmt.Errorf("tx database not initialized")
Expand Down
Loading

0 comments on commit fb145fa

Please sign in to comment.