wip
This commit is contained in:
parent
f8df875c34
commit
2bd769cce0
@ -66,6 +66,9 @@ Logger.prototype._log = function(color) {
|
|||||||
args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0];
|
args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0];
|
||||||
}
|
}
|
||||||
var fn = console.log;
|
var fn = console.log;
|
||||||
|
if (level === 'error') {
|
||||||
|
fn = console.error;
|
||||||
|
}
|
||||||
fn.apply(console, args);
|
fn.apply(console, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
22
lib/node.js
22
lib/node.js
@ -3,9 +3,8 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
var assert = require('assert');
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var Networks = bitcore.Networks;
|
|
||||||
var $ = bitcore.util.preconditions;
|
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
var index = require('./');
|
var index = require('./');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
@ -25,7 +24,6 @@ function Node(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.services) {
|
if (config.services) {
|
||||||
$.checkArgument(Array.isArray(config.services));
|
|
||||||
this._unloadedServices = config.services;
|
this._unloadedServices = config.services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,15 +47,7 @@ Node.prototype._init = function(config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Node.prototype._setNetwork = function(config) {
|
Node.prototype._setNetwork = function(config) {
|
||||||
if (config.network === 'testnet') {
|
this.network = config.network;
|
||||||
this.network = Networks.get('testnet');
|
|
||||||
} else if (config.network === 'regtest') {
|
|
||||||
Networks.enableRegtest();
|
|
||||||
this.network = Networks.get('regtest');
|
|
||||||
} else {
|
|
||||||
this.network = Networks.defaultNetwork;
|
|
||||||
}
|
|
||||||
$.checkState(this.network, 'Unrecognized network');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Node.prototype.openBus = function(options) {
|
Node.prototype.openBus = function(options) {
|
||||||
@ -107,7 +97,7 @@ Node.prototype._getServiceOrder = function(services) {
|
|||||||
|
|
||||||
var name = names[i];
|
var name = names[i];
|
||||||
var service = servicesByName[name];
|
var service = servicesByName[name];
|
||||||
$.checkState(service, 'Required dependency "' + name + '" not available.');
|
assert(service, 'Required dependency "' + name + '" not available.');
|
||||||
|
|
||||||
addToStack(service.module.dependencies);
|
addToStack(service.module.dependencies);
|
||||||
|
|
||||||
@ -131,9 +121,9 @@ Node.prototype._startService = function(serviceInfo, callback) {
|
|||||||
|
|
||||||
var config;
|
var config;
|
||||||
if (serviceInfo.config) {
|
if (serviceInfo.config) {
|
||||||
$.checkState(_.isObject(serviceInfo.config));
|
assert(_.isObject(serviceInfo.config));
|
||||||
$.checkState(!serviceInfo.config.node);
|
assert(serviceInfo.config.node);
|
||||||
$.checkState(!serviceInfo.config.name);
|
assert(serviceInfo.config.name);
|
||||||
config = serviceInfo.config;
|
config = serviceInfo.config;
|
||||||
} else {
|
} else {
|
||||||
config = {};
|
config = {};
|
||||||
|
|||||||
@ -371,7 +371,7 @@ AddressService.prototype._onReorg = function(commonAncestorHeader, oldBlockList)
|
|||||||
var address;
|
var address;
|
||||||
for(var k = 0; k < tx.inputs.length; k++) {
|
for(var k = 0; k < tx.inputs.length; k++) {
|
||||||
var input = tx.inputs[k];
|
var input = tx.inputs[k];
|
||||||
address = utils.getAddressString({ tx: tx, item: input, network: this._network });
|
address = input.address;
|
||||||
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
continue;
|
continue;
|
||||||
@ -386,7 +386,7 @@ AddressService.prototype._onReorg = function(commonAncestorHeader, oldBlockList)
|
|||||||
//outputs
|
//outputs
|
||||||
for(k = 0; k < tx.outputs.length; k++) {
|
for(k = 0; k < tx.outputs.length; k++) {
|
||||||
var output = tx.outputs[k];
|
var output = tx.outputs[k];
|
||||||
address = utils.getAddressString({ tx: tx, item: output, network: this._network });
|
address = output.address;
|
||||||
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
continue;
|
continue;
|
||||||
@ -431,7 +431,7 @@ AddressService.prototype.onBlock = function(block, callback) {
|
|||||||
|
|
||||||
AddressService.prototype._processInput = function(tx, input, opts) {
|
AddressService.prototype._processInput = function(tx, input, opts) {
|
||||||
|
|
||||||
var address = utils.getAddressString({ item: input });
|
var address = input.address;
|
||||||
|
|
||||||
if(!address) {
|
if(!address) {
|
||||||
return;
|
return;
|
||||||
@ -464,7 +464,7 @@ AddressService.prototype._processInput = function(tx, input, opts) {
|
|||||||
|
|
||||||
AddressService.prototype._processOutput = function(tx, output, index, opts) {
|
AddressService.prototype._processOutput = function(tx, output, index, opts) {
|
||||||
|
|
||||||
var address = utils.getAddressString({ item: output });
|
var address = output.address;
|
||||||
|
|
||||||
if(!address) {
|
if(!address) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -317,6 +317,7 @@ BlockService.prototype._handleReorg = function(hash, allHeaders) {
|
|||||||
this._reorging = false;
|
this._reorging = false;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// get the blocks from our current tip to the given hash, non-inclusive
|
// get the blocks from our current tip to the given hash, non-inclusive
|
||||||
|
|||||||
@ -6,9 +6,6 @@ var async = require('async');
|
|||||||
var levelup = require('levelup');
|
var levelup = require('levelup');
|
||||||
var leveldown = require('leveldown');
|
var leveldown = require('leveldown');
|
||||||
var mkdirp = require('mkdirp');
|
var mkdirp = require('mkdirp');
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var Networks = bitcore.Networks;
|
|
||||||
var $ = bitcore.util.preconditions;
|
|
||||||
var Service = require('../../service');
|
var Service = require('../../service');
|
||||||
var constants = require('../../constants');
|
var constants = require('../../constants');
|
||||||
var log = require('../../index').log;
|
var log = require('../../index').log;
|
||||||
@ -38,10 +35,10 @@ function DB(options) {
|
|||||||
|
|
||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
|
|
||||||
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.getNetworkName()];
|
this.GENESIS_HASH = constants.BITCOIN_GENESIS_HASH[this.node.network];
|
||||||
|
|
||||||
this.node.on('stopping', function() {
|
this.node.on('stopping', function() {
|
||||||
log.warn('Node is stopping, gently closing the database.');
|
log.warn('Node is stopping, gently closing the database. Please wait, this could take a while.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,19 +49,16 @@ DB.dependencies = [];
|
|||||||
DB.prototype._onError = function(err) {
|
DB.prototype._onError = function(err) {
|
||||||
log.error('Db Service: error: ' + err);
|
log.error('Db Service: error: ' + err);
|
||||||
this.node.stop();
|
this.node.stop();
|
||||||
process.exit(-1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DB.prototype._setDataPath = function() {
|
DB.prototype._setDataPath = function() {
|
||||||
$.checkState(this.node.datadir, 'Node is expected to have a "datadir" property');
|
assert(fs.existsSync(this.node.datadir), 'Node is expected to have a "datadir" property');
|
||||||
if (this.node.network === Networks.livenet) {
|
if (this.node.network === 'livenet' || this.node.network === 'mainnet') {
|
||||||
this.dataPath = this.node.datadir + '/bitcore-node.db';
|
this.dataPath = this.node.datadir + '/bitcore-node.db';
|
||||||
} else if (this.node.network === Networks.testnet) {
|
} else if (this.node.network === 'regtest') {
|
||||||
if (this.node.network.regtestEnabled) {
|
|
||||||
this.dataPath = this.node.datadir + '/regtest/bitcore-node.db';
|
this.dataPath = this.node.datadir + '/regtest/bitcore-node.db';
|
||||||
} else {
|
} else if (this.node.network === 'testnet') {
|
||||||
this.dataPath = this.node.datadir + '/testnet3/bitcore-node.db';
|
this.dataPath = this.node.datadir + '/testnet/bitcore-node.db';
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown network: ' + this.network);
|
throw new Error('Unknown network: ' + this.network);
|
||||||
}
|
}
|
||||||
@ -77,13 +71,12 @@ DB.prototype._setVersion = function(callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DB.prototype.start = function(callback) {
|
DB.prototype.start = function(callback) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!fs.existsSync(self.dataPath)) {
|
if (!fs.existsSync(this.dataPath)) {
|
||||||
mkdirp.sync(this.dataPath);
|
mkdirp.sync(this.dataPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
self._store = levelup(self.dataPath, { db: self.levelupStore, keyEncoding: 'binary', valueEncoding: 'binary'});
|
this._store = levelup(this.dataPath, { db: this.levelupStore, keyEncoding: 'binary', valueEncoding: 'binary'});
|
||||||
|
|
||||||
setImmediate(callback);
|
setImmediate(callback);
|
||||||
|
|
||||||
@ -135,7 +128,7 @@ DB.prototype.put = function(key, value, callback) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (self._stopping) {
|
if (self._stopping) {
|
||||||
return;
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
self._store.put(key, value, callback);
|
self._store.put(key, value, callback);
|
||||||
@ -195,6 +188,7 @@ DB.prototype.close = function(callback) {
|
|||||||
if (this._store && this._store.isOpen()) {
|
if (this._store && this._store.isOpen()) {
|
||||||
this._store.close(callback);
|
this._store.close(callback);
|
||||||
}
|
}
|
||||||
|
setImmediate(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
DB.prototype.getAPIMethods = function() {
|
DB.prototype.getAPIMethods = function() {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ MempoolService.dependencies = ['db', 'block'];
|
|||||||
|
|
||||||
MempoolService.prototype.getAPIMethods = function() {
|
MempoolService.prototype.getAPIMethods = function() {
|
||||||
var methods = [
|
var methods = [
|
||||||
['getMempoolTransaction', this, this.getTransaction, 1]
|
['getMempoolTransaction', this, this.getMempoolTransaction, 1]
|
||||||
];
|
];
|
||||||
return methods;
|
return methods;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ var utils = require('../../utils');
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var LRU = require('lru-cache');
|
var LRU = require('lru-cache');
|
||||||
var Unit = require('bitcore-lib').Unit;
|
var Unit = require('bitcore-lib').Unit;
|
||||||
|
var log = require('../../index').log;
|
||||||
|
|
||||||
function TransactionService(options) {
|
function TransactionService(options) {
|
||||||
BaseService.call(this, options);
|
BaseService.call(this, options);
|
||||||
@ -93,7 +94,6 @@ TransactionService.prototype.sendTransaction = function(tx, callback) {
|
|||||||
TransactionService.prototype.start = function(callback) {
|
TransactionService.prototype.start = function(callback) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
self._setListeners();
|
|
||||||
|
|
||||||
self._db.getPrefix(self.name, function(err, prefix) {
|
self._db.getPrefix(self.name, function(err, prefix) {
|
||||||
|
|
||||||
@ -161,12 +161,6 @@ TransactionService.prototype.onBlock = function(block, callback) {
|
|||||||
return self._processTransaction(tx, { block: block });
|
return self._processTransaction(tx, { block: block });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (operations && operations.length > 0) {
|
|
||||||
|
|
||||||
self._db.batch(operations);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(function() {
|
setImmediate(function() {
|
||||||
callback(null, operations);
|
callback(null, operations);
|
||||||
});
|
});
|
||||||
@ -175,13 +169,14 @@ TransactionService.prototype.onBlock = function(block, callback) {
|
|||||||
|
|
||||||
TransactionService.prototype._onReorg = function(commonAncestorHeader, oldBlockList) {
|
TransactionService.prototype._onReorg = function(commonAncestorHeader, oldBlockList) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
// if the common ancestor block height is greater than our own, then nothing to do for the reorg
|
// if the common ancestor block height is greater than our own, then nothing to do for the reorg
|
||||||
if (this._tip.height <= commonAncestorHeader.height) {
|
if (self._tip.height <= commonAncestorHeader.height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the tip to the common ancestor in case something goes wrong with the reorg
|
// set the tip to the common ancestor in case something goes wrong with the reorg
|
||||||
var tipOps = utils.encodeTip({ hash: commonAncestorHeader.hash, height: commonAncestorHeader.height }, this.name);
|
var tipOps = utils.encodeTip({ hash: commonAncestorHeader.hash, height: commonAncestorHeader.height }, self.name);
|
||||||
|
|
||||||
var removalOps = [{
|
var removalOps = [{
|
||||||
type: 'put',
|
type: 'put',
|
||||||
@ -195,12 +190,18 @@ TransactionService.prototype._onReorg = function(commonAncestorHeader, oldBlockL
|
|||||||
var tx = block.transactions[j];
|
var tx = block.transactions[j];
|
||||||
removalOps.push({
|
removalOps.push({
|
||||||
type: 'del',
|
type: 'del',
|
||||||
key: this._encoding.encodeTransactionKey(tx.id)
|
key: self._encoding.encodeTransactionKey(tx.id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._db.batch(removalOps);
|
self._db.batch(removalOps, function(err) {
|
||||||
|
if (err) {
|
||||||
|
log.error('Transaction Service: error removing operations during reorg.' + err);
|
||||||
|
self.node.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -227,14 +228,6 @@ TransactionService.prototype._processTransaction = function(tx, opts) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionService.prototype._setListeners = function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.on('reorg', self._onReorg.bind(self));
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
TransactionService.prototype._startSubscriptions = function() {
|
TransactionService.prototype._startSubscriptions = function() {
|
||||||
|
|
||||||
if (this._subscribed) {
|
if (this._subscribed) {
|
||||||
|
|||||||
162
lib/utils.js
162
lib/utils.js
@ -1,36 +1,22 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BufferUtil = bitcore.util.buffer;
|
|
||||||
var MAX_SAFE_INTEGER = 0x1fffffffffffff; // 2 ^ 53 - 1
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var constants = require('./constants');
|
var constants = require('./constants');
|
||||||
|
var BN = require('bn.js');
|
||||||
|
|
||||||
var utils = {};
|
var utils = {};
|
||||||
utils.isHash = function(value) {
|
|
||||||
return typeof value === 'string' && value.length === 64 && /^[0-9a-fA-F]+$/.test(value);
|
utils.isHeight = function(blockArg) {
|
||||||
};
|
return _.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg));
|
||||||
|
|
||||||
utils.isSafeNatural = function(value) {
|
|
||||||
return typeof value === 'number' &&
|
|
||||||
isFinite(value) &&
|
|
||||||
Math.floor(value) === value &&
|
|
||||||
value >= 0 &&
|
|
||||||
value <= MAX_SAFE_INTEGER;
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.startAtZero = function(obj, key) {
|
|
||||||
if (!obj.hasOwnProperty(key)) {
|
|
||||||
obj[key] = 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//start
|
||||||
utils.isAbsolutePath = require('path').isAbsolute;
|
utils.isAbsolutePath = require('path').isAbsolute;
|
||||||
if (!utils.isAbsolutePath) {
|
if (!utils.isAbsolutePath) {
|
||||||
utils.isAbsolutePath = require('path-is-absolute');
|
utils.isAbsolutePath = require('path-is-absolute');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//main
|
||||||
utils.parseParamsWithJSON = function(paramsArg) {
|
utils.parseParamsWithJSON = function(paramsArg) {
|
||||||
var params = paramsArg.map(function(paramArg) {
|
var params = paramsArg.map(function(paramArg) {
|
||||||
var param;
|
var param;
|
||||||
@ -45,9 +31,12 @@ utils.parseParamsWithJSON = function(paramsArg) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
utils.getTerminalKey = function(startKey) {
|
utils.getTerminalKey = function(startKey) {
|
||||||
var endKey = Buffer.from(startKey);
|
if (!startKey || !Buffer.isBuffer(startKey)) {
|
||||||
endKey.writeUInt8(startKey.readUInt8(startKey.length - 1) + 1, startKey.length - 1);
|
return;
|
||||||
return endKey;
|
}
|
||||||
|
var bn = new BN(startKey);
|
||||||
|
var endBN = bn.iaddn(1);
|
||||||
|
return endBN.toBuffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.diffTime = function(time) {
|
utils.diffTime = function(time) {
|
||||||
@ -55,27 +44,6 @@ utils.diffTime = function(time) {
|
|||||||
return (diff[0] * 1E9 + diff[1])/(1E9 * 1.0);
|
return (diff[0] * 1E9 + diff[1])/(1E9 * 1.0);
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.reverseBufferToString = function(buf) {
|
|
||||||
if (_.isString(buf)) {
|
|
||||||
buf = new Buffer(buf, 'hex');
|
|
||||||
}
|
|
||||||
return BufferUtil.reverse(buf).toString('hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.getAddressString = function(opts) {
|
|
||||||
|
|
||||||
if (!opts.item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this does not handle P2PK correctly, it uses the address and not the
|
|
||||||
// pubkey
|
|
||||||
if (opts.address) {
|
|
||||||
return address.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.sendError = function(err, res) {
|
utils.sendError = function(err, res) {
|
||||||
if (err.statusCode) {
|
if (err.statusCode) {
|
||||||
res.status(err.statusCode).send(err.message);
|
res.status(err.statusCode).send(err.message);
|
||||||
@ -85,108 +53,6 @@ utils.sendError = function(err, res) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.getWalletId = exports.generateJobId = function() {
|
|
||||||
return crypto.randomBytes(16).toString('hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.getWalletId = exports.generateJobId = function() {
|
|
||||||
return crypto.randomBytes(16).toString('hex');
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.toJSONL = function(obj) {
|
|
||||||
var str = JSON.stringify(obj);
|
|
||||||
str = str.replace(/\n/g, '');
|
|
||||||
return str + '\n';
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.normalizeTimeStamp = function(addressArg) {
|
|
||||||
var addresses = [addressArg];
|
|
||||||
if (Array.isArray(addressArg)) {
|
|
||||||
addresses = addressArg;
|
|
||||||
}
|
|
||||||
return addresses;
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.normalizeTimeStamp = function(value) {
|
|
||||||
if (value > 0xffffffff) {
|
|
||||||
value = Math.round(value/1000);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.delimitedStringParse = function(delim, str) {
|
|
||||||
function tryJSONparse(str) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(str);
|
|
||||||
} catch(e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ret = [];
|
|
||||||
|
|
||||||
if (delim === null) {
|
|
||||||
return tryJSONparse(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = str.split(delim);
|
|
||||||
for(var i = 0; i < list.length; i++) {
|
|
||||||
ret.push(tryJSONparse(list[i]));
|
|
||||||
}
|
|
||||||
ret = _.compact(ret);
|
|
||||||
return ret.length === 0 ? false : ret;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.toIntIfNumberLike = function(a) {
|
|
||||||
if (!/[^\d]+/.test(a)) {
|
|
||||||
return parseInt(a);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.getBlockInfoString = function(tip, best) {
|
|
||||||
|
|
||||||
var diff = best - tip;
|
|
||||||
var astr = diff + ' blocks behind.';
|
|
||||||
|
|
||||||
if (diff === -1) {
|
|
||||||
astr = Math.abs(diff) + ' block ahead. Peer may be syncing or we may need to reorganize our chain after new blocks arrive.';
|
|
||||||
} else if (diff < 1) {
|
|
||||||
astr = Math.abs(diff) + ' blocks ahead. Peer may be syncing or we may need to reorganize our chain after new blocks arrive.';
|
|
||||||
} else if (diff === 1) {
|
|
||||||
astr = diff + ' block behind.';
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
utils.joinListsOnIndex = function(indexList, list1, list2, direction) {
|
|
||||||
|
|
||||||
var newChains = [];
|
|
||||||
|
|
||||||
indexList.forEach(function(index) {
|
|
||||||
var otherList = list2[index];
|
|
||||||
if (direction && direction === 'reverse') {
|
|
||||||
newChains.push(otherList.concat(list1));
|
|
||||||
} else {
|
|
||||||
newChains.push(list1.concat(otherList));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return newChains;
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.removeItemsByIndexList = function(indexList, list) {
|
|
||||||
var ret = [];
|
|
||||||
for(var i = 0; i < list.length; i++) {
|
|
||||||
var item = list[i];
|
|
||||||
if (indexList.indexOf(i) === -1) {
|
|
||||||
ret.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.encodeTip = function(tip, name) {
|
utils.encodeTip = function(tip, name) {
|
||||||
var key = Buffer.concat([ constants.DB_PREFIX,
|
var key = Buffer.concat([ constants.DB_PREFIX,
|
||||||
new Buffer('tip-' + name, 'utf8') ]);
|
new Buffer('tip-' + name, 'utf8') ]);
|
||||||
@ -199,10 +65,6 @@ utils.encodeTip = function(tip, name) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.isHeight = function(blockArg) {
|
|
||||||
return _.isNumber(blockArg) || (blockArg.length < 40 && /^[0-9]+$/.test(blockArg))
|
|
||||||
};
|
|
||||||
|
|
||||||
utils.SimpleMap = function SimpleMap() {
|
utils.SimpleMap = function SimpleMap() {
|
||||||
var object = {};
|
var object = {};
|
||||||
var array = [];
|
var array = [];
|
||||||
|
|||||||
@ -1,56 +1,154 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var expect = require('chai').expect;
|
var chai = require('chai');
|
||||||
var bitcore = require('bitcore-lib');
|
var should = chai.should();
|
||||||
var DB = require('../../../lib/services/db');
|
var assert = chai.assert;
|
||||||
|
var expect = chai.expect;
|
||||||
|
var DBService = require('../../../lib/services/db');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var Levelup = require('levelup');
|
||||||
|
|
||||||
describe('DB', function() {
|
describe('DB', function() {
|
||||||
|
|
||||||
describe('Reorg', function() {
|
var dbService;
|
||||||
|
|
||||||
before(function() {
|
var sandbox;
|
||||||
this.db = new DB({
|
beforeEach(function() {
|
||||||
node: {
|
sandbox = sinon.sandbox.create();
|
||||||
network: bitcore.Networks.testnet,
|
dbService = new DBService({
|
||||||
datadir: '/tmp',
|
node: {
|
||||||
services: ''
|
services: [],
|
||||||
}
|
datadir: '/tmp',
|
||||||
|
network: 'regtest',
|
||||||
|
on: sinon.stub()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#start', function() {
|
||||||
|
it('should start the db service by creating a db dir, ' +
|
||||||
|
' if necessary, and setting the store', function(done) {
|
||||||
|
dbService.start(function() {
|
||||||
|
dbService._store.should.be.instanceOf(Levelup);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#stop', function() {
|
||||||
|
it('should stop if store not open', function(done) {
|
||||||
|
dbService.stop(function() {
|
||||||
|
var close = sandbox.stub().callsArg(0);
|
||||||
|
dbService._store = { close: close };
|
||||||
|
dbService._stopping.should.be.true;
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
this.db.tip = { hash: 'ff', height: 444 };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a reorg from a common ancenstor that is in our set', function() {
|
it('should stop if store open', function(done) {
|
||||||
var block1 = { hash: '11', header: { prevHash: new Buffer('ff', 'hex') } };
|
dbService.stop(function() {
|
||||||
var block2 = { hash: '22', header: { prevHash: new Buffer('11', 'hex') } };
|
var close = sandbox.stub().callsArg(0);
|
||||||
var block3 = { hash: '33', header: { prevHash: new Buffer('22', 'hex') } };
|
dbService._store = { close: close, isOpen: sinon.stub().returns(true) };
|
||||||
var block4 = { hash: '44', header: { prevHash: new Buffer('22', 'hex') } };
|
dbService._stopping.should.be.true;
|
||||||
//blocks must be passed in the order that they are received.
|
done();
|
||||||
var blocks = [ block3, block2, block1, block4 ];
|
});
|
||||||
expect(this.db.detectReorg(blocks)).to.deep.equal(block3);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should detect a reorg from a common ancenstor that is not in our set', function() {
|
describe('#_onError', function() {
|
||||||
var block1 = { hash: '11', header: { prevHash: new Buffer('ff', 'hex') } };
|
it('should stop the db', function() {
|
||||||
var block2 = { hash: '22', header: { prevHash: new Buffer('11', 'hex') } };
|
var stop = sandbox.stub();
|
||||||
var block3 = { hash: '33', header: { prevHash: new Buffer('22', 'hex') } };
|
dbService.node = { stop: stop };
|
||||||
var block4 = { hash: '44', header: { prevHash: new Buffer('ee', 'hex') } };
|
dbService._onError(new Error('some error'));
|
||||||
var blocks = [ block3, block2, block1, block4 ];
|
stop.should.be.calledOnce;
|
||||||
expect(this.db.detectReorg(blocks)).to.deep.equal(block4);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not detect a reorg', function() {
|
describe('#_setDataPath', function() {
|
||||||
this.db.reorgTipHash = null;
|
|
||||||
var block1 = { hash: '11', header: { prevHash: new Buffer('ff', 'hex') } };
|
it('should set the data path', function() {
|
||||||
var block2 = { hash: '22', header: { prevHash: new Buffer('11', 'hex') } };
|
dbService._setDataPath();
|
||||||
var block3 = { hash: '33', header: { prevHash: new Buffer('22', 'hex') } };
|
dbService.dataPath.should.equal('/tmp/regtest/bitcore-node.db');
|
||||||
var block4 = { hash: '44', header: { prevHash: new Buffer('33', 'hex') } };
|
|
||||||
var blocks = [ block3, block2, block1, block4 ];
|
|
||||||
var actual = this.db.detectReorg(blocks);
|
|
||||||
expect(actual).to.be.undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#_setVersion', function() {
|
||||||
|
it('should set the version', function(done) {
|
||||||
|
var put = sandbox.stub(dbService, 'put').callsArgWith(2, null);
|
||||||
|
dbService._setVersion(function(err) {
|
||||||
|
put.should.be.calledOnce;
|
||||||
|
put.args[0][0].toString('hex').should.deep.equal('ffff76657273696f6e');
|
||||||
|
put.args[0][1].toString('hex').should.deep.equal('00000001');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#get', function() {
|
||||||
|
it('should get a value from the db', function(done) {
|
||||||
|
var get = sandbox.stub().callsArgWith(2, null, 'data');
|
||||||
|
dbService._store = { get: get };
|
||||||
|
dbService.get('key', function(err, value) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
value.should.equal('data');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get a value while the node is shutting down', function(done) {
|
||||||
|
dbService._stopping = true;
|
||||||
|
dbService.get('key', function(err, value) {
|
||||||
|
err.message.should.equal('Shutdown sequence underway, not able to complete the query');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#put', function() {
|
||||||
|
it('should put a value in the db', function(done) {
|
||||||
|
var put = sandbox.stub().callsArgWith(2, null);
|
||||||
|
dbService._store = { put: put };
|
||||||
|
dbService.put(new Buffer('key'), new Buffer('value'), function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
put.should.be.calledOnce;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow an operation while the node is shutting down', function(done) {
|
||||||
|
dbService._stopping = true;
|
||||||
|
dbService.put(new Buffer('key'), new Buffer('value'), function(err) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#batch', function() {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#createReadStream', function() {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#createKeyStream', function() {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#close', function() {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getServiceTip', function() {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getPrefix', function() {
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var should = require('chai').should();
|
|
||||||
var sinon = require('sinon');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BufferUtil = bitcore.util.buffer;
|
|
||||||
var DB = require('../../../lib/services/db');
|
|
||||||
var Networks = bitcore.Networks;
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var mkdirp = require('mkdirp');
|
|
||||||
var blocks = require('../../data/blocks.json');
|
|
||||||
|
|
||||||
|
|
||||||
describe('DB', function() {
|
|
||||||
|
|
||||||
var bitcoind = {
|
|
||||||
on: function(event, callback) {
|
|
||||||
},
|
|
||||||
genesisBuffer: blocks.genesis
|
|
||||||
};
|
|
||||||
|
|
||||||
var node = {
|
|
||||||
network: Networks.testnet,
|
|
||||||
datadir: '/tmp/datadir',
|
|
||||||
services: { bitcoind: bitcoind },
|
|
||||||
on: sinon.stub(),
|
|
||||||
once: sinon.stub()
|
|
||||||
};
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
rimraf(node.datadir, function(err) {
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
mkdirp(node.datadir, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.db = new DB({node: node});
|
|
||||||
this.emitter = new EventEmitter();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('Reorg', function() {
|
|
||||||
|
|
||||||
it('should start db service', function(done) {
|
|
||||||
this.db.start(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var should = require('chai').should();
|
|
||||||
var sinon = require('sinon');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BufferUtil = bitcore.util.buffer;
|
|
||||||
var Reorg = require('../../../lib/services/db/reorg');
|
|
||||||
|
|
||||||
describe('Reorg', function() {
|
|
||||||
describe('full-test', function() {
|
|
||||||
before(function() {
|
|
||||||
sinon.stub(BufferUtil, 'reverse', function(input) {
|
|
||||||
return {
|
|
||||||
toString: function() {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function() {
|
|
||||||
BufferUtil.reverse.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle a reorg correctly', function(done) {
|
|
||||||
var tipBlocks = [
|
|
||||||
{hash: 'main0', header: {prevHash: null}},
|
|
||||||
{hash: 'main1', header: {prevHash: 'main0'}},
|
|
||||||
{hash: 'fork1', header: {prevHash: 'main1'}},
|
|
||||||
{hash: 'fork2', header: {prevHash: 'fork1'}}
|
|
||||||
];
|
|
||||||
|
|
||||||
var concurrentBlocks = [
|
|
||||||
{hash: 'main0', header: {prevHash: null}},
|
|
||||||
{hash: 'main1', header: {prevHash: 'main0'}},
|
|
||||||
{hash: 'fork1', header: {prevHash: 'main1'}},
|
|
||||||
{hash: 'fork2', header: {prevHash: 'fork1'}},
|
|
||||||
{hash: 'fork3', header: {prevHash: 'fork2'}}
|
|
||||||
];
|
|
||||||
|
|
||||||
var bitcoindBlocks = [
|
|
||||||
{hash: 'main0', header: {prevHash: null}},
|
|
||||||
{hash: 'main1', header: {prevHash: 'main0'}},
|
|
||||||
{hash: 'main2', header: {prevHash: 'main1'}},
|
|
||||||
{hash: 'main3', header: {prevHash: 'main2'}}
|
|
||||||
];
|
|
||||||
|
|
||||||
var allBlocks = tipBlocks.concat(concurrentBlocks, bitcoindBlocks);
|
|
||||||
|
|
||||||
var db = {
|
|
||||||
tip: tipBlocks[3],
|
|
||||||
concurrentTip: concurrentBlocks[4],
|
|
||||||
store: {
|
|
||||||
batch: sinon.stub().callsArg(1)
|
|
||||||
},
|
|
||||||
getConcurrentBlockOperations: sinon.stub().callsArgWith(2, null, []),
|
|
||||||
getSerialBlockOperations: sinon.stub().callsArgWith(2, null, []),
|
|
||||||
getConcurrentTipOperation: sinon.stub().returns(null),
|
|
||||||
getTipOperation: sinon.stub().returns(null)
|
|
||||||
};
|
|
||||||
|
|
||||||
var node = {
|
|
||||||
services: {
|
|
||||||
bitcoind: {
|
|
||||||
getBlock: function(hash, callback) {
|
|
||||||
var block;
|
|
||||||
for(var i = 0; i < allBlocks.length; i++) {
|
|
||||||
if(allBlocks[i].hash === hash) {
|
|
||||||
block = allBlocks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(function() {
|
|
||||||
if(!block) {
|
|
||||||
return callback(new Error('Block not found: ' + hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, block);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBlockHeader: function(hash, callback) {
|
|
||||||
var header;
|
|
||||||
for(var i = 0; i < allBlocks.length; i++) {
|
|
||||||
if(allBlocks[i].hash === hash) {
|
|
||||||
header = allBlocks[i].header;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(function() {
|
|
||||||
if(!header) {
|
|
||||||
return callback(new Error('Block header not found: ' + hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, header);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var reorg = new Reorg(node, db);
|
|
||||||
|
|
||||||
reorg.handleReorg(bitcoindBlocks[3].hash, function(err) {
|
|
||||||
should.not.exist(err);
|
|
||||||
|
|
||||||
db.tip.hash.should.equal('main3');
|
|
||||||
db.concurrentTip.hash.should.equal('main3');
|
|
||||||
|
|
||||||
db.getConcurrentBlockOperations.callCount.should.equal(5);
|
|
||||||
db.getConcurrentBlockOperations.args[0][0].should.equal(concurrentBlocks[4]);
|
|
||||||
db.getConcurrentBlockOperations.args[0][1].should.equal(false);
|
|
||||||
db.getConcurrentBlockOperations.args[1][0].should.equal(concurrentBlocks[3]);
|
|
||||||
db.getConcurrentBlockOperations.args[1][1].should.equal(false);
|
|
||||||
db.getConcurrentBlockOperations.args[2][0].should.equal(concurrentBlocks[2]);
|
|
||||||
db.getConcurrentBlockOperations.args[2][1].should.equal(false);
|
|
||||||
db.getConcurrentBlockOperations.args[3][0].should.equal(bitcoindBlocks[2]);
|
|
||||||
db.getConcurrentBlockOperations.args[3][1].should.equal(true);
|
|
||||||
db.getConcurrentBlockOperations.args[4][0].should.equal(bitcoindBlocks[3]);
|
|
||||||
db.getConcurrentBlockOperations.args[4][1].should.equal(true);
|
|
||||||
|
|
||||||
db.getSerialBlockOperations.callCount.should.equal(4);
|
|
||||||
db.getSerialBlockOperations.args[0][0].should.deep.equal(tipBlocks[3]);
|
|
||||||
db.getSerialBlockOperations.args[0][1].should.equal(false);
|
|
||||||
db.getSerialBlockOperations.args[1][0].should.deep.equal(tipBlocks[2]);
|
|
||||||
db.getSerialBlockOperations.args[1][1].should.equal(false);
|
|
||||||
db.getSerialBlockOperations.args[2][0].should.deep.equal(bitcoindBlocks[2]);
|
|
||||||
db.getSerialBlockOperations.args[2][1].should.equal(true);
|
|
||||||
db.getSerialBlockOperations.args[3][0].should.deep.equal(bitcoindBlocks[3]);
|
|
||||||
db.getSerialBlockOperations.args[3][1].should.equal(true);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user