Move last seen block to RPC client structure.

Pass the RPC client to the notification handlers.  Update the last
seen block for blockconnected notifications in the client structure
directly, protecting access with a mutex.
This commit is contained in:
Josh Rickmar 2014-07-07 16:57:00 -05:00
parent 770384be12
commit 061a220354
6 changed files with 267 additions and 183 deletions

View file

@ -104,18 +104,17 @@ func (a *Account) AddressUsed(addr btcutil.Address) bool {
// a UTXO must be in a block. If confirmations is 1 or greater, // a UTXO must be in a block. If confirmations is 1 or greater,
// the balance will be calculated based on how many how many blocks // the balance will be calculated based on how many how many blocks
// include a UTXO. // include a UTXO.
func (a *Account) CalculateBalance(confirms int) float64 { func (a *Account) CalculateBalance(confirms int) (btcutil.Amount, error) {
bs, err := GetCurBlock() rpcc, err := accessClient()
if bs.Height == int32(btcutil.BlockHeightUnknown) || err != nil { if err != nil {
return 0. return 0, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return 0, err
} }
bal, err := a.TxStore.Balance(confirms, bs.Height) return a.TxStore.Balance(confirms, bs.Height)
if err != nil {
log.Errorf("Cannot calculate balance: %v", err)
return 0
}
return bal.ToUnit(btcutil.AmountBTC)
} }
// CalculateAddressBalance sums the amounts of all unspent transaction // CalculateAddressBalance sums the amounts of all unspent transaction
@ -127,16 +126,20 @@ func (a *Account) CalculateBalance(confirms int) float64 {
// a UTXO must be in a block. If confirmations is 1 or greater, // a UTXO must be in a block. If confirmations is 1 or greater,
// the balance will be calculated based on how many how many blocks // the balance will be calculated based on how many how many blocks
// include a UTXO. // include a UTXO.
func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) float64 { func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) (btcutil.Amount, error) {
bs, err := GetCurBlock() rpcc, err := accessClient()
if bs.Height == int32(btcutil.BlockHeightUnknown) || err != nil { if err != nil {
return 0. return 0, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return 0, err
} }
var bal btcutil.Amount var bal btcutil.Amount
unspent, err := a.TxStore.UnspentOutputs() unspent, err := a.TxStore.UnspentOutputs()
if err != nil { if err != nil {
return 0. return 0, err
} }
for _, credit := range unspent { for _, credit := range unspent {
if credit.Confirmed(confirms, bs.Height) { if credit.Confirmed(confirms, bs.Height) {
@ -151,7 +154,7 @@ func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) fl
} }
} }
} }
return bal.ToUnit(btcutil.AmountBTC) return bal, nil
} }
// CurrentAddress gets the most recently requested Bitcoin payment address // CurrentAddress gets the most recently requested Bitcoin payment address
@ -203,14 +206,18 @@ func (a *Account) ListSinceBlock(since, curBlockHeight int32,
// transaction. This is intended to be used for listtransactions RPC // transaction. This is intended to be used for listtransactions RPC
// replies. // replies.
func (a *Account) ListTransactions(from, count int) ([]btcjson.ListTransactionsResult, error) { func (a *Account) ListTransactions(from, count int) ([]btcjson.ListTransactionsResult, error) {
txList := []btcjson.ListTransactionsResult{}
// Get current block. The block height used for calculating // Get current block. The block height used for calculating
// the number of tx confirmations. // the number of tx confirmations.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return txList, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return txList, err
} }
txList := []btcjson.ListTransactionsResult{}
records := a.TxStore.Records() records := a.TxStore.Records()
lastLookupIdx := len(records) - count lastLookupIdx := len(records) - count
@ -232,14 +239,19 @@ func (a *Account) ListTransactions(from, count int) ([]btcjson.ListTransactionsR
func (a *Account) ListAddressTransactions(pkHashes map[string]struct{}) ( func (a *Account) ListAddressTransactions(pkHashes map[string]struct{}) (
[]btcjson.ListTransactionsResult, error) { []btcjson.ListTransactionsResult, error) {
txList := []btcjson.ListTransactionsResult{}
// Get current block. The block height used for calculating // Get current block. The block height used for calculating
// the number of tx confirmations. // the number of tx confirmations.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return txList, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return txList, err
} }
txList := []btcjson.ListTransactionsResult{}
for _, r := range a.TxStore.Records() { for _, r := range a.TxStore.Records() {
for _, c := range r.Credits() { for _, c := range r.Credits() {
// We only care about the case where len(addrs) == 1, // We only care about the case where len(addrs) == 1,
@ -271,16 +283,21 @@ func (a *Account) ListAddressTransactions(pkHashes map[string]struct{}) (
// transaction. This is intended to be used for listalltransactions RPC // transaction. This is intended to be used for listalltransactions RPC
// replies. // replies.
func (a *Account) ListAllTransactions() ([]btcjson.ListTransactionsResult, error) { func (a *Account) ListAllTransactions() ([]btcjson.ListTransactionsResult, error) {
txList := []btcjson.ListTransactionsResult{}
// Get current block. The block height used for calculating // Get current block. The block height used for calculating
// the number of tx confirmations. // the number of tx confirmations.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return txList, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return txList, err
} }
// Search in reverse order: lookup most recently-added first. // Search in reverse order: lookup most recently-added first.
records := a.TxStore.Records() records := a.TxStore.Records()
txList := []btcjson.ListTransactionsResult{}
for i := len(records) - 1; i >= 0; i-- { for i := len(records) - 1; i >= 0; i-- {
jsonResults, err := records[i].ToJSON(a.name, bs.Height, a.Net()) jsonResults, err := records[i].ToJSON(a.name, bs.Height, a.Net())
if err != nil { if err != nil {
@ -478,7 +495,7 @@ func (a *Account) LockedOutpoints() []btcjson.TransactionInput {
// Track requests btcd to send notifications of new transactions for // Track requests btcd to send notifications of new transactions for
// each address stored in a wallet. // each address stored in a wallet.
func (a *Account) Track() { func (a *Account) Track() {
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
log.Errorf("No chain server client to track addresses.") log.Errorf("No chain server client to track addresses.")
return return
@ -495,7 +512,7 @@ func (a *Account) Track() {
addrs = append(addrs, addr) addrs = append(addrs, addr)
} }
if err := client.NotifyReceived(addrs); err != nil { if err := rpcc.NotifyReceived(addrs); err != nil {
log.Error("Unable to request transaction updates for address.") log.Error("Unable to request transaction updates for address.")
} }
@ -543,7 +560,7 @@ func (a *Account) RescanActiveJob() (*RescanJob, error) {
// credits that are not known to have been mined into a block, and attempts // credits that are not known to have been mined into a block, and attempts
// to send each to the chain server for relay. // to send each to the chain server for relay.
func (a *Account) ResendUnminedTxs() { func (a *Account) ResendUnminedTxs() {
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
log.Errorf("No chain server client to resend txs.") log.Errorf("No chain server client to resend txs.")
return return
@ -551,7 +568,7 @@ func (a *Account) ResendUnminedTxs() {
txs := a.TxStore.UnminedDebitTxs() txs := a.TxStore.UnminedDebitTxs()
for _, tx := range txs { for _, tx := range txs {
_, err := client.SendRawTransaction(tx.MsgTx(), false) _, err := rpcc.SendRawTransaction(tx.MsgTx(), false)
if err != nil { if err != nil {
// TODO(jrick): Check error for if this tx is a double spend, // TODO(jrick): Check error for if this tx is a double spend,
// remove it if so. // remove it if so.
@ -592,7 +609,11 @@ func (a *Account) ActivePaymentAddresses() map[string]struct{} {
// NewAddress returns a new payment address for an account. // NewAddress returns a new payment address for an account.
func (a *Account) NewAddress() (btcutil.Address, error) { func (a *Account) NewAddress() (btcutil.Address, error) {
// Get current block's height and hash. // Get current block's height and hash.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return nil, err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -613,11 +634,7 @@ func (a *Account) NewAddress() (btcutil.Address, error) {
AcctMgr.MarkAddressForAccount(addr, a) AcctMgr.MarkAddressForAccount(addr, a)
// Request updates from btcd for new transactions sent to this address. // Request updates from btcd for new transactions sent to this address.
client, err := accessClient() if err := rpcc.NotifyReceived([]btcutil.Address{addr}); err != nil {
if err != nil {
return nil, err
}
if err := client.NotifyReceived([]btcutil.Address{addr}); err != nil {
return nil, err return nil, err
} }
@ -627,7 +644,11 @@ func (a *Account) NewAddress() (btcutil.Address, error) {
// NewChangeAddress returns a new change address for an account. // NewChangeAddress returns a new change address for an account.
func (a *Account) NewChangeAddress() (btcutil.Address, error) { func (a *Account) NewChangeAddress() (btcutil.Address, error) {
// Get current block's height and hash. // Get current block's height and hash.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return nil, err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -648,11 +669,7 @@ func (a *Account) NewChangeAddress() (btcutil.Address, error) {
AcctMgr.MarkAddressForAccount(addr, a) AcctMgr.MarkAddressForAccount(addr, a)
// Request updates from btcd for new transactions sent to this address. // Request updates from btcd for new transactions sent to this address.
client, err := accessClient() if err := rpcc.NotifyReceived([]btcutil.Address{addr}); err != nil {
if err != nil {
return nil, err
}
if err := client.NotifyReceived([]btcutil.Address{addr}); err != nil {
return nil, err return nil, err
} }
@ -676,13 +693,13 @@ func (a *Account) RecoverAddresses(n int) error {
// Run a goroutine to rescan blockchain for recovered addresses. // Run a goroutine to rescan blockchain for recovered addresses.
go func() { go func() {
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
log.Errorf("Cannot access chain server client to " + log.Errorf("Cannot access chain server client to " +
"rescan recovered addresses.") "rescan recovered addresses.")
return return
} }
err = client.Rescan(lastInfo.FirstBlock(), addrs, nil) err = rpcc.Rescan(lastInfo.FirstBlock(), addrs, nil)
if err != nil { if err != nil {
log.Errorf("Rescanning for recovered addresses "+ log.Errorf("Rescanning for recovered addresses "+
"failed: %v", err) "failed: %v", err)
@ -703,13 +720,13 @@ func ReqSpentUtxoNtfns(credits []txstore.Credit) {
ops = append(ops, op) ops = append(ops, op)
} }
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
log.Errorf("Cannot access chain server client to " + log.Errorf("Cannot access chain server client to " +
"request spent output notifications.") "request spent output notifications.")
return return
} }
if err := client.NotifySpent(ops); err != nil { if err := rpcc.NotifySpent(ops); err != nil {
log.Errorf("Cannot request notifications for spent outputs: %v", log.Errorf("Cannot request notifications for spent outputs: %v",
err) err)
} }
@ -718,8 +735,12 @@ func ReqSpentUtxoNtfns(credits []txstore.Credit) {
// TotalReceived iterates through an account's transaction history, returning the // TotalReceived iterates through an account's transaction history, returning the
// total amount of bitcoins received for any account address. Amounts received // total amount of bitcoins received for any account address. Amounts received
// through multisig transactions are ignored. // through multisig transactions are ignored.
func (a *Account) TotalReceived(confirms int) (float64, error) { func (a *Account) TotalReceived(confirms int) (btcutil.Amount, error) {
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return 0, err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -738,6 +759,5 @@ func (a *Account) TotalReceived(confirms int) (float64, error) {
} }
} }
} }
return amount, nil
return amount.ToUnit(btcutil.AmountBTC), nil
} }

View file

@ -758,10 +758,16 @@ func (am *AccountManager) BlockNotify(bs *wallet.BlockStamp) {
// TODO: need a flag or check that the utxo store was actually // TODO: need a flag or check that the utxo store was actually
// modified, or this will notify even if there are no balance // modified, or this will notify even if there are no balance
// changes, or sending these notifications as the utxos are added. // changes, or sending these notifications as the utxos are added.
confirmed := a.CalculateBalance(1) confirmed, err := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - confirmed var unconfirmed btcutil.Amount
server.NotifyWalletBalance(a.name, confirmed) if err == nil {
server.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed) unconfirmed, err = a.CalculateBalance(0)
}
if err == nil {
unconfirmed -= confirmed
server.NotifyWalletBalance(a.name, confirmed)
server.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed)
}
// If this is the default account, update the block all accounts // If this is the default account, update the block all accounts
// are synced with, and schedule a wallet write. // are synced with, and schedule a wallet write.
@ -797,13 +803,13 @@ func (am *AccountManager) RecordSpendingTx(tx *btcutil.Tx, block *txstore.Block)
// CalculateBalance returns the balance, calculated using minconf block // CalculateBalance returns the balance, calculated using minconf block
// confirmations, of an account. // confirmations, of an account.
func (am *AccountManager) CalculateBalance(account string, minconf int) (float64, error) { func (am *AccountManager) CalculateBalance(account string, minconf int) (btcutil.Amount, error) {
a, err := am.Account(account) a, err := am.Account(account)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return a.CalculateBalance(minconf), nil return a.CalculateBalance(minconf)
} }
// CreateEncryptedWallet creates a new default account with a wallet file // CreateEncryptedWallet creates a new default account with a wallet file
@ -814,7 +820,11 @@ func (am *AccountManager) CreateEncryptedWallet(passphrase []byte) error {
} }
// Get current block's height and hash. // Get current block's height and hash.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return err return err
} }
@ -918,13 +928,37 @@ func (am *AccountManager) DumpWIFPrivateKey(addr btcutil.Address) (string, error
// ListAccounts returns a map of account names to their current account // ListAccounts returns a map of account names to their current account
// balances. The balances are calculated using minconf confirmations. // balances. The balances are calculated using minconf confirmations.
func (am *AccountManager) ListAccounts(minconf int) map[string]float64 { func (am *AccountManager) ListAccounts(minconf int) (map[string]btcutil.Amount, error) {
// Create and fill a map of account names and their balances. // Create and fill a map of account names and their balances.
pairs := make(map[string]float64) accts := am.AllAccounts()
for _, a := range am.AllAccounts() { pairs := make(map[string]btcutil.Amount, len(accts))
pairs[a.name] = a.CalculateBalance(minconf) for _, a := range accts {
bal, err := a.CalculateBalance(minconf)
if err != nil {
return nil, err
}
pairs[a.name] = bal
} }
return pairs return pairs, nil
}
// ListAccountsF64 returns a map of account names to their current account
// balances. The balances are calculated using minconf confirmations.
//
// The amounts are converted to float64 so this result may be marshaled
// as a JSON object for the listaccounts RPC.
func (am *AccountManager) ListAccountsF64(minconf int) (map[string]float64, error) {
// Create and fill a map of account names and their balances.
accts := am.AllAccounts()
pairs := make(map[string]float64, len(accts))
for _, a := range accts {
bal, err := a.CalculateBalance(minconf)
if err != nil {
return nil, err
}
pairs[a.name] = bal.ToUnit(btcutil.AmountBTC)
}
return pairs, nil
} }
// ListSinceBlock returns a slice of objects representing all transactions in // ListSinceBlock returns a slice of objects representing all transactions in
@ -983,14 +1017,19 @@ func (am *AccountManager) GetTransaction(txSha *btcwire.ShaHash) []accountTx {
func (am *AccountManager) ListUnspent(minconf, maxconf int, func (am *AccountManager) ListUnspent(minconf, maxconf int,
addresses map[string]bool) ([]*btcjson.ListUnspentResult, error) { addresses map[string]bool) ([]*btcjson.ListUnspentResult, error) {
bs, err := GetCurBlock() results := []*btcjson.ListUnspentResult{}
rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return results, err
}
bs, err := rpcc.BlockStamp()
if err != nil {
return results, err
} }
filter := len(addresses) != 0 filter := len(addresses) != 0
results := []*btcjson.ListUnspentResult{}
for _, a := range am.AllAccounts() { for _, a := range am.AllAccounts() {
unspent, err := a.TxStore.SortedUnspentOutputs() unspent, err := a.TxStore.SortedUnspentOutputs()
if err != nil { if err != nil {

59
cmd.go
View file

@ -23,67 +23,16 @@ import (
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"sync"
"time" "time"
"github.com/conformal/btcutil"
"github.com/conformal/btcwallet/wallet"
"github.com/conformal/btcwire"
) )
var ( var (
cfg *config cfg *config
server *rpcServer server *rpcServer
shutdownChan = make(chan struct{}) shutdownChan = make(chan struct{})
clientAccessChan = make(chan *rpcClient)
curBlock = struct {
sync.RWMutex
wallet.BlockStamp
}{
BlockStamp: wallet.BlockStamp{
Height: int32(btcutil.BlockHeightUnknown),
},
}
) )
// GetCurBlock returns the blockchain height and SHA hash of the most
// recently seen block. If no blocks have been seen since btcd has
// connected, btcd is queried for the current block height and hash.
func GetCurBlock() (wallet.BlockStamp, error) {
curBlock.RLock()
bs := curBlock.BlockStamp
curBlock.RUnlock()
if bs.Height != int32(btcutil.BlockHeightUnknown) {
return bs, nil
}
var bbHash *btcwire.ShaHash
var bbHeight int32
client, err := accessClient()
if err == nil {
bbHash, bbHeight, err = client.GetBestBlock()
}
if err != nil {
unknown := wallet.BlockStamp{
Height: int32(btcutil.BlockHeightUnknown),
}
return unknown, err
}
curBlock.Lock()
if bbHeight > curBlock.BlockStamp.Height {
bs = wallet.BlockStamp{
Height: bbHeight,
Hash: *bbHash,
}
curBlock.BlockStamp = bs
}
curBlock.Unlock()
return bs, nil
}
var clientAccessChan = make(chan *rpcClient)
func clientAccess(newClient <-chan *rpcClient) { func clientAccess(newClient <-chan *rpcClient) {
var client *rpcClient var client *rpcClient
for { for {

View file

@ -163,7 +163,11 @@ func (a *Account) txToPairs(pairs map[string]btcutil.Amount,
} }
// Get current block's height and hash. // Get current block's height and hash.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return nil, err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -72,7 +72,7 @@ type acceptedTx struct {
type ( type (
// Container type for any notification. // Container type for any notification.
notification interface { notification interface {
handleNotification() error handleNotification(*rpcClient) error
} }
blockConnected blockSummary blockConnected blockSummary
@ -85,18 +85,18 @@ type (
rescanProgress int32 rescanProgress int32
) )
func (n blockConnected) handleNotification() error { func (n blockConnected) handleNotification(c *rpcClient) error {
// Update the blockstamp for the newly-connected block. // Update the blockstamp for the newly-connected block.
bs := &wallet.BlockStamp{ bs := wallet.BlockStamp{
Height: n.height, Height: n.height,
Hash: *n.hash, Hash: *n.hash,
} }
curBlock.Lock() c.mtx.Lock()
curBlock.BlockStamp = *bs c.blockStamp = bs
curBlock.Unlock() c.mtx.Unlock()
AcctMgr.Grab() AcctMgr.Grab()
AcctMgr.BlockNotify(bs) AcctMgr.BlockNotify(&bs)
AcctMgr.Release() AcctMgr.Release()
// Pass notification to wallet clients too. // Pass notification to wallet clients too.
@ -121,7 +121,7 @@ func (n blockConnected) MarshalJSON() ([]byte, error) {
return nn.MarshalJSON() return nn.MarshalJSON()
} }
func (n blockDisconnected) handleNotification() error { func (n blockDisconnected) handleNotification(c *rpcClient) error {
AcctMgr.Grab() AcctMgr.Grab()
defer AcctMgr.Release() defer AcctMgr.Release()
@ -170,14 +170,14 @@ func parseBlock(block *btcws.BlockDetails) (*txstore.Block, int, error) {
return b, block.Index, nil return b, block.Index, nil
} }
func (n recvTx) handleNotification() error { func (n recvTx) handleNotification(c *rpcClient) error {
block, txIdx, err := parseBlock(n.block) block, txIdx, err := parseBlock(n.block)
if err != nil { if err != nil {
return InvalidNotificationError{err} return InvalidNotificationError{err}
} }
n.tx.SetIndex(txIdx) n.tx.SetIndex(txIdx)
bs, err := GetCurBlock() bs, err := c.BlockStamp()
if err != nil { if err != nil {
return fmt.Errorf("cannot get current block: %v", err) return fmt.Errorf("cannot get current block: %v", err)
} }
@ -233,7 +233,7 @@ func (n recvTx) handleNotification() error {
return nil return nil
} }
func (n redeemingTx) handleNotification() error { func (n redeemingTx) handleNotification(c *rpcClient) error {
block, txIdx, err := parseBlock(n.block) block, txIdx, err := parseBlock(n.block)
if err != nil { if err != nil {
return InvalidNotificationError{err} return InvalidNotificationError{err}
@ -246,26 +246,34 @@ func (n redeemingTx) handleNotification() error {
return err return err
} }
func (n rescanFinished) handleNotification() error { func (n rescanFinished) handleNotification(c *rpcClient) error {
AcctMgr.rm.MarkFinished(n) AcctMgr.rm.MarkFinished(n)
return nil return nil
} }
func (n rescanProgress) handleNotification() error { func (n rescanProgress) handleNotification(c *rpcClient) error {
AcctMgr.rm.MarkProgress(n) AcctMgr.rm.MarkProgress(n)
return nil return nil
} }
type rpcClient struct { type rpcClient struct {
*btcrpcclient.Client // client to btcd *btcrpcclient.Client // client to btcd
enqueueNotification chan notification
dequeueNotification chan notification mtx sync.Mutex
quit chan struct{} blockStamp wallet.BlockStamp
wg sync.WaitGroup
enqueueNotification chan notification
dequeueNotification chan notification
quit chan struct{}
wg sync.WaitGroup
} }
func newRPCClient(certs []byte) (*rpcClient, error) { func newRPCClient(certs []byte) (*rpcClient, error) {
client := rpcClient{ client := rpcClient{
blockStamp: wallet.BlockStamp{
Height: int32(btcutil.BlockHeightUnknown),
},
enqueueNotification: make(chan notification), enqueueNotification: make(chan notification),
dequeueNotification: make(chan notification), dequeueNotification: make(chan notification),
quit: make(chan struct{}), quit: make(chan struct{}),
@ -408,7 +416,7 @@ out:
func (c *rpcClient) handleNotifications() { func (c *rpcClient) handleNotifications() {
for n := range c.dequeueNotification { for n := range c.dequeueNotification {
err := n.handleNotification() err := n.handleNotification(c)
if err != nil { if err != nil {
switch e := err.(type) { switch e := err.(type) {
case InvalidNotificationError: case InvalidNotificationError:
@ -421,6 +429,30 @@ func (c *rpcClient) handleNotifications() {
c.wg.Done() c.wg.Done()
} }
// BlockStamp returns (as a blockstamp) the height and hash of the last seen
// block from the RPC client. If no blocks have been seen (the height is -1),
// the chain server is queried for the block and the result is saved for future
// calls, or an error is returned if the RPC is unsuccessful.
func (c *rpcClient) BlockStamp() (wallet.BlockStamp, error) {
c.mtx.Lock()
defer c.mtx.Unlock()
if c.blockStamp.Height != int32(btcutil.BlockHeightUnknown) {
return c.blockStamp, nil
}
hash, height, err := c.GetBestBlock()
if err != nil {
return wallet.BlockStamp{}, err
}
bs := wallet.BlockStamp{
Hash: *hash,
Height: height,
}
c.blockStamp = bs
return bs, nil
}
// Handshake first checks that the websocket connection between btcwallet and // Handshake first checks that the websocket connection between btcwallet and
// btcd is valid, that is, that there are no mismatching settings between // btcd is valid, that is, that there are no mismatching settings between
// the two processes (such as running on different Bitcoin networks). If the // the two processes (such as running on different Bitcoin networks). If the
@ -448,9 +480,9 @@ func (c *rpcClient) Handshake() error {
// saved block hash, assume that this btcd instance is not yet // saved block hash, assume that this btcd instance is not yet
// synced up to a previous btcd that was last used with this // synced up to a previous btcd that was last used with this
// wallet. // wallet.
bs, err := GetCurBlock() bs, err := c.BlockStamp()
if err != nil { if err != nil {
return fmt.Errorf("cannot get best block: %v", err) return err
} }
if server != nil { if server != nil {
server.NotifyNewBlockChainHeight(&bs) server.NotifyNewBlockChainHeight(&bs)

View file

@ -398,9 +398,9 @@ func (s *rpcServer) Stop() {
} }
// Disconnect the connected chain server, if any. // Disconnect the connected chain server, if any.
client, err := accessClient() rpcc, err := accessClient()
if err == nil { if err == nil {
client.Stop() rpcc.Stop()
} }
// Stop the account manager and finish all pending account file writes. // Stop the account manager and finish all pending account file writes.
@ -535,9 +535,9 @@ func (s *rpcServer) postPassthrough(w http.ResponseWriter, request rawRequest) {
// request's ID. // request's ID.
func passthrough(request rawRequest) []byte { func passthrough(request rawRequest) []byte {
var res json.RawMessage var res json.RawMessage
client, err := accessClient() rpcc, err := accessClient()
if err == nil { if err == nil {
res, err = client.RawRequest(request.Method, request.Params) res, err = rpcc.RawRequest(request.Method, request.Params)
} }
var jsonErr *btcjson.Error var jsonErr *btcjson.Error
if err != nil { if err != nil {
@ -878,9 +878,9 @@ func (s *rpcServer) PostClientRPC(w http.ResponseWriter, r *http.Request) {
// current connection status of btcwallet to btcd. // current connection status of btcwallet to btcd.
func (s *rpcServer) NotifyConnectionStatus(wsc *websocketClient) { func (s *rpcServer) NotifyConnectionStatus(wsc *websocketClient) {
connected := false connected := false
client, err := accessClient() rpcc, err := accessClient()
if err == nil { if err == nil {
connected = !client.Disconnected() connected = !rpcc.Disconnected()
} }
ntfn := btcws.NewBtcdConnectedNtfn(connected) ntfn := btcws.NewBtcdConnectedNtfn(connected)
mntfn, err := ntfn.MarshalJSON() mntfn, err := ntfn.MarshalJSON()
@ -1287,7 +1287,7 @@ func GetBalance(icmd btcjson.Cmd) (interface{}, error) {
if err == ErrNotFound { if err == ErrNotFound {
return nil, btcjson.ErrWalletInvalidAccountName return nil, btcjson.ErrWalletInvalidAccountName
} }
return balance, err return balance.ToUnit(btcutil.AmountBTC), err
} }
// GetInfo handles a getinfo request by returning the a structure containing // GetInfo handles a getinfo request by returning the a structure containing
@ -1296,22 +1296,25 @@ func GetBalance(icmd btcjson.Cmd) (interface{}, error) {
func GetInfo(icmd btcjson.Cmd) (interface{}, error) { func GetInfo(icmd btcjson.Cmd) (interface{}, error) {
// Call down to btcd for all of the information in this command known // Call down to btcd for all of the information in this command known
// by them. // by them.
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
info, err := client.GetInfo() info, err := rpcc.GetInfo()
if err != nil { if err != nil {
return nil, err return nil, err
} }
balance := float64(0.0) var balance btcutil.Amount
accounts := AcctMgr.ListAccounts(1) accounts, err := AcctMgr.ListAccounts(1)
if err != nil {
return nil, err
}
for _, v := range accounts { for _, v := range accounts {
balance += v balance += v
} }
info.WalletVersion = int32(wallet.VersCurrent.Uint32()) info.WalletVersion = int32(wallet.VersCurrent.Uint32())
info.Balance = balance info.Balance = balance.ToUnit(btcutil.AmountBTC)
// Keypool times are not tracked. set to current time. // Keypool times are not tracked. set to current time.
info.KeypoolOldest = time.Now().Unix() info.KeypoolOldest = time.Now().Unix()
info.KeypoolSize = int32(cfg.KeypoolSize) info.KeypoolSize = int32(cfg.KeypoolSize)
@ -1406,7 +1409,8 @@ func GetAddressBalance(icmd btcjson.Cmd) (interface{}, error) {
return nil, ErrAddressNotInWallet return nil, ErrAddressNotInWallet
} }
return a.CalculateAddressBalance(addr, int(cmd.Minconf)), nil bal, err := a.CalculateAddressBalance(addr, int(cmd.Minconf))
return bal.ToUnit(btcutil.AmountBTC), err
} }
// GetUnconfirmedBalance handles a getunconfirmedbalance extension request // GetUnconfirmedBalance handles a getunconfirmedbalance extension request
@ -1427,7 +1431,16 @@ func GetUnconfirmedBalance(icmd btcjson.Cmd) (interface{}, error) {
return nil, err return nil, err
} }
return a.CalculateBalance(0) - a.CalculateBalance(1), nil unconfirmed, err := a.CalculateBalance(0)
if err != nil {
return nil, err
}
confirmed, err := a.CalculateBalance(1)
if err != nil {
return nil, err
}
return (unconfirmed - confirmed).ToUnit(btcutil.AmountBTC), nil
} }
// ImportPrivKey handles an importprivkey request by parsing // ImportPrivKey handles an importprivkey request by parsing
@ -1502,8 +1515,15 @@ func (s *rpcServer) NotifyNewBlockChainHeight(bs *wallet.BlockStamp) {
// separate notifications for each account. // separate notifications for each account.
func (s *rpcServer) NotifyBalances() { func (s *rpcServer) NotifyBalances() {
for _, a := range AcctMgr.AllAccounts() { for _, a := range AcctMgr.AllAccounts() {
balance := a.CalculateBalance(1) balance, err := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - balance var unconfirmed btcutil.Amount
if err == nil {
unconfirmed, err = a.CalculateBalance(0)
}
if err != nil {
break
}
unconfirmed -= balance
s.NotifyWalletBalance(a.name, balance) s.NotifyWalletBalance(a.name, balance)
s.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed) s.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed)
} }
@ -1580,7 +1600,8 @@ func GetReceivedByAccount(icmd btcjson.Cmd) (interface{}, error) {
return nil, err return nil, err
} }
return a.TotalReceived(cmd.MinConf) bal, err := a.TotalReceived(cmd.MinConf)
return bal.ToUnit(btcutil.AmountBTC), err
} }
// GetTransaction handles a gettransaction request by returning details about // GetTransaction handles a gettransaction request by returning details about
@ -1602,7 +1623,11 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, error) {
return nil, btcjson.ErrNoTxInfo return nil, btcjson.ErrNoTxInfo
} }
bs, err := GetCurBlock() rpcc, err := accessClient()
if err != nil {
return nil, err
}
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1719,7 +1744,7 @@ func ListAccounts(icmd btcjson.Cmd) (interface{}, error) {
} }
// Return the map. This will be marshaled into a JSON object. // Return the map. This will be marshaled into a JSON object.
return AcctMgr.ListAccounts(cmd.MinConf), nil return AcctMgr.ListAccounts(cmd.MinConf)
} }
// ListLockUnspent handles a listlockunspent request by returning an slice of // ListLockUnspent handles a listlockunspent request by returning an slice of
@ -1763,14 +1788,17 @@ func ListReceivedByAddress(icmd btcjson.Cmd) (interface{}, error) {
confirmations int32 confirmations int32
} }
// Intermediate data for all addresses. rpcc, err := accessClient()
allAddrData := make(map[string]AddrData) if err != nil {
return nil, err
bs, err := GetCurBlock() }
bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Intermediate data for all addresses.
allAddrData := make(map[string]AddrData)
for _, account := range AcctMgr.AllAccounts() { for _, account := range AcctMgr.AllAccounts() {
if cmd.IncludeEmpty { if cmd.IncludeEmpty {
// Create an AddrData entry for each active address in the account. // Create an AddrData entry for each active address in the account.
@ -1842,7 +1870,7 @@ func ListSinceBlock(icmd btcjson.Cmd) (interface{}, error) {
return nil, btcjson.ErrInternal return nil, btcjson.ErrInternal
} }
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1853,14 +1881,14 @@ func ListSinceBlock(icmd btcjson.Cmd) (interface{}, error) {
if err != nil { if err != nil {
return nil, DeserializationError{err} return nil, DeserializationError{err}
} }
block, err := client.GetBlock(hash) block, err := rpcc.GetBlock(hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
height = int32(block.Height()) height = int32(block.Height())
} }
bs, err := GetCurBlock() bs, err := rpcc.BlockStamp()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1868,7 +1896,7 @@ func ListSinceBlock(icmd btcjson.Cmd) (interface{}, error) {
// For the result we need the block hash for the last block counted // For the result we need the block hash for the last block counted
// in the blockchain due to confirmations. We send this off now so that // in the blockchain due to confirmations. We send this off now so that
// it can arrive asynchronously while we figure out the rest. // it can arrive asynchronously while we figure out the rest.
gbh := client.GetBlockHashAsync(int64(bs.Height) + 1 - int64(cmd.TargetConfirmations)) gbh := rpcc.GetBlockHashAsync(int64(bs.Height) + 1 - int64(cmd.TargetConfirmations))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2038,7 +2066,7 @@ func LockUnspent(icmd btcjson.Cmd) (interface{}, error) {
func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount, func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount,
minconf int) (interface{}, error) { minconf int) (interface{}, error) {
client, err := accessClient() rpcc, err := accessClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2070,13 +2098,13 @@ func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amou
if err := AcctMgr.ds.FlushAccount(a); err != nil { if err := AcctMgr.ds.FlushAccount(a); err != nil {
return nil, fmt.Errorf("Cannot write account: %v", err) return nil, fmt.Errorf("Cannot write account: %v", err)
} }
err := client.NotifyReceived([]btcutil.Address{createdTx.changeAddr}) err := rpcc.NotifyReceived([]btcutil.Address{createdTx.changeAddr})
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
txSha, err := client.SendRawTransaction(createdTx.tx.MsgTx(), false) txSha, err := rpcc.SendRawTransaction(createdTx.tx.MsgTx(), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2179,8 +2207,11 @@ func handleSendRawTxReply(icmd btcjson.Cmd, txSha *btcwire.ShaHash, a *Account,
AcctMgr.ds.ScheduleTxStoreWrite(a) AcctMgr.ds.ScheduleTxStoreWrite(a)
// Notify websocket clients of the transaction. // Notify websocket clients of the transaction.
bs, err := GetCurBlock() rpcc, err := accessClient()
if err == nil { if err != nil {
return err
}
if bs, err := rpcc.BlockStamp(); err == nil {
ltr, err := debits.ToJSON(a.Name(), bs.Height, a.Net()) ltr, err := debits.ToJSON(a.Name(), bs.Height, a.Net())
if err != nil { if err != nil {
log.Errorf("Error adding sent tx history: %v", err) log.Errorf("Error adding sent tx history: %v", err)
@ -2199,8 +2230,15 @@ func handleSendRawTxReply(icmd btcjson.Cmd, txSha *btcwire.ShaHash, a *Account,
// Notify websocket clients of account's new unconfirmed and // Notify websocket clients of account's new unconfirmed and
// confirmed balance. // confirmed balance.
confirmed := a.CalculateBalance(1) confirmed, err := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - confirmed var unconfirmed btcutil.Amount
if err == nil {
unconfirmed, err = a.CalculateBalance(0)
}
if err != nil {
return err
}
unconfirmed -= confirmed
server.NotifyWalletBalance(a.name, confirmed) server.NotifyWalletBalance(a.name, confirmed)
server.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed) server.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed)
@ -2394,7 +2432,7 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, error) {
}] = script }] = script
} }
var client *rpcClient var rpcc *rpcClient
// Now we go and look for any inputs that we were not provided by // Now we go and look for any inputs that we were not provided by
// querying btcd with getrawtransaction. We queue up a bunch of async // querying btcd with getrawtransaction. We queue up a bunch of async
@ -2419,15 +2457,15 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, error) {
} }
// Never heard of this one before, request it. // Never heard of this one before, request it.
if client == nil { if rpcc == nil {
client, err = accessClient() rpcc, err = accessClient()
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
prevHash := &txIn.PreviousOutpoint.Hash prevHash := &txIn.PreviousOutpoint.Hash
requested[txIn.PreviousOutpoint.Hash] = &pendingTx{ requested[txIn.PreviousOutpoint.Hash] = &pendingTx{
resp: client.GetRawTransactionAsync(prevHash), resp: rpcc.GetRawTransactionAsync(prevHash),
inputs: []uint32{txIn.PreviousOutpoint.Index}, inputs: []uint32{txIn.PreviousOutpoint.Index},
} }
} }
@ -2814,8 +2852,9 @@ func (s *rpcServer) NotifyWalletLockStateChange(account string, locked bool) {
// NotifyWalletBalance sends a confirmed account balance notification // NotifyWalletBalance sends a confirmed account balance notification
// to all websocket clients. // to all websocket clients.
func (s *rpcServer) NotifyWalletBalance(account string, balance float64) { func (s *rpcServer) NotifyWalletBalance(account string, balance btcutil.Amount) {
ntfn := btcws.NewAccountBalanceNtfn(account, balance, true) fbal := balance.ToUnit(btcutil.AmountBTC)
ntfn := btcws.NewAccountBalanceNtfn(account, fbal, true)
mntfn, err := ntfn.MarshalJSON() mntfn, err := ntfn.MarshalJSON()
// If the marshal failed, it indicates that the btcws notification // If the marshal failed, it indicates that the btcws notification
// struct contains a field with a type that is not marshalable. // struct contains a field with a type that is not marshalable.
@ -2830,8 +2869,9 @@ func (s *rpcServer) NotifyWalletBalance(account string, balance float64) {
// NotifyWalletBalanceUnconfirmed sends a confirmed account balance // NotifyWalletBalanceUnconfirmed sends a confirmed account balance
// notification to all websocket clients. // notification to all websocket clients.
func (s *rpcServer) NotifyWalletBalanceUnconfirmed(account string, balance float64) { func (s *rpcServer) NotifyWalletBalanceUnconfirmed(account string, balance btcutil.Amount) {
ntfn := btcws.NewAccountBalanceNtfn(account, balance, false) fbal := balance.ToUnit(btcutil.AmountBTC)
ntfn := btcws.NewAccountBalanceNtfn(account, fbal, false)
mntfn, err := ntfn.MarshalJSON() mntfn, err := ntfn.MarshalJSON()
// If the marshal failed, it indicates that the btcws notification // If the marshal failed, it indicates that the btcws notification
// struct contains a field with a type that is not marshalable. // struct contains a field with a type that is not marshalable.