/* * Copyright (c) 2013, 2014 Conformal Systems LLC * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package wallet import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/waddrmgr" ) func (w *Wallet) handleChainNotifications() { sync := func(w *Wallet) { // At the moment there is no recourse if the rescan fails for // some reason, however, the wallet will not be marked synced // and many methods will error early since the wallet is known // to be out of date. err := w.syncWithChain() if err != nil && !w.ShuttingDown() { log.Warnf("Unable to synchronize wallet to chain: %v", err) } } for n := range w.chainSvr.Notifications() { var err error switch n := n.(type) { case chain.ClientConnected: go sync(w) case chain.BlockConnected: w.connectBlock(waddrmgr.BlockStamp(n)) case chain.BlockDisconnected: w.disconnectBlock(waddrmgr.BlockStamp(n)) case chain.RecvTx: err = w.addReceivedTx(n.Tx, n.Block) case chain.RedeemingTx: err = w.addRedeemingTx(n.Tx, n.Block) // The following are handled by the wallet's rescan // goroutines, so just pass them there. case *chain.RescanProgress, *chain.RescanFinished: w.rescanNotifications <- n } if err != nil { log.Errorf("Cannot handle chain server "+ "notification: %v", err) } } w.wg.Done() } // connectBlock handles a chain server notification by marking a wallet // that's currently in-sync with the chain server as being synced up to // the passed block. func (w *Wallet) connectBlock(bs waddrmgr.BlockStamp) { if !w.ChainSynced() { return } if err := w.Manager.SetSyncedTo(&bs); err != nil { log.Errorf("Failed to update address manager sync state in "+ "connect block for hash %v (height %d): %v", bs.Hash, bs.Height, err) } w.notifyConnectedBlock(bs) w.notifyBalances(bs.Height) } // disconnectBlock handles a chain server reorganize by rolling back all // block history from the reorged block for a wallet in-sync with the chain // server. func (w *Wallet) disconnectBlock(bs waddrmgr.BlockStamp) { if !w.ChainSynced() { return } // Disconnect the last seen block from the manager if it matches the // removed block. iter := w.Manager.NewIterateRecentBlocks() if iter != nil && iter.BlockStamp().Hash == bs.Hash { if iter.Prev() { prev := iter.BlockStamp() w.Manager.SetSyncedTo(&prev) } else { // The reorg is farther back than the recently-seen list // of blocks has recorded, so set it to unsynced which // will in turn lead to a rescan from either the // earliest blockstamp the addresses in the manager are // known to have been created. w.Manager.SetSyncedTo(nil) } } w.notifyDisconnectedBlock(bs) w.notifyBalances(bs.Height - 1) } func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error { // For every output, if it pays to a wallet address, insert the // transaction into the store (possibly moving it from unconfirmed to // confirmed), and add a credit record if one does not already exist. var txr *txstore.TxRecord txInserted := false for txOutIdx, txOut := range tx.MsgTx().TxOut { // Errors don't matter here. If addrs is nil, the range below // does nothing. _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addr) if err == nil { insert = true break } } if insert { if !txInserted { var err error txr, err = w.TxStore.InsertTx(tx, block) if err != nil { return err } // InsertTx may have moved a previous unmined // tx, so mark the entire store as dirty. w.TxStore.MarkDirty() txInserted = true } if txr.HasCredit(txOutIdx) { continue } _, err := txr.AddCredit(uint32(txOutIdx), false) if err != nil { return err } w.TxStore.MarkDirty() } } bs, err := w.chainSvr.BlockStamp() if err == nil { w.notifyBalances(bs.Height) } return nil } // addRedeemingTx inserts the notified spending transaction as a debit and // schedules the transaction store for a future file write. func (w *Wallet) addRedeemingTx(tx *btcutil.Tx, block *txstore.Block) error { txr, err := w.TxStore.InsertTx(tx, block) if err != nil { return err } if _, err := txr.AddDebits(); err != nil { return err } if err := w.markAddrsUsed(txr); err != nil { return err } bs, err := w.chainSvr.BlockStamp() if err == nil { w.notifyBalances(bs.Height) } return nil } func (w *Wallet) notifyBalances(curHeight int32) { // Don't notify unless wallet is synced to the chain server. if !w.ChainSynced() { return } // Notify any potential changes to the balance. confirmed, err := w.TxStore.Balance(1, curHeight) if err != nil { log.Errorf("Cannot determine 1-conf balance: %v", err) return } w.notifyConfirmedBalance(confirmed) unconfirmed, err := w.TxStore.Balance(0, curHeight) if err != nil { log.Errorf("Cannot determine 0-conf balance: %v", err) return } w.notifyUnconfirmedBalance(unconfirmed - confirmed) }