diff --git a/account.go b/account.go index 72bb2db..357a09b 100644 --- a/account.go +++ b/account.go @@ -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, diff --git a/accountstore.go b/accountstore.go index 32fc3b5..6fd4d22 100644 --- a/accountstore.go +++ b/accountstore.go @@ -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 -} diff --git a/cmdmgr.go b/cmdmgr.go index 9eca514..59bd4dc 100644 --- a/cmdmgr.go +++ b/cmdmgr.go @@ -59,8 +59,9 @@ var rpcHandlers = map[string]cmdHandler{ // Extensions exclusive to websocket connections. var wsHandlers = map[string]cmdHandler{ - "getbalances": GetBalances, - "walletislocked": WalletIsLocked, + "getbalances": GetBalances, + "listalltransactions": ListAllTransactions, + "walletislocked": WalletIsLocked, } // ProcessRequest checks the requests sent from a frontend. If the @@ -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) @@ -1017,7 +1081,7 @@ func NotifyWalletBalance(frontend chan []byte, account string, balance float64) frontend <- msg } -// NotifyWalletBalanceUnconfirmed sends a confirmed account balance +// NotifyWalletBalanceUnconfirmed sends a confirmed account balance // notification to a frontend. func NotifyWalletBalanceUnconfirmed(frontend chan []byte, account string, balance float64) { var id interface{} = "btcwallet:accountbalanceunconfirmed" @@ -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 +}