253 lines
6.3 KiB
JavaScript
253 lines
6.3 KiB
JavaScript
/**
|
|
* chainblock.js - chainblock object for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var bcoin = require('../bcoin');
|
|
var bn = require('bn.js');
|
|
var constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
var utils = bcoin.utils;
|
|
var assert = utils.assert;
|
|
var fs = bcoin.fs;
|
|
|
|
/**
|
|
* ChainBlock
|
|
*/
|
|
|
|
function ChainBlock(chain, data, prev) {
|
|
if (!(this instanceof ChainBlock))
|
|
return new ChainBlock(chain, data);
|
|
|
|
this.chain = chain;
|
|
this.hash = data.hash;
|
|
this.version = data.version;
|
|
this.prevBlock = data.prevBlock;
|
|
this.merkleRoot = data.merkleRoot;
|
|
this.ts = data.ts;
|
|
this.bits = data.bits;
|
|
this.nonce = data.nonce;
|
|
this.height = data.height;
|
|
this.chainwork = data.chainwork || this.getChainwork(prev);
|
|
|
|
assert(this.chainwork);
|
|
|
|
this.previous = [];
|
|
}
|
|
|
|
ChainBlock.BLOCK_SIZE = 116;
|
|
|
|
ChainBlock.prototype.getProof = function getProof() {
|
|
var target = utils.fromCompact(this.bits);
|
|
if (target.isNeg() || target.cmpn(0) === 0)
|
|
return new bn(0);
|
|
return new bn(1).ushln(256).div(target.addn(1));
|
|
};
|
|
|
|
ChainBlock.prototype.getChainwork = function getChainwork(prev) {
|
|
return (prev ? prev.chainwork : new bn(0)).add(this.getProof());
|
|
};
|
|
|
|
ChainBlock.prototype.isGenesis = function isGenesis() {
|
|
return this.hash === network.genesis.hash;
|
|
};
|
|
|
|
ChainBlock.prototype.toJSON = function toJSON() {
|
|
return {
|
|
hash: this.hash,
|
|
version: this.version,
|
|
prevBlock: this.prevBlock,
|
|
merkleRoot: this.merkleRoot,
|
|
ts: this.ts,
|
|
bits: this.bits,
|
|
nonce: this.nonce,
|
|
height: this.height
|
|
};
|
|
};
|
|
|
|
ChainBlock.fromJSON = function fromJSON(chain, json) {
|
|
return new ChainBlock(chain, json);
|
|
};
|
|
|
|
ChainBlock.prototype.toRaw = function toRaw() {
|
|
var res = new Buffer(ChainBlock.BLOCK_SIZE);
|
|
|
|
utils.write32(res, this.version, 0);
|
|
utils.copy(new Buffer(this.prevBlock, 'hex'), res, 4);
|
|
utils.copy(new Buffer(this.merkleRoot, 'hex'), res, 36);
|
|
utils.writeU32(res, this.ts, 68);
|
|
utils.writeU32(res, this.bits, 72);
|
|
utils.writeU32(res, this.nonce, 76);
|
|
utils.writeU32(res, this.height, 80);
|
|
utils.copy(new Buffer(this.chainwork.toArray('be', 32)), res, 84);
|
|
|
|
return res;
|
|
};
|
|
|
|
ChainBlock.fromRaw = function fromRaw(chain, p) {
|
|
return new ChainBlock(chain, {
|
|
hash: utils.toHex(utils.dsha256(p.slice(0, 80))),
|
|
version: utils.read32(p, 0),
|
|
prevBlock: utils.toHex(p.slice(4, 36)),
|
|
merkleRoot: utils.toHex(p.slice(36, 68)),
|
|
ts: utils.readU32(p, 68),
|
|
bits: utils.readU32(p, 72),
|
|
nonce: utils.readU32(p, 76),
|
|
height: utils.readU32(p, 80),
|
|
chainwork: new bn(p.slice(84, 116), 'be')
|
|
});
|
|
};
|
|
|
|
ChainBlock.prototype.getMedianTimeAsync = function getMedianTime(callback) {
|
|
var self = this;
|
|
var median = [];
|
|
var timeSpan = constants.block.medianTimespan;
|
|
var i = 0;
|
|
|
|
(function next(err, entry) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!entry || i >= timeSpan) {
|
|
median = median.sort();
|
|
return callback(null, median[median.length / 2 | 0]);
|
|
}
|
|
|
|
median[i] = entry.ts;
|
|
i++;
|
|
|
|
self.chain.db.get(entry.prevBlock, next);
|
|
})(null, this);
|
|
};
|
|
|
|
ChainBlock.prototype.isOutdatedAsync = function isOutdated(version, callback) {
|
|
return this.isSuperMajority(version, network.block.majorityRejectOutdated, callback);
|
|
};
|
|
|
|
ChainBlock.prototype.isUpgradedAsync = function isUpgraded(version, callback) {
|
|
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade, callback);
|
|
};
|
|
|
|
ChainBlock.prototype.isSuperMajorityAsync = function isSuperMajority(version, required, callback) {
|
|
var self = this;
|
|
var found = 0;
|
|
var majorityWindow = network.block.majorityWindow;
|
|
var i = 0;
|
|
|
|
(function next(err, entry) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!entry || i >= majorityWindow || found >= required)
|
|
return callback(null, found >= required);
|
|
|
|
if (entry.version >= version)
|
|
found++;
|
|
|
|
i++;
|
|
|
|
self.chain.db.get(entry.prevBlock, next);
|
|
})(null, this);
|
|
};
|
|
|
|
ChainBlock.prototype.alloc = function alloc(callback) {
|
|
var majorityWindow = network.block.majorityWindow;
|
|
var medianTimespan = constants.block.medianTimespan;
|
|
var powDiffInterval = network.powDiffInterval;
|
|
var allowMinDiff = network.powAllowMinDifficultyBlocks;
|
|
var max = Math.max(majorityWindow, medianTimespan);
|
|
if ((this.height + 1) % powDiffInterval === 0 || allowMinDiff)
|
|
max = Math.max(max, powDiffInterval);
|
|
return this._alloc(max, callback);
|
|
};
|
|
|
|
ChainBlock.prototype._alloc = function _alloc(max, callback) {
|
|
var self = this;
|
|
var entry = this;
|
|
|
|
assert(this.previous.length === 0);
|
|
|
|
// Try to do this iteratively and synchronously
|
|
// so we don't have to wait on nextTicks.
|
|
for (;;) {
|
|
this.previous.push(entry);
|
|
|
|
if (this.previous.length >= max)
|
|
return callback();
|
|
|
|
if (!this.chain.db.hasCache(entry.prevBlock)) {
|
|
this.previous.pop();
|
|
break;
|
|
}
|
|
|
|
entry = this.chain.db.getCache(entry.prevBlock);
|
|
}
|
|
|
|
(function next(err, entry) {
|
|
if (err) {
|
|
self.free();
|
|
return callback(err);
|
|
}
|
|
|
|
if (!entry)
|
|
return callback();
|
|
|
|
self.previous.push(entry);
|
|
|
|
if (self.previous.length >= max)
|
|
return callback();
|
|
|
|
self.chain.db.get(entry.prevBlock, next);
|
|
})(null, entry);
|
|
};
|
|
|
|
ChainBlock.prototype.free = function free() {
|
|
this.previous.length = 0;
|
|
};
|
|
|
|
ChainBlock.prototype.getMedianTime = function getMedianTime() {
|
|
var entry = this;
|
|
var median = [];
|
|
var timeSpan = constants.block.medianTimespan;
|
|
var i;
|
|
|
|
for (i = 0; i < timeSpan && entry; i++, entry = this.previous[i])
|
|
median.push(entry.ts);
|
|
|
|
median = median.sort();
|
|
|
|
return median[median.length / 2 | 0];
|
|
};
|
|
|
|
ChainBlock.prototype.isOutdated = function isOutdated(version) {
|
|
return this.isSuperMajority(version, network.block.majorityRejectOutdated);
|
|
};
|
|
|
|
ChainBlock.prototype.isUpgraded = function isUpgraded(version) {
|
|
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade);
|
|
};
|
|
|
|
ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required) {
|
|
var entry = this;
|
|
var found = 0;
|
|
var majorityWindow = network.block.majorityWindow;
|
|
var i;
|
|
|
|
for (i = 0; i < majorityWindow && found < required && entry; i++) {
|
|
if (entry.version >= version)
|
|
found++;
|
|
entry = this.previous[i + 1];
|
|
}
|
|
|
|
return found >= required;
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = ChainBlock;
|