bitcore-node-zcash/lib/chain.js

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;