diff --git a/test/chain-test.js b/test/chain-test.js index 9d40083e..2bfeea2e 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -14,7 +14,7 @@ var c = require('../lib/utils/spawn').cb; describe('Chain', function() { var chain, wallet, node, miner, walletdb; - var competingTip, oldTip, tip1, tip2, cb1, cb2; + var tip1, tip2, cb1, cb2; this.timeout(5000); @@ -24,254 +24,233 @@ describe('Chain', function() { miner = node.miner; node.on('error', function() {}); - function mineBlock(tip, tx, callback) { - c(miner.createBlock(tip), function(err, attempt) { - assert.ifError(err); - if (tx) { - var redeemer = bcoin.mtx(); - redeemer.addOutput({ - address: wallet.receiveAddress.getAddress(), - value: utils.satoshi('25.0') - }); - redeemer.addOutput({ - address: wallet.changeAddress.getAddress(), - value: utils.satoshi('5.0') - }); - redeemer.addInput(tx, 0); - redeemer.setLocktime(chain.height); - return c(wallet.sign(redeemer), function(err) { - assert.ifError(err); - attempt.addTX(redeemer.toTX()); - callback(null, attempt.mineSync()); - }); - } - callback(null, attempt.mineSync()); + var mineBlock = co(function* mineBlock(tip, tx) { + var attempt = yield miner.createBlock(tip); + var redeemer; + + if (!tx) + return attempt.mineSync(); + + redeemer = bcoin.mtx(); + + redeemer.addOutput({ + address: wallet.receiveAddress.getAddress(), + value: 25 * 1e8 }); - } + + redeemer.addOutput({ + address: wallet.changeAddress.getAddress(), + value: 5 * 1e8 + }); + + redeemer.addInput(tx, 0); + + redeemer.setLocktime(chain.height); + + yield wallet.sign(redeemer); + + attempt.addTX(redeemer.toTX()); + + return attempt.mineSync(); + }); function deleteCoins(tx) { + var i; + if (tx.txs) { deleteCoins(tx.txs); return; } + if (Array.isArray(tx)) { - tx.forEach(deleteCoins); + for (i = 0; i < tx.length; i++) + deleteCoins(tx[i]); return; } - tx.inputs.forEach(function(input) { - input.coin = null; - }); + + for (i = 0; i < tx.inputs.length; i++) + tx.inputs[i].coin = null; } - it('should open chain and miner', function(cb) { + it('should open chain and miner', cob(function *() { miner.mempool = null; constants.tx.COINBASE_MATURITY = 0; - c(node.open(), cb); - }); + yield node.open(); + })); - it('should open walletdb', function(cb) { - c(walletdb.create({}), function(err, w) { - assert.ifError(err); - wallet = w; - miner.address = wallet.getAddress(); - cb(); - }); - }); + it('should open walletdb', cob(function *() { + wallet = yield walletdb.create(); + miner.address = wallet.getAddress(); + })); - it('should mine a block', function(cb) { - c(miner.mineBlock(), function(err, block) { - assert.ifError(err); - assert(block); - cb(); - }); - }); + it('should mine a block', cob(function *() { + var block = yield miner.mineBlock(); + assert(block); + })); - it('should mine competing chains', function(cb) { - utils.forRangeSerial(0, 10, function(i, next) { - mineBlock(tip1, cb1, function(err, block1) { - assert.ifError(err); - cb1 = block1.txs[0]; - mineBlock(tip2, cb2, function(err, block2) { - assert.ifError(err); - cb2 = block2.txs[0]; - deleteCoins(block1); - c(chain.add(block1), function(err) { - assert.ifError(err); - deleteCoins(block2); - c(chain.add(block2), function(err) { - assert.ifError(err); - assert(chain.tip.hash === block1.hash('hex')); - competingTip = block2.hash('hex'); - c(chain.db.get(block1.hash('hex')), function(err, entry1) { - assert.ifError(err); - c(chain.db.get(block2.hash('hex')), function(err, entry2) { - assert.ifError(err); - assert(entry1); - assert(entry2); - tip1 = entry1; - tip2 = entry2; - c(chain.db.isMainChain(block2.hash('hex')), function(err, result) { - assert.ifError(err); - assert(!result); - next(); - }); - }); - }); - }); - }); - }); - }); - }, cb); - }); + it('should mine competing chains', cob(function *() { + var i, block1, block2; - it('should have correct balance', function(cb) { - setTimeout(function() { - c(wallet.getBalance(), function(err, balance) { - assert.equal(balance.unconfirmed, 0); - assert.equal(balance.confirmed, 500 * 1e8); - assert.equal(balance.total, 500 * 1e8); - cb(); - }); - }, 1000); - }); + for (i = 0; i < 10; i++) { + block1 = yield mineBlock(tip1, cb1); + cb1 = block1.txs[0]; + + block2 = yield mineBlock(tip2, cb2); + cb2 = block2.txs[0]; + + deleteCoins(block1); + yield chain.add(block1); + + deleteCoins(block2); + yield chain.add(block2); + + assert(chain.tip.hash === block1.hash('hex')); + + tip1 = yield chain.db.get(block1.hash('hex')); + tip2 = yield chain.db.get(block2.hash('hex')); + + assert(tip1); + assert(tip2); + + assert(!(yield tip2.isMainChain())); + } + })); + + it('should have correct balance', cob(function* () { + var balance; + + yield spawn.timeout(100); + + balance = yield wallet.getBalance(); + assert.equal(balance.unconfirmed, 0); + assert.equal(balance.confirmed, 500 * 1e8); + assert.equal(balance.total, 500 * 1e8); + })); + + it('should handle a reorg', cob(function *() { + var entry, block, forked; - it('should handle a reorg', function(cb) { assert.equal(walletdb.height, chain.height); assert.equal(chain.height, 10); - oldTip = chain.tip; - c(chain.db.get(competingTip), function(err, entry) { - assert.ifError(err); - assert(entry); - assert(chain.height === entry.height); - c(miner.mineBlock(entry), function(err, block) { - assert.ifError(err); - assert(block); - var forked = false; - chain.once('reorganize', function() { - forked = true; - }); - deleteCoins(block); - c(chain.add(block), function(err) { - assert.ifError(err); - assert(forked); - assert(chain.tip.hash === block.hash('hex')); - assert(chain.tip.chainwork.cmp(oldTip.chainwork) > 0); - cb(); - }); - }); + + entry = yield chain.db.get(tip2.hash); + assert(entry); + assert(chain.height === entry.height); + + block = yield miner.mineBlock(entry); + assert(block); + + forked = false; + chain.once('reorganize', function() { + forked = true; }); - }); - it('should have correct balance', function(cb) { - setTimeout(function() { - c(wallet.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.unconfirmed, 500 * 1e8); - assert.equal(balance.confirmed, 550 * 1e8); - assert.equal(balance.total, 1050 * 1e8); - cb(); - }); - }, 1000); - }); + deleteCoins(block); - it('should check main chain', function(cb) { - c(chain.db.isMainChain(oldTip), function(err, result) { - assert.ifError(err); - assert(!result); - cb(); - }); - }); + yield chain.add(block); - it('should mine a block after a reorg', function(cb) { - mineBlock(null, cb2, function(err, block) { - assert.ifError(err); - deleteCoins(block); - c(chain.add(block), function(err) { - assert.ifError(err); - c(chain.db.get(block.hash('hex')), function(err, entry) { - assert.ifError(err); - assert(entry); - assert(chain.tip.hash === entry.hash); - c(chain.db.isMainChain(entry.hash), function(err, result) { - assert.ifError(err); - assert(result); - cb(); - }); - }); - }); - }); - }); + assert(forked); + assert(chain.tip.hash === block.hash('hex')); + assert(chain.tip.chainwork.cmp(tip1.chainwork) > 0); + })); - it('should fail to mine a block with coins on an alternate chain', function(cb) { - mineBlock(null, cb1, function(err, block) { - assert.ifError(err); - deleteCoins(block); - c(chain.add(block), function(err) { - assert(err); - cb(); - }); - }); - }); + it('should have correct balance', cob(function *() { + var balance; - it('should get coin', function(cb) { - mineBlock(null, null, function(err, block) { - assert.ifError(err); - c(chain.add(block), function(err) { - assert.ifError(err); - mineBlock(null, block.txs[0], function(err, block) { - assert.ifError(err); - c(chain.add(block), function(err) { - assert.ifError(err); - var tx = block.txs[1]; - var output = bcoin.coin.fromTX(tx, 1); - c(chain.db.getCoin(tx.hash('hex'), 1), function(err, coin) { - assert.ifError(err); - assert.deepEqual(coin.toRaw(), output.toRaw()); - cb(); - }); - }); - }); - }); - }); - }); + yield spawn.timeout(100); - it('should get balance', function(cb) { - setTimeout(function() { - c(wallet.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.unconfirmed, 500 * 1e8); - assert.equal(balance.confirmed, 700 * 1e8); - assert.equal(balance.total, 1200 * 1e8); - assert(wallet.account.receiveDepth >= 8); - assert(wallet.account.changeDepth >= 7); - assert.equal(walletdb.height, chain.height); - assert.equal(walletdb.tip, chain.tip.hash); - c(wallet.getHistory(), function(err, txs) { - assert.ifError(err); - assert.equal(txs.length, 44); - cb(); - }); - }); - }, 100); - }); + balance = yield wallet.getBalance(); + assert.equal(balance.unconfirmed, 500 * 1e8); + assert.equal(balance.confirmed, 550 * 1e8); + assert.equal(balance.total, 1050 * 1e8); + })); - it('should rescan for transactions', function(cb) { + it('should check main chain', cob(function *() { + var result = yield tip1.isMainChain(); + assert(!result); + })); + + it('should mine a block after a reorg', cob(function *() { + var block, entry, result; + + block = yield mineBlock(null, cb2); + deleteCoins(block); + yield chain.add(block); + + entry = yield chain.db.get(block.hash('hex')); + assert(entry); + assert(chain.tip.hash === entry.hash); + + result = yield entry.isMainChain(); + assert(result); + })); + + it('should fail to mine a block with coins on an alternate chain', cob(function *() { + var block = yield mineBlock(null, cb1); + var err; + + deleteCoins(block); + + try { + yield chain.add(block); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.reason, 'bad-txns-inputs-missingorspent'); + })); + + it('should get coin', cob(function *() { + var block, tx, output, coin; + + block = yield mineBlock(); + yield chain.add(block); + block = yield mineBlock(null, block.txs[0]); + yield chain.add(block); + + tx = block.txs[1]; + output = bcoin.coin.fromTX(tx, 1); + + coin = yield chain.db.getCoin(tx.hash('hex'), 1); + + assert.deepEqual(coin.toRaw(), output.toRaw()); + })); + + it('should get balance', cob(function *() { + var balance, txs; + + yield spawn.timeout(100); + + balance = yield wallet.getBalance(); + assert.equal(balance.unconfirmed, 500 * 1e8); + assert.equal(balance.confirmed, 700 * 1e8); + assert.equal(balance.total, 1200 * 1e8); + + assert(wallet.account.receiveDepth >= 8); + assert(wallet.account.changeDepth >= 7); + + assert.equal(walletdb.height, chain.height); + assert.equal(walletdb.tip, chain.tip.hash); + + txs = yield wallet.getHistory(); + assert.equal(txs.length, 44); + })); + + it('should rescan for transactions', cob(function *() { var total = 0; - c(walletdb.getAddressHashes(), function(err, hashes) { - assert.ifError(err); - c(chain.db.scan(null, hashes, function(block, txs) { - total += txs.length; - return Promise.resolve(null); - }), function(err) { - assert.ifError(err); - assert.equal(total, 25); - cb(); - }); - }); - }); + var hashes = yield walletdb.getAddressHashes(); - it('should cleanup', function(cb) { + yield chain.db.scan(null, hashes, function(block, txs) { + total += txs.length; + return Promise.resolve(null); + }); + + assert.equal(total, 25); + })); + + it('should cleanup', cob(function *() { constants.tx.COINBASE_MATURITY = 100; - c(node.close(), cb); - }); + yield node.close(); + })); }); diff --git a/test/mempool-test.js b/test/mempool-test.js index 49fd5e1b..60fbe549 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -8,379 +8,356 @@ var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; var c = require('../lib/utils/spawn').cb; +var cob = require('../lib/utils/spawn').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); - var chain = new bcoin.chain({ + chain = new bcoin.chain({ name: 'mp-chain', db: 'memory' }); - var mempool = new bcoin.mempool({ + mempool = new bcoin.mempool({ chain: chain, name: 'mempool-test', db: 'memory' }); - var walletdb = new bcoin.walletdb({ + walletdb = new bcoin.walletdb({ name: 'mempool-wallet-test', db: 'memory', verify: true }); - var w, cached; + it('should open mempool', cob(function *() { + yield mempool.open(); + chain.state.flags |= constants.flags.VERIFY_WITNESS; + })); - mempool.on('error', function() {}); + it('should open walletdb', cob(function *() { + yield walletdb.open(); + })); - it('should open mempool', function(cb) { - c(mempool.open(), function(err) { - assert.ifError(err); - chain.state.flags |= constants.flags.VERIFY_WITNESS; - cb(); - }); - }); + it('should open wallet', cob(function *() { + wallet = yield walletdb.create(); + })); - it('should open walletdb', function(cb) { - c(walletdb.open(), cb); - }); - - it('should open wallet', function(cb) { - c(walletdb.create({}), function(err, wallet) { - assert.ifError(err); - w = wallet; - cb(); - }); - }); - - it('should handle incoming orphans and TXs', function(cb) { + it('should handle incoming orphans and TXs', cob(function *() { 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 dummyInput = { - prevout: { - hash: constants.ONE_HASH.toString('hex'), - index: 0 - }, - coin: { - version: 1, - height: 0, - value: 70000, - script: prev, - coinbase: false, - hash: constants.ONE_HASH.toString('hex'), - 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)]), + 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 - c(w.sign(t1), function(err, total) { - assert.ifError(err); - t1 = t1.toTX(); - var t2 = bcoin.mtx().addInput(t1, 0) // 50000 - .addOutput(w, 20000) - .addOutput(w, 20000); - // balance: 49000 - c(w.sign(t2), function(err, total) { - assert.ifError(err); - t2 = t2.toTX(); - var t3 = bcoin.mtx().addInput(t1, 1) // 10000 - .addInput(t2, 0) // 20000 - .addOutput(w, 23000); - // balance: 47000 - c(w.sign(t3), function(err, total) { - assert.ifError(err); - t3 = t3.toTX(); - var t4 = bcoin.mtx().addInput(t2, 1) // 24000 - .addInput(t3, 0) // 23000 - .addOutput(w, 11000) - .addOutput(w, 11000); - // balance: 22000 - c(w.sign(t4), function(err, total) { - assert.ifError(err); - t4 = t4.toTX(); - var f1 = bcoin.mtx().addInput(t4, 1) // 11000 - .addOutput(bcoin.address.fromData(new Buffer([])).toBase58(), 9000); - // balance: 11000 - c(w.sign(f1), function(err, total) { - assert.ifError(err); - f1 = f1.toTX(); - var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) - .addOutput(w, 6000); // 6000 instead of 500 - // Script inputs but do not sign - c(w.template(fake), function(err) { - assert.ifError(err); - // Fake signature - fake.inputs[0].script.set(0, new Buffer([0,0,0,0,0,0,0,0,0])); - 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 w.sign(t1); + t1 = t1.toTX(); - c(mempool.addTX(fake), function(err) { - assert.ifError(err); - c(mempool.addTX(t4), function(err) { - assert.ifError(err); - var balance = mempool.getBalance(); - assert.equal(balance, 0); - c(mempool.addTX(t1), function(err) { - assert.ifError(err); - var balance = mempool.getBalance(); - assert.equal(balance, 60000); - c(mempool.addTX(t2), function(err) { - assert.ifError(err); - var balance = mempool.getBalance(); - assert.equal(balance, 50000); - c(mempool.addTX(t3), function(err) { - assert.ifError(err); - var balance = mempool.getBalance(); - assert.equal(balance, 22000); - c(mempool.addTX(f1), function(err) { - assert.ifError(err); - var balance = mempool.getBalance(); - assert.equal(balance, 20000); - var txs = mempool.getHistory(); - assert(txs.some(function(tx) { - return tx.hash('hex') === f1.hash('hex'); - })); + t2 = bcoin.mtx() + .addInput(t1, 0) // 50000 + .addOutput(w.getAddress(), 20000) + .addOutput(w.getAddress(), 20000); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + // 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; }); }); - }); - it('should handle locktime', function(cb) { + 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(); - // 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.setLocktime(200); + 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; - t1.inputs[0].script = new bcoin.script([t1.signature(0, prev, kp.privateKey, 'all', 0)]), - t1 = t1.toTX(); - c(mempool.addTX(t1), function(err) { - chain.tip.height = 0; - assert.ifError(err); - cb(); - }); - }); - it('should handle invalid locktime', function(cb) { + 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(); - // 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.setLocktime(200); + 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; - t1.inputs[0].script = new bcoin.script([t1.signature(0, prev, kp.privateKey, 'all', 0)]), - t1 = t1.toTX(); - c(mempool.addTX(t1), function(err) { - chain.tip.height = 0; - assert(err); - cb(); - }); - }); - it('should not cache a malleated wtx with mutated sig', function(cb) { + 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; - // 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(); - c(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(); - c(mempool.addTX(tx), function(err) { - assert(err); - assert(!mempool.hasReject(tx.hash())); - cb(); - }); - }); + tx = bcoin.mtx() + .addOutput(w.getAddress(), 50000) + .addOutput(w.getAddress(), 10000); - it('should not cache a malleated wtx with wit removed', function(cb) { + 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; - // 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(); - c(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) { + 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(); - // 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(); - c(mempool.addTX(tx), function(err) { - assert(err); - assert(!err.malleated); - assert(mempool.hasReject(tx.hash())); - cached = tx; - cb(); - }); - }); + var tx, prev, prevHash, tx, err; - it('should clear reject cache', function(cb) { - var t1 = bcoin.mtx().addOutput(w, 50000); - var dummyInput = { + 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 @@ -389,19 +366,20 @@ describe('Mempool', function() { 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())); - c(mempool.addBlock(block), function(err) { - assert(!err); - assert(!mempool.hasReject(cached.hash())); - cb(); - }); - }); - it('should destroy mempool', function(cb) { - c(mempool.close(), cb); - }); + 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(); + })); }); diff --git a/test/wallet-test.js b/test/wallet-test.js index 64e67ad7..d43ea5fd 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -10,8 +10,8 @@ var spawn = require('../lib/utils/spawn'); var assert = require('assert'); var scriptTypes = constants.scriptTypes; var c = require('../lib/utils/spawn').cb; - -var FAKE_SIG = new Buffer([0,0,0,0,0,0,0,0,0]); +var co = require('../lib/utils/spawn').co; +var cob = require('../lib/utils/spawn').cob; var KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt' + 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ'; @@ -42,41 +42,28 @@ var dummyInput = { sequence: 0xffffffff }; -assert.range = function range(value, lo, hi, message) { - if (!(value >= lo && value <= hi)) { - throw new assert.AssertionError({ - message: message, - actual: value, - expected: lo + ', ' + hi, - operator: '>= && <=', - stackStartFunction: range - }); - } -}; - describe('Wallet', function() { - var walletdb = new bcoin.walletdb({ + var walletdb, wallet, doubleSpendWallet, doubleSpend; + + walletdb = new bcoin.walletdb({ name: 'wallet-test', db: 'memory', verify: true }); - var lastW; this.timeout(5000); - it('should open walletdb', function(cb) { + it('should open walletdb', cob(function *() { constants.tx.COINBASE_MATURITY = 0; - c(walletdb.open(), cb); - }); + yield walletdb.open(); + })); - it('should generate new key and address', function() { - c(walletdb.create(), function(err, w) { - assert.ifError(err); - var addr = w.getAddress('base58'); - assert(addr); - assert(bcoin.address.validate(addr)); - }); - }); + it('should generate new key and address', cob(function *() { + var w = yield walletdb.create(); + var addr = w.getAddress('base58'); + assert(addr); + assert(bcoin.address.validate(addr)); + })); it('should validate existing address', function() { assert(bcoin.address.validate('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc')); @@ -86,1046 +73,869 @@ describe('Wallet', function() { assert(!bcoin.address.validate('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc')); }); - it('should create and get wallet', function(cb) { - c(walletdb.create(), function(err, w1) { - assert.ifError(err); - w1.destroy(); - c(walletdb.get(w1.id), function(err, w1_) { - assert.ifError(err); - // assert(w1 !== w1_); - // assert(w1.master !== w1_.master); - assert.equal(w1.master.key.xprivkey, w1.master.key.xprivkey); - // assert(w1.account !== w1_.account); - assert.equal(w1.account.accountKey.xpubkey, w1.account.accountKey.xpubkey); - cb(); - }); - }); - }); + it('should create and get wallet', cob(function *() { + var w1, w2; - function p2pkh(witness, bullshitNesting, cb) { + w1 = yield walletdb.create(); + yield w1.destroy(); + + w2 = yield walletdb.get(w1.id); + + assert(w1 !== w2); + assert(w1.master !== w2.master); + assert.equal(w1.master.key.xprivkey, w2.master.key.xprivkey); + assert.equal(w1.account.accountKey.xpubkey, w2.account.accountKey.xpubkey); + })); + + var p2pkh = co(function* p2pkh(witness, bullshitNesting) { var flags = bcoin.constants.flags.STANDARD_VERIFY_FLAGS; + var w, addr, src, tx; if (witness) flags |= bcoin.constants.flags.VERIFY_WITNESS; - c(walletdb.create({ witness: witness }), function(err, w) { - assert.ifError(err); + w = yield walletdb.create({ witness: witness }); - var ad = bcoin.address.fromBase58(w.getAddress('base58')); + addr = bcoin.address.fromBase58(w.getAddress('base58')); - if (witness) - assert(ad.type === scriptTypes.WITNESSPUBKEYHASH); - else - assert(ad.type === scriptTypes.PUBKEYHASH); + if (witness) + assert.equal(addr.type, scriptTypes.WITNESSPUBKEYHASH); + else + assert.equal(addr.type, scriptTypes.PUBKEYHASH); - var src = bcoin.mtx({ - outputs: [{ - value: 5460 * 2, - address: bullshitNesting - ? w.getProgramAddress() - : w.getAddress() - }, { - value: 5460 * 2, - address: bcoin.address.fromData(new Buffer([])).toBase58() - }] - }); - - src.addInput(dummyInput); - - var tx = bcoin.mtx() - .addInput(src, 0) - .addOutput(w.getAddress(), 5460); - - c(w.sign(tx), function(err) { - assert.ifError(err); - assert(tx.verify(flags)); - cb(); - }); + src = bcoin.mtx({ + outputs: [{ + value: 5460 * 2, + address: bullshitNesting + ? w.getProgramAddress() + : w.getAddress() + }, { + value: 5460 * 2, + address: new bcoin.address() + }] }); - } - it('should sign/verify pubkeyhash tx', function(cb) { - p2pkh(false, false, cb); + src.addInput(dummyInput); + + tx = bcoin.mtx() + .addInput(src, 0) + .addOutput(w.getAddress(), 5460); + + yield w.sign(tx); + + assert(tx.verify(flags)); }); - it('should sign/verify witnesspubkeyhash tx', function(cb) { - p2pkh(true, false, cb); - }); + it('should sign/verify pubkeyhash tx', cob(function *() { + yield p2pkh(false, false); + })); - it('should sign/verify witnesspubkeyhash tx with bullshit nesting', function(cb) { - p2pkh(true, true, cb); - }); + it('should sign/verify witnesspubkeyhash tx', cob(function *() { + yield p2pkh(true, false); + })); - it('should multisign/verify TX', function(cb) { - c(walletdb.create({ + it('should sign/verify witnesspubkeyhash tx with bullshit nesting', cob(function *() { + yield p2pkh(true, true); + })); + + it('should multisign/verify TX', cob(function *() { + var w, k, keys, src, tx, maxSize; + + w = yield walletdb.create({ type: 'multisig', m: 1, n: 2 - }), function(err, w) { - assert.ifError(err); - var k2 = bcoin.hd.fromMnemonic().deriveAccount44(0).hdPublicKey; - c(w.addKey(k2), function(err) { - assert.ifError(err); - var keys = [ - w.getPublicKey(), - k2.derive('m/0/0').publicKey - ]; - // Input transaction (bare 1-of-2 multisig) - var src = bcoin.mtx({ - outputs: [{ - value: 5460 * 2, - script: bcoin.script.fromMultisig(1, 2, keys) - }, { - value: 5460 * 2, - address: bcoin.address.fromData(new Buffer([])).toBase58() - }] - }); - src.addInput(dummyInput); - - var tx = bcoin.mtx() - .addInput(src, 0) - .addOutput(w.getAddress(), 5460); - - var maxSize = tx.maxSize(); - c(w.sign(tx), function(err) { - assert.ifError(err); - assert(tx.toRaw().length <= maxSize); - assert(tx.verify()); - cb(); - }); - }); }); - }); - var dw, di; - it('should have TX pool and be serializable', function(cb) { - c(walletdb.create(), function(err, w) { - assert.ifError(err); - c(walletdb.create(), function(err, f) { - assert.ifError(err); - dw = w; + k = bcoin.hd.fromMnemonic().deriveAccount44(0).hdPublicKey; - // Coinbase - var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000); - t1.addInput(dummyInput); - // balance: 51000 - c(w.sign(t1), function(err) { - assert.ifError(err); - t1 = t1.toTX(); - var t2 = bcoin.mtx().addInput(t1, 0) // 50000 - .addOutput(w, 24000) - .addOutput(w, 24000); - di = t2.inputs[0]; - // balance: 49000 - c(w.sign(t2), function(err) { - assert.ifError(err); - t2 = t2.toTX(); - var t3 = bcoin.mtx().addInput(t1, 1) // 1000 - .addInput(t2, 0) // 24000 - .addOutput(w, 23000); - // balance: 47000 - c(w.sign(t3), function(err) { - assert.ifError(err); - t3 = t3.toTX(); - var t4 = bcoin.mtx().addInput(t2, 1) // 24000 - .addInput(t3, 0) // 23000 - .addOutput(w, 11000) - .addOutput(w, 11000); - // balance: 22000 - c(w.sign(t4), function(err) { - assert.ifError(err); - t4 = t4.toTX(); - var f1 = bcoin.mtx().addInput(t4, 1) // 11000 - .addOutput(f, 10000); - // balance: 11000 - c(w.sign(f1), function(err) { - assert.ifError(err); - f1 = f1.toTX(); - var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) - .addOutput(w, 500); - // Script inputs but do not sign - c(w.template(fake), function(err) { - assert.ifError(err); - // Fake signature - fake.inputs[0].script.set(0, FAKE_SIG); - fake.inputs[0].script.compile(); - // balance: 11000 - fake = fake.toTX(); + yield w.addKey(k); - // Fake TX should temporarly change output - c(walletdb.addTX(fake), function(err) { - assert.ifError(err); - c(walletdb.addTX(t4), function(err) { - assert.ifError(err); - c(w.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 22500); - c(walletdb.addTX(t1), function(err) { - c(w.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 73000); - c(walletdb.addTX(t2), function(err) { - assert.ifError(err); - c(w.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 47000); - c(walletdb.addTX(t3), function(err) { - assert.ifError(err); - c(w.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 22000); - c(walletdb.addTX(f1), function(err) { - assert.ifError(err); - c(w.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 11000); - c(w.getHistory(), function(err, txs) { - assert(txs.some(function(tx) { - return tx.hash('hex') === f1.hash('hex'); - })); - c(f.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 10000); - c(f.getHistory(), function(err, txs) { - assert.ifError(err); - assert(txs.some(function(tx) { - return tx.hash('hex') === f1.hash('hex'); - })); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + keys = [ + w.getPublicKey(), + k.derive('m/0/0').publicKey + ]; + + // Input transaction (bare 1-of-2 multisig) + src = bcoin.mtx({ + outputs: [{ + value: 5460 * 2, + script: bcoin.script.fromMultisig(1, 2, keys) + }, { + value: 5460 * 2, + address: new bcoin.address() + }] }); - }); - it('should cleanup spenders after double-spend', function(cb) { - var t1 = bcoin.mtx().addOutput(dw, 5000); - t1.addInput(di.coin); - c(dw.getHistory(), function(err, txs) { - assert.ifError(err); - assert.equal(txs.length, 5); - var total = txs.reduce(function(t, tx) { - return t + tx.getOutputValue(); - }, 0); - assert.equal(total, 154000); - c(dw.getCoins(), function(err, coins) { - assert.ifError(err); - c(dw.sign(t1), function(err) { - assert.ifError(err); - t1 = t1.toTX(); - c(dw.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 11000); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); - c(dw.getCoins(), function(err, coins) { - assert.ifError(err); - c(dw.getBalance(), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 6000); - c(dw.getHistory(), function(err, txs) { - assert.ifError(err); - assert.equal(txs.length, 2); - var total = txs.reduce(function(t, tx) { - return t + tx.getOutputValue(); - }, 0); - assert.equal(total, 56000); - cb(); - }); - }); - }); - }); - }); - }); - }); + src.addInput(dummyInput); + + tx = bcoin.mtx() + .addInput(src, 0) + .addOutput(w.getAddress(), 5460); + + maxSize = tx.maxSize(); + + yield w.sign(tx); + + assert(tx.toRaw().length <= maxSize); + assert(tx.verify()); + })); + + it('should have TX pool and be serializable', cob(function *() { + var w = yield walletdb.create(); + var f = yield walletdb.create(); + var t1, t2, t3, t4, f1, fake, balance, txs; + + doubleSpendWallet = w; + + // Coinbase + t1 = bcoin.mtx() + .addOutput(w.getAddress(), 50000) + .addOutput(w.getAddress(), 1000); + t1.addInput(dummyInput); + + // balance: 51000 + yield w.sign(t1); + t1 = t1.toTX(); + + t2 = bcoin.mtx() + .addInput(t1, 0) // 50000 + .addOutput(w.getAddress(), 24000) + .addOutput(w.getAddress(), 24000); + + doubleSpend = t2.inputs[0]; + + // balance: 49000 + yield w.sign(t2); + t2 = t2.toTX(); + t3 = bcoin.mtx() + .addInput(t1, 1) // 1000 + .addInput(t2, 0) // 24000 + .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(f.getAddress(), 10000); + + // balance: 11000 + yield w.sign(f1); + f1 = f1.toTX(); + fake = bcoin.mtx() + .addInput(t1, 1) // 1000 (already redeemed) + .addOutput(w.getAddress(), 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(); + // balance: 11000 + fake = fake.toTX(); + + // Fake TX should temporarly change output + yield walletdb.addTX(fake); + + yield walletdb.addTX(t4); + + balance = yield w.getBalance(); + assert.equal(balance.total, 22500); + + yield walletdb.addTX(t1); + + balance = yield w.getBalance(); + assert.equal(balance.total, 73000); + + yield walletdb.addTX(t2); + + balance = yield w.getBalance(); + assert.equal(balance.total, 47000); + + yield walletdb.addTX(t3); + + balance = yield w.getBalance(); + assert.equal(balance.total, 22000); + + yield walletdb.addTX(f1); + + balance = yield w.getBalance(); + assert.equal(balance.total, 11000); + + txs = yield w.getHistory(); + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); + + balance = yield f.getBalance(); + assert.equal(balance.total, 10000); + + txs = yield f.getHistory(); + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); + })); + + it('should cleanup spenders after double-spend', cob(function *() { + var w = doubleSpendWallet; + var tx, txs, total, balance; + + tx = bcoin.mtx().addOutput(w.getAddress(), 5000); + tx.addInput(doubleSpend.coin); + + txs = yield w.getHistory(); + assert.equal(txs.length, 5); + total = txs.reduce(function(t, tx) { + return t + tx.getOutputValue(); + }, 0); + + assert.equal(total, 154000); + + yield w.sign(tx); + tx = tx.toTX(); + + balance = yield w.getBalance(); + assert.equal(balance.total, 11000); + + yield walletdb.addTX(tx); + + balance = yield w.getBalance(); + assert.equal(balance.total, 6000); + + txs = yield w.getHistory(); + assert.equal(txs.length, 2); + + total = txs.reduce(function(t, tx) { + return t + tx.getOutputValue(); + }, 0); + assert.equal(total, 56000); + })); + + it('should fill tx with inputs', cob(function *() { + var w1 = yield walletdb.create(); + var w2 = yield walletdb.create(); + var t1, t2, t3, err; + + // Coinbase + t1 = bcoin.mtx() + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460); + + t1.addInput(dummyInput); + t1 = t1.toTX(); + + yield walletdb.addTX(t1); + + // Create new transaction + t2 = bcoin.mtx().addOutput(w2.getAddress(), 5460); + yield w1.fund(t2, { rate: 10000, round: true }); + yield w1.sign(t2); + t2 = t2.toTX(); + + assert(t2.verify()); + + assert.equal(t2.getInputValue(), 16380); + assert.equal(t2.getOutputValue(), 5460); + assert.equal(t2.getFee(), 10920); + + // Create new transaction + t3 = bcoin.mtx().addOutput(w2.getAddress(), 15000); + + try { + yield w1.fund(t3, { rate: 10000, round: true }); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.requiredFunds, 25000); + })); + + it('should fill tx with inputs with accurate fee', cob(function *() { + var w1 = yield walletdb.create({ master: KEY1 }); + var w2 = yield walletdb.create({ master: KEY2 }); + var t1, t2, t3, balance, err; + + // Coinbase + t1 = bcoin.mtx() + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460); + + t1.addInput(dummyInput); + t1 = t1.toTX(); + + yield walletdb.addTX(t1); + + // Create new transaction + t2 = bcoin.mtx().addOutput(w2.getAddress(), 5460); + yield w1.fund(t2, { rate: 10000 }); + + yield w1.sign(t2); + t2 = t2.toTX(); + assert(t2.verify()); + + assert.equal(t2.getInputValue(), 16380); + + // Should now have a change output: + assert.equal(t2.getOutputValue(), 11130); + + assert.equal(t2.getFee(), 5250); + + assert.equal(t2.getWeight(), 2084); + assert.equal(t2.getBaseSize(), 521); + assert.equal(t2.getSize(), 521); + assert.equal(t2.getVirtualSize(), 521); + + w2.once('balance', function(b) { + balance = b; }); - }); - it('should fill tx with inputs', function(cb) { - c(walletdb.create(), function(err, w1) { - assert.ifError(err); - c(walletdb.create(), function(err, w2) { - assert.ifError(err); + yield walletdb.addTX(t2); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + // Create new transaction + t3 = bcoin.mtx().addOutput(w2.getAddress(), 15000); - t1.addInput(dummyInput); - t1 = t1.toTX(); + try { + yield w1.fund(t3, { rate: 10000 }); + } catch (e) { + err = e; + } - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + assert(err); + assert(balance); + assert(balance.total === 5460); + })); - // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 5460); - c(w1.fund(t2, { rate: 10000, round: true }), function(err) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); - t2 = t2.toTX(); + it('should sign multiple inputs using different keys', cob(function *() { + var w1 = yield walletdb.create(); + var w2 = yield walletdb.create(); + var to = yield walletdb.create(); + var t1, t2, tx, cost, total, coins1, coins2, left; - assert(t2.verify()); + // Coinbase + t1 = bcoin.mtx() + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460); - assert.equal(t2.getInputValue(), 16380); - // If change < dust and is added to outputs: - // assert.equal(t2.getOutputValue(), 6380); - // If change > dust and is added to fee: - assert.equal(t2.getOutputValue(), 5460); - assert.equal(t2.getFee(), 10920); + t1.addInput(dummyInput); + t1 = t1.toTX(); - // Create new transaction - var t3 = bcoin.mtx().addOutput(w2, 15000); - c(w1.fund(t3, { rate: 10000, round: true }), function(err) { - assert(err); - assert.equal(err.requiredFunds, 25000); - cb(); - }); - }); - }); - }); - }); - }); - }); + // Coinbase + t2 = bcoin.mtx() + .addOutput(w2.getAddress(), 5460) + .addOutput(w2.getAddress(), 5460) + .addOutput(w2.getAddress(), 5460) + .addOutput(w2.getAddress(), 5460); - it('should fill tx with inputs with accurate fee', function(cb) { - c(walletdb.create({ master: KEY1 }), function(err, w1) { - assert.ifError(err); - c(walletdb.create({ master: KEY2 }), function(err, w2) { - assert.ifError(err); + t2.addInput(dummyInput); + t2 = t2.toTX(); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + yield walletdb.addTX(t1); + yield walletdb.addTX(t2); - t1.addInput(dummyInput); - t1 = t1.toTX(); + // Create our tx with an output + tx = bcoin.mtx(); + tx.addOutput(to.getAddress(), 5460); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + cost = tx.getOutputValue(); + total = cost * constants.tx.MIN_FEE; - // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 5460); - c(w1.fund(t2, { rate: 10000 }), function(err) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); - t2 = t2.toTX(); - assert(t2.verify()); + coins1 = yield w1.getCoins(); + coins2 = yield w2.getCoins(); - assert.equal(t2.getInputValue(), 16380); + // Add dummy output (for `left`) to calculate maximum TX size + tx.addOutput(w1.getAddress(), 0); - // Should now have a change output: - assert.equal(t2.getOutputValue(), 11130); + // Add our unspent inputs to sign + tx.addInput(coins1[0]); + tx.addInput(coins1[1]); + tx.addInput(coins2[0]); - assert.equal(t2.getFee(), 5250); + left = tx.getInputValue() - total; + if (left < constants.tx.DUST_THRESHOLD) { + tx.outputs[tx.outputs.length - 2].value += left; + left = 0; + } + if (left === 0) + tx.outputs.pop(); + else + tx.outputs[tx.outputs.length - 1].value = left; - assert.equal(t2.getWeight(), 2084); - assert.equal(t2.getBaseSize(), 521); - assert.equal(t2.getSize(), 521); - assert.equal(t2.getVirtualSize(), 521); + // Sign transaction + total = yield w1.sign(tx); + assert.equal(total, 2); - var balance; - w2.once('balance', function(b) { - balance = b; - }); + total = yield w2.sign(tx); + assert.equal(total, 1); - // Create new transaction - c(walletdb.addTX(t2), function(err) { - assert.ifError(err); - var t3 = bcoin.mtx().addOutput(w2, 15000); - c(w1.fund(t3, { rate: 10000 }), function(err) { - assert(err); - assert(balance); - assert(balance.total === 5460); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); + // Verify + assert.equal(tx.verify(), true); - it('should sign multiple inputs using different keys', function(cb) { - c(walletdb.create(), function(err, w1) { - assert.ifError(err); - c(walletdb.create(), function(err, w2) { - assert.ifError(err); - c(walletdb.create(), function(err, to) { - assert.ifError(err); + // Sign transaction using `inputs` and `off` params. + tx.inputs.length = 0; + tx.addInput(coins1[1]); + tx.addInput(coins1[2]); + tx.addInput(coins2[1]); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + total = yield w1.sign(tx); + assert.equal(total, 2); - t1.addInput(dummyInput); - t1 = t1.toTX(); + total = yield w2.sign(tx); + assert.equal(total, 1); - // Coinbase - var t2 = bcoin.mtx() - .addOutput(w2, 5460) - .addOutput(w2, 5460) - .addOutput(w2, 5460) - .addOutput(w2, 5460); + // Verify + assert.equal(tx.verify(), true); + })); - t2.addInput(dummyInput); - t2 = t2.toTX(); - - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); - c(walletdb.addTX(t2), function(err) { - assert.ifError(err); - - // Create our tx with an output - var tx = bcoin.mtx(); - tx.addOutput(to, 5460); - - var cost = tx.getOutputValue(); - var total = cost * constants.tx.MIN_FEE; - - c(w1.getCoins(), function(err, coins1) { - assert.ifError(err); - c(w2.getCoins(), function(err, coins2) { - assert.ifError(err); - - // Add dummy output (for `left`) to calculate maximum TX size - tx.addOutput(w1, 0); - - // Add our unspent inputs to sign - tx.addInput(coins1[0]); - tx.addInput(coins1[1]); - tx.addInput(coins2[0]); - - var left = tx.getInputValue() - total; - if (left < constants.tx.DUST_THRESHOLD) { - tx.outputs[tx.outputs.length - 2].value += left; - left = 0; - } - if (left === 0) - tx.outputs.pop(); - else - tx.outputs[tx.outputs.length - 1].value = left; - - // Sign transaction - c(w1.sign(tx), function(err, total) { - assert.ifError(err); - assert.equal(total, 2); - c(w2.sign(tx), function(err, total) { - assert.ifError(err); - assert.equal(total, 1); - - // Verify - assert.equal(tx.verify(), true); - - // Sign transaction using `inputs` and `off` params. - tx.inputs.length = 0; - tx.addInput(coins1[1]); - tx.addInput(coins1[2]); - tx.addInput(coins2[1]); - c(w1.sign(tx), function(err, total) { - assert.ifError(err); - assert.equal(total, 2); - c(w2.sign(tx), function(err, total) { - assert.ifError(err); - assert.equal(total, 1); - - // Verify - assert.equal(tx.verify(), true); - - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - - function multisig(witness, bullshitNesting, cb) { + var multisig = co(function* multisig(witness, bullshitNesting, cb) { var flags = bcoin.constants.flags.STANDARD_VERIFY_FLAGS; + var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change; if (witness) flags |= bcoin.constants.flags.VERIFY_WITNESS; // Create 3 2-of-3 wallets with our pubkeys as "shared keys" - var options = { + options = { witness: witness, type: 'multisig', m: 2, n: 3 }; - var w1, w2, w3, receive; + w1 = yield walletdb.create(options); + w2 = yield walletdb.create(options); + w3 = yield walletdb.create(options); + receive = yield walletdb.create(); - utils.serial([ - function(next) { - c(walletdb.create(options), function(err, w1_) { - assert.ifError(err); - w1 = w1_; - next(); - }); - }, - function(next) { - c(walletdb.create(options), function(err, w2_) { - assert.ifError(err); - w2 = w2_; - next(); - }); - }, - function(next) { - c(walletdb.create(options), function(err, w3_) { - assert.ifError(err); - w3 = w3_; - next(); - }); - }, - function(next) { - c(walletdb.create(), function(err, receive_) { - assert.ifError(err); - receive = receive_; - next(); - }); - } - ], function(err) { - assert.ifError(err); + yield w1.addKey(w2.accountKey); + yield w1.addKey(w3.accountKey); + yield w2.addKey(w1.accountKey); + yield w2.addKey(w3.accountKey); + yield w3.addKey(w1.accountKey); + yield w3.addKey(w2.accountKey); - spawn(function *() { - yield w1.addKey(w2.accountKey); - yield w1.addKey(w3.accountKey); - yield w2.addKey(w1.accountKey); - yield w2.addKey(w3.accountKey); - yield w3.addKey(w1.accountKey); - yield w3.addKey(w2.accountKey); - }).catch(cb).then(function(err) { - assert.ifError(err); + // Our p2sh address + b58 = w1.getAddress('base58'); + addr = bcoin.address.fromBase58(b58); - // w3 = bcoin.wallet.fromJSON(w3.toJSON()); + if (witness) + assert.equal(addr.type, scriptTypes.WITNESSSCRIPTHASH); + else + assert.equal(addr.type, scriptTypes.SCRIPTHASH); - // Our p2sh address - var addr = w1.getAddress('base58'); + assert.equal(w1.getAddress('base58'), b58); + assert.equal(w2.getAddress('base58'), b58); + assert.equal(w3.getAddress('base58'), b58); - var ad = bcoin.address.fromBase58(addr); + paddr = w1.getProgramAddress('base58'); + assert.equal(w1.getProgramAddress('base58'), paddr); + assert.equal(w2.getProgramAddress('base58'), paddr); + assert.equal(w3.getProgramAddress('base58'), paddr); - if (witness) - assert(ad.type === scriptTypes.WITNESSSCRIPTHASH); - else - assert(ad.type === scriptTypes.SCRIPTHASH); + // Add a shared unspent transaction to our wallets + utx = bcoin.mtx(); + if (bullshitNesting) + utx.addOutput({ address: paddr, value: 5460 * 10 }); + else + utx.addOutput({ address: addr, value: 5460 * 10 }); - assert.equal(w1.getAddress('base58'), addr); - assert.equal(w2.getAddress('base58'), addr); - assert.equal(w3.getAddress('base58'), addr); + utx.addInput(dummyInput); + utx = utx.toTX(); - var paddr = w1.getProgramAddress('base58'); - assert.equal(w1.getProgramAddress('base58'), paddr); - assert.equal(w2.getProgramAddress('base58'), paddr); - assert.equal(w3.getProgramAddress('base58'), paddr); + // Simulate a confirmation + utx.ps = 0; + utx.ts = 1; + utx.height = 1; - // Add a shared unspent transaction to our wallets - var utx = bcoin.mtx(); - if (bullshitNesting) - utx.addOutput({ address: paddr, value: 5460 * 10 }); - else - utx.addOutput({ address: addr, value: 5460 * 10 }); + assert.equal(w1.receiveDepth, 1); - utx.addInput(dummyInput); - utx = utx.toTX(); + yield walletdb.addTX(utx); + yield walletdb.addTX(utx); + yield walletdb.addTX(utx); - // Simulate a confirmation - utx.ps = 0; - utx.ts = 1; - utx.height = 1; + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 1); - assert.equal(w1.receiveDepth, 1); + assert(w1.getAddress('base58') !== b58); + b58 = w1.getAddress('base58'); + assert.equal(w1.getAddress('base58'), b58); + assert.equal(w2.getAddress('base58'), b58); + assert.equal(w3.getAddress('base58'), b58); - c(walletdb.addTX(utx), function(err) { - assert.ifError(err); - c(walletdb.addTX(utx), function(err) { - assert.ifError(err); - c(walletdb.addTX(utx), function(err) { - assert.ifError(err); + // Create a tx requiring 2 signatures + send = bcoin.mtx(); + send.addOutput({ address: receive.getAddress(), value: 5460 }); + assert(!send.verify(flags)); + yield w1.fund(send, { rate: 10000, round: true }); - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 1); + yield w1.sign(send); - assert(w1.getAddress('base58') !== addr); - addr = w1.getAddress('base58'); - assert.equal(w1.getAddress('base58'), addr); - assert.equal(w2.getAddress('base58'), addr); - assert.equal(w3.getAddress('base58'), addr); + assert(!send.verify(flags)); - // Create a tx requiring 2 signatures - var send = bcoin.mtx(); - send.addOutput({ address: receive.getAddress(), value: 5460 }); - assert(!send.verify(flags)); - c(w1.fund(send, { rate: 10000, round: true }), function(err) { - assert.ifError(err); + yield w2.sign(send); - c(w1.sign(send), function(err) { - assert.ifError(err); + send = send.toTX(); + assert(send.verify(flags)); - assert(!send.verify(flags)); - c(w2.sign(send), function(err) { - assert.ifError(err); + assert.equal(w1.changeDepth, 1); - send = send.toTX(); - assert(send.verify(flags)); + change = w1.changeAddress.getAddress('base58'); + assert.equal(w1.changeAddress.getAddress('base58'), change); + assert.equal(w2.changeAddress.getAddress('base58'), change); + assert.equal(w3.changeAddress.getAddress('base58'), change); - assert.equal(w1.changeDepth, 1); - var change = w1.changeAddress.getAddress('base58'); - assert.equal(w1.changeAddress.getAddress('base58'), change); - assert.equal(w2.changeAddress.getAddress('base58'), change); - assert.equal(w3.changeAddress.getAddress('base58'), change); + // Simulate a confirmation + send.ps = 0; + send.ts = 1; + send.height = 1; - // Simulate a confirmation - send.ps = 0; - send.ts = 1; - send.height = 1; + yield walletdb.addTX(send); + yield walletdb.addTX(send); + yield walletdb.addTX(send); - c(walletdb.addTX(send), function(err) { - assert.ifError(err); - c(walletdb.addTX(send), function(err) { - assert.ifError(err); - c(walletdb.addTX(send), function(err) { - assert.ifError(err); + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 2); - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 2); + assert(w1.getAddress('base58') === b58); + assert(w1.changeAddress.getAddress('base58') !== change); + change = w1.changeAddress.getAddress('base58'); + assert.equal(w1.changeAddress.getAddress('base58'), change); + assert.equal(w2.changeAddress.getAddress('base58'), change); + assert.equal(w3.changeAddress.getAddress('base58'), change); - assert(w1.getAddress('base58') === addr); - assert(w1.changeAddress.getAddress('base58') !== change); - change = w1.changeAddress.getAddress('base58'); - assert.equal(w1.changeAddress.getAddress('base58'), change); - assert.equal(w2.changeAddress.getAddress('base58'), change); - assert.equal(w3.changeAddress.getAddress('base58'), change); + if (witness) { + send.inputs[0].witness.set(2, 0); + } else { + send.inputs[0].script.set(2, 0); + send.inputs[0].script.compile(); + } - if (witness) { - send.inputs[0].witness.items[2] = new Buffer([]); - } else { - send.inputs[0].script.code[2] = new bcoin.opcode(0); - send.inputs[0].script.compile(); - } - - assert(!send.verify(flags)); - assert.equal(send.getFee(), 10000); - - // w3 = bcoin.wallet.fromJSON(w3.toJSON()); - // assert.equal(w3.receiveDepth, 2); - // assert.equal(w3.changeDepth, 2); - //assert.equal(w3.getAddress('base58'), addr); - //assert.equal(w3.changeAddress.getAddress('base58'), change); - - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - } - - it('should verify 2-of-3 scripthash tx', function(cb) { - multisig(false, false, cb); + assert(!send.verify(flags)); + assert.equal(send.getFee(), 10000); }); - it('should verify 2-of-3 witnessscripthash tx', function(cb) { - multisig(true, false, cb); - }); + it('should verify 2-of-3 scripthash tx', cob(function *() { + yield multisig(false, false); + })); - it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', function(cb) { - multisig(true, true, cb); - }); + it('should verify 2-of-3 witnessscripthash tx', cob(function *() { + yield multisig(true, false); + })); - it('should fill tx with account 1', function(cb) { - c(walletdb.create({}), function(err, w1) { - assert.ifError(err); - c(walletdb.create({}), function(err, w2) { - assert.ifError(err); - c(w1.createAccount({ name: 'foo' }), function(err, account) { - assert.ifError(err); - assert.equal(account.name, 'foo'); - assert.equal(account.accountIndex, 1); - c(w1.getAccount('foo'), function(err, account) { - assert.ifError(err); - assert.equal(account.name, 'foo'); - assert.equal(account.accountIndex, 1); + it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', cob(function *() { + yield multisig(true, true); + })); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(account.receiveAddress, 5460) - .addOutput(account.receiveAddress, 5460) - .addOutput(account.receiveAddress, 5460) - .addOutput(account.receiveAddress, 5460); + it('should fill tx with account 1', cob(function *() { + var w1 = yield walletdb.create(); + var w2 = yield walletdb.create(); + var account, accounts, rec, t1, t2, t3, err; - t1.addInput(dummyInput); - t1 = t1.toTX(); + account = yield w1.createAccount({ name: 'foo' }); + assert.equal(account.name, 'foo'); + assert.equal(account.accountIndex, 1); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + account = yield w1.getAccount('foo'); + assert.equal(account.name, 'foo'); + assert.equal(account.accountIndex, 1); + rec = account.receiveAddress; - // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 5460); - c(w1.fund(t2, { rate: 10000, round: true }), function(err) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); + // Coinbase + t1 = bcoin.mtx() + .addOutput(rec.getAddress(), 5460) + .addOutput(rec.getAddress(), 5460) + .addOutput(rec.getAddress(), 5460) + .addOutput(rec.getAddress(), 5460); - assert(t2.verify()); + t1.addInput(dummyInput); + t1 = t1.toTX(); - assert.equal(t2.getInputValue(), 16380); - // If change < dust and is added to outputs: - // assert.equal(t2.getOutputValue(), 6380); - // If change > dust and is added to fee: - assert.equal(t2.getOutputValue(), 5460); - assert.equal(t2.getFee(), 10920); + yield walletdb.addTX(t1); - // Create new transaction - var t3 = bcoin.mtx().addOutput(w2, 15000); - c(w1.fund(t3, { rate: 10000, round: true }), function(err) { - assert(err); - assert.equal(err.requiredFunds, 25000); - c(w1.getAccounts(), function(err, accounts) { - assert.ifError(err); - assert.deepEqual(accounts, ['default', 'foo']); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + // Create new transaction + t2 = bcoin.mtx().addOutput(w2.getAddress(), 5460); + yield w1.fund(t2, { rate: 10000, round: true }); + yield w1.sign(t2); - it('should fail to fill tx with account 1', function(cb) { - c(walletdb.create({}), function(err, w1) { - assert.ifError(err); - lastW = w1; - c(w1.createAccount({ name: 'foo' }), function(err, acc) { - assert.ifError(err); - assert.equal(acc.name, 'foo'); - assert.equal(acc.accountIndex, 1); - c(w1.getAccount('foo'), function(err, account) { - assert.ifError(err); - assert.equal(account.name, 'foo'); - assert.equal(account.accountIndex, 1); - // assert(account !== w1.account); - // assert(account !== acc); - assert(account.accountKey.xpubkey === acc.accountKey.xpubkey); - assert(w1.account.accountIndex === 0); - assert(account.receiveAddress.getAddress('base58') !== w1.account.receiveAddress.getAddress('base58')); - assert(w1.getAddress('base58') === w1.account.receiveAddress.getAddress('base58')); + assert(t2.verify()); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(account.receiveAddress, 5460); + assert.equal(t2.getInputValue(), 16380); + assert.equal(t2.getOutputValue(), 5460); + assert.equal(t2.getFee(), 10920); - t1.addInput(dummyInput); - t1 = t1.toTX(); + // Create new transaction + t3 = bcoin.mtx().addOutput(w2.getAddress(), 15000); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + try { + yield w1.fund(t3, { rate: 10000, round: true }); + } catch (e) { + err = e; + } - // Should fill from `foo` and fail - var t2 = bcoin.mtx().addOutput(w1, 5460); - c(w1.fund(t2, { rate: 10000, round: true, account: 'foo' }), function(err) { - assert(err); - // Should fill from whole wallet and succeed - var t2 = bcoin.mtx().addOutput(w1, 5460); - c(w1.fund(t2, { rate: 10000, round: true }), function(err) { - assert.ifError(err); + assert(err); + assert.equal(err.requiredFunds, 25000); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(account.receiveAddress, 5460) - .addOutput(account.receiveAddress, 5460) - .addOutput(account.receiveAddress, 5460); + accounts = yield w1.getAccounts(); + assert.deepEqual(accounts, ['default', 'foo']); + })); - t1.ps = 0xdeadbeef; - t1.addInput(dummyInput); - t1 = t1.toTX(); + it('should fail to fill tx with account 1', cob(function *() { + var w = yield walletdb.create(); + var acc, account, t1, t2, err; - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); - var t2 = bcoin.mtx().addOutput(w1, 5460); - // Should fill from `foo` and succeed - c(w1.fund(t2, { rate: 10000, round: true, account: 'foo' }), function(err) { - assert.ifError(err); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); - }); + wallet = w; - it('should fill tx with inputs when encrypted', function(cb) { - c(walletdb.create({ passphrase: 'foo' }), function(err, w1) { - assert.ifError(err); - w1.master.stop(); - w1.master.key = null; + acc = yield w.createAccount({ name: 'foo' }); + assert.equal(acc.name, 'foo'); + assert.equal(acc.accountIndex, 1); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + account = yield w.getAccount('foo'); + assert.equal(account.name, 'foo'); + assert.equal(account.accountIndex, 1); + assert(account.accountKey.xpubkey === acc.accountKey.xpubkey); + assert(w.account.accountIndex === 0); - t1.addInput(dummyInput); - t1 = t1.toTX(); + assert.notEqual( + account.receiveAddress.getAddress('base58'), + w.account.receiveAddress.getAddress('base58')); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + assert.equal(w.getAddress('base58'), + w.account.receiveAddress.getAddress('base58')); - // Create new transaction - var t2 = bcoin.mtx().addOutput(w1, 5460); - c(w1.fund(t2, { rate: 10000, round: true }), function(err) { - assert.ifError(err); - // Should fail - c(w1.sign(t2, 'bar'), function(err) { - assert(err); - assert(!t2.verify()); - // Should succeed - c(w1.sign(t2, 'foo'), function(err) { - assert.ifError(err); - assert(t2.verify()); - cb(); - }); - }); - }); - }); - }); - }); + // Coinbase + t1 = bcoin.mtx() + .addOutput(w.getAddress(), 5460) + .addOutput(w.getAddress(), 5460) + .addOutput(w.getAddress(), 5460) + .addOutput(account.receiveAddress.getAddress(), 5460); - it('should fill tx with inputs with subtract fee', function(cb) { - c(walletdb.create(), function(err, w1) { - assert.ifError(err); - c(walletdb.create(), function(err, w2) { - assert.ifError(err); + t1.addInput(dummyInput); + t1 = t1.toTX(); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + yield walletdb.addTX(t1); - t1.addInput(dummyInput); - t1 = t1.toTX(); + // Should fill from `foo` and fail + t2 = bcoin.mtx().addOutput(w.getAddress(), 5460); + try { + yield w.fund(t2, { rate: 10000, round: true, account: 'foo' }); + } catch (e) { + err = e; + } + assert(err); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + // Should fill from whole wallet and succeed + t2 = bcoin.mtx().addOutput(w.getAddress(), 5460); + yield w.fund(t2, { rate: 10000, round: true }); - // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 21840); - c(w1.fund(t2, { rate: 10000, round: true, subtractFee: true }), function(err) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); + // Coinbase + t1 = bcoin.mtx() + .addOutput(account.receiveAddress.getAddress(), 5460) + .addOutput(account.receiveAddress.getAddress(), 5460) + .addOutput(account.receiveAddress.getAddress(), 5460); - assert(t2.verify()); + t1.ps = 0xdeadbeef; + t1.addInput(dummyInput); + t1 = t1.toTX(); - assert.equal(t2.getInputValue(), 5460 * 4); - assert.equal(t2.getOutputValue(), 21840 - 10000); - assert.equal(t2.getFee(), 10000); + yield walletdb.addTX(t1); - cb(); - }); - }); - }); - }); - }); - }); + t2 = bcoin.mtx().addOutput(w.getAddress(), 5460); + // Should fill from `foo` and succeed + yield w.fund(t2, { rate: 10000, round: true, account: 'foo' }); + })); - it('should fill tx with inputs with subtract fee with create tx', function(cb) { - c(walletdb.create(), function(err, w1) { - assert.ifError(err); - c(walletdb.create(), function(err, w2) { - assert.ifError(err); + it('should fill tx with inputs when encrypted', cob(function *() { + var w = yield walletdb.create({ passphrase: 'foo' }); + var t1, t2, err; - // Coinbase - var t1 = bcoin.mtx() - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + w.master.stop(); + w.master.key = null; - t1.addInput(dummyInput); - t1 = t1.toTX(); + // Coinbase + t1 = bcoin.mtx() + .addOutput(w.getAddress(), 5460) + .addOutput(w.getAddress(), 5460) + .addOutput(w.getAddress(), 5460) + .addOutput(w.getAddress(), 5460); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + t1.addInput(dummyInput); + t1 = t1.toTX(); - var options = { - subtractFee: true, - rate: 10000, - round: true, - outputs: [{ address: w2.getAddress(), value: 21840 }] - }; + yield walletdb.addTX(t1); - // Create new transaction - c(w1.createTX(options), function(err, t2) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); + // Create new transaction + t2 = bcoin.mtx().addOutput(w.getAddress(), 5460); + yield w.fund(t2, { rate: 10000, round: true }); - assert(t2.verify()); + // Should fail + try { + yield w.sign(t2, { passphrase: 'bar' }); + } catch (e) { + err = e; + } - assert.equal(t2.getInputValue(), 5460 * 4); - assert.equal(t2.getOutputValue(), 21840 - 10000); - assert.equal(t2.getFee(), 10000); + assert(err); + assert(!t2.verify()); - cb(); - }); - }); - }); - }); - }); - }); + // Should succeed + yield w.sign(t2, { passphrase: 'foo' }); + assert(t2.verify()); + })); - it('should get range of txs', function(cb) { - var w1 = lastW; - c(w1.getRange({ start: 0xdeadbeef - 1000 }), function(err, txs) { - if (err) - return callback(err); - assert.equal(txs.length, 1); - cb(); - }); - }); + it('should fill tx with inputs with subtract fee', cob(function *() { + var w1 = yield walletdb.create(); + var w2 = yield walletdb.create(); + var t1, t2; - it('should get range of txs from account', function(cb) { - var w1 = lastW; - c(w1.getRange('foo', { start: 0xdeadbeef - 1000 }), function(err, txs) { - if (err) - return callback(err); - assert.equal(txs.length, 1); - cb(); - }); - }); + // Coinbase + t1 = bcoin.mtx() + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460); - it('should not get range of txs from non-existent account', function(cb) { - var w1 = lastW; - c(w1.getRange('bad', { start: 0xdeadbeef - 1000 }), function(err, txs) { - assert(err); - assert.equal(err.message, 'Account not found.'); - cb(); - }); - }); + t1.addInput(dummyInput); + t1 = t1.toTX(); - it('should get account balance', function(cb) { - var w1 = lastW; - c(w1.getBalance('foo'), function(err, balance) { - assert.ifError(err); - assert.equal(balance.total, 21840); - cb(); - }); - }); + yield walletdb.addTX(t1); - it('should import key', function(cb) { + // Create new transaction + t2 = bcoin.mtx().addOutput(w2.getAddress(), 21840); + yield w1.fund(t2, { rate: 10000, round: true, subtractFee: true }); + yield w1.sign(t2); + + assert(t2.verify()); + + assert.equal(t2.getInputValue(), 5460 * 4); + assert.equal(t2.getOutputValue(), 21840 - 10000); + assert.equal(t2.getFee(), 10000); + })); + + it('should fill tx with inputs with subtract fee with create tx', cob(function *() { + var w1 = yield walletdb.create(); + var w2 = yield walletdb.create(); + var options, t1, t2; + + // Coinbase + t1 = bcoin.mtx() + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460) + .addOutput(w1.getAddress(), 5460); + + t1.addInput(dummyInput); + t1 = t1.toTX(); + + yield walletdb.addTX(t1); + + options = { + subtractFee: true, + rate: 10000, + round: true, + outputs: [{ address: w2.getAddress(), value: 21840 }] + }; + + // Create new transaction + t2 = yield w1.createTX(options); + yield w1.sign(t2); + + assert(t2.verify()); + + assert.equal(t2.getInputValue(), 5460 * 4); + assert.equal(t2.getOutputValue(), 21840 - 10000); + assert.equal(t2.getFee(), 10000); + })); + + it('should get range of txs', cob(function *() { + var w = wallet; + var txs = yield w.getRange({ start: 0xdeadbeef - 1000 }); + assert.equal(txs.length, 1); + })); + + it('should get range of txs from account', cob(function *() { + var w = wallet; + var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 }); + assert.equal(txs.length, 1); + })); + + it('should not get range of txs from non-existent account', cob(function *() { + var w = wallet; + var txs, err; + + try { + txs = yield w.getRange('bad', { start: 0xdeadbeef - 1000 }); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.message, 'Account not found.'); + })); + + it('should get account balance', cob(function *() { + var w = wallet; + var balance = yield w.getBalance('foo'); + assert.equal(balance.total, 21840); + })); + + it('should import key', cob(function *() { var key = bcoin.keyring.generate(); - c(walletdb.create({ passphrase: 'test' }), function(err, w1) { - assert.ifError(err); - c(w1.importKey('default', key, 'test'), function(err) { - assert.ifError(err); - c(w1.getKeyRing(key.getHash('hex')), function(err, k) { - if (err) - return callback(err); + var w = yield walletdb.create({ passphrase: 'test' }); + var options, k, t1, t2, tx; - assert.equal(k.getHash('hex'), key.getHash('hex')); + yield w.importKey('default', key, 'test'); - // Coinbase - var t1 = bcoin.mtx() - .addOutput(key.getAddress(), 5460) - .addOutput(key.getAddress(), 5460) - .addOutput(key.getAddress(), 5460) - .addOutput(key.getAddress(), 5460); + k = yield w.getKeyRing(key.getHash('hex')); - t1.addInput(dummyInput); - t1 = t1.toTX(); + assert.equal(k.getHash('hex'), key.getHash('hex')); - c(walletdb.addTX(t1), function(err) { - assert.ifError(err); + // Coinbase + t1 = bcoin.mtx() + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460) + .addOutput(key.getAddress(), 5460); - c(w1.getTX(t1.hash('hex')), function(err, tx) { - assert.ifError(err); - assert(tx); - assert.equal(t1.hash('hex'), tx.hash('hex')); + t1.addInput(dummyInput); + t1 = t1.toTX(); - var options = { - rate: 10000, - round: true, - outputs: [{ address: w1.getAddress(), value: 7000 }] - }; + yield walletdb.addTX(t1); - // Create new transaction - c(w1.createTX(options), function(err, t2) { - assert.ifError(err); - c(w1.sign(t2), function(err) { - assert.ifError(err); - assert(t2.verify()); - assert(t2.inputs[0].prevout.hash === tx.hash('hex')); - cb(); - }); - }); - }); - }); - }); - }); - }); - }); + tx = yield w.getTX(t1.hash('hex')); + assert(tx); + assert.equal(t1.hash('hex'), tx.hash('hex')); - it('should cleanup', function(cb) { - c(walletdb.dump(), function(err, records) { - assert.ifError(err); - constants.tx.COINBASE_MATURITY = 100; - cb(); - }); - }); + options = { + rate: 10000, + round: true, + outputs: [{ address: w.getAddress(), value: 7000 }] + }; + + // Create new transaction + t2 = yield w.createTX(options); + yield w.sign(t2); + assert(t2.verify()); + assert(t2.inputs[0].prevout.hash === tx.hash('hex')); + })); + + it('should cleanup', cob(function *() { + var records = yield walletdb.dump(); + constants.tx.COINBASE_MATURITY = 100; + })); });