test: node/chain tests. add memwallet.

This commit is contained in:
Christopher Jeffrey 2017-01-30 18:37:28 -08:00
parent 95fab44015
commit df5228c849
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 890 additions and 49 deletions

View File

@ -6,21 +6,17 @@ 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 Chain = require('../lib/blockchain/chain');
var Miner = require('../lib/mining/miner');
var MTX = require('../lib/primitives/mtx');
// var Client = require('../lib/wallet/client');
var MemWallet = require('./util/memwallet');
var util = require('../lib/utils/util');
describe('Chain', function() {
var node = new FullNode({ db: 'memory', apiKey: 'foo', network: 'regtest' });
var chain = node.chain;
var walletdb = node.walletdb;
var miner = node.miner;
var wallet, tip1, tip2, cb1, cb2, mineBlock;
// walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
walletdb.options.resolution = false;
node.on('error', function() {});
var chain = new Chain({ db: 'memory', network: 'regtest' });
var miner = new Miner({ chain: chain });
var wallet = new MemWallet({ network: 'regtest' });
var tip1, tip2, cb1, cb2, mineBlock;
this.timeout(5000);
@ -40,21 +36,28 @@ describe('Chain', function() {
rtx.setLocktime(chain.height);
yield wallet.sign(rtx);
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* () {
miner.mempool = null;
consensus.COINBASE_MATURITY = 0;
yield node.open();
yield chain.open();
yield miner.open();
}));
it('should open walletdb', co(function* () {
wallet = yield walletdb.create();
it('should open wallet', co(function* () {
miner.addresses.length = 0;
miner.addAddress(wallet.getReceive());
}));
@ -100,19 +103,15 @@ describe('Chain', function() {
});
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);
assert.equal(wallet.balance, 550 * 1e8);
//assert.equal(wallet.unconfirmed, 550 * 1e8);
//assert.equal(wallet.confirmed, 550 * 1e8);
}));
it('should handle a reorg', co(function* () {
var entry, block, forked;
assert.equal(walletdb.state.height, chain.height);
// assert.equal(wallet.height, chain.height);
assert.equal(chain.height, 11);
entry = yield chain.db.getEntry(tip2.hash);
@ -141,13 +140,9 @@ describe('Chain', function() {
});
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);
assert.equal(wallet.balance, 600 * 1e8);
// assert.equal(wallet.unconfirmed, 1100 * 1e8);
// assert.equal(wallet.confirmed, 600 * 1e8);
}));
it('should check main chain', co(function* () {
@ -225,21 +220,20 @@ describe('Chain', function() {
}));
it('should get balance', co(function* () {
var balance, txs;
var txs;
yield co.timeout(100);
assert.equal(wallet.balance, 750 * 1e8);
// assert.equal(wallet.unconfirmed, 1250 * 1e8);
// assert.equal(wallet.confirmed, 750 * 1e8);
balance = yield wallet.getBalance();
assert.equal(balance.unconfirmed, 1250 * 1e8);
assert.equal(balance.confirmed, 750 * 1e8);
assert(wallet.receiveDepth >= 7);
assert(wallet.changeDepth >= 6);
assert(wallet.account.receiveDepth >= 7);
assert(wallet.account.changeDepth >= 6);
// assert.equal(wallet.height, chain.height);
assert.equal(walletdb.state.height, chain.height);
txs = yield wallet.getHistory();
assert.equal(txs.length, 45);
// txs = wallet.getHistory();
// assert.equal(txs.length, 45);
assert.equal(wallet.txs, 26);
}));
it('should get tips and remove chains', co(function* () {
@ -259,7 +253,7 @@ describe('Chain', function() {
it('should rescan for transactions', co(function* () {
var total = 0;
yield chain.db.scan(0, walletdb.filter, function(block, txs) {
yield chain.db.scan(0, wallet.filter, function(block, txs) {
total += txs.length;
return Promise.resolve();
});
@ -324,7 +318,7 @@ describe('Chain', function() {
redeemer.setLocktime(chain.height);
yield wallet.sign(redeemer);
wallet.sign(redeemer);
attempt.addTX(redeemer.toTX(), redeemer.view);
@ -439,13 +433,13 @@ describe('Chain', function() {
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 have correct wallet balance', co(function* () {
assert.equal(wallet.balance, 1289250000000);
}));
it('should cleanup', co(function* () {
consensus.COINBASE_MATURITY = 100;
yield node.close();
yield miner.close();
yield chain.close();
}));
});

451
test/node-test.js Normal file
View File

@ -0,0 +1,451 @@
'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 Client = require('../lib/wallet/client');
describe('Node', function() {
var node = new FullNode({ db: 'memory', apiKey: 'foo', network: 'regtest' });
var chain = node.chain;
var walletdb = node.walletdb;
var miner = node.miner;
var wallet, tip1, tip2, cb1, cb2, mineBlock;
// walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
walletdb.options.resolution = false;
node.on('error', function() {});
this.timeout(5000);
mineBlock = co(function* mineBlock(tip, tx) {
var attempt = yield miner.createBlock(tip);
var rtx;
if (!tx)
return yield attempt.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);
attempt.addTX(rtx.toTX(), rtx.view);
return yield attempt.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 attempt = yield miner.createBlock();
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);
attempt.addTX(redeemer.toTX(), redeemer.view);
return yield attempt.mineAsync();
});
it('should test csv', co(function* () {
var tx = (yield chain.db.getBlock(chain.height)).txs[0];
var block = yield mineCSV(tx);
var csv, attempt, 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);
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.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, attempt, 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);
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.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, attempt, 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);
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.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 cleanup', co(function* () {
consensus.COINBASE_MATURITY = 100;
yield node.close();
}));
});

396
test/util/memwallet.js Normal file
View File

@ -0,0 +1,396 @@
/*!
* memwallet.js - in-memory wallet object for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var Network = require('../../lib/protocol/network');
var util = require('../../lib/utils/util');
var MTX = require('../../lib/primitives/mtx');
var HD = require('../../lib/hd/hd');
var Bloom = require('../../lib/utils/bloom');
var KeyRing = require('../../lib/primitives/keyring');
var Outpoint = require('../../lib/primitives/outpoint');
var Coin = require('../../lib/primitives/coin');
function MemWallet(options) {
if (!(this instanceof MemWallet))
return new MemWallet(options);
this.network = Network.primary;
this.master = null;
this.key = null;
this.account = 0;
this.receiveDepth = 1;
this.changeDepth = 1;
this.receive = null;
this.change = null;
this.coins = {};
this.undo = {};
this.paths = {};
this.balance = 0;
this.txs = 0;
this.filter = Bloom.fromRate(1000000, 0.001, -1);
if (options)
this.fromOptions(options);
this.init();
}
MemWallet.prototype.fromOptions = function fromOptions(options) {
if (options.network != null) {
assert(options.network);
this.network = Network.get(options.network);
}
if (options.master != null) {
assert(options.master);
this.master = HD.PrivateKey.fromOptions(options.master, this.network);
}
if (options.key != null) {
assert(HD.isPrivate(options.key));
this.key = options.key;
}
if (options.account != null) {
assert(typeof options.account === 'number');
this.account = options.account;
}
if (options.receiveDepth != null) {
assert(typeof options.receiveDepth === 'number');
this.receiveDepth = options.receiveDepth;
}
if (options.changeDepth != null) {
assert(typeof options.changeDepth === 'number');
this.changeDepth = options.changeDepth;
}
return this;
};
MemWallet.prototype.init = function init() {
var i;
if (!this.master)
this.master = HD.PrivateKey.fromMnemonic(null, this.network);
if (!this.key)
this.key = this.master.deriveAccount44(this.account);
i = this.receiveDepth;
while (i--)
this.createReceive();
i = this.changeDepth;
while (i--)
this.createChange();
};
MemWallet.prototype.createReceive = function createReceive() {
var index = this.receiveDepth++;
var key = this.deriveReceive(index);
var hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths[hash] = new Path(hash, 0, index);
this.receive = key;
return key;
};
MemWallet.prototype.createChange = function createChange() {
var index = this.changeDepth++;
var key = this.deriveChange(index);
var hash = key.getHash('hex');
this.filter.add(hash, 'hex');
this.paths[hash] = new Path(hash, 1, index);
this.change = key;
return key;
};
MemWallet.prototype.deriveReceive = function deriveReceive(index) {
return this.deriveKey(0, index);
};
MemWallet.prototype.deriveChange = function deriveChange(index) {
return this.deriveKey(1, index);
};
MemWallet.prototype.derivePath = function derivePath(path) {
return this.deriveKey(path.branch, path.index);
};
MemWallet.prototype.deriveKey = function deriveKey(branch, index) {
var key = this.master.deriveAccount44(this.account);
key = key.derive(branch).derive(index);
return new KeyRing({
network: this.network,
privateKey: key.privateKey
});
};
MemWallet.prototype.getKey = function getKey(hash) {
var path = this.paths[hash];
if (!path)
return;
return this.derivePath(path);
};
MemWallet.prototype.getPath = function getPath(hash) {
return this.paths[hash];
};
MemWallet.prototype.getCoin = function getCoin(key) {
return this.coins[key];
};
MemWallet.prototype.getUndo = function getUndo(key) {
return this.undo[key];
};
MemWallet.prototype.addCoin = function addCoin(coin) {
var op = Outpoint(coin.hash, coin.index);
var key = op.toKey();
this.filter.add(op.toRaw());
delete this.undo[key];
this.coins[key] = coin;
this.balance += coin.value;
};
MemWallet.prototype.removeCoin = function removeCoin(key) {
var coin = this.coins[key];
if (!coin)
return;
this.undo[key] = coin;
this.balance -= coin.value;
delete this.coins[key];
};
MemWallet.prototype.getReceive = function getReceive() {
return this.receive.getAddress();
};
MemWallet.prototype.getChange = function getChange() {
return this.change.getAddress();
};
MemWallet.prototype.getCoins = function getCoins() {
return util.values(this.coins);
};
MemWallet.prototype.syncKey = function syncKey(path) {
switch (path.branch) {
case 0:
if (path.index === this.receiveDepth - 1)
this.createReceive();
break;
case 1:
if (path.index === this.changeDepth - 1)
this.createChange();
break;
default:
assert(false);
break;
}
};
MemWallet.prototype.addBlock = function addBlock(entry, txs) {
var i, tx;
for (i = 0; i < txs.length; i++) {
tx = txs[i];
this.addTX(tx, entry.height);
}
};
MemWallet.prototype.removeBlock = function removeBlock(entry, txs) {
var i, tx;
for (i = txs.length - 1; i >= 0; i--) {
tx = txs[i];
this.removeTX(tx, entry.height);
}
};
MemWallet.prototype.addTX = function addTX(tx, height) {
var result = false;
var i, op, path, addr, coin, input, output;
if (height == null)
height = -1;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
op = input.prevout.toKey();
coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
addr = output.getHash('hex');
if (!addr)
continue;
path = this.getPath(addr);
if (!path)
continue;
result = true;
coin = Coin.fromTX(tx, i, height);
this.addCoin(coin);
this.syncKey(path);
}
if (result)
this.txs++;
return result;
};
MemWallet.prototype.removeTX = function removeTX(tx, height) {
var hash = tx.hash('hex');
var result = false;
var i, op, coin, input, output;
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
op = Outpoint(hash, i).toKey();
coin = this.getCoin(op);
if (!coin)
continue;
result = true;
this.removeCoin(op);
}
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
op = input.prevout.toKey();
coin = this.getUndo(op);
if (!coin)
continue;
result = true;
this.addCoin(coin);
}
if (result)
this.txs--;
return result;
};
MemWallet.prototype.deriveInputs = function deriveInputs(mtx) {
var keys = [];
var i, input, coin, addr, path, key;
for (i = 0; i < mtx.inputs.length; i++) {
input = mtx.inputs[i];
coin = mtx.view.getOutput(input);
if (!coin)
continue;
addr = coin.getHash('hex');
if (!addr)
continue;
path = this.getPath(addr);
if (!path)
continue;
key = this.derivePath(path);
keys.push(key);
}
return keys;
};
MemWallet.prototype.fund = function fund(mtx, options) {
var coins = this.getCoins();
if (!options)
options = {};
return mtx.fund(coins, {
selection: options.selection,
round: options.round,
depth: options.depth,
hardFee: options.hardFee,
subtractFee: options.subtractFee,
changeAddress: this.getChange(),
height: -1,
rate: options.rate,
maxFee: options.maxFee
});
};
MemWallet.prototype.sign = function sign(mtx) {
var keys = this.deriveInputs(mtx);
mtx.template(keys);
mtx.sign(keys);
};
MemWallet.prototype.send = function send(options) {
var self = this;
var mtx = new MTX(options);
var tx;
this.fund(mtx, options).then(function() {
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
mtx.sortMembers();
if (options.locktime != null)
mtx.setLocktime(options.locktime);
self.sign(mtx);
if (!mtx.isSigned())
throw new Error('Cannot sign tx.');
tx = mtx.toTX();
self.addTX(tx);
}).catch(function(err) {
throw err;
});
return tx;
};
function Path(hash, branch, index) {
this.hash = hash;
this.branch = branch;
this.index = index;
}
module.exports = MemWallet;