'use strict'; var bn = require('bn.js'); var bcoin = require('../').set('main'); var constants = bcoin.constants; var utils = bcoin.utils; var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; var cob = require('../lib/utils/co').cob; function dummy(prev, prevHash) { if (!prevHash) prevHash = constants.ONE_HASH.toString('hex'); return { 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 }; } describe('Mempool', function() { var chain, mempool, walletdb; var wallet, cached; this.timeout(5000); chain = new bcoin.chain({ name: 'mp-chain', db: 'memory' }); mempool = new bcoin.mempool({ chain: chain, name: 'mempool-test', db: 'memory' }); walletdb = new bcoin.walletdb({ name: 'mempool-wallet-test', db: 'memory', verify: true }); it('should open mempool', cob(function* () { yield mempool.open(); chain.state.flags |= constants.flags.VERIFY_WITNESS; })); it('should open walletdb', cob(function* () { yield walletdb.open(); })); it('should open wallet', cob(function* () { wallet = yield walletdb.create(); })); it('should handle incoming orphans and TXs', cob(function* () { var kp = bcoin.keyring.generate(); var w = wallet; var t1, t2, t3, t4, f1, fake, prev, sig, balance, txs; t1 = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); t1.addInput(dummy(prev)); sig = t1.signature(0, prev, kp.privateKey, 'all', 0); t1.inputs[0].script = new bcoin.script([sig]), // balance: 51000 yield w.sign(t1); t1 = t1.toTX(); t2 = bcoin.mtx() .addInput(t1, 0) // 50000 .addOutput(w.getAddress(), 20000) .addOutput(w.getAddress(), 20000); // balance: 49000 yield w.sign(t2); t2 = t2.toTX(); t3 = bcoin.mtx() .addInput(t1, 1) // 10000 .addInput(t2, 0) // 20000 .addOutput(w.getAddress(), 23000); // balance: 47000 yield w.sign(t3); t3 = t3.toTX(); t4 = bcoin.mtx() .addInput(t2, 1) // 24000 .addInput(t3, 0) // 23000 .addOutput(w.getAddress(), 11000) .addOutput(w.getAddress(), 11000); // balance: 22000 yield w.sign(t4); t4 = t4.toTX(); f1 = bcoin.mtx() .addInput(t4, 1) // 11000 .addOutput(new bcoin.address(), 9000); // balance: 11000 yield w.sign(f1); f1 = f1.toTX(); fake = bcoin.mtx() .addInput(t1, 1) // 1000 (already redeemed) .addOutput(w.getAddress(), 6000); // 6000 instead of 500 // Script inputs but do not sign yield w.template(fake); // Fake signature fake.inputs[0].script.set(0, constants.ZERO_SIG); fake.inputs[0].script.compile(); fake = fake.toTX(); // balance: 11000 [t2, t3, t4, f1, fake].forEach(function(tx) { tx.inputs.forEach(function(input) { input.coin = null; }); }); yield mempool.addTX(fake); yield mempool.addTX(t4); balance = mempool.getBalance(); assert.equal(balance, 0); yield mempool.addTX(t1); balance = mempool.getBalance(); assert.equal(balance, 60000); yield mempool.addTX(t2); balance = mempool.getBalance(); assert.equal(balance, 50000); yield mempool.addTX(t3); balance = mempool.getBalance(); assert.equal(balance, 22000); yield mempool.addTX(f1); balance = mempool.getBalance(); assert.equal(balance, 20000); txs = mempool.getHistory(); assert(txs.some(function(tx) { return tx.hash('hex') === f1.hash('hex'); })); })); it('should handle locktime', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, sig; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); tx.setLocktime(200); chain.tip.height = 200; sig = tx.signature(0, prev, kp.privateKey, 'all', 0); tx.inputs[0].script = new bcoin.script([sig]), tx = tx.toTX(); yield mempool.addTX(tx); chain.tip.height = 0; })); it('should handle invalid locktime', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, sig, err; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); tx.setLocktime(200); chain.tip.height = 200 - 1; sig = tx.signature(0, prev, kp.privateKey, 'all', 0); tx.inputs[0].script = new bcoin.script([sig]), tx = tx.toTX(); try { yield mempool.addTX(tx); } catch (e) { err = e; } assert(err); chain.tip.height = 0; })); it('should not cache a malleated wtx with mutated sig', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, prevs, sig, tx, err; kp.witness = true; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([0, kp.keyHash]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); prevs = bcoin.script.fromPubkeyhash(kp.keyHash); sig = tx.signature(0, prevs, kp.privateKey, 'all', 1); sig[sig.length - 1] = 0; tx.inputs[0].witness = new bcoin.witness([sig, kp.publicKey]); tx = tx.toTX(); try { yield mempool.addTX(tx); } catch (e) { err = e; } assert(err); assert(!mempool.hasReject(tx.hash())); })); it('should not cache a malleated tx with unnecessary witness', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, sig, tx, err; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); sig = tx.signature(0, prev, kp.privateKey, 'all', 0); tx.inputs[0].script = new bcoin.script([sig]); tx.inputs[0].witness.push(new Buffer(0)); tx = tx.toTX(); try { yield mempool.addTX(tx); } catch (e) { err = e; } assert(err); assert(!mempool.hasReject(tx.hash())); })); it('should not cache a malleated wtx with wit removed', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, tx, err; kp.witness = true; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([0, kp.keyHash]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); tx = tx.toTX(); try { yield mempool.addTX(tx); } catch (e) { err = e; } assert(err); assert(err.malleated); assert(!mempool.hasReject(tx.hash())); })); it('should cache non-malleated tx without sig', cob(function* () { var w = wallet; var kp = bcoin.keyring.generate(); var tx, prev, prevHash, tx, err; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000) .addOutput(w.getAddress(), 10000); prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); prevHash = crypto.randomBytes(32).toString('hex'); tx.addInput(dummy(prev, prevHash)); tx = tx.toTX(); try { yield mempool.addTX(tx); } catch (e) { err = e; } assert(err); assert(!err.malleated); assert(mempool.hasReject(tx.hash())); cached = tx; })); it('should clear reject cache', cob(function* () { var w = wallet; var tx, input, tx, block; tx = bcoin.mtx() .addOutput(w.getAddress(), 50000); input = { prevout: { hash: constants.NULL_HASH, index: 0xffffffff }, coin: null, script: new bcoin.script(), sequence: 0xffffffff }; tx.addInput(input); tx = tx.toTX(); block = new bcoin.block(); block.txs.push(tx); assert(mempool.hasReject(cached.hash())); yield mempool.addBlock(block); assert(!mempool.hasReject(cached.hash())); })); it('should destroy mempool', cob(function* () { yield mempool.close(); })); });