fcoin/test/chain-test.js
2017-03-04 15:37:27 -08:00

828 lines
21 KiB
JavaScript

'use strict';
var assert = require('assert');
var BN = require('bn.js');
var consensus = require('../lib/protocol/consensus');
var encoding = require('../lib/utils/encoding');
var co = require('../lib/utils/co');
var Coin = require('../lib/primitives/coin');
var Script = require('../lib/script/script');
var Chain = require('../lib/blockchain/chain');
var Miner = require('../lib/mining/miner');
var MTX = require('../lib/primitives/mtx');
var MemWallet = require('./util/memwallet');
var Network = require('../lib/protocol/network');
var Output = require('../lib/primitives/output');
var util = require('../lib/utils/util');
var opcodes = Script.opcodes;
describe('Chain', function() {
var network = Network.get('regtest');
var chain = new Chain({ db: 'memory', network: network });
var miner = new Miner({ chain: chain, version: 4 });
var wallet = new MemWallet({ network: network });
var wwallet = new MemWallet({ network: network, witness: true });
var tip1, tip2, addBlock, mineCSV;
this.timeout(45000);
addBlock = co(function* addBlock(attempt) {
var block = yield attempt.mineAsync();
var entry;
try {
entry = yield chain.add(block);
} catch (e) {
assert(e.type === 'VerifyError');
return e.reason;
}
if (!entry)
return 'bad-prevblk';
return 'OK';
});
mineCSV = co(function* mineCSV(tx) {
var attempt = yield miner.createBlock();
var rtx;
rtx = new MTX();
rtx.addOutput({
script: [
Script.array(new BN(1)),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
],
value: 10000
});
rtx.addTX(tx, 0);
rtx.setLocktime(chain.height);
wallet.sign(rtx);
attempt.addTX(rtx.toTX(), rtx.view);
return yield attempt.mineAsync();
});
chain.on('connect', function(entry, block) {
wallet.addBlock(entry, block.txs);
});
chain.on('disconnect', function(entry, block) {
wallet.removeBlock(entry, block.txs);
});
it('should open chain and miner', co(function* () {
yield chain.open();
yield miner.open();
}));
it('should add addrs to miner', co(function* () {
miner.addresses.length = 0;
miner.addAddress(wallet.getReceive());
}));
it('should mine 200 blocks', co(function* () {
var i, block;
for (i = 0; i < 200; i++) {
block = yield miner.mineBlock();
assert(block);
assert(yield chain.add(block));
}
assert.equal(chain.height, 200);
}));
it('should mine competing chains', co(function* () {
var i, mtx, at1, at2, blk1, blk2, hash1, hash2;
for (i = 0; i < 10; i++) {
at1 = yield miner.createBlock(tip1);
at2 = yield miner.createBlock(tip2);
mtx = yield wallet.create({
outputs: [{
address: wallet.getAddress(),
value: 10 * 1e8
}]
});
at1.addTX(mtx.toTX(), mtx.view);
at2.addTX(mtx.toTX(), mtx.view);
blk1 = yield at1.mineAsync();
blk2 = yield at2.mineAsync();
hash1 = blk1.hash('hex');
hash2 = blk2.hash('hex');
assert(yield chain.add(blk1));
assert(yield chain.add(blk2));
assert(chain.tip.hash === hash1);
tip1 = yield chain.db.getEntry(hash1);
tip2 = yield chain.db.getEntry(hash2);
assert(tip1);
assert(tip2);
assert(!(yield tip2.isMainChain()));
}
}));
it('should have correct chain value', function() {
assert.equal(chain.db.state.value, 897500000000);
assert.equal(chain.db.state.coin, 220);
assert.equal(chain.db.state.tx, 221);
});
it('should have correct wallet balance', co(function* () {
assert.equal(wallet.balance, 897500000000);
}));
it('should handle a reorg', co(function* () {
var forked = false;
var entry, block;
assert.equal(chain.height, 210);
entry = yield chain.db.getEntry(tip2.hash);
assert(entry);
assert(chain.height === entry.height);
block = yield miner.mineBlock(entry);
assert(block);
chain.once('reorganize', function() {
forked = true;
});
assert(yield chain.add(block));
assert(forked);
assert(chain.tip.hash === block.hash('hex'));
assert(chain.tip.chainwork.cmp(tip1.chainwork) > 0);
}));
it('should have correct chain value', function() {
assert.equal(chain.db.state.value, 900000000000);
assert.equal(chain.db.state.coin, 221);
assert.equal(chain.db.state.tx, 222);
});
it('should have correct wallet balance', co(function* () {
assert.equal(wallet.balance, 900000000000);
}));
it('should check main chain', co(function* () {
var result = yield tip1.isMainChain();
assert(!result);
}));
it('should mine a block after a reorg', co(function* () {
var block = yield miner.mineBlock();
var hash, entry, result;
assert(yield chain.add(block));
hash = block.hash('hex');
entry = yield chain.db.getEntry(hash);
assert(entry);
assert(chain.tip.hash === entry.hash);
result = yield entry.isMainChain();
assert(result);
}));
it('should prevent double spend on new chain', co(function* () {
var attempt = yield miner.createBlock();
var mtx, block;
mtx = yield wallet.create({
outputs: [{
address: wallet.getAddress(),
value: 10 * 1e8
}]
});
attempt.addTX(mtx.toTX(), mtx.view);
block = yield attempt.mineAsync();
assert(yield chain.add(block));
attempt = yield miner.createBlock();
assert(mtx.outputs.length > 1);
mtx.outputs.pop();
attempt.addTX(mtx.toTX(), mtx.view);
assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent');
}));
it('should fail to connect coins on an alternate chain', co(function* () {
var block = yield chain.db.getBlock(tip1.hash);
var cb = block.txs[0];
var mtx = new MTX();
var attempt;
mtx.addTX(cb, 0);
mtx.addOutput(wallet.getAddress(), 10 * 1e8);
wallet.sign(mtx);
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent');
}));
it('should have correct chain value', function() {
assert.equal(chain.db.state.value, 905000000000);
assert.equal(chain.db.state.coin, 224);
assert.equal(chain.db.state.tx, 225);
});
it('should get coin', co(function* () {
var mtx, attempt, block, tx, output, coin;
mtx = yield wallet.send({
outputs: [
{
address: wallet.getAddress(),
value: 1e8
},
{
address: wallet.getAddress(),
value: 1e8
},
{
address: wallet.getAddress(),
value: 1e8
}
]
});
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
block = yield attempt.mineAsync();
assert(yield chain.add(block));
tx = block.txs[1];
output = Coin.fromTX(tx, 2, chain.height);
coin = yield chain.db.getCoin(tx.hash('hex'), 2);
assert.deepEqual(coin.toRaw(), output.toRaw());
}));
it('should have correct wallet balance', co(function* () {
assert.equal(wallet.balance, 907500000000);
assert.equal(wallet.receiveDepth, 15);
assert.equal(wallet.changeDepth, 14);
assert.equal(wallet.txs, 226);
}));
it('should get tips and remove chains', co(function* () {
var tips = yield chain.db.getTips();
assert.notEqual(tips.indexOf(chain.tip.hash), -1);
assert.equal(tips.length, 2);
yield chain.db.removeChains();
tips = yield chain.db.getTips();
assert.notEqual(tips.indexOf(chain.tip.hash), -1);
assert.equal(tips.length, 1);
}));
it('should rescan for transactions', co(function* () {
var total = 0;
yield chain.db.scan(0, wallet.filter, function(block, txs) {
total += txs.length;
return Promise.resolve();
});
assert.equal(total, 226);
}));
it('should activate csv', co(function* () {
var deployments = network.deployments;
var i, block, prev, state, cache;
miner.options.version = -1;
assert.equal(chain.height, 214);
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, deployments.csv);
assert.equal(state, 1);
for (i = 0; i < 417; i++) {
block = yield miner.mineBlock();
assert(yield chain.add(block));
switch (chain.height) {
case 288:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, deployments.csv);
assert.equal(state, 1);
break;
case 432:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, deployments.csv);
assert.equal(state, 2);
break;
case 576:
prev = yield chain.tip.getPrevious();
state = yield chain.getState(prev, deployments.csv);
assert.equal(state, 3);
break;
}
}
assert.equal(chain.height, 631);
assert(chain.state.hasCSV());
assert(chain.state.hasWitness());
cache = yield chain.db.getStateCache();
assert.deepEqual(cache, chain.db.stateCache);
assert.equal(chain.db.stateCache.updates.length, 0);
assert(yield chain.db.verifyDeployments());
}));
it('should have activated segwit', co(function* () {
var deployments = network.deployments;
var prev = yield chain.tip.getPrevious();
var state = yield chain.getState(prev, deployments.segwit);
assert.equal(state, 3);
}));
it('should test csv', co(function* () {
var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, rtx;
assert(yield chain.add(block));
csv = block.txs[1];
rtx = new MTX();
rtx.addOutput({
script: [
Script.array(new BN(2)),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
],
value: 10000
});
rtx.addTX(csv, 0);
rtx.setSequence(0, 1, false);
attempt = yield miner.createBlock();
attempt.addTX(rtx.toTX(), rtx.view);
block = yield attempt.mineAsync();
assert(yield chain.add(block));
}));
it('should fail csv with bad sequence', co(function* () {
var csv = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var rtx = new MTX();
var attempt;
rtx.addOutput({
script: [
Script.array(new BN(1)),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
],
value: 10 * 1e8
});
rtx.addTX(csv, 0);
rtx.setSequence(0, 1, false);
attempt = yield miner.createBlock();
attempt.addTX(rtx.toTX(), rtx.view);
assert(yield addBlock(attempt), 'mandatory-script-verify-flag-failed');
}));
it('should mine a block', co(function* () {
var block = yield miner.mineBlock();
assert(block);
assert(yield chain.add(block));
}));
it('should fail csv lock checks', co(function* () {
var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, rtx;
assert(yield chain.add(block));
csv = block.txs[1];
rtx = new MTX();
rtx.addOutput({
script: [
Script.array(new BN(2)),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
],
value: 1 * 1e8
});
rtx.addTX(csv, 0);
rtx.setSequence(0, 2, false);
attempt = yield miner.createBlock();
attempt.addTX(rtx.toTX(), rtx.view);
assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal');
}));
it('should have correct wallet balance', co(function* () {
assert.equal(wallet.balance, 1412499980000);
}));
it('should fail to connect bad bits', co(function* () {
var attempt = yield miner.createBlock();
attempt.block.bits = 553713663;
assert.equal(yield addBlock(attempt), 'bad-diffbits');
}));
it('should fail to connect bad MTP', co(function* () {
var mtp = yield chain.tip.getMedianTime();
var attempt = yield miner.createBlock();
attempt.block.ts = mtp - 1;
assert.equal(yield addBlock(attempt), 'time-too-old');
}));
it('should fail to connect bad time', co(function* () {
var attempt = yield miner.createBlock();
var now = network.now() + 3 * 60 * 60;
attempt.block.ts = now;
assert.equal(yield addBlock(attempt), 'time-too-new');
}));
it('should fail to connect bad locktime', co(function* () {
var attempt = yield miner.createBlock();
var tx = yield wallet.send({ locktime: 100000 });
attempt.block.txs.push(tx.toTX());
attempt.refresh();
assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal');
}));
it('should fail to connect bad cb height', co(function* () {
var bip34height = network.block.bip34height;
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var input = tx.inputs[0];
input.script.set(0, new BN(10));
input.script.compile();
attempt.refresh();
try {
network.block.bip34height = 0;
assert.equal(yield addBlock(attempt), 'bad-cb-height');
} finally {
network.block.bip34height = bip34height;
}
}));
it('should fail to connect bad witness nonce size', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var input = tx.inputs[0];
input.witness.set(0, new Buffer(33));
input.witness.compile();
assert.equal(yield addBlock(attempt), 'bad-witness-nonce-size');
}));
it('should fail to connect bad witness nonce', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var input = tx.inputs[0];
input.witness.set(0, encoding.ONE_HASH);
input.witness.compile();
assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match');
}));
it('should fail to connect bad witness commitment', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var output = tx.outputs[1];
var commit;
assert(output.script.isCommitment());
commit = util.copy(output.script.get(1));
commit.fill(0, 10);
output.script.set(1, commit);
output.script.compile();
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match');
}));
it('should fail to connect unexpected witness', co(function* () {
var attempt = yield miner.createBlock();
var tx = attempt.block.txs[0];
var output = tx.outputs[1];
assert(output.script.isCommitment());
tx.outputs.pop();
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'unexpected-witness');
}));
it('should add wit addrs to miner', co(function* () {
miner.addresses.length = 0;
miner.addAddress(wwallet.getReceive());
assert.equal(wwallet.getReceive().getType(), 'witnesspubkeyhash');
}));
it('should mine 2000 witness blocks', co(function* () {
var i, block;
for (i = 0; i < 2001; i++) {
block = yield miner.mineBlock();
assert(block);
assert(yield chain.add(block));
}
assert.equal(chain.height, 2636);
}));
it('should mine a witness tx', co(function* () {
var block = yield chain.db.getBlock(chain.height - 2000);
var cb = block.txs[0];
var mtx = new MTX();
var attempt;
mtx.addTX(cb, 0);
mtx.addOutput(wwallet.getAddress(), 1000);
wwallet.sign(mtx);
attempt = yield miner.createBlock();
attempt.addTX(mtx.toTX(), mtx.view);
block = yield attempt.mineAsync();
assert(yield chain.add(block));
}));
it('should mine fail to connect too much weight', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var mtx = new MTX();
var i, j, block, cb;
for (i = start; i <= end; i++) {
block = yield chain.db.getBlock(i);
cb = block.txs[0];
mtx = new MTX();
mtx.addTX(cb, 0);
for (j = 0; j < 16; j++)
mtx.addOutput(wwallet.getAddress(), 1);
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
}
attempt.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-weight');
}));
it('should mine fail to connect too much size', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var mtx = new MTX();
var i, j, block, cb;
for (i = start; i <= end; i++) {
block = yield chain.db.getBlock(i);
cb = block.txs[0];
mtx = new MTX();
mtx.addTX(cb, 0);
for (j = 0; j < 20; j++)
mtx.addOutput(wwallet.getAddress(), 1);
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
}
attempt.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-length');
}));
it('should mine a big block', co(function* () {
var start = chain.height - 2000;
var end = chain.height - 200;
var attempt = yield miner.createBlock();
var mtx = new MTX();
var i, j, block, cb;
for (i = start; i <= end; i++) {
block = yield chain.db.getBlock(i);
cb = block.txs[0];
mtx = new MTX();
mtx.addTX(cb, 0);
for (j = 0; j < 15; j++)
mtx.addOutput(wwallet.getAddress(), 1);
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
}
attempt.refresh();
assert.equal(yield addBlock(attempt), 'OK');
}));
it('should fail to connect bad versions', co(function* () {
var i, attempt;
for (i = 0; i <= 3; i++) {
attempt = yield miner.createBlock();
attempt.block.version = i;
assert.equal(yield addBlock(attempt), 'bad-version');
}
}));
it('should fail to connect bad amount', co(function* () {
var attempt = yield miner.createBlock();
attempt.block.txs[0].outputs[0].value += 1;
attempt.updateMerkle();
assert.equal(yield addBlock(attempt), 'bad-cb-amount');
}));
it('should fail to connect premature cb spend', co(function* () {
var attempt = yield miner.createBlock();
var block = yield chain.db.getBlock(chain.height - 98);
var cb = block.txs[0];
var mtx = new MTX();
mtx.addTX(cb, 0);
mtx.addOutput(wwallet.getAddress(), 1);
wwallet.sign(mtx);
attempt.addTX(mtx.toTX(), mtx.view);
assert.equal(yield addBlock(attempt),
'bad-txns-premature-spend-of-coinbase');
}));
it('should fail to connect vout belowout', co(function* () {
var attempt = yield miner.createBlock();
var block = yield chain.db.getBlock(chain.height - 99);
var cb = block.txs[0];
var mtx = new MTX();
mtx.addTX(cb, 0);
mtx.addOutput(wwallet.getAddress(), 1e8);
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
attempt.refresh();
assert.equal(yield addBlock(attempt),
'bad-txns-in-belowout');
}));
it('should fail to connect outtotal toolarge', co(function* () {
var attempt = yield miner.createBlock();
var block = yield chain.db.getBlock(chain.height - 99);
var cb = block.txs[0];
var mtx = new MTX();
mtx.addTX(cb, 0);
mtx.addOutput(wwallet.getAddress(), Math.floor(consensus.MAX_MONEY / 2));
mtx.addOutput(wwallet.getAddress(), Math.floor(consensus.MAX_MONEY / 2));
mtx.addOutput(wwallet.getAddress(), Math.floor(consensus.MAX_MONEY / 2));
wwallet.sign(mtx);
attempt.block.txs.push(mtx.toTX());
attempt.refresh();
assert.equal(yield addBlock(attempt),
'bad-txns-txouttotal-toolarge');
}));
it('should mine 111 multisig blocks', co(function* () {
var i, j, script, attempt, cb, output, val, block;
script = new Script();
script.push(new BN(20));
for (i = 0; i < 20; i++)
script.push(encoding.ZERO_KEY);
script.push(new BN(20));
script.push(opcodes.OP_CHECKMULTISIG);
script.compile();
script = Script.fromScripthash(script.hash160());
for (i = 0; i < 111; i++) {
attempt = yield miner.createBlock();
cb = attempt.block.txs[0];
val = cb.outputs[0].value;
cb.outputs[0].value = 0;
for (j = 0; j < Math.min(100, val); j++) {
output = new Output();
output.script = script.clone();
output.value = 1;
cb.outputs.push(output);
}
attempt.updateMerkle();
block = yield attempt.mineAsync();
assert(yield chain.add(block));
}
assert.equal(chain.height, 2749);
}));
it('should fail to connect too many sigops', co(function* () {
var start = chain.height - 110;
var end = chain.height - 100;
var attempt = yield miner.createBlock();
var i, j, mtx, script, block, cb;
script = new Script();
script.push(new BN(20));
for (i = 0; i < 20; i++)
script.push(encoding.ZERO_KEY);
script.push(new BN(20));
script.push(opcodes.OP_CHECKMULTISIG);
script.compile();
for (i = start; i <= end; i++) {
block = yield chain.db.getBlock(i);
cb = block.txs[0];
if (cb.outputs.length === 2)
continue;
mtx = new MTX();
for (j = 2; j < cb.outputs.length; j++) {
mtx.addTX(cb, j);
mtx.inputs[j - 2].script = new Script([script.toRaw()]);
}
mtx.addOutput(wwallet.getAddress(), 1);
attempt.block.txs.push(mtx.toTX());
}
attempt.refresh();
assert.equal(yield addBlock(attempt), 'bad-blk-sigops');
}));
it('should cleanup', co(function* () {
yield miner.close();
yield chain.close();
}));
});