2014-01-03 19:34:37 +01:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// This file implements the notification handlers for btcd-side notifications.
|
2014-01-03 19:34:37 +01:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"github.com/conformal/btcjson"
|
|
|
|
"github.com/conformal/btcutil"
|
|
|
|
"github.com/conformal/btcwallet/tx"
|
|
|
|
"github.com/conformal/btcwallet/wallet"
|
|
|
|
"github.com/conformal/btcwire"
|
|
|
|
"github.com/conformal/btcws"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
type notificationHandler func(btcjson.Cmd)
|
2014-01-03 19:34:37 +01:00
|
|
|
|
|
|
|
var notificationHandlers = map[string]notificationHandler{
|
|
|
|
btcws.BlockConnectedNtfnMethod: NtfnBlockConnected,
|
|
|
|
btcws.BlockDisconnectedNtfnMethod: NtfnBlockDisconnected,
|
|
|
|
btcws.ProcessedTxNtfnMethod: NtfnProcessedTx,
|
|
|
|
btcws.TxMinedNtfnMethod: NtfnTxMined,
|
|
|
|
btcws.TxSpentNtfnMethod: NtfnTxSpent,
|
|
|
|
}
|
|
|
|
|
|
|
|
// NtfnProcessedTx handles the btcws.ProcessedTxNtfn notification.
|
2014-01-30 16:14:02 +01:00
|
|
|
func NtfnProcessedTx(n btcjson.Cmd) {
|
2014-01-03 19:34:37 +01:00
|
|
|
ptn, ok := n.(*btcws.ProcessedTxNtfn)
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("%v handler: unexpected type", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create useful types from the JSON strings.
|
|
|
|
receiver, err := btcutil.DecodeAddr(ptn.Receiver)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: error parsing receiver: %v", n.Method(), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
txID, err := btcwire.NewShaHashFromStr(ptn.TxID)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: error parsing txid: %v", n.Method(), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
blockHash, err := btcwire.NewShaHashFromStr(ptn.BlockHash)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: error parsing block hash: %v", n.Method(), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pkscript, err := hex.DecodeString(ptn.PkScript)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: error parsing pkscript: %v", n.Method(), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup account for address in result.
|
|
|
|
aname, err := LookupAccountByAddress(ptn.Receiver)
|
|
|
|
if err == ErrNotFound {
|
|
|
|
log.Warnf("Received rescan result for unknown address %v", ptn.Receiver)
|
|
|
|
return
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(aname)
|
|
|
|
if err == ErrNotFound {
|
2014-01-03 19:34:37 +01:00
|
|
|
log.Errorf("Missing account for rescaned address %v", ptn.Receiver)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create RecvTx to add to tx history.
|
|
|
|
t := &tx.RecvTx{
|
|
|
|
TxID: *txID,
|
|
|
|
TxOutIdx: ptn.TxOutIndex,
|
|
|
|
TimeReceived: time.Now().Unix(),
|
|
|
|
BlockHeight: ptn.BlockHeight,
|
|
|
|
BlockHash: *blockHash,
|
|
|
|
BlockIndex: int32(ptn.BlockIndex),
|
|
|
|
BlockTime: ptn.BlockTime,
|
|
|
|
Amount: ptn.Amount,
|
|
|
|
ReceiverHash: receiver.ScriptAddress(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
req := SendTxHistSyncRequest{
|
|
|
|
txid: *txID,
|
|
|
|
response: make(chan SendTxHistSyncResponse),
|
|
|
|
}
|
|
|
|
SendTxHistSyncChans.access <- req
|
|
|
|
resp := <-req.response
|
|
|
|
if resp.ok {
|
|
|
|
// Wait until send history has been recorded.
|
|
|
|
<-resp.c
|
|
|
|
SendTxHistSyncChans.remove <- *txID
|
|
|
|
}
|
|
|
|
|
|
|
|
// Record the tx history.
|
2014-01-30 16:14:02 +01:00
|
|
|
a.TxStore.InsertRecvTx(t)
|
|
|
|
AcctMgr.ds.ScheduleTxStoreWrite(a)
|
2014-01-03 19:34:37 +01:00
|
|
|
// 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(txID, ptn.TxOutIndex)
|
|
|
|
previouslyNotifiedReq := NotifiedRecvTxRequest{
|
|
|
|
op: *recvTxOP,
|
|
|
|
response: make(chan NotifiedRecvTxResponse),
|
|
|
|
}
|
|
|
|
NotifiedRecvTxChans.access <- previouslyNotifiedReq
|
|
|
|
if <-previouslyNotifiedReq.response {
|
|
|
|
NotifyMinedTx <- t
|
|
|
|
NotifiedRecvTxChans.remove <- *recvTxOP
|
|
|
|
} else {
|
|
|
|
// Notify frontends of new recv tx and mark as notified.
|
|
|
|
NotifiedRecvTxChans.add <- *recvTxOP
|
2014-02-18 04:18:30 +01:00
|
|
|
NotifyNewTxDetails(allClients, a.Name(), t.TxInfo(a.Name(),
|
2014-01-23 01:00:10 +01:00
|
|
|
ptn.BlockHeight, a.Wallet.Net())[0])
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if !ptn.Spent {
|
|
|
|
u := &tx.Utxo{
|
|
|
|
Amt: uint64(ptn.Amount),
|
|
|
|
Height: ptn.BlockHeight,
|
|
|
|
Subscript: pkscript,
|
|
|
|
}
|
|
|
|
copy(u.Out.Hash[:], txID[:])
|
|
|
|
u.Out.Index = uint32(ptn.TxOutIndex)
|
|
|
|
copy(u.AddrHash[:], receiver.ScriptAddress())
|
|
|
|
copy(u.BlockHash[:], blockHash[:])
|
2014-01-30 16:14:02 +01:00
|
|
|
a.UtxoStore.Insert(u)
|
|
|
|
AcctMgr.ds.ScheduleUtxoStoreWrite(a)
|
2014-01-03 19:34:37 +01:00
|
|
|
|
|
|
|
// If this notification came from mempool, notify frontends of
|
|
|
|
// the new unconfirmed balance immediately. Otherwise, wait until
|
|
|
|
// the blockconnected notifiation is processed.
|
|
|
|
if u.Height == -1 {
|
|
|
|
bal := a.CalculateBalance(0) - a.CalculateBalance(1)
|
2014-02-18 04:18:30 +01:00
|
|
|
NotifyWalletBalanceUnconfirmed(allClients, a.name, bal)
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify frontends of new account balance.
|
|
|
|
confirmed := a.CalculateBalance(1)
|
|
|
|
unconfirmed := a.CalculateBalance(0) - confirmed
|
2014-02-18 04:18:30 +01:00
|
|
|
NotifyWalletBalance(allClients, a.name, confirmed)
|
|
|
|
NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed)
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2014-01-30 16:14:02 +01:00
|
|
|
func NtfnBlockConnected(n btcjson.Cmd) {
|
2014-01-03 19:34:37 +01:00
|
|
|
bcn, ok := n.(*btcws.BlockConnectedNtfn)
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("%v handler: unexpected type", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hash, err := btcwire.NewShaHashFromStr(bcn.Hash)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: invalid hash string", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
AcctMgr.BlockNotify(bs)
|
2014-01-03 19:34:37 +01:00
|
|
|
|
|
|
|
// Pass notification to frontends too.
|
2014-01-30 16:14:02 +01:00
|
|
|
marshaled, _ := n.MarshalJSON()
|
2014-02-18 04:18:30 +01:00
|
|
|
allClients <- marshaled
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2014-01-30 16:14:02 +01:00
|
|
|
func NtfnBlockDisconnected(n btcjson.Cmd) {
|
2014-01-03 19:34:37 +01:00
|
|
|
bdn, ok := n.(*btcws.BlockDisconnectedNtfn)
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("%v handler: unexpected type", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hash, err := btcwire.NewShaHashFromStr(bdn.Hash)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: invalid hash string", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback Utxo and Tx data stores.
|
2014-01-30 16:14:02 +01:00
|
|
|
AcctMgr.Rollback(bdn.Height, hash)
|
2014-01-03 19:34:37 +01:00
|
|
|
|
|
|
|
// Pass notification to frontends too.
|
2014-01-30 16:14:02 +01:00
|
|
|
marshaled, _ := n.MarshalJSON()
|
2014-02-18 04:18:30 +01:00
|
|
|
allClients <- marshaled
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NtfnTxMined handles btcd notifications resulting from newly
|
|
|
|
// mined transactions that originated from this wallet.
|
2014-01-30 16:14:02 +01:00
|
|
|
func NtfnTxMined(n btcjson.Cmd) {
|
2014-01-03 19:34:37 +01:00
|
|
|
tmn, ok := n.(*btcws.TxMinedNtfn)
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("%v handler: unexpected type", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
txid, err := btcwire.NewShaHashFromStr(tmn.TxID)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: invalid hash string", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
blockhash, err := btcwire.NewShaHashFromStr(tmn.BlockHash)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: invalid block hash string", n.Method())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
err = AcctMgr.RecordMinedTx(txid, blockhash,
|
2014-01-03 19:34:37 +01:00
|
|
|
tmn.BlockHeight, tmn.Index, tmn.BlockTime)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%v handler: %v", n.Method(), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove mined transaction from pool.
|
|
|
|
UnminedTxs.Lock()
|
|
|
|
delete(UnminedTxs.m, TXID(*txid))
|
|
|
|
UnminedTxs.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NtfnTxSpent handles btcd txspent notifications resulting from a block
|
|
|
|
// transaction being processed that spents a wallet UTXO.
|
2014-01-30 16:14:02 +01:00
|
|
|
func NtfnTxSpent(n btcjson.Cmd) {
|
2014-01-03 19:34:37 +01:00
|
|
|
// TODO(jrick): This might actually be useless and maybe it shouldn't
|
|
|
|
// be implemented.
|
|
|
|
}
|