diff --git a/cmd.go b/cmd.go index 30d05ea..e3c56b1 100644 --- a/cmd.go +++ b/cmd.go @@ -32,13 +32,23 @@ import ( "time" ) +const ( + satoshiPerBTC = 100000000 +) + var ( ErrNoWallet = errors.New("Wallet file does not exist.") ) var ( - log seelog.LoggerInterface = seelog.Default - cfg *config + log seelog.LoggerInterface = seelog.Default + cfg *config + curHeight = struct { + sync.RWMutex + h int64 + }{ + h: btcutil.BlockHeightUnknown, + } wallets = struct { sync.RWMutex m map[string]*BtcWallet @@ -185,6 +195,82 @@ func OpenWallet(cfg *config, account string) (*BtcWallet, error) { return w, nil } +func getCurHeight() (height int64) { + curHeight.RLock() + height = curHeight.h + curHeight.RUnlock() + if height != btcutil.BlockHeightUnknown { + return height + } else { + seq.Lock() + n := seq.n + seq.n++ + seq.Unlock() + + m, err := btcjson.CreateMessageWithId("getblockcount", + fmt.Sprintf("btcwallet(%v)", n)) + if err != nil { + // Can't continue. + return btcutil.BlockHeightUnknown + } + + c := make(chan int64) + + replyHandlers.Lock() + replyHandlers.m[n] = func(result, e interface{}) bool { + if e != nil { + c <- btcutil.BlockHeightUnknown + return true + } + if balance, ok := result.(float64); ok { + c <- int64(balance) + } else { + c <- btcutil.BlockHeightUnknown + } + return true + } + replyHandlers.Unlock() + + // send message + btcdMsgs <- m + + // Block until reply is ready. + height = <-c + curHeight.Lock() + if height > curHeight.h { + curHeight.h = height + } else { + height = curHeight.h + } + curHeight.Unlock() + + return height + } +} + +func (w *BtcWallet) CalculateBalance(confirmations int) float64 { + var bal int64 // Measured in satoshi + + height := getCurHeight() + if height == btcutil.BlockHeightUnknown { + return 0. + } + + w.UtxoStore.RLock() + for _, u := range w.UtxoStore.s.Confirmed { + if int(height-u.Height) >= confirmations { + bal += u.Amt + } + } + for _, u := range w.UtxoStore.s.Unconfirmed { + if int(height-u.Height) >= confirmations { + bal += u.Amt + } + } + w.UtxoStore.RUnlock() + return float64(bal) / satoshiPerBTC +} + func (w *BtcWallet) Track() { seq.Lock() n := seq.n @@ -225,7 +311,7 @@ func (w *BtcWallet) RescanForAddress(addr string, blocks ...int) { msg, _ := json.Marshal(m) replyHandlers.Lock() - replyHandlers.m[n] = func(result interface{}) bool { + replyHandlers.m[n] = func(result, e interface{}) bool { // TODO(jrick) // btcd returns a nil result when the rescan is complete. @@ -254,12 +340,26 @@ func (w *BtcWallet) ReqNewTxsForAddress(addr string) { btcdMsgs <- msg } -func (w *BtcWallet) NewBlockTxHandler(result interface{}) bool { +func (w *BtcWallet) NewBlockTxHandler(result, e interface{}) bool { + if e != nil { + if v, ok := e.(map[string]interface{}); ok { + if msg, ok := v["message"]; ok { + log.Errorf("Tx Handler: Error received from btcd: %s", msg) + return false + } + } + log.Errorf("Tx Handler: Error is non-nil but cannot be parsed.") + } + // TODO(jrick): btcd also sends the block hash in the reply. // Do we want it saved as well? v, ok := result.(map[string]interface{}) if !ok { - log.Error("Tx Handler: Unexpected result type.") + // The first result sent from btcd is nil. This could be used to + // indicate that the request for notifications succeeded. + if result != nil { + log.Errorf("Tx Handler: Unexpected result type %T.", result) + } return false } sender58, ok := v["sender"].(string) diff --git a/cmdmgr.go b/cmdmgr.go index 9b2d6ca..2cd2793 100644 --- a/cmdmgr.go +++ b/cmdmgr.go @@ -245,6 +245,7 @@ func GetBalance(reply chan []byte, msg []byte) { json.Unmarshal(msg, &v) params := v["params"].([]interface{}) var wname string + conf := 1 if len(params) > 0 { if s, ok := params[0].(string); ok { wname = s @@ -252,13 +253,20 @@ func GetBalance(reply chan []byte, msg []byte) { ReplyError(reply, v["id"], &InvalidParams) } } + if len(params) > 1 { + if f, ok := params[1].(float64); ok { + conf = int(f) + } else { + ReplyError(reply, v["id"], &InvalidParams) + } + } wallets.RLock() w := wallets.m[wname] wallets.RUnlock() var result interface{} if w != nil { - result = 0 // TODO(jrick) + result = w.CalculateBalance(conf) ReplySuccess(reply, v["id"], result) } else { e := WalletInvalidAccountName diff --git a/sockets.go b/sockets.go index 6c1b019..fd3ae6d 100644 --- a/sockets.go +++ b/sockets.go @@ -33,7 +33,8 @@ var ( // Channel to close to notify that connection to btcd has been lost. btcdDisconnected = make(chan int) - // Channel to send messages btcwallet does not understand to btcd. + // Channel to send messages btcwallet does not understand and requests + // from btcwallet to btcd. btcdMsgs = make(chan []byte, 100) // Adds a frontend listener channel @@ -51,9 +52,9 @@ var ( // handler function to route the reply to. replyHandlers = struct { sync.Mutex - m map[uint64]func(interface{}) bool + m map[uint64]func(interface{}, interface{}) bool }{ - m: make(map[uint64]func(interface{}) bool), + m: make(map[uint64]func(interface{}, interface{}) bool), } ) @@ -231,7 +232,7 @@ func ProcessBtcdNotificationReply(b []byte) { replyHandlers.Unlock() if f != nil { go func() { - if f(m["result"]) { + if f(m["result"], m["error"]) { replyHandlers.Lock() delete(replyHandlers.m, routeId) replyHandlers.Unlock()