From 1761266ba9ae5bd19da7f97f3a796d65e1a01fc7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 6 Feb 2016 19:10:40 -0800 Subject: [PATCH] improve tx-pool. --- lib/bcoin/pool.js | 66 +++++++++++++++++++++++--------- lib/bcoin/tx-pool.js | 91 ++++++++++++++++++++++++++------------------ lib/bcoin/tx.js | 28 +++++++++++--- lib/bcoin/wallet.js | 35 ++++++++++------- 4 files changed, 144 insertions(+), 76 deletions(-) diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 3489a1db..1262f0b5 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -1053,15 +1053,12 @@ Pool.prototype.isWatched = function(tx, bloom) { return false; }; -Pool.prototype.addWallet = function addWallet(w, defaultTs) { +Pool.prototype.addWallet = function addWallet(w) { var self = this; var e; if (this.loading) - return this.once('load', this.addWallet.bind(this, w, defaultTs)); - - if (w.loading) - return w.once('load', this.addWallet.bind(this, w, defaultTs)); + return this.once('load', this.addWallet.bind(this, w)); if (this.wallets.indexOf(w) !== -1) return false; @@ -1071,7 +1068,7 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) { e = new EventEmitter(); - function search(ts) { + function search() { // Relay pending TXs // NOTE: It is important to do it after search, because search could // add TS to pending TXs, thus making them confirmed @@ -1082,17 +1079,21 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) { if (self.options.fullNode) return; - // Search for last week by default - if (!ts) - ts = defaultTs || (utils.now() - 7 * 24 * 3600); + if (self._pendingSearch) + return; - self.searchWallet(ts); + self._pendingSearch = true; + + utils.nextTick(function() { + self._pendingSearch = false; + self.searchWallet(); + }); } - if (w.loaded) - search(w.lastTs); + if (w.loading) + w.once('load', search); else - w.once('load', function() { search(w.lastTs) }); + search(); return e; }; @@ -1160,16 +1161,21 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) { delete w._poolOnRemove; }; -Pool.prototype.searchWallet = function(w) { +Pool.prototype.searchWallet = function(w, h) { var self = this; - var ts; + var ts, height; assert(!this.loading); if (this.options.fullNode) return; - if (!w) { + if (w == null) { + height = this.wallets.reduce(function(ts, w) { + if (w.lastHeight < height) + return w.lastHeight; + return ts; + }, Infinity); ts = this.wallets.reduce(function(ts, w) { if (w.lastTs < ts) return w.lastTs; @@ -1178,18 +1184,40 @@ Pool.prototype.searchWallet = function(w) { assert(ts !== Infinity); } else if (typeof w === 'number') { ts = w; + height = h; } else { - if (!w.loaded) { + if (w.loading) { w.once('load', function() { self.searchWallet(w); }); return; } ts = w.lastTs; - if (!ts) - ts = utils.now() - 7 * 24 * 3600; + height = w.lastHeight; } + // Always prefer height + if (height > 0) { + // Back one week + if (!height || height === -1) + height = this.chain.height() - (7 * 24 * 6); + + utils.nextTick(function() { + utils.debug('Wallet height: %s', height); + utils.debug( + 'Reverted chain to height=%d', + self.chain.height() + ); + }); + + this.chain.resetHeight(height); + + return; + } + + if (!ts) + ts = utils.now() - 7 * 24 * 3600; + utils.nextTick(function() { utils.debug('Wallet time: %s', new Date(ts * 1000)); utils.debug( diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index 80f734e5..de6dbb58 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -30,6 +30,7 @@ function TXPool(wallet) { this._unspent = {}; this._orphans = {}; this._lastTs = 0; + this._lastHeight = 0; this._loaded = false; this._addresses = {}; this._sent = new bn(0); @@ -76,22 +77,20 @@ TXPool.prototype._init = function init() { s.on('end', function() { self._loaded = true; - self.emit('load', self._lastTs); + self.emit('load', self._lastTs, self._lastHeight); }); }; TXPool.prototype.add = function add(tx, noWrite, strict) { var hash = tx.hash('hex'); - var updated; - var i, input, key, unspent, index, orphan; - var out, key, orphans, some; + var updated = false; + var i, input, output, unspent, index, orphan; + var key, orphans, some; this._wallet.fillPrevout(tx); - if (strict) { - if (!this._wallet.ownInput(tx) && !this._wallet.ownOutput(tx)) - return false; - } + if (!this._wallet.ownInput(tx) && !this._wallet.ownOutput(tx)) + return false; // Ignore stale pending transactions if (tx.ts === 0 && tx.ps + 2 * 24 * 3600 < utils.now()) { @@ -107,14 +106,15 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { this._all[hash].ts = tx.ts; this._all[hash].block = tx.block; this._storeTX(hash, tx, noWrite); - this.emit('tx', tx); + this._lastTs = Math.max(tx.ts, this._lastTs); + this._lastHeight = Math.max(tx.getHeight(), this._lastHeight); + this.emit('update', this._lastTs, this._lastHeight, tx); this.emit('confirmed', tx); } return false; } - this._all[hash] = tx; - updated = false; + this._all[hash] = tx; // Consume unspent money or add orphans for (i = 0; i < tx.inputs.length; i++) { @@ -143,15 +143,8 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { } // Only add orphans if this input is ours. - // If there is no previous output, there's no way to truly - // verify this is ours, so we assume it is. If we add the - // signature checking code to ownInput for p2sh and p2pk, - // we could in theory use ownInput here (and down below) - // instead. - if (input.output) { - if (!this._wallet.ownOutput(input.output)) - continue; - } + if (!this._wallet.ownInput(input)) + continue; // Add orphan, if no parent transaction is yet known orphan = { tx: tx, index: input.prevout.index }; @@ -161,16 +154,6 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { this._orphans[key] = [orphan]; } - if (!this._wallet.ownOutput(tx)) { - if (updated) - this.emit('update', this._lastTs, tx); - - // Save spending TXs without adding unspents - // if (this._wallet.ownInput(tx)) - this._storeTX(hash, tx, noWrite); - return; - } - function checkOrphan(orphan) { var index = orphan.tx._inputIndex(tx.hash('hex'), orphan.index); assert(index !== -1); @@ -184,13 +167,14 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { this._removeTX(orphan.tx, noWrite); return false; } + this._addInput(orphan.tx, index); return true; } // Add unspent outputs or fullfill orphans for (i = 0; i < tx.outputs.length; i++) { - out = tx.outputs[i]; + output = tx.outputs[i]; // Do not add unspents for outputs that aren't ours. if (!this._wallet.ownOutput(tx, i)) @@ -216,16 +200,41 @@ TXPool.prototype.add = function add(tx, noWrite, strict) { } this._lastTs = Math.max(tx.ts, this._lastTs); + this._lastHeight = Math.max(tx.getHeight(), this._lastHeight); if (updated) - this.emit('update', this._lastTs, tx); - - this._storeTX(hash, tx, noWrite); + this.emit('update', this._lastTs, this._lastHeight, tx); this.emit('tx', tx); + if (tx.ts !== 0) + this.emit('confirmed', tx); + + this._storeTX(hash, tx, noWrite); + return true; }; +TXPool.prototype.getTX = function getTX(hash) { + return this._all[hash]; +}; + +TXPool.prototype.getUnspent = function getUnspent(hash, index) { + return this._unspent[hash + '/' + index]; +}; + +TXPool.prototype.addUnspent = function addUnspent(coin) { + var id = coin.hash + '/' + coin.index; + if (!this._unspent[id]) { + this._unspent[id] = coin; + this._addOutput(coin); + this._lastHeight = Math.max(coin.height, this._lastHeight); + this.emit('update', this._lastTs, this._lastHeight); + // Weird workaround to get addresses to update + if (coin.height !== -1) + this.emit('confirmed', coin); + } +}; + TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) { var self = this; @@ -285,11 +294,17 @@ TXPool.prototype.getAll = function getAll(address) { }; TXPool.prototype._addOutput = function _addOutput(tx, i, remove) { - var output = tx.outputs[i]; - var address; + if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin)) { + var output = tx; + if (!this._wallet.ownOutput(output)) + return; + } else { + var output = tx.outputs[i]; + var address; - if (!this._wallet.ownOutput(tx, i)) - return; + if (!this._wallet.ownOutput(tx, i)) + return; + } address = output.getAddress(); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index e7cb93e8..54b672b6 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1435,13 +1435,16 @@ TX.prototype.hasPrevout = function hasPrevout() { }); }; -TX.prototype.fillPrevout = function fillPrevout(txs) { +TX.prototype.fillPrevout = function fillPrevout(txs, unspent) { var inputs; - if (txs instanceof bcoin.txPool) + if (txs instanceof bcoin.txPool) { + unspent = txs._unspent; txs = txs._all; - else if (txs instanceof bcoin.wallet) + } else if (txs instanceof bcoin.wallet) { + unspent = txs.tx._unspent; txs = txs.tx._all; + } if (Array.isArray(txs)) { txs = txs.reduce(function(out, tx) { @@ -1450,9 +1453,24 @@ TX.prototype.fillPrevout = function fillPrevout(txs) { }, {}); } + if (Array.isArray(unspent)) { + unspent = unspent.reduce(function(out, coin) { + out[coin.hash + '/' + coin.index] = coin; + return out; + }, {}); + } + inputs = this.inputs.filter(function(input) { - if (!input.output && txs[input.prevout.hash]) - input.output = bcoin.coin(txs[input.prevout.hash], input.prevout.index); + var key; + + if (!input.output) { + key = input.prevout.hash + '/' + input.prevout.index; + if (unspent && unspent[key]) + input.output = unspent[key]; + else if (txs && txs[input.prevout.hash]) + input.output = bcoin.coin(txs[input.prevout.hash], input.prevout.index); + } + return !!input.output; }, this); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 0456ba9d..77021b37 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -151,6 +151,7 @@ function Wallet(options) { this.storage = options.storage; this.loading = true; this.lastTs = 0; + this.lastHeight = 0; // This is a chicken and egg problem for BIP45. Real address keys cannot be // generated until all shared keys have been added to the wallet. The flow of @@ -476,36 +477,42 @@ Wallet.prototype._init = function init() { if (this.tx._loaded) { this.loading = false; - this._pruneAddresses(); + // this._pruneAddresses(); return; } // Notify owners about new accepted transactions - this.tx.on('update', function(lastTs, tx) { + this.tx.on('update', function(lastTs, lastHeight, tx) { var b = this.getBalance(); if (prevBalance && prevBalance.cmp(b) !== 0) self.emit('balance', b); - self.emit('update', tx); + if (tx) + self.emit('update', tx); + self.lastTs = Math.max(lastTs, self.lastTs); + self.lastHeight = Math.max(lastHeight, self.lastHeight); prevBalance = b; }); this.tx.on('tx', function(tx) { - // TX using this address was confirmed. - // Allocate a new address. - if (tx.block) { - if (self.currentAddress.ownOutput(tx)) - self.currentAddress = self.createAddress(); - if (self.changeAddress.ownOutput(tx)) - self.changeAddress = self.createAddress(true); - self._pruneAddresses(); - } self.emit('tx', tx); }); - this.tx.once('load', function(ts) { + this.tx.on('confirmed', function(tx) { + // TX using this address was confirmed. + // Allocate a new address. + if (self.currentAddress.ownOutput(tx)) + self.currentAddress = self.createAddress(); + if (self.changeAddress.ownOutput(tx)) + self.changeAddress = self.createAddress(true); + // self._pruneAddresses(); + self.emit('confirmed', tx); + }); + + this.tx.once('load', function(ts, height) { self.loading = false; self.lastTs = ts; - self._pruneAddresses(); + self.lastHeight = height; + // self._pruneAddresses(); self.emit('load', ts); });