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:
Josh Rickmar 2013-12-02 17:34:36 -05:00
parent 3c528f81ec
commit af1438eecd
3 changed files with 115 additions and 107 deletions

View file

@ -153,6 +153,39 @@ func (a *Account) ListTransactions(from, count int) ([]map[string]interface{}, e
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
// non-watching addresses in a wallets.
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.Unlock()
// Notify frontends of new tx.
NotifyNewTxDetails(frontendNotificationMaster, a.Name(), t.TxInfo(a.Name(),
int32(height), a.Wallet.Net()))
if !spent {
// First, iterate through all stored utxos. If an unconfirmed utxo
// (not present in a block) has the same outpoint as this utxo,

View file

@ -20,7 +20,6 @@ import (
"bytes"
"errors"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcwallet/tx"
"github.com/conformal/btcwallet/wallet"
"github.com/conformal/btcwire"
@ -434,104 +433,3 @@ func (store *AccountStore) OpenAccount(name string, cfg *config) error {
}
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
}

View file

@ -60,6 +60,7 @@ var rpcHandlers = map[string]cmdHandler{
// Extensions exclusive to websocket connections.
var wsHandlers = map[string]cmdHandler{
"getbalances": GetBalances,
"listalltransactions": ListAllTransactions,
"walletislocked": WalletIsLocked,
}
@ -417,8 +418,9 @@ func ListAccounts(frontend chan []byte, icmd btcjson.Cmd) {
ReplySuccess(frontend, cmd.Id(), pairs)
}
// ListTransactions replies to a listtransactions request by returning a
// JSON object with details of sent and recevied wallet transactions.
// ListTransactions replies to a listtransactions request by returning an
// array of JSON objects with details of sent and recevied wallet
// transactions.
func ListTransactions(frontend chan []byte, icmd btcjson.Cmd) {
// Type assert icmd to access parameters.
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
// outputs for a wallet to another payment address. Leftover inputs
// 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.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.
a.UtxoStore.Lock()
modified := a.UtxoStore.s.Remove(txInfo.inputs)
@ -1031,3 +1095,12 @@ func NotifyWalletBalanceUnconfirmed(frontend chan []byte, account string, balanc
msg, _ := json.Marshal(&m)
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
}