'use strict'; var bn = require('bn.js'); var bcoin = require('../').set('regtest'); var constants = bcoin.constants; var utils = bcoin.utils; var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var opcodes = constants.opcodes; constants.tx.COINBASE_MATURITY = 0; describe('Chain', function() { var chain, wallet, node, miner, walletdb; var competingTip, oldTip, tip1, tip2, cb1, cb2; this.timeout(5000); node = new bcoin.fullnode({ db: 'memory' }); chain = node.chain; walletdb = node.walletdb; miner = node.miner; node.on('error', function() {}); function mineBlock(tip, tx, callback) { 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 wallet.sign(redeemer, function(err) { assert.ifError(err); attempt.addTX(redeemer.toTX()); callback(null, attempt.mineSync()); }); } callback(null, attempt.mineSync()); }); } function deleteCoins(tx) { if (tx.txs) { deleteCoins(tx.txs); return; } if (Array.isArray(tx)) { tx.forEach(deleteCoins); return; } tx.inputs.forEach(function(input) { input.coin = null; }); } it('should open chain and miner', function(cb) { miner.mempool = null; node.open(cb); }); it('should open walletdb', function(cb) { walletdb.create({}, function(err, w) { assert.ifError(err); wallet = w; miner.address = wallet.getAddress(); cb(); }); }); it('should mine a block', function(cb) { miner.mineBlock(function(err, block) { assert.ifError(err); assert(block); cb(); }); }); 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); chain.add(block1, function(err) { assert.ifError(err); deleteCoins(block2); chain.add(block2, function(err) { assert.ifError(err); assert(chain.tip.hash === block1.hash('hex')); competingTip = block2.hash('hex'); chain.db.get(block1.hash('hex'), function(err, entry1) { assert.ifError(err); chain.db.get(block2.hash('hex'), function(err, entry2) { assert.ifError(err); assert(entry1); assert(entry2); tip1 = entry1; tip2 = entry2; chain.db.isMainChain(block2.hash('hex'), function(err, result) { assert.ifError(err); assert(!result); next(); }); }); }); }); }); }); }); }, cb); }); it('should handle a reorg', function(cb) { assert.equal(walletdb.height, chain.height); assert.equal(chain.height, 10); oldTip = chain.tip; chain.db.get(competingTip, function(err, entry) { assert.ifError(err); assert(entry); assert(chain.height === entry.height); miner.mineBlock(entry, function(err, block) { assert.ifError(err); assert(block); var forked = false; chain.once('reorganize', function() { forked = true; }); deleteCoins(block); 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(); }); }); }); }); it('should check main chain', function(cb) { chain.db.isMainChain(oldTip, function(err, result) { assert.ifError(err); assert(!result); cb(); }); }); it('should mine a block after a reorg', function(cb) { mineBlock(null, cb2, function(err, block) { assert.ifError(err); deleteCoins(block); chain.add(block, function(err) { assert.ifError(err); chain.db.get(block.hash('hex'), function(err, entry) { assert.ifError(err); assert(entry); assert(chain.tip.hash === entry.hash); chain.db.isMainChain(entry.hash, function(err, result) { assert.ifError(err); assert(result); cb(); }); }); }); }); }); 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); chain.add(block, function(err) { assert(err); cb(); }); }); }); it('should get coin', function(cb) { mineBlock(null, null, function(err, block) { assert.ifError(err); chain.add(block, function(err) { assert.ifError(err); mineBlock(null, block.txs[0], function(err, block) { assert.ifError(err); chain.add(block, function(err) { assert.ifError(err); var tx = block.txs[1]; var output = bcoin.coin.fromTX(tx, 1); chain.db.getCoin(tx.hash('hex'), 1, function(err, coin) { assert.ifError(err); assert.deepEqual(coin.toRaw(), output.toRaw()); cb(); }); }); }); }); }); }); it('should get balance', function(cb) { setTimeout(function() { wallet.getBalance(function(err, balance) { assert.ifError(err); assert.equal(balance.unconfirmed, 23000000000); assert.equal(balance.confirmed, 97000000000); assert.equal(balance.total, 120000000000); assert.equal(wallet.account.receiveDepth, 8); assert.equal(wallet.account.changeDepth, 7); assert.equal(walletdb.height, chain.height); assert.equal(walletdb.tip, chain.tip.hash); wallet.getHistory(function(err, txs) { assert.ifError(err); assert.equal(txs.length, 44); cb(); }); }); }, 100); }); it('should rescan for transactions', function(cb) { var total = 0; walletdb.getAddressHashes(function(err, hashes) { assert.ifError(err); chain.db.scan(null, hashes, function(block, txs, next) { total += txs.length; next(); }, function(err) { assert.ifError(err); assert.equal(total, 25); cb(); }); }); }); it('should cleanup', function(cb) { constants.tx.COINBASE_MATURITY = 100; node.close(cb); }); });