add tests
This commit is contained in:
parent
f9fef7a07f
commit
fa572237a6
|
@ -1,58 +0,0 @@
|
|||
var async = require('async');
|
||||
|
||||
var d = Date.now();
|
||||
|
||||
var i = 0;
|
||||
|
||||
function fn1(next) {
|
||||
console.log('fn1');
|
||||
setImmediate(function() {
|
||||
next(null, [])
|
||||
});
|
||||
}
|
||||
|
||||
function fn2(next) {
|
||||
console.log('fn2');
|
||||
setImmediate(function() {
|
||||
next(null, [])
|
||||
});
|
||||
}
|
||||
|
||||
async.whilst(
|
||||
function() {
|
||||
return i < 100000;
|
||||
},
|
||||
function(callback) {
|
||||
i++;
|
||||
async.series(
|
||||
[
|
||||
fn1.bind(this),
|
||||
fn2.bind(this)
|
||||
],
|
||||
callback
|
||||
);
|
||||
},
|
||||
function() {
|
||||
console.log('Milliseconds', Date.now() - d);
|
||||
}
|
||||
);
|
||||
|
||||
/*async.times(100000, function(n, next) {
|
||||
async.series(
|
||||
[
|
||||
function fn1(next) {
|
||||
setImmediate(function() {
|
||||
next(null, []);
|
||||
});
|
||||
},
|
||||
function fn2(next) {
|
||||
setImmediate(function() {
|
||||
next(null, []);
|
||||
});
|
||||
}
|
||||
],
|
||||
next
|
||||
);
|
||||
}, function() {
|
||||
console.log('Milliseconds', Date.now() - d);
|
||||
});*/
|
|
@ -111,6 +111,11 @@ Node.prototype._loadDB = function(config) {
|
|||
// Other modules can inherit from our DB and replace it with their own
|
||||
DB = config.DB;
|
||||
}
|
||||
|
||||
if(!config.db) {
|
||||
config.db = {};
|
||||
}
|
||||
|
||||
config.db.network = this.network;
|
||||
|
||||
this.db = new DB(config.db);
|
||||
|
|
10
package.json
10
package.json
|
@ -30,7 +30,9 @@
|
|||
"preinstall": "./bin/build-libbitcoind",
|
||||
"install": "./bin/build-bindings",
|
||||
"start": "export LD_LIBRARY_PATH=`./platform/os.sh osdir` && node example",
|
||||
"debug_install": "./bin/build-libbitcoind debug && ./bin/build-bindings debug"
|
||||
"debug_install": "./bin/build-libbitcoind debug && ./bin/build-bindings debug",
|
||||
"test": "NODE_ENV=test mocha --recursive",
|
||||
"coverage": "istanbul cover _mocha -- --recursive"
|
||||
},
|
||||
"tags": [
|
||||
"bitcoin",
|
||||
|
@ -44,7 +46,8 @@
|
|||
"chainlib": "^0.1.1",
|
||||
"libbitcoinconsensus": "^0.0.3",
|
||||
"errno": "^0.1.2",
|
||||
"async": "1.3.0"
|
||||
"async": "1.3.0",
|
||||
"memdown": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"benchmark": "1.0.0",
|
||||
|
@ -52,6 +55,7 @@
|
|||
"bitcore": "^0.12.12",
|
||||
"chai": "^3.0.0",
|
||||
"mocha": "~1.16.2",
|
||||
"sinon": "^1.15.4"
|
||||
"sinon": "^1.15.4",
|
||||
"proxyquire": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
var sinon = require('sinon');
|
||||
var bitcore = require('bitcore');
|
||||
var BN = bitcore.crypto.BN;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var bitcoindjs = require('../');
|
||||
var Block = bitcoindjs.Block;
|
||||
var chainData = require('./data/pow-chain.json');
|
||||
|
||||
describe('Bitcoin Block', function() {
|
||||
|
||||
describe('@constructor', function() {
|
||||
it('set bits and nonce', function() {
|
||||
var block = new Block(chainData[1]);
|
||||
should.exist(block.bits);
|
||||
block.bits.should.equal(chainData[1].bits);
|
||||
should.exist(block.nonce);
|
||||
block.nonce.should.equal(chainData[1].nonce);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validate', function() {
|
||||
it('rejects with invalid proof of work because hash is greater', function(done) {
|
||||
var block = new Block(chainData[1]);
|
||||
block.validProofOfWork = sinon.stub().returns(false);
|
||||
block.validate({
|
||||
db: {
|
||||
getBlock: sinon.stub().callsArgWith(1, null, {})
|
||||
}
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.match(/Invalid proof of work/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('rejects with unexpected bits for chain', function(done) {
|
||||
var block = new Block(chainData[1]);
|
||||
block.validProofOfWork = sinon.stub().returns(true);
|
||||
block.validate({
|
||||
db: {
|
||||
getBlock: sinon.stub().callsArgWith(1, null, {})
|
||||
},
|
||||
getNextWorkRequired: sinon.stub().callsArgWith(1, null, 0x1d00ffff)
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.match(/Invalid proof of work, expected block bits/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validProofOfWork', function() {
|
||||
it('returns false if block hash is greater than target from bits', function() {
|
||||
var block = new Block(chainData[1]);
|
||||
var target = new BN('00000000c4769616456f587f6744a779f19a1f9056431ad03d0949458ec85ac0', 'hex');
|
||||
var valid = block.validProofOfWork({
|
||||
getTargetFromBits: sinon.stub().returns(target)
|
||||
});
|
||||
valid.should.equal(false);
|
||||
});
|
||||
|
||||
it('returns true if block hash is less than target from bits', function() {
|
||||
var block = new Block(chainData[1]);
|
||||
var target = new BN('f2345678c4769616456f587f6744a779f19a1f9056431ad03d0949458ec85ac0', 'hex');
|
||||
var valid = block.validProofOfWork({
|
||||
getTargetFromBits: sinon.stub().returns(target)
|
||||
});
|
||||
valid.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fromBuffer', function() {
|
||||
var buffer = new Buffer('010000004404c1ff5f300e5ed830b45ec9f68fbe9a0c51c4b4eaa4ce09a03ac4ddde01750000000000000000000000000000000000000000000000000000000000000000b134de547fcc071f4a020000abcdef', 'hex');
|
||||
|
||||
it('deserializes correctly', function() {
|
||||
var block = Block.fromBuffer(buffer);
|
||||
block.version.should.equal(1);
|
||||
block.prevHash.should.equal('7501deddc43aa009cea4eab4c4510c9abe8ff6c95eb430d85e0e305fffc10444');
|
||||
block.merkleRoot.should.equal(new Buffer(Array(32)).toString('hex'));
|
||||
block.timestamp.should.be.instanceof(Date);
|
||||
block.timestamp.toISOString().should.equal('2015-02-13T17:30:25.000Z');
|
||||
block.bits.should.equal(520604799);
|
||||
block.nonce.should.equal(586);
|
||||
block.data.should.deep.equal(new Buffer('abcdef', 'hex'));
|
||||
});
|
||||
it('roundtrip serialization', function() {
|
||||
var actual = Block.fromBuffer(buffer).toBuffer();
|
||||
actual.should.deep.equal(buffer);
|
||||
});
|
||||
it('set null prevHash if null hash buffer', function() {
|
||||
var blockBuffer = new Buffer('0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d4d5fd834b0100007fcc071f4a020000abcdef', 'hex');
|
||||
var block = Block.fromBuffer(blockBuffer);
|
||||
block.hasOwnProperty('prevHash').should.equal(true);
|
||||
should.equal(block.prevHash, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#headerToBufferWriter', function() {
|
||||
it('serializes correctly', function() {
|
||||
var block = new Block(chainData[1]);
|
||||
var bw = new BufferWriter();
|
||||
block.headerToBufferWriter(bw);
|
||||
bw.bufs[0].toString('hex').should.equal('01000000'); // version
|
||||
BufferReader(bw.bufs[1]).readReverse().toString('hex').should.equal(chainData[1].prevHash); // prevhash
|
||||
Number(bw.bufs[2].toString('hex')).should.equal(0); // merkle root
|
||||
should.exist(bw.bufs[3]); // time
|
||||
bw.bufs[3].length.should.equal(4);
|
||||
should.exist(bw.bufs[4]); // bits
|
||||
bw.bufs[4].length.should.equal(4);
|
||||
should.exist(bw.bufs[5]); // nonce
|
||||
bw.bufs[5].length.should.equal(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bitcoin Block', function() {
|
||||
it('should load and serialize the Bitcoin testnet genesis block correctly', function() {
|
||||
var blockBuffer = new Buffer('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000', 'hex');
|
||||
var block = Block.fromBuffer(blockBuffer);
|
||||
block.hash.should.equal('000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943');
|
||||
});
|
||||
it('should load and serialize Bitcoin testnet #1 block correctly', function() {
|
||||
var blockBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
|
||||
var block = Block.fromBuffer(blockBuffer);
|
||||
block.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
|
||||
block.toBuffer().should.deep.equal(blockBuffer);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,240 @@
|
|||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
var sinon = require('sinon');
|
||||
var async = require('async');
|
||||
var proxyquire = require('proxyquire');
|
||||
var memdown = require('memdown');
|
||||
|
||||
var bitcoindjs = require('../');
|
||||
var DB = bitcoindjs.DB;
|
||||
var Chain = bitcoindjs.Chain;
|
||||
var Block = bitcoindjs.Block;
|
||||
|
||||
var chainData = require('./data/testnet-blocks.json');
|
||||
|
||||
describe('Bitcoin Chain', function() {
|
||||
|
||||
describe('@constructor', function() {
|
||||
|
||||
it('can create a new instance with and without `new`', function() {
|
||||
var chain = new Chain();
|
||||
chain = Chain();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#_writeBlock', function() {
|
||||
it('should update hashes and call putBlock', function(done) {
|
||||
var chain = new Chain();
|
||||
chain.db = {
|
||||
putBlock: sinon.stub().callsArg(1)
|
||||
};
|
||||
chain._writeBlock({hash: 'hash', prevHash: 'prevhash'}, function(err) {
|
||||
should.not.exist(err);
|
||||
chain.db.putBlock.callCount.should.equal(1);
|
||||
chain.cache.hashes.hash.should.equal('prevhash');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_validateBlock', function() {
|
||||
it('should call the callback', function(done) {
|
||||
var chain = new Chain();
|
||||
chain._validateBlock('block', function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInterval', function() {
|
||||
|
||||
it('get default interval', function() {
|
||||
var chain = new Chain();
|
||||
chain.targetTimespan.toString(10).should.equal('1209600000');
|
||||
chain.targetSpacing.toString(10).should.equal('600000');
|
||||
chain.getDifficultyInterval().toString(10).should.equal('2016');
|
||||
});
|
||||
|
||||
it('get custom interval', function() {
|
||||
var chain = new Chain({
|
||||
targetTimespan: 30 * 60 * 1000
|
||||
});
|
||||
chain.getDifficultyInterval().toString(10).should.equal('3');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#buildGenesisBlock', function() {
|
||||
it('can handle no options', function() {
|
||||
var db = {
|
||||
buildGenesisData: sinon.stub().returns({})
|
||||
};
|
||||
var chain = new Chain({db: db});
|
||||
var block = chain.buildGenesisBlock();
|
||||
should.exist(block);
|
||||
block.should.be.instanceof(Block);
|
||||
db.buildGenesisData.calledOnce.should.equal(true);
|
||||
});
|
||||
|
||||
it('set timestamp, nonce, bits, merkleRoot and data of the genesis', function() {
|
||||
var db = {
|
||||
buildGenesisData: sinon.stub().returns({
|
||||
merkleRoot: 'merkleRoot',
|
||||
buffer: new Buffer('abcdef', 'hex')
|
||||
})
|
||||
};
|
||||
var chain = new Chain({db: db});
|
||||
var timestamp = '2015-03-20T14:46:01.118Z';
|
||||
var block = chain.buildGenesisBlock({
|
||||
timestamp: timestamp,
|
||||
nonce: 1,
|
||||
bits: 520617984
|
||||
});
|
||||
should.exist(block);
|
||||
block.should.be.instanceof(Block);
|
||||
block.timestamp.toISOString().should.equal(timestamp);
|
||||
block.nonce.should.equal(1);
|
||||
block.bits.should.equal(520617984);
|
||||
block.merkleRoot.should.equal('merkleRoot');
|
||||
block.data.should.deep.equal(new Buffer('abcdef', 'hex'));
|
||||
db.buildGenesisData.calledOnce.should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getRetargetedBits', function() {
|
||||
it('should get the correct bits', function() {
|
||||
var chain = new Chain();
|
||||
var bits = chain.getRetargetedBits(486604799, 12 * 24 * 60 * 60 * 1000);
|
||||
bits.should.equal(484142299);
|
||||
});
|
||||
it('should get the correct bits if actual timespan was really small', function() {
|
||||
var chain = new Chain();
|
||||
var bits1 = chain.getRetargetedBits(486604799, 2 * 24 * 60 * 60 * 1000);
|
||||
bits1.should.equal(473956288);
|
||||
var bits2 = chain.getRetargetedBits(486604799, 1 * 24 * 60 * 60 * 1000);
|
||||
bits2.should.equal(473956288);
|
||||
});
|
||||
it('should get the correct bits if actual timespan was really large', function() {
|
||||
var chain = new Chain();
|
||||
var bits1 = chain.getRetargetedBits(436567560, 60 * 24 * 60 * 60 * 1000);
|
||||
bits1.should.equal(437647392);
|
||||
var bits2 = chain.getRetargetedBits(436567560, 70 * 24 * 60 * 60 * 1000);
|
||||
bits2.should.equal(437647392);
|
||||
});
|
||||
it('should not give higher than max bits', function() {
|
||||
var chain = new Chain();
|
||||
var bits = chain.getRetargetedBits(486604799, 16 * 24 * 60 * 60 * 1000);
|
||||
bits.should.equal(486604799);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTargetFromBits/#getBitsFromTarget', function() {
|
||||
|
||||
var target1;
|
||||
var target2;
|
||||
var target3;
|
||||
|
||||
it('should calculate the target correctly', function() {
|
||||
var chain = new Chain();
|
||||
var target1 = chain.getTargetFromBits(0x1b0404cb);
|
||||
var expected = '00000000000404cb000000000000000000000000000000000000000000000000';
|
||||
target1.toString('hex', 32).should.equal(expected);
|
||||
});
|
||||
|
||||
it('should error if bits is too small', function() {
|
||||
var chain = new Chain();
|
||||
(function(){
|
||||
var target1 = chain.getTargetFromBits(Chain.DEFAULTS.MIN_BITS - 1);
|
||||
}).should.throw('bits is too small');
|
||||
});
|
||||
|
||||
it('should error if bits is too large', function() {
|
||||
var chain = new Chain();
|
||||
(function(){
|
||||
var target1 = chain.getTargetFromBits(Chain.DEFAULTS.MAX_BITS + 1);
|
||||
}).should.throw('bits is too big');
|
||||
});
|
||||
|
||||
it('should get the bits', function() {
|
||||
var chain = new Chain();
|
||||
var expected = '00000000000404cb000000000000000000000000000000000000000000000000';
|
||||
var bits = chain.getBitsFromTarget(expected);
|
||||
bits.should.equal(0x1b0404cb);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getDifficultyFromBits', function() {
|
||||
it('should return the correct difficulty', function() {
|
||||
var genesis = {bits: 0x1d00ffff};
|
||||
var chain = new Chain({genesis: genesis});
|
||||
|
||||
var difficulty = chain.getDifficultyFromBits(0x1818bb87);
|
||||
difficulty.toString(10).should.equal('44455415962');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getBlockWeight', function() {
|
||||
it('should return the correct block weight for normal targets', function(done) {
|
||||
var block = {bits: 0x1d00ffff};
|
||||
var db = {
|
||||
getBlock: sinon.stub().callsArgWith(1, null, block)
|
||||
};
|
||||
var chain = new Chain({db: db});
|
||||
chain.getBlockWeight(block, function(err, weight) {
|
||||
weight.toString(16).should.equal('100010001');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should correctly report an error if it happens', function(done) {
|
||||
var block = {bits: 0x1d00ffff};
|
||||
var db = {
|
||||
getBlock: sinon.stub().callsArgWith(1, new Error('fake error'))
|
||||
};
|
||||
var chain = new Chain({db: db});
|
||||
chain.getBlockWeight(block, function(err, weight) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('fake error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should correctly report an error for a null block', function(done) {
|
||||
var block = {bits: 0x1d00ffff};
|
||||
var db = {
|
||||
getBlock: sinon.stub().callsArgWith(1, null, null)
|
||||
};
|
||||
var chain = new Chain({db: db});
|
||||
chain.getBlockWeight(block, function(err, weight) {
|
||||
should.exist(err);
|
||||
err.message.should.match(/Block not found/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bitcoin POW', function() {
|
||||
it('should calculate correct difficulty for block 201600', function(done) {
|
||||
var chain = new Chain();
|
||||
var beginBlock = {
|
||||
timestamp: new Date(1348092851000),
|
||||
bits: 436591499
|
||||
};
|
||||
var lastBlock = {
|
||||
timestamp: new Date(1349227021000),
|
||||
bits: 436591499
|
||||
};
|
||||
chain.getHeightForBlock = sinon.stub().callsArgWith(1, null, 201599);
|
||||
chain.getBlockAtHeight = sinon.stub().callsArgWith(2, null, beginBlock);
|
||||
chain.getNextWorkRequired(lastBlock, function(err, bits) {
|
||||
should.not.exist(err);
|
||||
bits.should.equal(436567560);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"comment": "sends to tx[1]",
|
||||
"hex":"0100000001dee8f4266e83072e0ad258125cc5a42ac25d2d2c73e6e2e873413b3939af1605000000006b483045022100ae987d056f81d2c982b71b0406f2374c1958b24bd289d77371347e275d2a62c002205148b17173be18af4e1e73ce2b0fd600734ea77087754bdba5dc7d645b01880a01210226ab3b46f85bf32f63778c680e16ef8b3fcb51d638a7980d651bfaeae6c17752ffffffff0170820300000000001976a9142baf68e3681df183375a4f4c10306de9a5c6cc7788ac00000000"
|
||||
},
|
||||
{
|
||||
"comment": "spends from tx[0] (valid)",
|
||||
"hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a2944000000006a47304402203c2bc91a170facdc5ef4b5b94c413bc7a10f65e09b326d205f070b17aa94d67102205b684111af2a20171eb65db73e6c73f9e77e6e6f739e050bc052ed6ecc9feb4a01210365d8756a4f3fc738105cfab8d80a85189bdb4db5af83374e645b79e2aadd976effffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000"
|
||||
},
|
||||
{
|
||||
"comment": "spends from tx[0] (missing signature)",
|
||||
"hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a29440000000000ffffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000"
|
||||
}
|
||||
]
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,58 @@
|
|||
[
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": null,
|
||||
"timestamp": "2015-04-16T20:02:24.777Z",
|
||||
"bits": 520617984,
|
||||
"nonce": 0,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "bab3003201bdf327ac03735e70a5f02968bc1e8cf74cc9045ae960c074139386",
|
||||
"timestamp": "2015-04-16T20:02:26.650Z",
|
||||
"bits": 520617984,
|
||||
"nonce": 6256,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "0002cbf2807997765971f14bdd7c748e93c315c2d3af35b85c6604126c788fa8",
|
||||
"timestamp": "2015-04-16T20:02:27.885Z",
|
||||
"bits": 520617984,
|
||||
"nonce": 12232,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "0007b3fec55496a3741caa992aac55395921a965e8cec6192659f266eec39f62",
|
||||
"timestamp": "2015-04-16T20:02:28.559Z",
|
||||
"bits": 520355840,
|
||||
"nonce": 6492,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "000002de06d8fdd4d7036fc99ddc8c9b432bfa910e0968756b988e25f7f43d8e",
|
||||
"timestamp": "2015-04-16T20:02:29.593Z",
|
||||
"bits": 520355840,
|
||||
"nonce": 9502,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "00016a5c727ef18b406b16c188af017c101f5a32c2d881e92dd7aa1746b185ea",
|
||||
"timestamp": "2015-04-16T20:02:30.713Z",
|
||||
"bits": 520355840,
|
||||
"nonce": 10586,
|
||||
"data": ""
|
||||
},
|
||||
{
|
||||
"version": 1,
|
||||
"prevHash": "00023ac90a33ce9faf07f407b525f186180c902fbb2e71c48cd73ffb25a8dc5b",
|
||||
"timestamp": "22015-04-16T20:02:31.440Z",
|
||||
"bits": 520181077,
|
||||
"nonce": 6983,
|
||||
"data": ""
|
||||
}
|
||||
]
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"xprivkey": "tprv8ZgxMBicQKsPeHnrBXa8NfcpBQExTXq9gz8ijFPx6EJonZ5xiMt4kqWYRPJb5AmvNkTxV5qMTzkCUUfC4s3MQhpBcLg9AtpCAg5mqMeRqVr",
|
||||
"utxos": {
|
||||
"mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha": [
|
||||
{
|
||||
"txId" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 0,
|
||||
"address" : "mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha",
|
||||
"script" : "76a914a01e048440b4cc651df6bdc41ec47781788b4b6d88ac",
|
||||
"satoshis" : 70000
|
||||
}
|
||||
],
|
||||
"mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L": [
|
||||
{
|
||||
"txId" : "b0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 1,
|
||||
"address" : "mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L",
|
||||
"script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac",
|
||||
"satoshis" : 30000
|
||||
}
|
||||
]
|
||||
},
|
||||
"utxosAmount": {
|
||||
"mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha": [
|
||||
{
|
||||
"txId" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 0,
|
||||
"address" : "mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha",
|
||||
"script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac",
|
||||
"amount" : 0.0007
|
||||
}
|
||||
],
|
||||
"mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L": [
|
||||
{
|
||||
"txId" : "b0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 1,
|
||||
"address" : "mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L",
|
||||
"script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac",
|
||||
"amount" : 0.0003
|
||||
}
|
||||
]
|
||||
},
|
||||
"utxosNaN": {
|
||||
"mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha": [
|
||||
{
|
||||
"txId" : "a0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 0,
|
||||
"address" : "mv7aNQh6soUFysgh1Ax82BKBTyf1V4qWha",
|
||||
"script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac"
|
||||
}
|
||||
],
|
||||
"mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L": [
|
||||
{
|
||||
"txId" : "b0a08e397203df68392ee95b3f08b0b3b3e2401410a38d46ae0874f74846f2e9",
|
||||
"outputIndex" : 1,
|
||||
"address" : "mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L",
|
||||
"script" : "76a914089acaba6af8b2b4fb4bed3b747ab1e4e60b496588ac"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,597 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var chainlib = require('chainlib');
|
||||
var levelup = chainlib.deps.levelup;
|
||||
var bitcoindjs = require('../');
|
||||
var DB = bitcoindjs.DB;
|
||||
var blockData = require('./data/livenet-345003.json');
|
||||
var bitcore = require('bitcore');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var errors = bitcoindjs.errors;
|
||||
var memdown = require('memdown');
|
||||
|
||||
describe('Bitcoin DB', function() {
|
||||
var coinbaseAmount = 50 * 1e8;
|
||||
|
||||
describe('#getBlock', function() {
|
||||
var db = new DB({store: memdown});
|
||||
db.bitcoind = {
|
||||
getBlock: sinon.stub().callsArgWith(1, null, new Buffer(blockData, 'hex'))
|
||||
};
|
||||
db.Block = {
|
||||
fromBuffer: sinon.stub().returns('block')
|
||||
};
|
||||
|
||||
it('should get the block from bitcoind.js', function(done) {
|
||||
db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
|
||||
should.not.exist(err);
|
||||
block.should.equal('block');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should give an error when bitcoind.js gives an error', function(done) {
|
||||
db.bitcoind.getBlock = sinon.stub().callsArgWith(1, new Error('error'));
|
||||
db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#putBlock', function() {
|
||||
it('should call _updatePrevHashIndex', function(done) {
|
||||
var db = new DB({store: memdown});
|
||||
db._updatePrevHashIndex = sinon.stub().callsArg(1);
|
||||
db.putBlock('block', function(err) {
|
||||
should.not.exist(err);
|
||||
db._updatePrevHashIndex.called.should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#buildGenesisData', function() {
|
||||
it('build genisis data', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.buildCoinbaseTransaction = sinon.stub().returns({
|
||||
toBuffer: sinon.stub().returns(new Buffer('abcdef', 'hex'))
|
||||
});
|
||||
db.getMerkleRoot = sinon.stub().returns('merkleRoot');
|
||||
var data = db.buildGenesisData();
|
||||
data.buffer.should.deep.equal(new Buffer('01abcdef', 'hex'));
|
||||
data.merkleRoot.should.equal('merkleRoot');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#buildCoinbaseTransaction', function() {
|
||||
it('should correctly build a coinbase transaction with no fees', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.wallet = {
|
||||
getAddress: sinon.stub().returns('mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L')
|
||||
};
|
||||
db.coinbaseAmount = coinbaseAmount;
|
||||
var coinbaseTx = db.buildCoinbaseTransaction();
|
||||
coinbaseTx.inputs.length.should.equal(1);
|
||||
var input = coinbaseTx.inputs[0];
|
||||
var expectedTxId = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||
input.prevTxId.toString('hex').should.equal(expectedTxId);
|
||||
should.exist(input.outputIndex);
|
||||
should.exist(input.sequenceNumber);
|
||||
should.exist(input._script); // coinbase input script returns null
|
||||
coinbaseTx.outputs.length.should.equal(1);
|
||||
var output = coinbaseTx.outputs[0];
|
||||
output.satoshis.should.equal(coinbaseAmount);
|
||||
});
|
||||
|
||||
it('should correctly build a coinbase transaction with fees', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.wallet = {
|
||||
getAddress: sinon.stub().returns('mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L')
|
||||
};
|
||||
db.coinbaseAmount = coinbaseAmount;
|
||||
var transactions = [
|
||||
{
|
||||
_getInputAmount: sinon.stub().returns(5000),
|
||||
_getOutputAmount: sinon.stub().returns(4000),
|
||||
isCoinbase: sinon.stub().returns(false)
|
||||
},
|
||||
{
|
||||
_getInputAmount: sinon.stub().returns(8000),
|
||||
_getOutputAmount: sinon.stub().returns(7000),
|
||||
isCoinbase: sinon.stub().returns(false)
|
||||
}
|
||||
];
|
||||
var coinbaseTx = db.buildCoinbaseTransaction(transactions);
|
||||
coinbaseTx.inputs.length.should.equal(1);
|
||||
var input = coinbaseTx.inputs[0];
|
||||
var expectedTxId = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||
input.prevTxId.toString('hex').should.equal(expectedTxId);
|
||||
should.exist(input.outputIndex);
|
||||
should.exist(input.sequenceNumber);
|
||||
should.exist(input._script); // coinbase input returns null
|
||||
coinbaseTx.outputs.length.should.equal(1);
|
||||
var output = coinbaseTx.outputs[0];
|
||||
output.satoshis.should.equal(coinbaseAmount + 2000);
|
||||
});
|
||||
|
||||
it('should throw an error if wallet not included', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
(function() {
|
||||
db.buildCoinbaseTransaction();
|
||||
}).should.throw('Wallet required to build coinbase');
|
||||
});
|
||||
|
||||
it('will build a coinbase database with different data', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.wallet = {
|
||||
getAddress: sinon.stub().returns('mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L')
|
||||
};
|
||||
var tx1 = db.buildCoinbaseTransaction().uncheckedSerialize();
|
||||
var tx2 = db.buildCoinbaseTransaction().uncheckedSerialize();
|
||||
tx1.should.not.equal(tx2);
|
||||
});
|
||||
|
||||
it('can pass in custom data', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.wallet = {
|
||||
getAddress: sinon.stub().returns('mzso6uXxfDCq4L6xAffUD9BPWo6bdFBZ2L')
|
||||
};
|
||||
var tx1 = db.buildCoinbaseTransaction(null, new Buffer('abcdef', 'hex'));
|
||||
var data = tx1.inputs[0]._script.getData();
|
||||
data.should.deep.equal(new Buffer('abcdef', 'hex'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getOutputTotal', function() {
|
||||
it('should return the correct value including the coinbase', function() {
|
||||
var totals = [10, 20, 30];
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var transactions = totals.map(function(total) {
|
||||
return {
|
||||
_getOutputAmount: function() {
|
||||
return total;
|
||||
},
|
||||
isCoinbase: function() {
|
||||
return total === 10 ? true : false;
|
||||
}
|
||||
};
|
||||
});
|
||||
var grandTotal = db.getOutputTotal(transactions);
|
||||
grandTotal.should.equal(60);
|
||||
});
|
||||
it('should return the correct value excluding the coinbase', function() {
|
||||
var totals = [10, 20, 30];
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var transactions = totals.map(function(total) {
|
||||
return {
|
||||
_getOutputAmount: function() {
|
||||
return total;
|
||||
},
|
||||
isCoinbase: function() {
|
||||
return total === 10 ? true : false;
|
||||
}
|
||||
};
|
||||
});
|
||||
var grandTotal = db.getOutputTotal(transactions, true);
|
||||
grandTotal.should.equal(50)
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInputTotal', function() {
|
||||
it('should return the correct value', function() {
|
||||
var totals = [10, 20, 30];
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var transactions = totals.map(function(total) {
|
||||
return {
|
||||
_getInputAmount: function() {
|
||||
return total;
|
||||
},
|
||||
isCoinbase: sinon.stub().returns(false)
|
||||
};
|
||||
});
|
||||
var grandTotal = db.getInputTotal(transactions);
|
||||
grandTotal.should.equal(60);
|
||||
});
|
||||
it('should return 0 if the tx is a coinbase', function() {
|
||||
var db = new DB({store: memdown});
|
||||
var tx = {
|
||||
isCoinbase: sinon.stub().returns(true)
|
||||
};
|
||||
var total = db.getInputTotal([tx]);
|
||||
total.should.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_updateOutputs', function() {
|
||||
var block = bitcore.Block.fromString(blockData);
|
||||
var db = new DB({path: 'path', store: memdown, network: 'livenet'});
|
||||
db.getTransactionsFromBlock = function() {
|
||||
return block.transactions.slice(0, 8);
|
||||
};
|
||||
|
||||
var data = [
|
||||
{
|
||||
key: {
|
||||
address: '1F1MAvhTKg2VG29w8cXsiSN2PJ8gSsrJw',
|
||||
timestamp: 1424836934000,
|
||||
txid: 'fdbefe0d064729d85556bd3ab13c3a889b685d042499c02b4aa2064fb1e16923',
|
||||
outputIndex: 0
|
||||
},
|
||||
value: {
|
||||
satoshis: 2502227470,
|
||||
script: 'OP_DUP OP_HASH160 20 0x02a61d2066d19e9e2fd348a8320b7ebd4dd3ca2b OP_EQUALVERIFY OP_CHECKSIG',
|
||||
blockHeight: 345003
|
||||
}
|
||||
},
|
||||
{
|
||||
key: {
|
||||
prevTxId: '3d7d5d98df753ef2a4f82438513c509e3b11f3e738e94a7234967b03a03123a9',
|
||||
prevOutputIndex: 32
|
||||
},
|
||||
value: {
|
||||
txid: '5780f3ee54889a0717152a01abee9a32cec1b0cdf8d5537a08c7bd9eeb6bfbca',
|
||||
inputIndex: 0,
|
||||
timestamp: 1424836934000
|
||||
}
|
||||
},
|
||||
{
|
||||
key: {
|
||||
address: '1Ep5LA4T6Y7zaBPiwruUJurjGFvCJHzJhm',
|
||||
timestamp: 1424836934000,
|
||||
txid: 'e66f3b989c790178de2fc1a5329f94c0d8905d0d3df4e7ecf0115e7f90a6283d',
|
||||
outputIndex: 1
|
||||
},
|
||||
value: {
|
||||
satoshis: 3100000,
|
||||
script: 'OP_DUP OP_HASH160 20 0x9780ccd5356e2acc0ee439ee04e0fe69426c7528 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
blockHeight: 345003
|
||||
}
|
||||
}
|
||||
];
|
||||
var key0 = data[0].key;
|
||||
var value0 = data[0].value;
|
||||
var key3 = data[1].key;
|
||||
var value3 = data[1].value;
|
||||
var key64 = data[2].key;
|
||||
var value64 = data[2].value;
|
||||
|
||||
it('should create the correct operations when updating/adding outputs', function(done) {
|
||||
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, true, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(81);
|
||||
operations[0].type.should.equal('put');
|
||||
var expected0 = ['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-');
|
||||
operations[0].key.should.equal(expected0);
|
||||
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
|
||||
operations[3].type.should.equal('put');
|
||||
var expected3 = ['sp', key3.prevTxId, key3.prevOutputIndex].join('-');
|
||||
operations[3].key.should.equal(expected3);
|
||||
operations[3].value.should.equal([value3.txid, value3.inputIndex, value3.timestamp].join(':'));
|
||||
operations[64].type.should.equal('put');
|
||||
var expected64 = ['outs', key64.address, key64.timestamp, key64.txid, key64.outputIndex].join('-');
|
||||
operations[64].key.should.equal(expected64);
|
||||
operations[64].value.should.equal([value64.satoshis, value64.script, value64.blockHeight].join(':'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should create the correct operations when removing outputs', function(done) {
|
||||
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, false, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(81);
|
||||
operations[0].type.should.equal('del');
|
||||
operations[0].key.should.equal(['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-'));
|
||||
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
|
||||
operations[3].type.should.equal('del');
|
||||
operations[3].key.should.equal(['sp', key3.prevTxId, key3.prevOutputIndex].join('-'));
|
||||
operations[3].value.should.equal([value3.txid, value3.inputIndex, value3.timestamp].join(':'));
|
||||
operations[64].type.should.equal('del');
|
||||
operations[64].key.should.equal(['outs', key64.address, key64.timestamp, key64.txid, key64.outputIndex].join('-'));
|
||||
operations[64].value.should.equal([value64.satoshis, value64.script, value64.blockHeight].join(':'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should continue if output script is null', function(done) {
|
||||
var db = new DB({path: 'path', store: memdown, network: 'livenet'});
|
||||
var transactions = [
|
||||
{
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
script: null,
|
||||
satoshis: 1000,
|
||||
}
|
||||
],
|
||||
isCoinbase: sinon.stub().returns(false)
|
||||
}
|
||||
];
|
||||
db.getTransactionsFromBlock = function() {
|
||||
return transactions;
|
||||
};
|
||||
|
||||
db._updateOutputs({height: 345003, timestamp: new Date(1424836934000)}, false, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#_onChainAddBlock', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
|
||||
db.store = {
|
||||
batch: sinon.stub().callsArg(1)
|
||||
};
|
||||
|
||||
it('should give error when there is a failure to write', function() {
|
||||
var errordb = new DB({path: 'path', store: memdown});
|
||||
errordb._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
|
||||
errordb.store = {
|
||||
batch: sinon.stub().callsArgWith(1, new Error('error'))
|
||||
};
|
||||
errordb._onChainAddBlock('block', function(err) {
|
||||
should.exist(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call block processing functions and write to database', function(done) {
|
||||
db._onChainAddBlock('block', function(err) {
|
||||
should.not.exist(err);
|
||||
db._updateOutputs.calledOnce.should.equal(true);
|
||||
db._updateOutputs.calledWith('block', true).should.equal(true);
|
||||
db.store.batch.args[0][0].should.deep.equal(['1a', '1b']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should halt on an error and not write to database', function(done) {
|
||||
db._updateOutputs.reset();
|
||||
db.store.batch.reset();
|
||||
db._updateOutputs = sinon.stub().callsArgWith(2, new Error('error'));
|
||||
db._onChainAddBlock('block', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
db._updateOutputs.calledOnce.should.equal(true);
|
||||
db._updateOutputs.calledWith('block', true).should.equal(true);
|
||||
db.store.batch.called.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_onChainRemoveBlock', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
|
||||
db.store = {
|
||||
batch: sinon.stub().callsArg(1)
|
||||
};
|
||||
|
||||
it('should give error when there is a failure to write', function() {
|
||||
var errordb = new DB({path: 'path', store: memdown});
|
||||
errordb._updateOutputs = sinon.stub().callsArgWith(2, null, ['1a', '1b']);
|
||||
errordb.store = {
|
||||
batch: sinon.stub().callsArgWith(1, new Error('error'))
|
||||
};
|
||||
errordb._onChainRemoveBlock('block', function(err) {
|
||||
should.exist(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call block processing functions and write to database', function(done) {
|
||||
db._onChainRemoveBlock('block', function(err) {
|
||||
should.not.exist(err);
|
||||
db._updateOutputs.calledOnce.should.equal(true);
|
||||
db._updateOutputs.calledWith('block', false).should.equal(true);
|
||||
db.store.batch.args[0][0].should.deep.equal(['1a', '1b']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should halt on an error and not write to database', function(done) {
|
||||
db._updateOutputs.reset();
|
||||
db.store.batch.reset();
|
||||
db._updateOutputs = sinon.stub().callsArgWith(2, new Error('error'));
|
||||
db._onChainRemoveBlock('block', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
db._updateOutputs.calledOnce.should.equal(true);
|
||||
db._updateOutputs.calledWith('block', false).should.equal(true);
|
||||
db.store.batch.called.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAPIMethods', function() {
|
||||
it('should return the correct methods', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var methods = db.getAPIMethods();
|
||||
methods.length.should.equal(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getBalance', function() {
|
||||
it('should sum up the unspent outputs', function(done) {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var outputs = [
|
||||
{satoshis: 1000}, {satoshis: 2000}, {satoshis: 3000}
|
||||
];
|
||||
db.getUnspentOutputs = sinon.stub().callsArgWith(2, null, outputs);
|
||||
db.getBalance('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N', false, function(err, balance) {
|
||||
should.not.exist(err);
|
||||
balance.should.equal(6000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('will handle error from unspent outputs', function(done) {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.getUnspentOutputs = sinon.stub().callsArgWith(2, new Error('error'));
|
||||
db.getBalance('someaddress', false, function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#getOutputs', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
var address = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
|
||||
|
||||
it('should get outputs for an address', function(done) {
|
||||
var readStream1 = new EventEmitter();
|
||||
db.store = {
|
||||
createReadStream: sinon.stub().returns(readStream1)
|
||||
};
|
||||
db.getOutputs(address, true, function(err, outputs) {
|
||||
should.not.exist(err);
|
||||
outputs.length.should.equal(2);
|
||||
outputs[0].address.should.equal(address);
|
||||
outputs[0].txid.should.equal('125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87');
|
||||
outputs[0].outputIndex.should.equal(1);
|
||||
outputs[0].satoshis.should.equal(4527773864);
|
||||
outputs[0].script.should.equal('OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG');
|
||||
outputs[0].blockHeight.should.equal(345000);
|
||||
outputs[1].address.should.equal(address);
|
||||
outputs[1].txid.should.equal('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7');
|
||||
outputs[1].outputIndex.should.equal(2);
|
||||
outputs[1].satoshis.should.equal(10000);
|
||||
outputs[1].script.should.equal('OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG');
|
||||
outputs[1].blockHeight.should.equal(345004);
|
||||
done();
|
||||
});
|
||||
|
||||
var data1 = {
|
||||
key: ['outs', address, '1424835319000', '125dd0e50fc732d67c37b6c56be7f9dc00b6859cebf982ee2cc83ed2d604bf87', '1'].join('-'),
|
||||
value: ['4527773864', 'OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG', '345000'].join(':')
|
||||
};
|
||||
|
||||
var data2 = {
|
||||
key: ['outs', address, '1424837300000', '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', '2'].join('-'),
|
||||
value: ['10000', 'OP_DUP OP_HASH160 038a213afdfc551fc658e9a2a58a86e98d69b687 OP_EQUALVERIFY OP_CHECKSIG', '345004'].join(':')
|
||||
};
|
||||
|
||||
readStream1.emit('data', data1);
|
||||
readStream1.emit('data', data2);
|
||||
readStream1.emit('close');
|
||||
});
|
||||
|
||||
it('should give an error if the readstream has an error', function(done) {
|
||||
var readStream2 = new EventEmitter();
|
||||
db.store = {
|
||||
createReadStream: sinon.stub().returns(readStream2)
|
||||
};
|
||||
|
||||
db.getOutputs(address, true, function(err, outputs) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('readstreamerror');
|
||||
done();
|
||||
});
|
||||
|
||||
readStream2.emit('error', new Error('readstreamerror'));
|
||||
process.nextTick(function() {
|
||||
readStream2.emit('close');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getUnspentOutputs', function() {
|
||||
it('should filter out spent outputs', function(done) {
|
||||
var outputs = [
|
||||
{
|
||||
satoshis: 1000,
|
||||
spent: false,
|
||||
},
|
||||
{
|
||||
satoshis: 2000,
|
||||
spent: true
|
||||
},
|
||||
{
|
||||
satoshis: 3000,
|
||||
spent: false
|
||||
}
|
||||
];
|
||||
var i = 0;
|
||||
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.getOutputs = sinon.stub().callsArgWith(2, null, outputs);
|
||||
db.isUnspent = function(output, queryMempool, callback) {
|
||||
callback(!outputs[i].spent);
|
||||
i++;
|
||||
};
|
||||
|
||||
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
||||
should.not.exist(err);
|
||||
outputs.length.should.equal(2);
|
||||
outputs[0].satoshis.should.equal(1000);
|
||||
outputs[1].satoshis.should.equal(3000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should handle an error from getOutputs', function(done) {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.getOutputs = sinon.stub().callsArgWith(2, new Error('error'));
|
||||
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should handle when there are no outputs', function(done) {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.getOutputs = sinon.stub().callsArgWith(2, null, []);
|
||||
db.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
||||
should.exist(err);
|
||||
err.should.be.instanceof(errors.NoOutputs);
|
||||
outputs.length.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isUnspent', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
|
||||
it('should give true when isSpent() gives false', function(done) {
|
||||
db.isSpent = sinon.stub().callsArgWith(2, false);
|
||||
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
|
||||
unspent.should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give false when isSpent() gives true', function(done) {
|
||||
db.isSpent = sinon.stub().callsArgWith(2, true);
|
||||
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
|
||||
unspent.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give false when isSpent() returns an error', function(done) {
|
||||
db.isSpent = sinon.stub().callsArgWith(2, new Error('error'));
|
||||
db.isUnspent('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(unspent) {
|
||||
unspent.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isSpent', function() {
|
||||
var db = new DB({path: 'path', store: memdown});
|
||||
db.bitcoind = {
|
||||
isSpent: sinon.stub().returns(true)
|
||||
};
|
||||
|
||||
it('should give true if bitcoind.isSpent gives true', function(done) {
|
||||
db.isSpent('output', true, function(spent) {
|
||||
spent.should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,412 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var bitcore = require('bitcore');
|
||||
var Networks = bitcore.Networks;
|
||||
var blockData = require('./data/livenet-345003.json');
|
||||
var Block = require('../lib/block');
|
||||
var proxyquire = require('proxyquire');
|
||||
var chainlib = require('chainlib');
|
||||
var OriginalNode = chainlib.Node;
|
||||
|
||||
var BaseNode = function() {};
|
||||
util.inherits(BaseNode, EventEmitter);
|
||||
BaseNode.log = chainlib.log;
|
||||
BaseNode.prototype._loadConfiguration = sinon.spy();
|
||||
BaseNode.prototype._initialize = sinon.spy();
|
||||
chainlib.Node = BaseNode;
|
||||
|
||||
var Node = proxyquire('../lib/node', {chainlib: chainlib});
|
||||
chainlib.Node = OriginalNode;
|
||||
|
||||
describe('Bitcoind Node', function() {
|
||||
describe('#_loadConfiguration', function() {
|
||||
it('should call the necessary methods', function() {
|
||||
var node = new Node({});
|
||||
node._loadBitcoind = sinon.spy();
|
||||
node._loadConfiguration({});
|
||||
node._loadBitcoind.called.should.equal(true);
|
||||
BaseNode.prototype._loadConfiguration.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('#setSyncStrategy', function() {
|
||||
it('will call p2p.startSync', function() {
|
||||
var node = new Node({});
|
||||
node.p2p = {
|
||||
startSync: sinon.spy()
|
||||
};
|
||||
node.setSyncStrategy(Node.SYNC_STRATEGIES.P2P);
|
||||
node.p2p.startSync.callCount.should.equal(1);
|
||||
});
|
||||
it('will call this._syncBitcoind and disable p2p sync', function() {
|
||||
var node = new Node({});
|
||||
node.p2p = {};
|
||||
node._syncBitcoind = sinon.spy();
|
||||
node.setSyncStrategy(Node.SYNC_STRATEGIES.BITCOIND);
|
||||
node._syncBitcoind.callCount.should.equal(1);
|
||||
node.p2p.disableSync.should.equal(true);
|
||||
});
|
||||
it('will error with an unknown strategy', function() {
|
||||
var node = new Node({});
|
||||
(function(){
|
||||
node.setSyncStrategy('unknown');
|
||||
}).should.throw('Strategy "unknown" is unknown');
|
||||
});
|
||||
});
|
||||
describe('#_loadBitcoind', function() {
|
||||
it('should initialize', function() {
|
||||
var node = new Node({});
|
||||
node._loadBitcoind({});
|
||||
should.exist(node.bitcoind);
|
||||
});
|
||||
it('should initialize with testnet', function() {
|
||||
var node = new Node({});
|
||||
node._loadBitcoind({testnet: true});
|
||||
should.exist(node.bitcoind);
|
||||
});
|
||||
});
|
||||
describe('#_syncBitcoind', function() {
|
||||
it('will get and add block up to the tip height', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = {
|
||||
synced: false
|
||||
};
|
||||
node.Block = Block;
|
||||
node.syncStrategy = Node.SYNC_STRATEGIES.BITCOIND;
|
||||
node.setSyncStrategy = sinon.stub();
|
||||
node.bitcoind = {
|
||||
getInfo: sinon.stub().returns({blocks: 2}),
|
||||
getBlock: sinon.stub().callsArgWith(1, null, new Buffer(blockData))
|
||||
};
|
||||
node.chain = {
|
||||
tip: {
|
||||
__height: 0
|
||||
},
|
||||
addBlock: function(block, callback) {
|
||||
node.chain.tip.__height += 1;
|
||||
callback();
|
||||
}
|
||||
};
|
||||
node.on('synced', function() {
|
||||
node.p2p.synced.should.equal(true);
|
||||
node.setSyncStrategy.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
node._syncBitcoind();
|
||||
});
|
||||
it('will exit and emit error with error from bitcoind.getBlock', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = {
|
||||
synced: false
|
||||
};
|
||||
node.syncStrategy = Node.SYNC_STRATEGIES.BITCOIND;
|
||||
node.setSyncStrategy = sinon.stub();
|
||||
node.bitcoind = {
|
||||
getInfo: sinon.stub().returns({blocks: 2}),
|
||||
getBlock: sinon.stub().callsArgWith(1, new Error('test error'))
|
||||
};
|
||||
node.chain = {
|
||||
tip: {
|
||||
__height: 0
|
||||
}
|
||||
};
|
||||
node.on('error', function(err) {
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._syncBitcoind();
|
||||
});
|
||||
it('will exit if sync strategy is changed to bitcoind', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = {
|
||||
synced: false
|
||||
};
|
||||
node.syncStrategy = Node.SYNC_STRATEGIES.P2P;
|
||||
node.setSyncStrategy = sinon.stub();
|
||||
node.bitcoind = {
|
||||
getInfo: sinon.stub().returns({blocks: 2})
|
||||
};
|
||||
node.chain = {
|
||||
tip: {
|
||||
__height: 0
|
||||
}
|
||||
};
|
||||
node.on('synced', function() {
|
||||
node.p2p.synced.should.equal(true);
|
||||
node.setSyncStrategy.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
node._syncBitcoind();
|
||||
});
|
||||
});
|
||||
describe('#_loadNetwork', function() {
|
||||
it('should add the network that was listed in the config', function() {
|
||||
var config = {
|
||||
network: {
|
||||
name: 'chainlib',
|
||||
alias: 'chainlib',
|
||||
pubkeyhash: 0x1c,
|
||||
privatekey: 0x1e,
|
||||
scripthash: 0x28,
|
||||
xpubkey: 0x02e8de8f,
|
||||
xprivkey: 0x02e8da54,
|
||||
networkMagic: 0x0c110907,
|
||||
port: 9333
|
||||
}
|
||||
};
|
||||
var node = new Node(config);
|
||||
node._loadNetwork(config);
|
||||
var network = Networks.get('chainlib');
|
||||
should.exist(network);
|
||||
node.network.name.should.equal('chainlib');
|
||||
});
|
||||
it('should use the testnet network if testnet is specified', function() {
|
||||
var config = {
|
||||
testnet: true
|
||||
};
|
||||
|
||||
var node = new Node(config);
|
||||
node._loadNetwork(config);
|
||||
node.network.name.should.equal('testnet');
|
||||
});
|
||||
it('should use the livenet network if nothing is specified', function() {
|
||||
var config = {};
|
||||
|
||||
var node = new Node(config);
|
||||
node._loadNetwork(config);
|
||||
node.network.name.should.equal('livenet');
|
||||
});
|
||||
});
|
||||
describe('#_loadDB', function() {
|
||||
it('should load the db', function() {
|
||||
var DB = function() {};
|
||||
var config = {
|
||||
DB: DB
|
||||
};
|
||||
|
||||
var node = new Node(config);
|
||||
node._loadDB(config);
|
||||
node.db.should.be.instanceof(DB);
|
||||
});
|
||||
});
|
||||
describe('#_loadP2P', function() {
|
||||
it('should load p2p', function() {
|
||||
var config = {};
|
||||
|
||||
var node = new Node(config);
|
||||
node.db = {
|
||||
Transaction: bitcore.Transaction
|
||||
};
|
||||
node.network = Networks.get('testnet');
|
||||
node._loadP2P(config);
|
||||
should.exist(node.p2p);
|
||||
node.p2p.noListen.should.equal(true);
|
||||
node.p2p.pool.network.should.deep.equal(node.network);
|
||||
node.db.Transaction.should.equal(bitcore.Transaction);
|
||||
});
|
||||
});
|
||||
describe('#_loadConsensus', function() {
|
||||
var node = new Node({});
|
||||
|
||||
it('should use the genesis specified in the config', function() {
|
||||
var config = {
|
||||
genesis: '0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000'
|
||||
};
|
||||
node._loadConsensus(config);
|
||||
should.exist(node.chain);
|
||||
node.chain.genesis.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
|
||||
});
|
||||
it('should use the testnet genesis if testnet is specified', function() {
|
||||
var config = {
|
||||
testnet: true
|
||||
};
|
||||
node._loadConsensus(config);
|
||||
should.exist(node.chain);
|
||||
node.chain.genesis.hash.should.equal('000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943');
|
||||
});
|
||||
it('should use the livenet genesis if nothing is specified', function() {
|
||||
var config = {};
|
||||
node._loadConsensus(config);
|
||||
should.exist(node.chain);
|
||||
node.chain.genesis.hash.should.equal('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeBitcoind', function() {
|
||||
it('will call db.initialize() on ready event', function(done) {
|
||||
var node = new Node({});
|
||||
node.bitcoind = new EventEmitter();
|
||||
node.db = {
|
||||
initialize: sinon.spy()
|
||||
};
|
||||
sinon.stub(chainlib.log, 'info');
|
||||
node.bitcoind.on('ready', function() {
|
||||
setImmediate(function() {
|
||||
chainlib.log.info.callCount.should.equal(1);
|
||||
chainlib.log.info.restore();
|
||||
node.db.initialize.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeBitcoind();
|
||||
node.bitcoind.emit('ready');
|
||||
});
|
||||
it('will call emit an error from bitcoind.js', function(done) {
|
||||
var node = new Node({});
|
||||
node.bitcoind = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeBitcoind();
|
||||
node.bitcoind.emit('error', new Error('test error'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeDatabase', function() {
|
||||
it('will call chain.initialize() on ready event', function(done) {
|
||||
var node = new Node({});
|
||||
node.db = new EventEmitter();
|
||||
node.chain = {
|
||||
initialize: sinon.spy()
|
||||
};
|
||||
sinon.stub(chainlib.log, 'info');
|
||||
node.db.on('ready', function() {
|
||||
setImmediate(function() {
|
||||
chainlib.log.info.callCount.should.equal(1);
|
||||
chainlib.log.info.restore();
|
||||
node.chain.initialize.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeDatabase();
|
||||
node.db.emit('ready');
|
||||
});
|
||||
it('will call emit an error from db', function(done) {
|
||||
var node = new Node({});
|
||||
node.db = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeDatabase();
|
||||
node.db.emit('error', new Error('test error'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeChain', function() {
|
||||
it('will call p2p.initialize() on ready event', function(done) {
|
||||
var node = new Node({});
|
||||
node.chain = new EventEmitter();
|
||||
node.p2p = {
|
||||
initialize: sinon.spy()
|
||||
};
|
||||
sinon.stub(chainlib.log, 'info');
|
||||
node.chain.on('ready', function() {
|
||||
setImmediate(function() {
|
||||
chainlib.log.info.callCount.should.equal(1);
|
||||
chainlib.log.info.restore();
|
||||
node.p2p.initialize.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
node._initializeChain();
|
||||
node.chain.emit('ready');
|
||||
});
|
||||
it('will call emit an error from chain', function(done) {
|
||||
var node = new Node({});
|
||||
node.chain = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeChain();
|
||||
node.chain.emit('error', new Error('test error'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initializeP2P', function() {
|
||||
it('will emit node "ready" when p2p is ready', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = new EventEmitter();
|
||||
sinon.stub(chainlib.log, 'info');
|
||||
node.on('ready', function() {
|
||||
chainlib.log.info.callCount.should.equal(1);
|
||||
chainlib.log.info.restore();
|
||||
done();
|
||||
});
|
||||
node._initializeP2P();
|
||||
node.p2p.emit('ready');
|
||||
});
|
||||
it('will call emit an error from p2p', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = new EventEmitter();
|
||||
node.on('error', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test error');
|
||||
done();
|
||||
});
|
||||
node._initializeP2P();
|
||||
node.p2p.emit('error', new Error('test error'));
|
||||
});
|
||||
it('will relay synced event from p2p to node', function(done) {
|
||||
var node = new Node({});
|
||||
node.p2p = new EventEmitter();
|
||||
node.on('synced', function() {
|
||||
done();
|
||||
});
|
||||
node._initializeP2P();
|
||||
node.p2p.emit('synced');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_initialize', function() {
|
||||
|
||||
it('should initialize', function(done) {
|
||||
var node = new Node({});
|
||||
node.chain = {};
|
||||
node.Block = 'Block';
|
||||
node.bitcoind = 'bitcoind';
|
||||
node.p2p = {};
|
||||
node.db = {};
|
||||
|
||||
node._initializeBitcoind = sinon.spy();
|
||||
node._initializeDatabase = sinon.spy();
|
||||
node._initializeChain = sinon.spy();
|
||||
node._initializeP2P = sinon.spy();
|
||||
node._initialize();
|
||||
|
||||
// references
|
||||
node.db.chain.should.equal(node.chain);
|
||||
node.db.Block.should.equal(node.Block);
|
||||
node.db.bitcoind.should.equal(node.bitcoind);
|
||||
node.chain.db.should.equal(node.db);
|
||||
node.chain.p2p.should.equal(node.p2p);
|
||||
node.chain.db.should.equal(node.db);
|
||||
node.p2p.db.should.equal(node.db);
|
||||
node.p2p.chain.should.equal(node.chain);
|
||||
|
||||
// events
|
||||
node._initializeBitcoind.callCount.should.equal(1);
|
||||
node._initializeDatabase.callCount.should.equal(1);
|
||||
node._initializeChain.callCount.should.equal(1);
|
||||
node._initializeP2P.callCount.should.equal(1);
|
||||
|
||||
// start syncing
|
||||
node.setSyncStrategy = sinon.spy();
|
||||
node.on('ready', function() {
|
||||
node.setSyncStrategy.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
node.emit('ready');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -0,0 +1,300 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var bitcoinlib = require('../');
|
||||
var Transaction = bitcoinlib.Transaction;
|
||||
var transactionData = require('./data/bitcoin-transactions.json');
|
||||
var memdown = require('memdown');
|
||||
var DB = bitcoinlib.DB;
|
||||
var db = new DB({store: memdown});
|
||||
var chainlib = require('chainlib');
|
||||
var levelup = chainlib.deps.levelup;
|
||||
|
||||
describe('Bitcoin Transaction', function() {
|
||||
describe('#validate', function() {
|
||||
it('should give an error if verify() fails', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx.verify = sinon.stub().returns('invalid tx');
|
||||
tx.validate(db, [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('invalid tx');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should give an error if one if the async series functions fails', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx._validateInputs = sinon.stub().callsArg(2);
|
||||
tx._validateOutputs = sinon.stub().callsArgWith(0, new Error('output validation error'));
|
||||
tx._checkSufficientInputs = sinon.stub().callsArg(0);
|
||||
tx.verify = sinon.stub().returns(true);
|
||||
tx.validate(db, [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('output validation error');
|
||||
tx._validateInputs.calledOnce.should.equal(true);
|
||||
tx._validateOutputs.calledOnce.should.equal(true);
|
||||
tx._checkSufficientInputs.called.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should call all the functions if there is no error', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx._validateInputs = sinon.stub().callsArg(2);
|
||||
tx._validateOutputs = sinon.stub().callsArg(0);
|
||||
tx._checkSufficientInputs = sinon.stub().callsArg(0);
|
||||
tx.verify = sinon.stub().returns(true);
|
||||
tx.validate(db, [], function(err) {
|
||||
should.not.exist(err);
|
||||
tx._validateInputs.calledOnce.should.equal(true);
|
||||
tx._validateOutputs.calledOnce.should.equal(true);
|
||||
tx._checkSufficientInputs.calledOnce.should.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#_validateInputs', function() {
|
||||
it('should call all the functions and complete when no errors', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx.inputs = ['input'];
|
||||
sinon.stub(tx, '_populateInput', function(db, input, poolTransactions, callback) {
|
||||
return callback(null, input, 'populateInput');
|
||||
});
|
||||
sinon.stub(tx, '_checkSpent', function(db, input, poolTransactions, callback) {
|
||||
return callback();
|
||||
});
|
||||
sinon.stub(tx, '_checkScript', function(input, index, callback) {
|
||||
return callback();
|
||||
});
|
||||
|
||||
tx._validateInputs('db', [], function(err) {
|
||||
should.not.exist(err);
|
||||
tx._populateInput.calledOnce.should.equal(true);
|
||||
tx._populateInput.calledWith('db', 'input');
|
||||
tx._checkSpent.calledOnce.should.equal(true);
|
||||
tx._populateInput.calledWith('db', 'input');
|
||||
tx._checkScript.calledOnce.should.equal(true);
|
||||
tx._populateInput.calledWith('input');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should halt on an error', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx.inputs = ['input'];
|
||||
sinon.stub(tx, '_populateInput', function(db, input, poolTransactions, callback) {
|
||||
return callback();
|
||||
});
|
||||
sinon.stub(tx, '_checkSpent', function(db, input, poolTransactions, callback) {
|
||||
return callback(new Error('error'));
|
||||
});
|
||||
sinon.stub(tx, '_checkScript', function(input, callback) {
|
||||
return callback();
|
||||
});
|
||||
|
||||
tx._validateInputs('db', [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
tx._populateInput.calledOnce.should.equal(true);
|
||||
tx._populateInput.calledWith('input');
|
||||
tx._checkSpent.calledOnce.should.equal(true);
|
||||
tx._populateInput.calledWith('input');
|
||||
tx._checkScript.called.should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#populateInputs', function() {
|
||||
it('will call _populateInput with transactions', function() {
|
||||
var tx = new Transaction();
|
||||
tx._populateInput = sinon.stub().callsArg(3);
|
||||
tx.inputs = ['input'];
|
||||
var transactions = [];
|
||||
var db = {};
|
||||
tx.populateInputs(db, transactions, function(err) {
|
||||
tx._populateInput.callCount.should.equal(1);
|
||||
tx._populateInput.args[0][0].should.equal(db);
|
||||
tx._populateInput.args[0][1].should.equal('input');
|
||||
tx._populateInput.args[0][2].should.equal(transactions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_populateInput', function() {
|
||||
var input = {
|
||||
prevTxId: new Buffer('d6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3', 'hex'),
|
||||
outputIndex: 0
|
||||
};
|
||||
it('should give an error if the input does not have a valid prevTxId', function(done) {
|
||||
var badInput = {
|
||||
prevTxId: 'bad'
|
||||
};
|
||||
var tx = new Transaction();
|
||||
tx._populateInput({}, badInput, [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('Input is expected to have prevTxId as a buffer');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('if an error happened it should pass it along', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new Error('error'))
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should return an error if the transaction for the input does not exist', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('Previous tx ' + input.prevTxId.toString('hex') + ' not found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should look through poolTransactions if database does not have transaction', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
|
||||
};
|
||||
var transactions = [
|
||||
{
|
||||
hash: 'd6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3',
|
||||
outputs: ['output']
|
||||
}
|
||||
];
|
||||
tx._populateInput(db, input, transactions, function(err) {
|
||||
should.not.exist(err);
|
||||
input.output.should.equal('output');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should not return an error if an error did not occur', function(done) {
|
||||
var prevTx = new Transaction();
|
||||
prevTx.outputs = ['output'];
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, null, prevTx)
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.not.exist(err);
|
||||
input.output.should.equal('output');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#_checkSpent', function() {
|
||||
it('should return an error if input was spent', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
isSpentDB: sinon.stub().callsArgWith(1, true)
|
||||
};
|
||||
tx._checkSpent(db, [], 'input', function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('Input already spent');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should not return an error if input was unspent', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
isSpentDB: sinon.stub().callsArgWith(1, false)
|
||||
};
|
||||
tx._checkSpent(db, [], 'input', function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#_checkScript', function() {
|
||||
it('should not have an error with a valid script', function(done) {
|
||||
var prevTx = new Transaction();
|
||||
prevTx.fromString(transactionData[0].hex);
|
||||
var tx = new Transaction();
|
||||
tx.fromString(transactionData[1].hex);
|
||||
var input = tx.inputs[0];
|
||||
input.output = prevTx.outputs[0];
|
||||
|
||||
tx._checkScript(input, 0, function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should have an error when signature is missing', function(done) {
|
||||
var prevTx = new Transaction();
|
||||
prevTx.fromString(transactionData[0].hex);
|
||||
var tx = new Transaction();
|
||||
tx.fromString(transactionData[2].hex);
|
||||
var input = tx.inputs[0];
|
||||
input.output = prevTx.outputs[0];
|
||||
|
||||
tx._checkScript(input, 0, function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#_checkSufficientInputs', function() {
|
||||
var inputs = [
|
||||
{
|
||||
outputIndex: 0,
|
||||
output: {
|
||||
satoshis: 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
outputIndex: 0,
|
||||
output: {
|
||||
satoshis: 2000
|
||||
}
|
||||
},
|
||||
{
|
||||
outputIndex: 1,
|
||||
output: {
|
||||
satoshis: 3000
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
var outputs = [
|
||||
{
|
||||
satoshis: 4000
|
||||
},
|
||||
{
|
||||
satoshis: 3000
|
||||
}
|
||||
];
|
||||
|
||||
it('should give an error if inputs are less than outputs', function(done) {
|
||||
var tx = new Transaction();
|
||||
tx.inputs = inputs;
|
||||
tx.outputs = outputs;
|
||||
tx._checkSufficientInputs(function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('Insufficient inputs');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should not give an error if inputs are greater than or equal to outputs', function(done) {
|
||||
inputs[2].output = {
|
||||
satoshis: 8000
|
||||
};
|
||||
|
||||
var tx = new Transaction();
|
||||
tx.inputs = inputs;
|
||||
tx.outputs = outputs;
|
||||
tx._checkSufficientInputs(function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue