diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index c6c7b480..2bb92e1f 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -99,6 +99,7 @@ function Mempool(options) { : this.network.requireStandard; this.rejectAbsurdFees = this.options.rejectAbsurdFees !== false; this.prematureWitness = !!this.options.prematureWitness; + this.paranoid = !!this.options.paranoid; this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE; this.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY; @@ -866,16 +867,16 @@ Mempool.prototype.verify = function verify(entry, callback) { 'bad-txns-nonstandard-inputs', 0)); } - // if (self.chain.state.hasWitness()) { - // if (!tx.hasStandardWitness(true, ret)) { - // ret = new VerifyError(tx, - // 'nonstandard', - // ret.reason, - // ret.score); - // ret.malleated = ret.score > 0; - // return callback(ret); - // } - // } + if (self.chain.state.hasWitness()) { + if (!tx.hasStandardWitness(ret)) { + ret = new VerifyError(tx, + 'nonstandard', + ret.reason, + ret.score); + ret.malleated = ret.score > 0; + return callback(ret); + } + } } if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) { @@ -954,6 +955,9 @@ Mempool.prototype.verify = function verify(entry, callback) { // Standard verification self.checkInputs(tx, flags1, function(error) { if (!error) { + if (!self.paranoid) + return callback(); + return self.checkResult(tx, mandatory, function(err, result) { if (err) { assert(err.type !== 'VerifyError', @@ -1192,8 +1196,7 @@ Mempool.prototype.getDepends = function getDepends(tx) { */ Mempool.prototype.storeOrphan = function storeOrphan(tx) { - var prevout = {}; - var missing = []; + var missing = {}; var i, hash, input, prev; if (tx.getWeight() > constants.tx.MAX_WEIGHT) { @@ -1211,26 +1214,26 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx) { if (this.hasReject(input.prevout.hash)) { this.logger.debug('Not storing orphan %s (rejected parents).', tx.rhash); + this.rejects.add(tx.hash()); return; } - - missing.push(input.prevout.hash); } hash = tx.hash('hex'); for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - if (!input.coin) - prevout[input.prevout.hash] = true; + if (input.coin) + continue; + missing[input.prevout.hash] = true; } - prevout = Object.keys(prevout); + missing = Object.keys(missing); - assert(prevout.length > 0); + assert(missing.length > 0); - for (i = 0; i < prevout.length; i++) { - prev = prevout[i]; + for (i = 0; i < missing.length; i++) { + prev = missing[i]; if (!this.waiting[prev]) this.waiting[prev] = []; this.waiting[prev].push(hash); diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 823555aa..7f59bbcd 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -1374,13 +1374,13 @@ TX.prototype.hasStandardInputs = function hasStandardInputs() { * @returns {Boolean} */ -TX.prototype.hasStandardWitness = function hasStandardWitness(strict, ret) { +TX.prototype.hasStandardWitness = function hasStandardWitness(ret) { var result; if (!ret) ret = new VerifyResult(); - result = this._hasStandardWitness(); + result = this.getWitnessStandard(); switch (result) { case BAD_WITNESS: @@ -1392,12 +1392,9 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(strict, ret) { ret.score = 100; return false; case BAD_NONSTD_P2WSH: - if (strict) { - ret.reason = 'bad-witness-nonstandard'; - ret.score = 0; - return false; - } - return true; + ret.reason = 'bad-witness-nonstandard'; + ret.score = 0; + return false; } return true; @@ -1410,7 +1407,7 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(strict, ret) { * @returns {Boolean} */ -TX.prototype._hasStandardWitness = function _hasStandardWitness() { +TX.prototype.getWitnessStandard = function getWitnessStandard() { var ret = BAD_OKAY; var i, j, input, prev, hash, redeem, m, n; diff --git a/test/mempool-test.js b/test/mempool-test.js index d4412d52..84a88d2a 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -28,12 +28,16 @@ describe('Mempool', function() { verify: true }); - var w; + var w, cached; mempool.on('error', function() {}); it('should open mempool', function(cb) { - mempool.open(cb); + mempool.open(function(err) { + assert.ifError(err); + chain.state.flags |= constants.flags.VERIFY_WITNESS; + cb(); + }); }); it('should open walletdb', function(cb) { @@ -233,6 +237,169 @@ describe('Mempool', function() { }); }); + it('should not cache a malleated wtx with mutated sig', function(cb) { + var kp = bcoin.keyring.generate(); + kp.witness = true; + // Coinbase + var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 + var prev = new bcoin.script([0, kp.keyHash]); + var prevHash = crypto.randomBytes(32).toString('hex'); + var dummyInput = { + prevout: { + hash: prevHash, + index: 0 + }, + coin: { + version: 1, + height: 0, + value: 70000, + script: prev, + coinbase: false, + hash: prevHash, + index: 0 + }, + script: new bcoin.script([]), + sequence: 0xffffffff + }; + t1.addInput(dummyInput); + var prevs = bcoin.script.fromPubkeyhash(kp.keyHash); + var sig = new bcoin.witness([t1.signature(0, prevs, kp.privateKey, 'all', 1), kp.publicKey]); + var sig2 = new bcoin.witness([t1.signature(0, prevs, kp.privateKey, 'all', 1), kp.publicKey]); + sig2.items[0][sig2.items[0].length - 1] = 0; + t1.inputs[0].witness = sig2; + var tx = t1.toTX(); + mempool.addTX(tx, function(err) { + assert(err); + assert(!mempool.hasReject(tx.hash())); + cb(); + }); + }); + + it('should not cache a malleated tx with unnecessary witness', function(cb) { + var kp = bcoin.keyring.generate(); + // Coinbase + var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 + var prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); + var prevHash = crypto.randomBytes(32).toString('hex'); + var dummyInput = { + prevout: { + hash: prevHash, + index: 0 + }, + coin: { + version: 1, + height: 0, + value: 70000, + script: prev, + coinbase: false, + hash: prevHash, + index: 0 + }, + script: new bcoin.script([]), + sequence: 0xffffffff + }; + t1.addInput(dummyInput); + t1.inputs[0].script = new bcoin.script([t1.signature(0, prev, kp.privateKey, 'all', 0)]), + t1.inputs[0].witness.push(new Buffer(0)); + var tx = t1.toTX(); + mempool.addTX(tx, function(err) { + assert(err); + assert(!mempool.hasReject(tx.hash())); + cb(); + }); + }); + + it('should not cache a malleated wtx with wit removed', function(cb) { + var kp = bcoin.keyring.generate(); + kp.witness = true; + // Coinbase + var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 + var prev = new bcoin.script([0, kp.keyHash]); + var prevHash = crypto.randomBytes(32).toString('hex'); + var dummyInput = { + prevout: { + hash: prevHash, + index: 0 + }, + coin: { + version: 1, + height: 0, + value: 70000, + script: prev, + coinbase: false, + hash: prevHash, + index: 0 + }, + script: new bcoin.script([]), + sequence: 0xffffffff + }; + t1.addInput(dummyInput); + var tx = t1.toTX(); + mempool.addTX(tx, function(err) { + assert(err); + assert(err.malleated); + assert(!mempool.hasReject(tx.hash())); + cb(); + }); + }); + + it('should cache non-malleated tx without sig', function(cb) { + var kp = bcoin.keyring.generate(); + // Coinbase + var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 + var prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); + var prevHash = crypto.randomBytes(32).toString('hex'); + var dummyInput = { + prevout: { + hash: prevHash, + index: 0 + }, + coin: { + version: 1, + height: 0, + value: 70000, + script: prev, + coinbase: false, + hash: prevHash, + index: 0 + }, + script: new bcoin.script([]), + sequence: 0xffffffff + }; + t1.addInput(dummyInput); + var tx = t1.toTX(); + mempool.addTX(tx, function(err) { + assert(err); + assert(!err.malleated); + assert(mempool.hasReject(tx.hash())); + cached = tx; + cb(); + }); + }); + + it('should clear reject cache', function(cb) { + var t1 = bcoin.mtx().addOutput(w, 50000); + var dummyInput = { + prevout: { + hash: constants.NULL_HASH, + index: 0xffffffff + }, + coin: null, + script: new bcoin.script(), + sequence: 0xffffffff + }; + t1.addInput(dummyInput); + var tx = t1.toTX(); + var block = new bcoin.block(); + block.txs.push(tx); + assert(mempool.hasReject(cached.hash())); + mempool.addBlock(block, function(err) { + assert(!err); + assert(!mempool.hasReject(cached.hash())); + cb(); + }); + }); + it('should destroy mempool', function(cb) { mempool.close(cb); });