lbcwallet/ntfns.go

266 lines
8.1 KiB
Go
Raw Normal View History

/*
* 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.
*/
// This file implements the notification handlers for btcd-side notifications.
package main
import (
"encoding/hex"
"fmt"
"sync"
"time"
"github.com/conformal/btcjson"
"github.com/conformal/btcscript"
"github.com/conformal/btcutil"
"github.com/conformal/btcwallet/tx"
"github.com/conformal/btcwallet/wallet"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
)
func parseBlock(block *btcws.BlockDetails) (*tx.BlockDetails, error) {
if block == nil {
return nil, nil
}
blksha, err := btcwire.NewShaHashFromStr(block.Hash)
if err != nil {
return nil, err
}
return &tx.BlockDetails{
Height: block.Height,
Hash: *blksha,
Index: int32(block.Index),
Time: time.Unix(block.Time, 0),
}, nil
}
type notificationHandler func(btcjson.Cmd) error
var notificationHandlers = map[string]notificationHandler{
btcws.BlockConnectedNtfnMethod: NtfnBlockConnected,
btcws.BlockDisconnectedNtfnMethod: NtfnBlockDisconnected,
btcws.RecvTxNtfnMethod: NtfnRecvTx,
btcws.RedeemingTxNtfnMethod: NtfnRedeemingTx,
}
// NtfnRecvTx handles the btcws.RecvTxNtfn notification.
func NtfnRecvTx(n btcjson.Cmd) error {
rtx, ok := n.(*btcws.RecvTxNtfn)
if !ok {
return fmt.Errorf("%v handler: unexpected type", n.Method())
}
bs, err := GetCurBlock()
if err != nil {
return fmt.Errorf("%v handler: cannot get current block: %v", n.Method(), err)
}
rawTx, err := hex.DecodeString(rtx.HexTx)
if err != nil {
return fmt.Errorf("%v handler: bad hexstring: %v", n.Method(), err)
}
tx_, err := btcutil.NewTxFromBytes(rawTx)
if err != nil {
return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err)
}
var block *tx.BlockDetails
if rtx.Block != nil {
block, err = parseBlock(rtx.Block)
if err != nil {
return fmt.Errorf("%v handler: bad block: %v", n.Method(), err)
}
}
// For transactions originating from this wallet, the sent tx history should
// be recorded before the received history. If wallet created this tx, wait
// for the sent history to finish being recorded before continuing.
//
// TODO(jrick) this is wrong due to tx malleability. Cannot safely use the
// txsha as an identifier.
req := SendTxHistSyncRequest{
txsha: *tx_.Sha(),
response: make(chan SendTxHistSyncResponse),
}
SendTxHistSyncChans.access <- req
resp := <-req.response
if resp.ok {
// Wait until send history has been recorded.
<-resp.c
SendTxHistSyncChans.remove <- *tx_.Sha()
}
// For every output, find all accounts handling that output address (if any)
// and record the received txout.
for outIdx, txout := range tx_.MsgTx().TxOut {
var accounts []*Account
var received time.Time
_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, cfg.Net())
for _, addr := range addrs {
aname, err := LookupAccountByAddress(addr.EncodeAddress())
if err == ErrNotFound {
continue
}
// This cannot reasonably fail if the above succeeded.
a, _ := AcctMgr.Account(aname)
accounts = append(accounts, a)
if block != nil {
received = block.Time
} else {
received = time.Now()
}
}
for _, a := range accounts {
record, err := a.TxStore.InsertRecvTxOut(tx_, uint32(outIdx), false, received, block)
if err != nil {
return err
}
AcctMgr.ds.ScheduleTxStoreWrite(a)
// Notify frontends of tx. If the tx is unconfirmed, it is always
// notified and the outpoint is marked as notified. If the outpoint
// has already been notified and is now in a block, a txmined notifiction
// should be sent once to let frontends that all previous send/recvs
// for this unconfirmed tx are now confirmed.
recvTxOP := btcwire.NewOutPoint(tx_.Sha(), uint32(outIdx))
previouslyNotifiedReq := NotifiedRecvTxRequest{
op: *recvTxOP,
response: make(chan NotifiedRecvTxResponse),
}
NotifiedRecvTxChans.access <- previouslyNotifiedReq
if <-previouslyNotifiedReq.response {
NotifiedRecvTxChans.remove <- *recvTxOP
} else {
// Notify frontends of new recv tx and mark as notified.
NotifiedRecvTxChans.add <- *recvTxOP
// need access to the RecvTxOut to get the json info object
NotifyNewTxDetails(allClients, a.Name(),
record.TxInfo(a.Name(), bs.Height, a.Wallet.Net())[0])
}
// Notify frontends of new account balance.
confirmed := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - confirmed
NotifyWalletBalance(allClients, a.name, confirmed)
NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed)
}
}
return nil
}
// NtfnBlockConnected handles btcd notifications resulting from newly
// connected blocks to the main blockchain.
//
// TODO(jrick): Send block time with notification. This will be used
// to mark wallet files with a possibly-better earliest block height,
// and will greatly reduce rescan times for wallets created with an
// out of sync btcd.
func NtfnBlockConnected(n btcjson.Cmd) error {
bcn, ok := n.(*btcws.BlockConnectedNtfn)
if !ok {
return fmt.Errorf("%v handler: unexpected type", n.Method())
}
hash, err := btcwire.NewShaHashFromStr(bcn.Hash)
if err != nil {
return fmt.Errorf("%v handler: invalid hash string", n.Method())
}
// Update the blockstamp for the newly-connected block.
bs := &wallet.BlockStamp{
Height: bcn.Height,
Hash: *hash,
}
curBlock.Lock()
curBlock.BlockStamp = *bs
curBlock.Unlock()
// btcd notifies btcwallet about transactions first, and then sends
// the new block notification. New balance notifications for txs
// in blocks are therefore sent here after all tx notifications
// have arrived and finished being processed by the handlers.
workers := NotifyBalanceRequest{
block: *hash,
wg: make(chan *sync.WaitGroup),
}
NotifyBalanceSyncerChans.access <- workers
if wg := <-workers.wg; wg != nil {
wg.Wait()
NotifyBalanceSyncerChans.remove <- *hash
}
AcctMgr.BlockNotify(bs)
// Pass notification to frontends too.
marshaled, _ := n.MarshalJSON()
allClients <- marshaled
return nil
}
// NtfnBlockDisconnected handles btcd notifications resulting from
// blocks disconnected from the main chain in the event of a chain
// switch and notifies frontends of the new blockchain height.
func NtfnBlockDisconnected(n btcjson.Cmd) error {
bdn, ok := n.(*btcws.BlockDisconnectedNtfn)
if !ok {
return fmt.Errorf("%v handler: unexpected type", n.Method())
}
hash, err := btcwire.NewShaHashFromStr(bdn.Hash)
if err != nil {
return fmt.Errorf("%v handler: invalid hash string", n.Method())
}
// Rollback Utxo and Tx data stores.
AcctMgr.Rollback(bdn.Height, hash)
// Pass notification to frontends too.
marshaled, _ := n.MarshalJSON()
allClients <- marshaled
return nil
}
// NtfnRedeemingTx handles btcd redeemingtx notifications resulting from a
// transaction spending a watched outpoint.
func NtfnRedeemingTx(n btcjson.Cmd) error {
cn, ok := n.(*btcws.RedeemingTxNtfn)
if !ok {
return fmt.Errorf("%v handler: unexpected type", n.Method())
}
rawTx, err := hex.DecodeString(cn.HexTx)
if err != nil {
return fmt.Errorf("%v handler: bad hexstring: %v", n.Method(), err)
}
tx_, err := btcutil.NewTxFromBytes(rawTx)
if err != nil {
return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err)
}
block, err := parseBlock(cn.Block)
if err != nil {
return fmt.Errorf("%v handler: bad block: %v", n.Method(), err)
}
AcctMgr.RecordSpendingTx(tx_, block)
return nil
}