lbcwallet/wallet/chainntfns.go
2015-04-20 18:20:19 -04:00

210 lines
5.9 KiB
Go

/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* 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/legacy/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:
err = 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) error {
if !w.ChainSynced() {
return nil
}
// 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)
err := w.TxStore.Rollback(prev.Height)
if err != nil {
return err
}
} 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)
return nil
}
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)
}