Add frontend support for displaying txs.
This change adds a new websocket extension command, listalltransaction, which works just like listtransactions except it does not take the count or from optional args, and will return an array of all transaction details. Notifications for newly-added transactions are now sent to frontends as well, using the newtx notification. No support for updating tx details or removing failed txs is implemented yet, and will be when cleanly failing a tx send is implemented later.
This commit is contained in:
parent
3c528f81ec
commit
af1438eecd
3 changed files with 115 additions and 107 deletions
37
account.go
37
account.go
|
@ -153,6 +153,39 @@ func (a *Account) ListTransactions(from, count int) ([]map[string]interface{}, e
|
||||||
return txInfoList, nil
|
return txInfoList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAllTransactions returns a slice of maps with details about a recorded
|
||||||
|
// transaction. This is intended to be used for listalltransactions RPC
|
||||||
|
// replies.
|
||||||
|
func (a *Account) ListAllTransactions() ([]map[string]interface{}, error) {
|
||||||
|
// Get current block. The block height used for calculating
|
||||||
|
// the number of tx confirmations.
|
||||||
|
bs, err := GetCurBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var txInfoList []map[string]interface{}
|
||||||
|
a.mtx.RLock()
|
||||||
|
a.TxStore.RLock()
|
||||||
|
|
||||||
|
// Search in reverse order: lookup most recently-added first.
|
||||||
|
for i := len(a.TxStore.s) - 1; i >= 0; i-- {
|
||||||
|
switch e := a.TxStore.s[i].(type) {
|
||||||
|
case *tx.SendTx:
|
||||||
|
infos := e.TxInfo(a.name, bs.Height, a.Net())
|
||||||
|
txInfoList = append(txInfoList, infos...)
|
||||||
|
|
||||||
|
case *tx.RecvTx:
|
||||||
|
info := e.TxInfo(a.name, bs.Height, a.Net())
|
||||||
|
txInfoList = append(txInfoList, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.mtx.RUnlock()
|
||||||
|
a.TxStore.RUnlock()
|
||||||
|
|
||||||
|
return txInfoList, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DumpPrivKeys returns the WIF-encoded private keys for all addresses
|
// DumpPrivKeys returns the WIF-encoded private keys for all addresses
|
||||||
// non-watching addresses in a wallets.
|
// non-watching addresses in a wallets.
|
||||||
func (a *Account) DumpPrivKeys() ([]string, error) {
|
func (a *Account) DumpPrivKeys() ([]string, error) {
|
||||||
|
@ -618,6 +651,10 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
|
||||||
a.TxStore.dirty = true
|
a.TxStore.dirty = true
|
||||||
a.TxStore.Unlock()
|
a.TxStore.Unlock()
|
||||||
|
|
||||||
|
// Notify frontends of new tx.
|
||||||
|
NotifyNewTxDetails(frontendNotificationMaster, a.Name(), t.TxInfo(a.Name(),
|
||||||
|
int32(height), a.Wallet.Net()))
|
||||||
|
|
||||||
if !spent {
|
if !spent {
|
||||||
// First, iterate through all stored utxos. If an unconfirmed utxo
|
// First, iterate through all stored utxos. If an unconfirmed utxo
|
||||||
// (not present in a block) has the same outpoint as this utxo,
|
// (not present in a block) has the same outpoint as this utxo,
|
||||||
|
|
102
accountstore.go
102
accountstore.go
|
@ -20,7 +20,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/conformal/btcjson"
|
|
||||||
"github.com/conformal/btcwallet/tx"
|
"github.com/conformal/btcwallet/tx"
|
||||||
"github.com/conformal/btcwallet/wallet"
|
"github.com/conformal/btcwallet/wallet"
|
||||||
"github.com/conformal/btcwire"
|
"github.com/conformal/btcwire"
|
||||||
|
@ -434,104 +433,3 @@ func (store *AccountStore) OpenAccount(name string, cfg *config) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *AccountStore) handleSendRawTxReply(frontend chan []byte, icmd btcjson.Cmd,
|
|
||||||
result interface{}, e *btcjson.Error, a *Account,
|
|
||||||
txInfo *CreatedTx) bool {
|
|
||||||
|
|
||||||
store.Lock()
|
|
||||||
defer store.Unlock()
|
|
||||||
|
|
||||||
if e != nil {
|
|
||||||
ReplyError(frontend, icmd.Id(), e)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
txIDStr, ok := result.(string)
|
|
||||||
if !ok {
|
|
||||||
e := &btcjson.Error{
|
|
||||||
Code: btcjson.ErrInternal.Code,
|
|
||||||
Message: "Unexpected type from btcd reply",
|
|
||||||
}
|
|
||||||
ReplyError(frontend, icmd.Id(), e)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
txID, err := btcwire.NewShaHashFromStr(txIDStr)
|
|
||||||
if err != nil {
|
|
||||||
e := &btcjson.Error{
|
|
||||||
Code: btcjson.ErrInternal.Code,
|
|
||||||
Message: "Invalid hash string from btcd reply",
|
|
||||||
}
|
|
||||||
ReplyError(frontend, icmd.Id(), e)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to transaction store.
|
|
||||||
sendtx := &tx.SendTx{
|
|
||||||
TxID: *txID,
|
|
||||||
Time: txInfo.time.Unix(),
|
|
||||||
BlockHeight: -1,
|
|
||||||
Fee: txInfo.fee,
|
|
||||||
Receivers: txInfo.outputs,
|
|
||||||
}
|
|
||||||
a.TxStore.Lock()
|
|
||||||
a.TxStore.s = append(a.TxStore.s, sendtx)
|
|
||||||
a.TxStore.dirty = true
|
|
||||||
a.TxStore.Unlock()
|
|
||||||
|
|
||||||
// Remove previous unspent outputs now spent by the tx.
|
|
||||||
a.UtxoStore.Lock()
|
|
||||||
modified := a.UtxoStore.s.Remove(txInfo.inputs)
|
|
||||||
a.UtxoStore.dirty = a.UtxoStore.dirty || modified
|
|
||||||
|
|
||||||
// Add unconfirmed change utxo (if any) to UtxoStore.
|
|
||||||
if txInfo.changeUtxo != nil {
|
|
||||||
a.UtxoStore.s = append(a.UtxoStore.s, txInfo.changeUtxo)
|
|
||||||
a.ReqSpentUtxoNtfn(txInfo.changeUtxo)
|
|
||||||
a.UtxoStore.dirty = true
|
|
||||||
}
|
|
||||||
a.UtxoStore.Unlock()
|
|
||||||
|
|
||||||
// Disk sync tx and utxo stores.
|
|
||||||
if err := a.writeDirtyToDisk(); err != nil {
|
|
||||||
log.Errorf("cannot sync dirty wallet: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify all frontends of account's new unconfirmed and
|
|
||||||
// confirmed balance.
|
|
||||||
confirmed := a.CalculateBalance(1)
|
|
||||||
unconfirmed := a.CalculateBalance(0) - confirmed
|
|
||||||
NotifyWalletBalance(frontendNotificationMaster, a.name, confirmed)
|
|
||||||
NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, unconfirmed)
|
|
||||||
|
|
||||||
// btcd cannot be trusted to successfully relay the tx to the
|
|
||||||
// Bitcoin network. Even if this succeeds, the rawtx must be
|
|
||||||
// saved and checked for an appearence in a later block. btcd
|
|
||||||
// will make a best try effort, but ultimately it's btcwallet's
|
|
||||||
// responsibility.
|
|
||||||
//
|
|
||||||
// Add hex string of raw tx to sent tx pool. If btcd disconnects
|
|
||||||
// and is reconnected, these txs are resent.
|
|
||||||
UnminedTxs.Lock()
|
|
||||||
UnminedTxs.m[TXID(*txID)] = txInfo
|
|
||||||
UnminedTxs.Unlock()
|
|
||||||
log.Infof("Successfully sent transaction %v", result)
|
|
||||||
ReplySuccess(frontend, icmd.Id(), result)
|
|
||||||
|
|
||||||
// The comments to be saved differ based on the underlying type
|
|
||||||
// of the cmd, so switch on the type to check whether it is a
|
|
||||||
// SendFromCmd or SendManyCmd.
|
|
||||||
//
|
|
||||||
// TODO(jrick): If message succeeded in being sent, save the
|
|
||||||
// transaction details with comments.
|
|
||||||
switch cmd := icmd.(type) {
|
|
||||||
case *btcjson.SendFromCmd:
|
|
||||||
_ = cmd.Comment
|
|
||||||
_ = cmd.CommentTo
|
|
||||||
|
|
||||||
case *btcjson.SendManyCmd:
|
|
||||||
_ = cmd.Comment
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
77
cmdmgr.go
77
cmdmgr.go
|
@ -60,6 +60,7 @@ var rpcHandlers = map[string]cmdHandler{
|
||||||
// Extensions exclusive to websocket connections.
|
// Extensions exclusive to websocket connections.
|
||||||
var wsHandlers = map[string]cmdHandler{
|
var wsHandlers = map[string]cmdHandler{
|
||||||
"getbalances": GetBalances,
|
"getbalances": GetBalances,
|
||||||
|
"listalltransactions": ListAllTransactions,
|
||||||
"walletislocked": WalletIsLocked,
|
"walletislocked": WalletIsLocked,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,8 +418,9 @@ func ListAccounts(frontend chan []byte, icmd btcjson.Cmd) {
|
||||||
ReplySuccess(frontend, cmd.Id(), pairs)
|
ReplySuccess(frontend, cmd.Id(), pairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTransactions replies to a listtransactions request by returning a
|
// ListTransactions replies to a listtransactions request by returning an
|
||||||
// JSON object with details of sent and recevied wallet transactions.
|
// array of JSON objects with details of sent and recevied wallet
|
||||||
|
// transactions.
|
||||||
func ListTransactions(frontend chan []byte, icmd btcjson.Cmd) {
|
func ListTransactions(frontend chan []byte, icmd btcjson.Cmd) {
|
||||||
// Type assert icmd to access parameters.
|
// Type assert icmd to access parameters.
|
||||||
cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
|
cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
|
||||||
|
@ -467,6 +469,59 @@ func ListTransactions(frontend chan []byte, icmd btcjson.Cmd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListAllTransactions replies to a listtransactions request by returning
|
||||||
|
// an array of JSON objects with details of sent and recevied wallet
|
||||||
|
// transactions. This is similar to ListTransactions, except it takes
|
||||||
|
// only a single optional argument for the account name and replies with
|
||||||
|
// all transactions.
|
||||||
|
func ListAllTransactions(frontend chan []byte, icmd btcjson.Cmd) {
|
||||||
|
// Type assert icmd to access parameters.
|
||||||
|
cmd, ok := icmd.(*btcws.ListAllTransactionsCmd)
|
||||||
|
if !ok {
|
||||||
|
ReplyError(frontend, icmd.Id(), &btcjson.ErrInternal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := accountstore.Account(cmd.Account)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
break
|
||||||
|
|
||||||
|
case ErrAcctNotExist:
|
||||||
|
ReplyError(frontend, cmd.Id(),
|
||||||
|
&btcjson.ErrWalletInvalidAccountName)
|
||||||
|
return
|
||||||
|
|
||||||
|
default: // all other non-nil errors
|
||||||
|
e := &btcjson.Error{
|
||||||
|
Code: btcjson.ErrWallet.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
ReplyError(frontend, cmd.Id(), e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch txList, err := a.ListAllTransactions(); err {
|
||||||
|
case nil:
|
||||||
|
// Reply with the list of tx information.
|
||||||
|
ReplySuccess(frontend, cmd.Id(), txList)
|
||||||
|
|
||||||
|
case ErrBtcdDisconnected:
|
||||||
|
e := &btcjson.Error{
|
||||||
|
Code: btcjson.ErrInternal.Code,
|
||||||
|
Message: "btcd disconnected",
|
||||||
|
}
|
||||||
|
ReplyError(frontend, cmd.Id(), e)
|
||||||
|
|
||||||
|
default:
|
||||||
|
e := &btcjson.Error{
|
||||||
|
Code: btcjson.ErrWallet.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
ReplyError(frontend, cmd.Id(), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SendFrom creates a new transaction spending unspent transaction
|
// SendFrom creates a new transaction spending unspent transaction
|
||||||
// outputs for a wallet to another payment address. Leftover inputs
|
// outputs for a wallet to another payment address. Leftover inputs
|
||||||
// not sent to the payment address or a fee for the miner are sent
|
// not sent to the payment address or a fee for the miner are sent
|
||||||
|
@ -718,6 +773,15 @@ func handleSendRawTxReply(frontend chan []byte, icmd btcjson.Cmd,
|
||||||
a.TxStore.dirty = true
|
a.TxStore.dirty = true
|
||||||
a.TxStore.Unlock()
|
a.TxStore.Unlock()
|
||||||
|
|
||||||
|
// Notify frontends of new SendTx.
|
||||||
|
bs, err := GetCurBlock()
|
||||||
|
if err == nil {
|
||||||
|
for _, details := range sendtx.TxInfo(a.Name(), bs.Height, a.Net()) {
|
||||||
|
NotifyNewTxDetails(frontendNotificationMaster, a.Name(),
|
||||||
|
details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove previous unspent outputs now spent by the tx.
|
// Remove previous unspent outputs now spent by the tx.
|
||||||
a.UtxoStore.Lock()
|
a.UtxoStore.Lock()
|
||||||
modified := a.UtxoStore.s.Remove(txInfo.inputs)
|
modified := a.UtxoStore.s.Remove(txInfo.inputs)
|
||||||
|
@ -1031,3 +1095,12 @@ func NotifyWalletBalanceUnconfirmed(frontend chan []byte, account string, balanc
|
||||||
msg, _ := json.Marshal(&m)
|
msg, _ := json.Marshal(&m)
|
||||||
frontend <- msg
|
frontend <- msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyNewTxDetails sends details of a new transaction to a frontend.
|
||||||
|
func NotifyNewTxDetails(frontend chan []byte, account string,
|
||||||
|
details map[string]interface{}) {
|
||||||
|
|
||||||
|
ntfn := btcws.NewTxNtfn(account, details)
|
||||||
|
mntfn, _ := ntfn.MarshalJSON()
|
||||||
|
frontend <- mntfn
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue