263 lines
7.0 KiB
JavaScript
263 lines
7.0 KiB
JavaScript
'use strict';
|
|
|
|
var util = require('util');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var bitcore = require('bitcore');
|
|
var BN = bitcore.crypto.BN;
|
|
var $ = bitcore.util.preconditions;
|
|
var Block = require('./block');
|
|
var index = require('./index');
|
|
var log = index.log;
|
|
var utils = require('./utils');
|
|
|
|
var MAX_STACK_DEPTH = 1000;
|
|
|
|
/**
|
|
* Will instantiate a new Chain instance
|
|
* @param {Object} options - The options for the chain
|
|
* @param {Number} options.minBits - The minimum number of bits
|
|
* @param {Number} options.maxBits - The maximum number of bits
|
|
* @param {BN|Number} options.targetTimespan - The number of milliseconds for difficulty retargeting
|
|
* @param {BN|Number} options.targetSpacing - The number of milliseconds between blocks
|
|
* @returns {Chain}
|
|
* @extends BaseChain
|
|
* @constructor
|
|
*/
|
|
function Chain(opts) {
|
|
/* jshint maxstatements: 30 */
|
|
if (!(this instanceof Chain)) {
|
|
return new Chain(opts);
|
|
}
|
|
|
|
var self = this;
|
|
if(!opts) {
|
|
opts = {};
|
|
}
|
|
|
|
this.db = opts.db;
|
|
this.p2p = opts.p2p;
|
|
|
|
this.genesis = opts.genesis;
|
|
this.genesisOptions = opts.genesisOptions;
|
|
this.genesisWeight = new BN(0);
|
|
this.tip = null;
|
|
this.overrideTip = opts.overrideTip;
|
|
this.cache = {
|
|
hashes: {}, // dictionary of hash -> prevHash
|
|
chainHashes: {}
|
|
};
|
|
this.lastSavedMetadata = null;
|
|
this.lastSavedMetadataThreshold = 0; // Set this during syncing for faster performance
|
|
this.blockQueue = [];
|
|
this.processingBlockQueue = false;
|
|
this.builder = opts.builder || false;
|
|
this.ready = false;
|
|
|
|
this.on('initialized', function() {
|
|
self.initialized = true;
|
|
});
|
|
|
|
this.on('initialized', this._onInitialized.bind(this));
|
|
|
|
this.on('ready', function() {
|
|
log.debug('Chain is ready');
|
|
self.ready = true;
|
|
self.startBuilder();
|
|
});
|
|
|
|
this.minBits = opts.minBits || Chain.DEFAULTS.MIN_BITS;
|
|
this.maxBits = opts.maxBits || Chain.DEFAULTS.MAX_BITS;
|
|
|
|
this.maxHashes = opts.maxHashes || Chain.DEFAULTS.MAX_HASHES;
|
|
|
|
this.targetTimespan = opts.targetTimespan || Chain.DEFAULTS.TARGET_TIMESPAN;
|
|
this.targetSpacing = opts.targetSpacing || Chain.DEFAULTS.TARGET_SPACING;
|
|
|
|
this.node = opts.node;
|
|
|
|
return this;
|
|
}
|
|
|
|
util.inherits(Chain, EventEmitter);
|
|
|
|
Chain.DEFAULTS = {
|
|
MAX_HASHES: new BN('10000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
|
TARGET_TIMESPAN: 14 * 24 * 60 * 60 * 1000, // two weeks
|
|
TARGET_SPACING: 10 * 60 * 1000, // ten minutes
|
|
MAX_BITS: 0x1d00ffff,
|
|
MIN_BITS: 0x03000000
|
|
};
|
|
|
|
Chain.prototype._onInitialized = function() {
|
|
this.emit('ready');
|
|
};
|
|
|
|
Chain.prototype.start = function(callback) {
|
|
this.genesis = Block.fromBuffer(this.node.bitcoind.genesisBuffer);
|
|
this.once('initialized', callback);
|
|
this.initialize();
|
|
};
|
|
|
|
Chain.prototype.initialize = function() {
|
|
var self = this;
|
|
|
|
// Does our database already have a tip?
|
|
self.db.getMetadata(function getMetadataCallback(err, metadata) {
|
|
if(err) {
|
|
return self.emit('error', err);
|
|
} else if(!metadata || !metadata.tip) {
|
|
self.tip = self.genesis;
|
|
self.tip.__height = 0;
|
|
self.tip.__weight = self.genesisWeight;
|
|
self.db.putBlock(self.genesis, function putBlockCallback(err) {
|
|
if(err) {
|
|
return self.emit('error', err);
|
|
}
|
|
self.db._onChainAddBlock(self.genesis, function(err) {
|
|
if(err) {
|
|
return self.emit('error', err);
|
|
}
|
|
|
|
self.emit('addblock', self.genesis);
|
|
self.saveMetadata();
|
|
self.emit('initialized');
|
|
});
|
|
});
|
|
} else {
|
|
metadata.tip = metadata.tip;
|
|
self.db.getBlock(metadata.tip, function getBlockCallback(err, tip) {
|
|
if(err) {
|
|
return self.emit('error', err);
|
|
}
|
|
|
|
self.tip = tip;
|
|
self.tip.__height = metadata.tipHeight;
|
|
self.tip.__weight = new BN(metadata.tipWeight, 'hex');
|
|
self.cache = metadata.cache;
|
|
self.emit('initialized');
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
Chain.prototype.stop = function(callback) {
|
|
setImmediate(callback);
|
|
};
|
|
|
|
Chain.prototype._writeBlock = function(block, callback) {
|
|
// Update hashes
|
|
this.cache.hashes[block.hash] = block.prevHash;
|
|
// call db.putBlock to update prevHash index, but it won't write the block to disk
|
|
this.db.putBlock(block, callback);
|
|
};
|
|
|
|
Chain.prototype._validateBlock = function(block, callback) {
|
|
// All validation is done by bitcoind
|
|
setImmediate(callback);
|
|
};
|
|
|
|
Chain.prototype.startBuilder = function() {
|
|
// Unused in bitcoind.js
|
|
};
|
|
|
|
Chain.prototype.getWeight = function getWeight(blockHash, callback) {
|
|
var self = this;
|
|
var blockIndex = self.db.bitcoind.getBlockIndex(blockHash);
|
|
|
|
setImmediate(function() {
|
|
if (blockIndex) {
|
|
callback(null, new BN(blockIndex.chainWork, 'hex'));
|
|
} else {
|
|
return callback(new Error('Weight not found for ' + blockHash));
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Will get an array of hashes all the way to the genesis block for
|
|
* the chain based on "block hash" as the tip.
|
|
*
|
|
* @param {String} block hash - a block hash
|
|
* @param {Function} callback - A function that accepts: Error and Array of hashes
|
|
*/
|
|
Chain.prototype.getHashes = function getHashes(tipHash, callback) {
|
|
var self = this;
|
|
|
|
$.checkArgument(utils.isHash(tipHash));
|
|
|
|
var hashes = [];
|
|
var depth = 0;
|
|
|
|
getHashAndContinue(null, tipHash);
|
|
|
|
function getHashAndContinue(err, hash) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
depth++;
|
|
|
|
hashes.unshift(hash);
|
|
|
|
if (hash === self.genesis.hash) {
|
|
// Stop at the genesis block
|
|
self.cache.chainHashes[tipHash] = hashes;
|
|
callback(null, hashes);
|
|
} else if(self.cache.chainHashes[hash]) {
|
|
hashes.shift();
|
|
hashes = self.cache.chainHashes[hash].concat(hashes);
|
|
delete self.cache.chainHashes[hash];
|
|
self.cache.chainHashes[tipHash] = hashes;
|
|
callback(null, hashes);
|
|
} else {
|
|
// Continue with the previous hash
|
|
// check cache first
|
|
var prevHash = self.cache.hashes[hash];
|
|
if(prevHash) {
|
|
// Don't let the stack get too deep. Otherwise we will crash.
|
|
if(depth >= MAX_STACK_DEPTH) {
|
|
depth = 0;
|
|
return setImmediate(function() {
|
|
getHashAndContinue(null, prevHash);
|
|
});
|
|
} else {
|
|
return getHashAndContinue(null, prevHash);
|
|
}
|
|
} else {
|
|
// do a db call if we don't have it
|
|
self.db.getPrevHash(hash, function(err, prevHash) {
|
|
if(err) {
|
|
return callback(err);
|
|
}
|
|
|
|
return getHashAndContinue(null, prevHash);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
Chain.prototype.saveMetadata = function saveMetadata(callback) {
|
|
var self = this;
|
|
|
|
callback = callback || function() {};
|
|
|
|
if(self.lastSavedMetadata && Date.now() < self.lastSavedMetadata.getTime() + self.lastSavedMetadataThreshold) {
|
|
return callback();
|
|
}
|
|
|
|
var metadata = {
|
|
tip: self.tip ? self.tip.hash : null,
|
|
tipHeight: self.tip && self.tip.__height ? self.tip.__height : 0,
|
|
tipWeight: self.tip && self.tip.__weight ? self.tip.__weight.toString(16) : '0',
|
|
cache: self.cache
|
|
};
|
|
|
|
self.lastSavedMetadata = new Date();
|
|
|
|
self.db.putMetadata(metadata, callback);
|
|
};
|
|
|
|
module.exports = Chain;
|