726 lines
17 KiB
JavaScript
726 lines
17 KiB
JavaScript
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var BN = require('bn.js');
|
|
var consensus = require('../lib/protocol/consensus');
|
|
var co = require('../lib/utils/co');
|
|
var Coin = require('../lib/primitives/coin');
|
|
var Script = require('../lib/script/script');
|
|
var FullNode = require('../lib/node/fullnode');
|
|
var MTX = require('../lib/primitives/mtx');
|
|
var TX = require('../lib/primitives/tx');
|
|
var Address = require('../lib/primitives/address');
|
|
var plugin = require('../lib/wallet/plugin');
|
|
|
|
describe('Node', function() {
|
|
var node = new FullNode({
|
|
db: 'memory',
|
|
apiKey: 'foo',
|
|
network: 'regtest'
|
|
});
|
|
var chain = node.chain;
|
|
var walletdb = node.use(plugin);
|
|
var miner = node.miner;
|
|
var wallet, tip1, tip2, cb1, cb2, mineBlock;
|
|
var tx1, tx2;
|
|
|
|
node.on('error', function() {});
|
|
|
|
this.timeout(5000);
|
|
|
|
mineBlock = co(function* mineBlock(tip, tx) {
|
|
var job = yield miner.createJob(tip);
|
|
var rtx;
|
|
|
|
if (!tx)
|
|
return yield job.mineAsync();
|
|
|
|
rtx = new MTX();
|
|
|
|
rtx.addTX(tx, 0);
|
|
|
|
rtx.addOutput(wallet.getReceive(), 25 * 1e8);
|
|
rtx.addOutput(wallet.getChange(), 5 * 1e8);
|
|
|
|
rtx.setLocktime(chain.height);
|
|
|
|
yield wallet.sign(rtx);
|
|
|
|
job.addTX(rtx.toTX(), rtx.view);
|
|
job.refresh();
|
|
|
|
return yield job.mineAsync();
|
|
});
|
|
|
|
it('should open chain and miner', co(function* () {
|
|
miner.mempool = null;
|
|
consensus.COINBASE_MATURITY = 0;
|
|
yield node.open();
|
|
}));
|
|
|
|
it('should open walletdb', co(function* () {
|
|
wallet = yield walletdb.create();
|
|
miner.addresses.length = 0;
|
|
miner.addAddress(wallet.getReceive());
|
|
}));
|
|
|
|
it('should mine a block', co(function* () {
|
|
var block = yield miner.mineBlock();
|
|
assert(block);
|
|
yield chain.add(block);
|
|
}));
|
|
|
|
it('should mine competing chains', co(function* () {
|
|
var i, block1, block2;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
block1 = yield mineBlock(tip1, cb1);
|
|
cb1 = block1.txs[0];
|
|
|
|
block2 = yield mineBlock(tip2, cb2);
|
|
cb2 = block2.txs[0];
|
|
|
|
yield chain.add(block1);
|
|
|
|
yield chain.add(block2);
|
|
|
|
assert(chain.tip.hash === block1.hash('hex'));
|
|
|
|
tip1 = yield chain.db.getEntry(block1.hash('hex'));
|
|
tip2 = yield chain.db.getEntry(block2.hash('hex'));
|
|
|
|
assert(tip1);
|
|
assert(tip2);
|
|
|
|
assert(!(yield tip2.isMainChain()));
|
|
|
|
yield co.wait();
|
|
}
|
|
}));
|
|
|
|
it('should have correct chain value', function() {
|
|
assert.equal(chain.db.state.value, 55000000000);
|
|
assert.equal(chain.db.state.coin, 20);
|
|
assert.equal(chain.db.state.tx, 21);
|
|
});
|
|
|
|
it('should have correct balance', co(function* () {
|
|
var balance;
|
|
|
|
yield co.timeout(100);
|
|
|
|
balance = yield wallet.getBalance();
|
|
assert.equal(balance.unconfirmed, 550 * 1e8);
|
|
assert.equal(balance.confirmed, 550 * 1e8);
|
|
}));
|
|
|
|
it('should handle a reorg', co(function* () {
|
|
var entry, block, forked;
|
|
|
|
assert.equal(walletdb.state.height, chain.height);
|
|
assert.equal(chain.height, 11);
|
|
|
|
entry = yield chain.db.getEntry(tip2.hash);
|
|
assert(entry);
|
|
assert(chain.height === entry.height);
|
|
|
|
block = yield miner.mineBlock(entry);
|
|
assert(block);
|
|
|
|
forked = false;
|
|
chain.once('reorganize', function() {
|
|
forked = true;
|
|
});
|
|
|
|
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, 60000000000);
|
|
assert.equal(chain.db.state.coin, 21);
|
|
assert.equal(chain.db.state.tx, 22);
|
|
});
|
|
|
|
it('should have correct balance', co(function* () {
|
|
var balance;
|
|
|
|
yield co.timeout(100);
|
|
|
|
balance = yield wallet.getBalance();
|
|
assert.equal(balance.unconfirmed, 1100 * 1e8);
|
|
assert.equal(balance.confirmed, 600 * 1e8);
|
|
}));
|
|
|
|
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 mineBlock(null, cb2);
|
|
var entry, result;
|
|
|
|
yield chain.add(block);
|
|
|
|
entry = yield chain.db.getEntry(block.hash('hex'));
|
|
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 block = yield mineBlock(null, cb2);
|
|
var tip = chain.tip;
|
|
var err;
|
|
|
|
try {
|
|
yield chain.add(block);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.equal(err.reason, 'bad-txns-inputs-missingorspent');
|
|
assert(chain.tip === tip);
|
|
}));
|
|
|
|
it('should fail to mine a block with coins on an alternate chain', co(function* () {
|
|
var block = yield mineBlock(null, cb1);
|
|
var tip = chain.tip;
|
|
var err;
|
|
|
|
try {
|
|
yield chain.add(block);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.equal(err.reason, 'bad-txns-inputs-missingorspent');
|
|
assert(chain.tip === tip);
|
|
}));
|
|
|
|
it('should have correct chain value', function() {
|
|
assert.equal(chain.db.state.value, 65000000000);
|
|
assert.equal(chain.db.state.coin, 23);
|
|
assert.equal(chain.db.state.tx, 24);
|
|
});
|
|
|
|
it('should get coin', co(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 = Coin.fromTX(tx, 1, chain.height);
|
|
|
|
coin = yield chain.db.getCoin(tx.hash('hex'), 1);
|
|
|
|
assert.deepEqual(coin.toRaw(), output.toRaw());
|
|
}));
|
|
|
|
it('should get balance', co(function* () {
|
|
var balance, txs;
|
|
|
|
yield co.timeout(100);
|
|
|
|
balance = yield wallet.getBalance();
|
|
assert.equal(balance.unconfirmed, 1250 * 1e8);
|
|
assert.equal(balance.confirmed, 750 * 1e8);
|
|
|
|
assert(wallet.account.receiveDepth >= 7);
|
|
assert(wallet.account.changeDepth >= 6);
|
|
|
|
assert.equal(walletdb.state.height, chain.height);
|
|
|
|
txs = yield wallet.getHistory();
|
|
assert.equal(txs.length, 45);
|
|
}));
|
|
|
|
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, walletdb.filter, function(block, txs) {
|
|
total += txs.length;
|
|
return Promise.resolve();
|
|
});
|
|
|
|
assert.equal(total, 26);
|
|
}));
|
|
|
|
it('should activate csv', co(function* () {
|
|
var deployments = chain.network.deployments;
|
|
var i, block, prev, state, cache;
|
|
|
|
prev = yield chain.tip.getPrevious();
|
|
state = yield chain.getState(prev, deployments.csv);
|
|
assert(state === 0);
|
|
|
|
for (i = 0; i < 417; i++) {
|
|
block = yield miner.mineBlock();
|
|
yield chain.add(block);
|
|
switch (chain.height) {
|
|
case 144:
|
|
prev = yield chain.tip.getPrevious();
|
|
state = yield chain.getState(prev, deployments.csv);
|
|
assert(state === 1);
|
|
break;
|
|
case 288:
|
|
prev = yield chain.tip.getPrevious();
|
|
state = yield chain.getState(prev, deployments.csv);
|
|
assert(state === 2);
|
|
break;
|
|
case 432:
|
|
prev = yield chain.tip.getPrevious();
|
|
state = yield chain.getState(prev, deployments.csv);
|
|
assert(state === 3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(chain.height === 432);
|
|
assert(chain.state.hasCSV());
|
|
|
|
cache = yield chain.db.getStateCache();
|
|
assert.deepEqual(cache, chain.db.stateCache);
|
|
assert.equal(chain.db.stateCache.updates.length, 0);
|
|
assert(yield chain.db.verifyDeployments());
|
|
}));
|
|
|
|
var mineCSV = co(function* mineCSV(tx) {
|
|
var job = yield miner.createJob();
|
|
var redeemer;
|
|
|
|
redeemer = new MTX();
|
|
|
|
redeemer.addOutput({
|
|
script: [
|
|
Script.array(new BN(1)),
|
|
Script.opcodes.OP_CHECKSEQUENCEVERIFY
|
|
],
|
|
value: 10 * 1e8
|
|
});
|
|
|
|
redeemer.addTX(tx, 0);
|
|
|
|
redeemer.setLocktime(chain.height);
|
|
|
|
yield wallet.sign(redeemer);
|
|
|
|
job.addTX(redeemer.toTX(), redeemer.view);
|
|
job.refresh();
|
|
|
|
return yield job.mineAsync();
|
|
});
|
|
|
|
it('should test csv', co(function* () {
|
|
var tx = (yield chain.db.getBlock(chain.height)).txs[0];
|
|
var block = yield mineCSV(tx);
|
|
var csv, job, redeemer;
|
|
|
|
yield chain.add(block);
|
|
|
|
csv = block.txs[1];
|
|
|
|
redeemer = new MTX();
|
|
|
|
redeemer.addOutput({
|
|
script: [
|
|
Script.array(new BN(2)),
|
|
Script.opcodes.OP_CHECKSEQUENCEVERIFY
|
|
],
|
|
value: 10 * 1e8
|
|
});
|
|
|
|
redeemer.addTX(csv, 0);
|
|
redeemer.setSequence(0, 1, false);
|
|
|
|
job = yield miner.createJob();
|
|
|
|
job.addTX(redeemer.toTX(), redeemer.view);
|
|
job.refresh();
|
|
|
|
block = yield job.mineAsync();
|
|
|
|
yield chain.add(block);
|
|
}));
|
|
|
|
it('should fail csv with bad sequence', co(function* () {
|
|
var csv = (yield chain.db.getBlock(chain.height)).txs[1];
|
|
var block, job, redeemer, err;
|
|
|
|
redeemer = new MTX();
|
|
|
|
redeemer.addOutput({
|
|
script: [
|
|
Script.array(new BN(1)),
|
|
Script.opcodes.OP_CHECKSEQUENCEVERIFY
|
|
],
|
|
value: 10 * 1e8
|
|
});
|
|
|
|
redeemer.addTX(csv, 0);
|
|
redeemer.setSequence(0, 1, false);
|
|
|
|
job = yield miner.createJob();
|
|
|
|
job.addTX(redeemer.toTX(), redeemer.view);
|
|
job.refresh();
|
|
|
|
block = yield job.mineAsync();
|
|
|
|
try {
|
|
yield chain.add(block);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert(err.reason, 'mandatory-script-verify-flag-failed');
|
|
}));
|
|
|
|
it('should mine a block', co(function* () {
|
|
var block = yield miner.mineBlock();
|
|
assert(block);
|
|
yield chain.add(block);
|
|
}));
|
|
|
|
it('should fail csv lock checks', co(function* () {
|
|
var tx = (yield chain.db.getBlock(chain.height)).txs[0];
|
|
var block = yield mineCSV(tx);
|
|
var csv, job, redeemer, err;
|
|
|
|
yield chain.add(block);
|
|
|
|
csv = block.txs[1];
|
|
|
|
redeemer = new MTX();
|
|
|
|
redeemer.addOutput({
|
|
script: [
|
|
Script.array(new BN(2)),
|
|
Script.opcodes.OP_CHECKSEQUENCEVERIFY
|
|
],
|
|
value: 10 * 1e8
|
|
});
|
|
|
|
redeemer.addTX(csv, 0);
|
|
redeemer.setSequence(0, 2, false);
|
|
|
|
job = yield miner.createJob();
|
|
|
|
job.addTX(redeemer.toTX(), redeemer.view);
|
|
job.refresh();
|
|
|
|
block = yield job.mineAsync();
|
|
|
|
try {
|
|
yield chain.add(block);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err);
|
|
assert.equal(err.reason, 'bad-txns-nonfinal');
|
|
}));
|
|
|
|
it('should rescan for transactions', co(function* () {
|
|
yield walletdb.rescan(0);
|
|
assert.equal(wallet.txdb.state.confirmed, 1289250000000);
|
|
}));
|
|
|
|
it('should reset miner mempool', co(function* () {
|
|
miner.mempool = node.mempool;
|
|
}));
|
|
|
|
it('should not get a block template', co(function* () {
|
|
var json = yield node.rpc.call({
|
|
method: 'getblocktemplate'
|
|
}, {});
|
|
assert(json.error);
|
|
assert.equal(json.error.code, -8);
|
|
}));
|
|
|
|
it('should get a block template', co(function* () {
|
|
var json;
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'getblocktemplate',
|
|
params: [
|
|
{rules: ['segwit']}
|
|
],
|
|
id: '1'
|
|
}, {});
|
|
|
|
assert(typeof json.result.curtime === 'number');
|
|
assert(typeof json.result.mintime === 'number');
|
|
assert(typeof json.result.maxtime === 'number');
|
|
assert(typeof json.result.expires === 'number');
|
|
|
|
assert.deepStrictEqual(json, {
|
|
result: {
|
|
capabilities: [ 'proposal' ],
|
|
mutable: [ 'time', 'transactions', 'prevblock' ],
|
|
version: 536870912,
|
|
rules: [ 'csv', '!segwit', 'testdummy' ],
|
|
vbavailable: {},
|
|
vbrequired: 0,
|
|
height: 437,
|
|
previousblockhash: node.chain.tip.rhash(),
|
|
target: '7fffff0000000000000000000000000000000000000000000000000000000000',
|
|
bits: '207fffff',
|
|
noncerange: '00000000ffffffff',
|
|
curtime: json.result.curtime,
|
|
mintime: json.result.mintime,
|
|
maxtime: json.result.maxtime,
|
|
expires: json.result.expires,
|
|
sigoplimit: 80000,
|
|
sizelimit: 4000000,
|
|
weightlimit: 4000000,
|
|
longpollid: node.chain.tip.rhash() + '0000000000',
|
|
submitold: false,
|
|
coinbaseaux: { flags: '6d696e65642062792062636f696e' },
|
|
coinbasevalue: 1250000000,
|
|
coinbasetxn: undefined,
|
|
default_witness_commitment: '6a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9',
|
|
transactions: []
|
|
},
|
|
error: null,
|
|
id: '1'
|
|
});
|
|
}));
|
|
|
|
it('should send a block template proposal', co(function* () {
|
|
var attempt = yield node.miner.createBlock();
|
|
var block, hex, json;
|
|
|
|
attempt.refresh();
|
|
|
|
block = attempt.toBlock();
|
|
|
|
hex = block.toRaw().toString('hex');
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'getblocktemplate',
|
|
params: [{
|
|
mode: 'proposal',
|
|
data: hex
|
|
}]
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
assert(json.result === null);
|
|
}));
|
|
|
|
it('should submit a block', co(function* () {
|
|
var block = yield node.miner.mineBlock();
|
|
var hex = block.toRaw().toString('hex');
|
|
var json;
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'submitblock',
|
|
params: [hex]
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
assert(json.result === null);
|
|
assert.equal(node.chain.tip.hash, block.hash('hex'));
|
|
}));
|
|
|
|
it('should validate an address', co(function* () {
|
|
var addr = new Address();
|
|
var json;
|
|
|
|
addr.network = node.network;
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'validateaddress',
|
|
params: [addr.toBase58()]
|
|
}, {});
|
|
|
|
assert.deepStrictEqual(json.result, {
|
|
isvalid: true,
|
|
address: addr.toBase58(),
|
|
scriptPubKey: Script.fromAddress(addr).toJSON(),
|
|
ismine: false,
|
|
iswatchonly: false
|
|
});
|
|
}));
|
|
|
|
it('should add transaction to mempool', co(function* () {
|
|
var mtx, tx, missing;
|
|
|
|
mtx = yield wallet.createTX({
|
|
rate: 100000,
|
|
outputs: [{
|
|
value: 100000,
|
|
address: wallet.getAddress()
|
|
}]
|
|
});
|
|
|
|
yield wallet.sign(mtx);
|
|
|
|
assert(mtx.isSigned());
|
|
|
|
tx1 = mtx;
|
|
tx = mtx.toTX();
|
|
|
|
yield wallet.db.addTX(tx);
|
|
|
|
missing = yield node.mempool.addTX(tx);
|
|
assert(!missing || missing.length === 0);
|
|
|
|
assert.equal(node.mempool.totalTX, 1);
|
|
}));
|
|
|
|
it('should add lesser transaction to mempool', co(function* () {
|
|
var mtx, tx, missing;
|
|
|
|
mtx = yield wallet.createTX({
|
|
rate: 1000,
|
|
outputs: [{
|
|
value: 50000,
|
|
address: wallet.getAddress()
|
|
}]
|
|
});
|
|
|
|
yield wallet.sign(mtx);
|
|
|
|
assert(mtx.isSigned());
|
|
|
|
tx2 = mtx;
|
|
tx = mtx.toTX();
|
|
|
|
yield wallet.db.addTX(tx);
|
|
|
|
missing = yield node.mempool.addTX(tx);
|
|
assert(!missing || missing.length === 0);
|
|
|
|
assert.equal(node.mempool.totalTX, 2);
|
|
}));
|
|
|
|
it('should get a block template', co(function* () {
|
|
var fees = 0;
|
|
var weight = 0;
|
|
var i, item, json, result;
|
|
|
|
node.rpc.refreshBlock();
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'getblocktemplate',
|
|
params: [
|
|
{rules: ['segwit']}
|
|
],
|
|
id: '1'
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
assert(json.result);
|
|
|
|
result = json.result;
|
|
|
|
for (i = 0; i < result.transactions.length; i++) {
|
|
item = result.transactions[i];
|
|
fees += item.fee;
|
|
weight += item.weight;
|
|
}
|
|
|
|
assert.equal(result.transactions.length, 2);
|
|
assert.equal(fees, tx1.getFee() + tx2.getFee());
|
|
assert.equal(weight, tx1.getWeight() + tx2.getWeight());
|
|
assert.equal(result.transactions[0].hash, tx1.txid());
|
|
assert.equal(result.transactions[1].hash, tx2.txid());
|
|
assert.equal(result.coinbasevalue, 125e7 + fees);
|
|
}));
|
|
|
|
it('should get raw transaction', co(function* () {
|
|
var json, tx;
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'getrawtransaction',
|
|
params: [tx2.txid()],
|
|
id: '1'
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
tx = TX.fromRaw(json.result, 'hex');
|
|
assert.equal(tx.txid(), tx2.txid());
|
|
}));
|
|
|
|
it('should prioritise transaction', co(function* () {
|
|
var json;
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'prioritisetransaction',
|
|
params: [tx2.txid(), 0, 10000000],
|
|
id: '1'
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
assert(json.result === true);
|
|
}));
|
|
|
|
it('should get a block template', co(function* () {
|
|
var fees = 0;
|
|
var weight = 0;
|
|
var i, item, json, result;
|
|
|
|
node.rpc.refreshBlock();
|
|
|
|
json = yield node.rpc.call({
|
|
method: 'getblocktemplate',
|
|
params: [
|
|
{rules: ['segwit']}
|
|
],
|
|
id: '1'
|
|
}, {});
|
|
|
|
assert(!json.error);
|
|
assert(json.result);
|
|
|
|
result = json.result;
|
|
|
|
for (i = 0; i < result.transactions.length; i++) {
|
|
item = result.transactions[i];
|
|
fees += item.fee;
|
|
weight += item.weight;
|
|
}
|
|
|
|
assert.equal(result.transactions.length, 2);
|
|
assert.equal(fees, tx1.getFee() + tx2.getFee());
|
|
assert.equal(weight, tx1.getWeight() + tx2.getWeight());
|
|
assert.equal(result.transactions[0].hash, tx2.txid());
|
|
assert.equal(result.transactions[1].hash, tx1.txid());
|
|
assert.equal(result.coinbasevalue, 125e7 + fees);
|
|
}));
|
|
|
|
it('should cleanup', co(function* () {
|
|
consensus.COINBASE_MATURITY = 100;
|
|
yield node.close();
|
|
}));
|
|
});
|