Optimize NotifyNewTxListener.

Looking up transactions from the database is an expensive operation.
This commit modifies the NotifyNewTxListener code to simply iterate the
transactions in the block instead of looking them up from the db.

Currently the wallet code needs a spent flag which ultimately shouldn't be
required.  For now, the spent data is simply created on the fly which is
still significantly faster than doing database transaction lookups.

Closes #24.
This commit is contained in:
Dave Collins 2013-10-29 15:48:22 -05:00
parent d7d2385fb0
commit e76fada2d2

View file

@ -1169,26 +1169,43 @@ func (s *rpcServer) NotifyBlockDisconnected(block *btcutil.Block) {
// of new transactions (with both spent and unspent outputs) for a watched // of new transactions (with both spent and unspent outputs) for a watched
// address. // address.
func (s *rpcServer) NotifyBlockTXs(db btcdb.Db, block *btcutil.Block) { func (s *rpcServer) NotifyBlockTXs(db btcdb.Db, block *btcutil.Block) {
txShaList, err := block.TxShas() // Build a map of in-flight transactions to see if any of the inputs in
if err != nil { // this block are referencing other transactions earlier in this block.
log.Error("Bad block; All notifications for block dropped.") txInFlight := map[btcwire.ShaHash]int{}
return transactions := block.Transactions()
spent := make([][]bool, len(transactions))
for i, tx := range transactions {
spent[i] = make([]bool, len(tx.MsgTx().TxOut))
txInFlight[*tx.Sha()] = i
} }
txList := db.FetchTxByShaList(txShaList)
for _, txReply := range txList { // The newBlockNotifyCheckTxOut current needs spent data. This can
if txReply.Err == nil && txReply.Tx != nil { // this can ultimately be optimized out by making sure the notifications
go s.newBlockNotifyCheckTxIn(txReply.Tx.TxIn) // are in order. For now, just create the spent data.
go s.newBlockNotifyCheckTxOut(db, block, txReply) for i, tx := range transactions[1:] {
for _, txIn := range tx.MsgTx().TxIn {
originHash := &txIn.PreviousOutpoint.Hash
if inFlightIndex, ok := txInFlight[*originHash]; ok &&
i >= inFlightIndex {
prevIndex := txIn.PreviousOutpoint.Index
spent[inFlightIndex][prevIndex] = true
} }
} }
} }
for i, tx := range transactions {
go s.newBlockNotifyCheckTxIn(tx)
go s.newBlockNotifyCheckTxOut(block, tx, spent[i])
}
}
// newBlockNotifyCheckTxIn is a helper function to iterate through // newBlockNotifyCheckTxIn is a helper function to iterate through
// each transaction input of a new block and perform any checks and // each transaction input of a new block and perform any checks and
// notify listening frontends when necessary. // notify listening frontends when necessary.
func (s *rpcServer) newBlockNotifyCheckTxIn(txins []*btcwire.TxIn) { func (s *rpcServer) newBlockNotifyCheckTxIn(tx *btcutil.Tx) {
for wltNtfn, cxt := range s.ws.requests.m { for wltNtfn, cxt := range s.ws.requests.m {
for _, txin := range txins { for _, txin := range tx.MsgTx().TxIn {
for op, id := range cxt.spentRequests { for op, id := range cxt.spentRequests {
if txin.PreviousOutpoint != op { if txin.PreviousOutpoint != op {
continue continue
@ -1220,9 +1237,9 @@ func (s *rpcServer) newBlockNotifyCheckTxIn(txins []*btcwire.TxIn) {
// newBlockNotifyCheckTxOut is a helper function to iterate through // newBlockNotifyCheckTxOut is a helper function to iterate through
// each transaction output of a new block and perform any checks and // each transaction output of a new block and perform any checks and
// notify listening frontends when necessary. // notify listening frontends when necessary.
func (s *rpcServer) newBlockNotifyCheckTxOut(db btcdb.Db, block *btcutil.Block, tx *btcdb.TxListReply) { func (s *rpcServer) newBlockNotifyCheckTxOut(block *btcutil.Block, tx *btcutil.Tx, spent []bool) {
for wltNtfn, cxt := range s.ws.requests.m { for wltNtfn, cxt := range s.ws.requests.m {
for i, txout := range tx.Tx.TxOut { for i, txout := range tx.MsgTx().TxOut {
_, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript) _, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript)
if err != nil { if err != nil {
log.Debug("Error getting payment address from tx; dropping any Tx notifications.") log.Debug("Error getting payment address from tx; dropping any Tx notifications.")
@ -1258,11 +1275,11 @@ func (s *rpcServer) newBlockNotifyCheckTxOut(db btcdb.Db, block *btcutil.Block,
Receiver: txaddr, Receiver: txaddr,
BlockHash: blkhash.String(), BlockHash: blkhash.String(),
Height: block.Height(), Height: block.Height(),
TxHash: tx.Sha.String(), TxHash: tx.Sha().String(),
Index: uint32(i), Index: uint32(i),
Amount: txout.Value, Amount: txout.Value,
PkScript: btcutil.Base58Encode(txout.PkScript), PkScript: btcutil.Base58Encode(txout.PkScript),
Spent: tx.TxSpent[i], Spent: spent[i],
}, },
Error: nil, Error: nil,
Id: &id, Id: &id,