From 1aa78e2248f83b4d2d18d32d7d91df581c7f1ddd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 26 Sep 2017 17:20:16 -0700 Subject: [PATCH] mempool: sanitize after reorg. --- lib/blockchain/chain.js | 4 +-- lib/mempool/mempool.js | 53 ++++++++++++++++++++++++++++++++++++- lib/mempool/mempoolentry.js | 17 +++++++++--- lib/node/fullnode.js | 9 +++++++ lib/node/spvnode.js | 6 ++++- 5 files changed, 81 insertions(+), 8 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index bb3e9450..f83555c1 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -905,7 +905,7 @@ Chain.prototype.reorganize = async function reorganize(competitor) { competitor.height ); - this.emit('reorganize', tip, competitor); + await this.fire('reorganize', tip, competitor); }; /** @@ -957,7 +957,7 @@ Chain.prototype.reorganizeSPV = async function reorganizeSPV(competitor) { 'Chain replay from height %d necessary.', fork.height); - this.emit('reorganize', tip, competitor); + await this.fire('reorganize', tip, competitor); }; /** diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index c61cad83..aae2755a 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -10,6 +10,7 @@ const assert = require('assert'); const path = require('path'); const AsyncObject = require('../utils/asyncobject'); const common = require('../blockchain/common'); +const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const util = require('../utils/util'); const random = require('../crypto/random'); @@ -198,8 +199,9 @@ Mempool.prototype._addBlock = async function _addBlock(block, txs) { if (!entry) { this.removeOrphan(hash); - this.resolveOrphans(tx); this.removeDoubleSpends(tx); + if (this.waiting.has(hash)) + await this.handleOrphans(tx); continue; } @@ -303,6 +305,55 @@ Mempool.prototype._removeBlock = async function _removeBlock(block, txs) { total, block.height); }; +/** + * Sanitize the mempool after a reorg. + * @private + * @returns {Promise} + */ + +Mempool.prototype._handleReorg = async function _handleReorg() { + const height = this.chain.height + 1; + const mtp = await this.chain.getMedianTime(this.chain.tip); + const remove = []; + + for (const [hash, entry] of this.map) { + const {tx} = entry; + + if (!tx.isFinal(height, mtp)) { + remove.push(hash); + continue; + } + + if (tx.version > 1) { + let hasLocks = false; + + for (const {sequence} of tx.inputs) { + if (!(sequence & consensus.SEQUENCE_DISABLE_FLAG)) { + hasLocks = true; + break; + } + } + + if (hasLocks) { + remove.push(hash); + continue; + } + } + + if (entry.coinbase) + remove.push(hash); + } + + for (const hash of remove) { + const entry = this.getEntry(hash); + + if (!entry) + continue; + + this.evictEntry(entry); + } +}; + /** * Reset the mempool. * @method diff --git a/lib/mempool/mempoolentry.js b/lib/mempool/mempoolentry.js index 2ca6f637..2091e07f 100644 --- a/lib/mempool/mempoolentry.js +++ b/lib/mempool/mempoolentry.js @@ -43,6 +43,7 @@ function MempoolEntry(options) { this.deltaFee = 0; this.time = 0; this.value = 0; + this.coinbase = false; this.dependencies = false; this.descFee = 0; this.descSize = 0; @@ -67,6 +68,7 @@ MempoolEntry.prototype.fromOptions = function fromOptions(options) { this.deltaFee = options.deltaFee; this.time = options.time; this.value = options.value; + this.coinbase = options.coinbase; this.dependencies = options.dependencies; this.descFee = options.descFee; this.descSize = options.descSize; @@ -99,11 +101,14 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, view, height) { const fee = tx.getFee(view); let dependencies = false; + let coinbase = false; + for (const {prevout} of tx.inputs) { - if (view.getHeight(prevout) === -1) { + if (view.isCoinbase(prevout)) + coinbase = true; + + if (view.getHeight(prevout) === -1) dependencies = true; - break; - } } this.tx = tx; @@ -115,6 +120,7 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, view, height) { this.deltaFee = fee; this.time = util.now(); this.value = value; + this.coinbase = coinbase; this.dependencies = dependencies; this.descFee = fee; this.descSize = size; @@ -229,6 +235,7 @@ MempoolEntry.prototype.memUsage = function memUsage() { let total = 0; total += 176; // mempool entry + total += 48; // coinbase total += 48; // dependencies total += 208; // tx @@ -297,7 +304,7 @@ MempoolEntry.prototype.isFree = function isFree(height) { */ MempoolEntry.prototype.getSize = function getSize() { - return this.tx.getSize() + 41; + return this.tx.getSize() + 42; }; /** @@ -315,6 +322,7 @@ MempoolEntry.prototype.toRaw = function toRaw() { bw.writeU64(this.fee); bw.writeU32(this.time); bw.writeU64(this.value); + bw.writeU8(this.coinbase ? 1 : 0); bw.writeU8(this.dependencies ? 1 : 0); return bw.render(); }; @@ -337,6 +345,7 @@ MempoolEntry.prototype.fromRaw = function fromRaw(data) { this.deltaFee = this.fee; this.time = br.readU32(); this.value = br.readU64(); + this.coinbase = br.readU8() === 1; this.dependencies = br.readU8() === 1; this.descFee = this.fee; this.descSize = this.size; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index de314ec1..7d07755d 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -197,6 +197,15 @@ FullNode.prototype._init = function _init() { this.emit('disconnect', entry, block); }); + this.chain.hook('reorganize', async (tip, competitor) => { + try { + await this.mempool._handleReorg(); + } catch (e) { + this.error(e); + } + this.emit('reorganize', tip, competitor); + }); + this.chain.hook('reset', async (tip) => { try { await this.mempool._reset(); diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index eb96fc9d..e266af73 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -143,7 +143,11 @@ SPVNode.prototype._init = function _init() { }); this.chain.on('disconnect', (entry, block) => { - this.emit('disconnect', entry); + this.emit('disconnect', entry, block); + }); + + this.chain.on('reorganize', (tip, competitor) => { + this.emit('reorganize', tip, competitor); }); this.chain.on('reset', (tip) => {