358 lines
12 KiB
JavaScript
358 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
var should = require('chai').should();
|
|
var sinon = require('sinon');
|
|
var bitcoindjs = require('../');
|
|
var DB = bitcoindjs.DB;
|
|
var blockData = require('./data/livenet-345003.json');
|
|
var transactionData = require('./data/bitcoin-transactions.json');
|
|
var errors = bitcoindjs.errors;
|
|
var memdown = require('memdown');
|
|
var inherits = require('util').inherits;
|
|
var BaseModule = require('../lib/module');
|
|
|
|
describe('Bitcoin DB', function() {
|
|
var coinbaseAmount = 50 * 1e8;
|
|
|
|
describe('#getTransaction', function() {
|
|
it('will return a NotFound error', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.bitcoind = {
|
|
getTransaction: sinon.stub().callsArgWith(2, null, null)
|
|
};
|
|
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
|
|
db.getTransaction(txid, true, function(err) {
|
|
err.should.be.instanceof(errors.Transaction.NotFound);
|
|
done();
|
|
});
|
|
});
|
|
it('will return an error from bitcoind', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.bitcoind = {
|
|
getTransaction: sinon.stub().callsArgWith(2, new Error('test error'))
|
|
};
|
|
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
|
|
db.getTransaction(txid, true, function(err) {
|
|
err.message.should.equal('test error');
|
|
done();
|
|
});
|
|
});
|
|
it('will return an error from bitcoind', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.bitcoind = {
|
|
getTransaction: sinon.stub().callsArgWith(2, null, new Buffer(transactionData[0].hex, 'hex'))
|
|
};
|
|
var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
|
|
db.getTransaction(txid, true, function(err, tx) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
should.exist(tx);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
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 callback', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.putBlock('block', function(err) {
|
|
should.not.exist(err);
|
|
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.coinbaseAddress = '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.coinbaseAddress = '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 coinbaseAddress not included', function() {
|
|
var db = new DB({path: 'path', store: memdown});
|
|
(function() {
|
|
db.buildCoinbaseTransaction();
|
|
}).should.throw('coinbaseAddress required to build coinbase');
|
|
});
|
|
|
|
it('will build a coinbase database with different data', function() {
|
|
var db = new DB({path: 'path', store: memdown});
|
|
db.coinbaseAddress = '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.coinbaseAddress = '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('#_onChainAddBlock', function() {
|
|
it('should remove block from mempool and call blockHandler with true', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.mempool = {
|
|
removeBlock: sinon.stub()
|
|
};
|
|
db.blockHandler = sinon.stub().callsArg(2);
|
|
db._onChainAddBlock({hash: 'hash'}, function(err) {
|
|
should.not.exist(err);
|
|
db.mempool.removeBlock.args[0][0].should.equal('hash');
|
|
db.blockHandler.args[0][1].should.equal(true);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#_onChainRemoveBlock', function() {
|
|
it('should call blockHandler with false', function(done) {
|
|
var db = new DB({store: memdown});
|
|
db.blockHandler = sinon.stub().callsArg(2);
|
|
db._onChainRemoveBlock({hash: 'hash'}, function(err) {
|
|
should.not.exist(err);
|
|
db.blockHandler.args[0][1].should.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#blockHandler', function() {
|
|
var db = new DB({store: memdown});
|
|
var Module1 = function() {};
|
|
Module1.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op1', 'op2', 'op3']);
|
|
var Module2 = function() {};
|
|
Module2.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op4', 'op5']);
|
|
db.modules = [
|
|
new Module1(),
|
|
new Module2()
|
|
];
|
|
db.store = {
|
|
batch: sinon.stub().callsArg(1)
|
|
};
|
|
|
|
it('should call blockHandler in all modules and perform operations', function(done) {
|
|
db.blockHandler('block', true, function(err) {
|
|
should.not.exist(err);
|
|
db.store.batch.args[0][0].should.deep.equal(['op1', 'op2', 'op3', 'op4', 'op5']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should give an error if one of the modules gives an error', function(done) {
|
|
var Module3 = function() {};
|
|
Module3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error'));
|
|
db.modules.push(new Module3());
|
|
|
|
db.blockHandler('block', true, function(err) {
|
|
should.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#getAPIMethods', function() {
|
|
it('should return the correct db methods', function() {
|
|
var db = new DB({store: memdown});
|
|
db.modules = [];
|
|
var methods = db.getAPIMethods();
|
|
methods.length.should.equal(2);
|
|
});
|
|
|
|
it('should also return modules API methods', function() {
|
|
var module1 = {
|
|
getAPIMethods: function() {
|
|
return [
|
|
['module1-one', module1, module1, 2],
|
|
['module1-two', module1, module1, 2]
|
|
];
|
|
}
|
|
};
|
|
var module2 = {
|
|
getAPIMethods: function() {
|
|
return [
|
|
['moudle2-one', module2, module2, 1]
|
|
];
|
|
}
|
|
};
|
|
|
|
var db = new DB({store: memdown});
|
|
db.modules = [module1, module2];
|
|
|
|
var methods = db.getAPIMethods();
|
|
methods.length.should.equal(5);
|
|
});
|
|
});
|
|
|
|
describe('#addModule', function() {
|
|
it('instantiate module and add to db.modules', function() {
|
|
var Module1 = function(options) {
|
|
BaseModule.call(this, options);
|
|
};
|
|
inherits(Module1, BaseModule);
|
|
|
|
var db = new DB({store: memdown});
|
|
db.modules = [];
|
|
db.addModule(Module1);
|
|
|
|
db.modules.length.should.equal(1);
|
|
should.exist(db.modules[0].db);
|
|
});
|
|
|
|
it('should throw an error if module is not an instance of BaseModule', function() {
|
|
var Module2 = function(options) {};
|
|
var db = new DB({store: memdown});
|
|
db.modules = [];
|
|
|
|
(function() {
|
|
db.addModule(Module2);
|
|
}).should.throw('bitcore.ErrorInvalidArgumentType');
|
|
});
|
|
});
|
|
});
|