918 lines
29 KiB
JavaScript
918 lines
29 KiB
JavaScript
'use strict';
|
|
|
|
var should = require('chai').should();
|
|
var sinon = require('sinon');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var proxyquire = require('proxyquire');
|
|
var index = require('../../');
|
|
var DB = index.services.DB;
|
|
var blockData = require('../data/livenet-345003.json');
|
|
var bitcore = require('bitcore-lib');
|
|
var Networks = bitcore.Networks;
|
|
var Block = bitcore.Block;
|
|
var BufferUtil = bitcore.util.buffer;
|
|
var Transaction = bitcore.Transaction;
|
|
var transactionData = require('../data/bitcoin-transactions.json');
|
|
var chainHashes = require('../data/hashes.json');
|
|
var chainData = require('../data/testnet-blocks.json');
|
|
var errors = index.errors;
|
|
var memdown = require('memdown');
|
|
var levelup = require('levelup');
|
|
|
|
describe('DB Service', function() {
|
|
|
|
function hexlebuf(hexString){
|
|
return BufferUtil.reverse(new Buffer(hexString, 'hex'));
|
|
}
|
|
|
|
function lebufhex(buf) {
|
|
return BufferUtil.reverse(buf).toString('hex');
|
|
}
|
|
|
|
var baseConfig = {
|
|
node: {
|
|
network: Networks.testnet,
|
|
datadir: 'testdir'
|
|
},
|
|
store: memdown
|
|
};
|
|
|
|
var genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
|
|
|
|
|
|
describe('#_setDataPath', function() {
|
|
it('should set the database path', function() {
|
|
var config = {
|
|
node: {
|
|
network: Networks.livenet,
|
|
datadir: process.env.HOME + '/.bitcoin'
|
|
},
|
|
store: memdown
|
|
};
|
|
var db = new DB(config);
|
|
db.dataPath.should.equal(process.env.HOME + '/.bitcoin/bitcore-node.db');
|
|
});
|
|
it('should load the db for testnet', function() {
|
|
var config = {
|
|
node: {
|
|
network: Networks.testnet,
|
|
datadir: process.env.HOME + '/.bitcoin'
|
|
},
|
|
store: memdown
|
|
};
|
|
var db = new DB(config);
|
|
db.dataPath.should.equal(process.env.HOME + '/.bitcoin/testnet3/bitcore-node.db');
|
|
});
|
|
it('error with unknown network', function() {
|
|
var config = {
|
|
node: {
|
|
network: 'unknown',
|
|
datadir: process.env.HOME + '/.bitcoin'
|
|
},
|
|
store: memdown
|
|
};
|
|
(function() {
|
|
var db = new DB(config);
|
|
}).should.throw('Unknown network');
|
|
});
|
|
it('should load the db with regtest', function() {
|
|
// Switch to use regtest
|
|
// Networks.remove(Networks.testnet);
|
|
Networks.add({
|
|
name: 'regtest',
|
|
alias: 'regtest',
|
|
pubkeyhash: 0x6f,
|
|
privatekey: 0xef,
|
|
scripthash: 0xc4,
|
|
xpubkey: 0x043587cf,
|
|
xprivkey: 0x04358394,
|
|
networkMagic: 0xfabfb5da,
|
|
port: 18444,
|
|
dnsSeeds: [ ]
|
|
});
|
|
var regtest = Networks.get('regtest');
|
|
var config = {
|
|
node: {
|
|
network: regtest,
|
|
datadir: process.env.HOME + '/.bitcoin'
|
|
},
|
|
store: memdown
|
|
};
|
|
var db = new DB(config);
|
|
db.dataPath.should.equal(process.env.HOME + '/.bitcoin/regtest/bitcore-node.db');
|
|
Networks.remove(regtest);
|
|
});
|
|
});
|
|
|
|
describe('#start', function() {
|
|
var TestDB;
|
|
|
|
before(function() {
|
|
TestDB = proxyquire('../../lib/services/db', {
|
|
fs: {
|
|
existsSync: sinon.stub().returns(true)
|
|
},
|
|
levelup: sinon.stub()
|
|
});
|
|
});
|
|
|
|
it('should emit ready', function(done) {
|
|
var db = new TestDB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
on: sinon.spy(),
|
|
genesisBuffer: genesisBuffer
|
|
};
|
|
db.loadTip = sinon.stub().callsArg(0);
|
|
db.connectBlock = sinon.stub().callsArg(1);
|
|
db.sync = sinon.stub();
|
|
var readyFired = false;
|
|
db.on('ready', function() {
|
|
readyFired = true;
|
|
});
|
|
db.start(function() {
|
|
readyFired.should.equal(true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('will call sync when there is a new tip', function(done) {
|
|
var db = new TestDB(baseConfig);
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = new EventEmitter();
|
|
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
|
db.loadTip = sinon.stub().callsArg(0);
|
|
db.connectBlock = sinon.stub().callsArg(1);
|
|
db.sync = sinon.stub();
|
|
db.start(function() {
|
|
db.sync = function() {
|
|
done();
|
|
};
|
|
db.node.services.bitcoind.emit('tip', 10);
|
|
});
|
|
});
|
|
|
|
it('will not call sync when there is a new tip and shutting down', function(done) {
|
|
var db = new TestDB(baseConfig);
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = new EventEmitter();
|
|
db.node.services.bitcoind.syncPercentage = sinon.spy();
|
|
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
|
db.loadTip = sinon.stub().callsArg(0);
|
|
db.connectBlock = sinon.stub().callsArg(1);
|
|
db.node.stopping = true;
|
|
db.sync = sinon.stub();
|
|
db.start(function() {
|
|
db.sync.callCount.should.equal(1);
|
|
db.node.services.bitcoind.once('tip', function() {
|
|
db.sync.callCount.should.equal(1);
|
|
done();
|
|
});
|
|
db.node.services.bitcoind.emit('tip', 10);
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe('#stop', function() {
|
|
it('should wait until db has stopped syncing before closing leveldb', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.store = {
|
|
close: sinon.stub().callsArg(0)
|
|
};
|
|
db.bitcoindSyncing = true;
|
|
|
|
db.stop(function(err) {
|
|
should.not.exist(err);
|
|
done();
|
|
});
|
|
|
|
setTimeout(function() {
|
|
db.bitcoindSyncing = false;
|
|
}, 15);
|
|
});
|
|
});
|
|
|
|
describe('#getTransaction', function() {
|
|
it('will return a NotFound error', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.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(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.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(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.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('#loadTip', function() {
|
|
it('genesis block if no metadata is found in the db', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.genesis = Block.fromBuffer(genesisBuffer);
|
|
db.store = {
|
|
get: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
|
|
};
|
|
db.connectBlock = sinon.stub().callsArg(1);
|
|
db.sync = sinon.stub();
|
|
db.loadTip(function() {
|
|
should.exist(db.tip);
|
|
db.tip.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('tip from the database if it exists', function(done) {
|
|
var node = {
|
|
network: Networks.testnet,
|
|
datadir: 'testdir',
|
|
services: {
|
|
bitcoind: {
|
|
genesisBuffer: genesisBuffer,
|
|
on: sinon.stub(),
|
|
getBlockIndex: sinon.stub().returns({height: 1})
|
|
}
|
|
}
|
|
};
|
|
var tipHash = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
|
|
var tip = Block.fromBuffer(genesisBuffer);
|
|
var db = new DB({node: node});
|
|
db.store = {
|
|
get: sinon.stub().callsArgWith(2, null, new Buffer(tipHash, 'hex'))
|
|
};
|
|
db.getBlock = sinon.stub().callsArgWith(1, null, tip);
|
|
db.sync = sinon.stub();
|
|
db.loadTip(function() {
|
|
should.exist(db.tip);
|
|
db.tip.hash.should.equal(tipHash);
|
|
db.tip.__height.should.equal(1);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('give error if levelup error', function(done) {
|
|
var node = {
|
|
network: Networks.testnet,
|
|
datadir: 'testdir',
|
|
services: {
|
|
bitcoind: {
|
|
genesisBuffer: genesisBuffer,
|
|
on: sinon.stub()
|
|
}
|
|
}
|
|
};
|
|
var db = new DB({node: node});
|
|
db.store = {
|
|
get: sinon.stub().callsArgWith(2, new Error('test'))
|
|
};
|
|
db.loadTip(function(err) {
|
|
should.exist(err);
|
|
err.message.should.equal('test');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should try 3 times before giving error from getBlock', function(done) {
|
|
var node = {
|
|
network: Networks.testnet,
|
|
datadir: 'testdir',
|
|
services: {
|
|
bitcoind: {
|
|
genesisBuffer: genesisBuffer,
|
|
on: sinon.stub(),
|
|
getBlockIndex: sinon.stub().returns({height: 1})
|
|
}
|
|
}
|
|
};
|
|
var db = new DB({node: node});
|
|
db.retryInterval = 10;
|
|
var tipHash = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
|
|
db.store = {
|
|
get: sinon.stub().callsArgWith(2, null, new Buffer(tipHash, 'hex'))
|
|
};
|
|
db.getBlock = sinon.stub().callsArgWith(1, new Error('test'));
|
|
db.loadTip(function(err) {
|
|
should.exist(err);
|
|
db.getBlock.callCount.should.equal(3);
|
|
err.message.should.equal('test');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#getBlock', function() {
|
|
var db = new DB(baseConfig);
|
|
var blockBuffer = new Buffer(blockData, 'hex');
|
|
var expectedBlock = Block.fromBuffer(blockBuffer);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlock: sinon.stub().callsArgWith(1, null, blockBuffer)
|
|
};
|
|
|
|
it('should get the block from bitcoin daemon', function(done) {
|
|
db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
|
|
should.not.exist(err);
|
|
block.hash.should.equal(expectedBlock.hash);
|
|
done();
|
|
});
|
|
});
|
|
it('should give an error when bitcoind.js gives an error', function(done) {
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {};
|
|
db.node.services.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('#getBlockHashesByTimestamp', function() {
|
|
it('should get the correct block hashes', function(done) {
|
|
var db = new DB(baseConfig);
|
|
var readStream = new EventEmitter();
|
|
db.store = {
|
|
createReadStream: sinon.stub().returns(readStream)
|
|
};
|
|
|
|
var block1 = {
|
|
hash: '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b',
|
|
timestamp: 1441911909
|
|
};
|
|
|
|
var block2 = {
|
|
hash: '000000000383752a55a0b2891ce018fd0fdc0b6352502772b034ec282b4a1bf6',
|
|
timestamp: 1441913112
|
|
};
|
|
|
|
db.getBlockHashesByTimestamp(1441914000, 1441911000, function(err, hashes) {
|
|
should.not.exist(err);
|
|
hashes.should.deep.equal([block2.hash, block1.hash]);
|
|
done();
|
|
});
|
|
|
|
readStream.emit('data', {
|
|
key: db._encodeBlockIndexKey(block2.timestamp),
|
|
value: db._encodeBlockIndexValue(block2.hash)
|
|
});
|
|
|
|
readStream.emit('data', {
|
|
key: db._encodeBlockIndexKey(block1.timestamp),
|
|
value: db._encodeBlockIndexValue(block1.hash)
|
|
});
|
|
|
|
readStream.emit('close');
|
|
});
|
|
|
|
it('should give an error if the stream has an error', function(done) {
|
|
var db = new DB(baseConfig);
|
|
var readStream = new EventEmitter();
|
|
db.store = {
|
|
createReadStream: sinon.stub().returns(readStream)
|
|
};
|
|
|
|
db.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) {
|
|
should.exist(err);
|
|
err.message.should.equal('error');
|
|
done();
|
|
});
|
|
|
|
readStream.emit('error', new Error('error'));
|
|
|
|
readStream.emit('close');
|
|
});
|
|
|
|
it('should give an error if the timestamp is out of range', function(done) {
|
|
var db = new DB(baseConfig);
|
|
var readStream = new EventEmitter();
|
|
db.store = {
|
|
createReadStream: sinon.stub().returns(readStream)
|
|
};
|
|
|
|
db.getBlockHashesByTimestamp(-1, -5, function(err, hashes) {
|
|
should.exist(err);
|
|
err.message.should.equal('Invalid Argument: timestamp out of bounds');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#getPrevHash', function() {
|
|
it('should return prevHash from bitcoind', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlockIndex: sinon.stub().returns({
|
|
prevHash: 'prevhash'
|
|
})
|
|
};
|
|
|
|
db.getPrevHash('hash', function(err, prevHash) {
|
|
should.not.exist(err);
|
|
prevHash.should.equal('prevhash');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should give an error if bitcoind could not find it', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlockIndex: sinon.stub().returns(null)
|
|
};
|
|
|
|
db.getPrevHash('hash', function(err, prevHash) {
|
|
should.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#getTransactionWithBlockInfo', function() {
|
|
it('should give a transaction with height and timestamp', function(done) {
|
|
var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
|
|
var info = {
|
|
blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6',
|
|
height: 530482,
|
|
timestamp: 1439559434000,
|
|
buffer: txBuffer
|
|
};
|
|
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, info)
|
|
};
|
|
|
|
db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) {
|
|
should.not.exist(err);
|
|
tx.__blockHash.should.equal(info.blockHash);
|
|
tx.__height.should.equal(info.height);
|
|
tx.__timestamp.should.equal(info.timestamp);
|
|
done();
|
|
});
|
|
});
|
|
it('should give an error if one occurred', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, new Error('error'))
|
|
};
|
|
|
|
db.getTransactionWithBlockInfo('tx', true, function(err, tx) {
|
|
should.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#sendTransaction', function() {
|
|
it('should handle a basic serialized transaction hex string', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
sendTransaction: sinon.stub().returns('txid')
|
|
};
|
|
|
|
var tx = 'hexstring';
|
|
db.sendTransaction(tx, function(err, txid) {
|
|
should.not.exist(err);
|
|
txid.should.equal('txid');
|
|
done();
|
|
});
|
|
});
|
|
it('should give the txid on success', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
sendTransaction: sinon.stub().returns('txid')
|
|
};
|
|
|
|
var tx = new Transaction();
|
|
tx.serialize = sinon.stub().returns('txstring');
|
|
db.sendTransaction(tx, function(err, txid) {
|
|
should.not.exist(err);
|
|
tx.serialize.callCount.should.equal(1);
|
|
txid.should.equal('txid');
|
|
done();
|
|
});
|
|
});
|
|
it('should give an error if bitcoind threw an error', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
sendTransaction: sinon.stub().throws(new Error('error'))
|
|
};
|
|
|
|
var tx = new Transaction();
|
|
tx.serialize = sinon.stub().returns('txstring');
|
|
db.sendTransaction(tx, function(err, txid) {
|
|
tx.serialize.callCount.should.equal(1);
|
|
should.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#estimateFee', function() {
|
|
it('should pass along the fee from bitcoind', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
estimateFee: sinon.stub().returns(1000)
|
|
};
|
|
|
|
db.estimateFee(5, function(err, fee) {
|
|
should.not.exist(err);
|
|
fee.should.equal(1000);
|
|
db.node.services.bitcoind.estimateFee.args[0][0].should.equal(5);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#connectBlock', function() {
|
|
it('should remove block from mempool and call blockHandler with true', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.mempool = {
|
|
removeBlock: sinon.stub()
|
|
};
|
|
db.runAllBlockHandlers = sinon.stub().callsArg(2);
|
|
db.connectBlock({hash: 'hash'}, function(err) {
|
|
should.not.exist(err);
|
|
db.runAllBlockHandlers.args[0][1].should.equal(true);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#disconnectBlock', function() {
|
|
it('should call blockHandler with false', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.runAllBlockHandlers = sinon.stub().callsArg(2);
|
|
db.disconnectBlock({hash: 'hash'}, function(err) {
|
|
should.not.exist(err);
|
|
db.runAllBlockHandlers.args[0][1].should.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#runAllBlockHandlers', function() {
|
|
var db = new DB(baseConfig);
|
|
var Service1 = function() {};
|
|
Service1.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op1', 'op2', 'op3']);
|
|
var Service2 = function() {};
|
|
Service2.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op4', 'op5']);
|
|
var Service3 = function() {};
|
|
var Service4 = function() {};
|
|
Service4.prototype.blockHandler = sinon.stub().callsArgWith(2, null, 'bad-value');
|
|
db.node = {};
|
|
db.node.services = {
|
|
service1: new Service1(),
|
|
service2: new Service2()
|
|
};
|
|
db.store = {
|
|
batch: sinon.stub().callsArg(1)
|
|
};
|
|
|
|
var block = {
|
|
hash: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863',
|
|
header: {
|
|
timestamp: 1441906365
|
|
}
|
|
};
|
|
|
|
it('should call blockHandler in all services and perform operations', function(done) {
|
|
db.runAllBlockHandlers(block, true, function(err) {
|
|
should.not.exist(err);
|
|
var tipOp = {
|
|
type: 'put',
|
|
key: DB.PREFIXES.TIP,
|
|
value: new Buffer('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863', 'hex')
|
|
}
|
|
var blockOp = {
|
|
type: 'put',
|
|
key: db._encodeBlockIndexKey(1441906365),
|
|
value: db._encodeBlockIndexValue('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863')
|
|
};
|
|
db.store.batch.args[0][0].should.deep.equal([tipOp, blockOp, 'op1', 'op2', 'op3', 'op4', 'op5']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should give an error if one of the services gives an error', function(done) {
|
|
var Service3 = function() {};
|
|
Service3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error'));
|
|
db.node.services.service3 = new Service3();
|
|
|
|
db.runAllBlockHandlers(block, true, function(err) {
|
|
should.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should not give an error if a service does not have blockHandler', function(done) {
|
|
db.node = {};
|
|
db.node.services = {
|
|
service3: new Service3()
|
|
};
|
|
|
|
db.runAllBlockHandlers(block, true, function(err) {
|
|
should.not.exist(err);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should throw an error if blockHandler gives unexpected result', function() {
|
|
db.node = {};
|
|
db.node.services = {
|
|
service4: new Service4()
|
|
};
|
|
|
|
(function() {
|
|
db.runAllBlockHandlers(block, true, function(err) {
|
|
should.not.exist(err);
|
|
});
|
|
}).should.throw('bitcore.ErrorInvalidArgument');
|
|
});
|
|
});
|
|
|
|
describe('#getAPIMethods', function() {
|
|
it('should return the correct db methods', function() {
|
|
var db = new DB(baseConfig);
|
|
db.node = {};
|
|
db.node.services = {};
|
|
var methods = db.getAPIMethods();
|
|
methods.length.should.equal(6);
|
|
});
|
|
});
|
|
|
|
describe('#findCommonAncestor', function() {
|
|
it('will find an ancestor 6 deep', function(done) {
|
|
var db = new DB(baseConfig);
|
|
db.tip = {
|
|
hash: chainHashes[chainHashes.length - 1]
|
|
};
|
|
|
|
var expectedAncestor = chainHashes[chainHashes.length - 6];
|
|
|
|
var mainBlocks = {};
|
|
for(var i = chainHashes.length - 1; i > chainHashes.length - 10; i--) {
|
|
var hash = chainHashes[i];
|
|
var prevHash = hexlebuf(chainHashes[i - 1]);
|
|
mainBlocks[hash] = {
|
|
header: {
|
|
prevHash: prevHash
|
|
}
|
|
};
|
|
}
|
|
|
|
var forkedBlocks = {
|
|
'd7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82': {
|
|
header: {
|
|
prevHash: hexlebuf('76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a')
|
|
},
|
|
hash: 'd7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82'
|
|
},
|
|
'76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a': {
|
|
header: {
|
|
prevHash: hexlebuf('f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c')
|
|
},
|
|
hash: '76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a'
|
|
},
|
|
'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c': {
|
|
header: {
|
|
prevHash: hexlebuf('2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31')
|
|
},
|
|
hash: 'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c'
|
|
},
|
|
'2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31': {
|
|
header: {
|
|
prevHash: hexlebuf('adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453')
|
|
},
|
|
hash: '2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31'
|
|
},
|
|
'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453': {
|
|
header: {
|
|
prevHash: hexlebuf('3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618')
|
|
},
|
|
hash: 'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453'
|
|
},
|
|
'3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618': {
|
|
header: {
|
|
prevHash: hexlebuf(expectedAncestor)
|
|
},
|
|
hash: '3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618'
|
|
}
|
|
};
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlockIndex: function(hash) {
|
|
var forkedBlock = forkedBlocks[hash];
|
|
var mainBlock = mainBlocks[hash];
|
|
var prevHash;
|
|
if (forkedBlock && forkedBlock.header.prevHash) {
|
|
prevHash = BufferUtil.reverse(forkedBlock.header.prevHash).toString('hex');
|
|
} else if (mainBlock && mainBlock.header.prevHash){
|
|
prevHash = BufferUtil.reverse(mainBlock.header.prevHash).toString('hex');
|
|
} else {
|
|
return null;
|
|
}
|
|
return {
|
|
prevHash: prevHash
|
|
};
|
|
}
|
|
};
|
|
var block = forkedBlocks['d7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82'];
|
|
db.findCommonAncestor(block, function(err, ancestorHash) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
ancestorHash.should.equal(expectedAncestor);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#syncRewind', function() {
|
|
it('will undo blocks 6 deep', function() {
|
|
var db = new DB(baseConfig);
|
|
var ancestorHash = chainHashes[chainHashes.length - 6];
|
|
db.tip = {
|
|
__height: 10,
|
|
hash: chainHashes[chainHashes.length],
|
|
header: {
|
|
prevHash: hexlebuf(chainHashes[chainHashes.length - 1])
|
|
}
|
|
};
|
|
db.emit = sinon.stub();
|
|
db.getBlock = function(hash, callback) {
|
|
setImmediate(function() {
|
|
for(var i = chainHashes.length; i > 0; i--) {
|
|
var block = {
|
|
hash: chainHashes[i],
|
|
header: {
|
|
prevHash: hexlebuf(chainHashes[i - 1])
|
|
}
|
|
};
|
|
if (chainHashes[i] === hash) {
|
|
callback(null, block);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
db.node.services = {};
|
|
db.disconnectBlock = function(block, callback) {
|
|
setImmediate(callback);
|
|
};
|
|
db.findCommonAncestor = function(block, callback) {
|
|
setImmediate(function() {
|
|
callback(null, ancestorHash);
|
|
});
|
|
};
|
|
var forkedBlock = {};
|
|
db.syncRewind(forkedBlock, function(err) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
db.tip.__height.should.equal(4);
|
|
db.tip.hash.should.equal(ancestorHash);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#sync', function() {
|
|
var node = new EventEmitter();
|
|
var syncConfig = {
|
|
node: node,
|
|
store: memdown
|
|
};
|
|
syncConfig.node.network = Networks.testnet;
|
|
syncConfig.node.datadir = 'testdir';
|
|
it('will get and add block up to the tip height', function(done) {
|
|
var db = new DB(syncConfig);
|
|
var blockBuffer = new Buffer(blockData, 'hex');
|
|
var block = Block.fromBuffer(blockBuffer);
|
|
db.node.services = {};
|
|
db.runAllMempoolIndexes = sinon.stub().callsArg(0);
|
|
db.node.services.bitcoind = {
|
|
getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
|
|
isSynced: sinon.stub().returns(true),
|
|
height: 1
|
|
};
|
|
db.tip = {
|
|
__height: 0,
|
|
hash: lebufhex(block.header.prevHash)
|
|
};
|
|
db.emit = sinon.stub();
|
|
db.cache = {
|
|
hashes: {}
|
|
};
|
|
db.connectBlock = function(block, callback) {
|
|
db.tip.__height += 1;
|
|
callback();
|
|
};
|
|
db.node.once('synced', function() {
|
|
db.runAllMempoolIndexes.callCount.should.equal(1);
|
|
done();
|
|
});
|
|
db.sync();
|
|
});
|
|
it('will exit and emit error with error from bitcoind.getBlock', function(done) {
|
|
var db = new DB(syncConfig);
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlock: sinon.stub().callsArgWith(1, new Error('test error')),
|
|
height: 1
|
|
};
|
|
db.tip = {
|
|
__height: 0
|
|
};
|
|
db.node.on('error', function(err) {
|
|
err.message.should.equal('test error');
|
|
done();
|
|
});
|
|
db.sync();
|
|
});
|
|
it('will stop syncing when the node is stopping', function(done) {
|
|
var db = new DB(syncConfig);
|
|
var blockBuffer = new Buffer(blockData, 'hex');
|
|
var block = Block.fromBuffer(blockBuffer);
|
|
db.node.services = {};
|
|
db.node.services.bitcoind = {
|
|
getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
|
|
isSynced: sinon.stub().returns(true),
|
|
height: 1
|
|
};
|
|
db.tip = {
|
|
__height: 0,
|
|
hash: block.prevHash
|
|
};
|
|
db.emit = sinon.stub();
|
|
db.cache = {
|
|
hashes: {}
|
|
};
|
|
db.connectBlock = function(block, callback) {
|
|
db.tip.__height += 1;
|
|
callback();
|
|
};
|
|
db.node.stopping = true;
|
|
var synced = false;
|
|
db.node.once('synced', function() {
|
|
synced = true;
|
|
});
|
|
db.sync();
|
|
setTimeout(function() {
|
|
synced.should.equal(false);
|
|
done();
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
});
|