/*!
* chainentry.js - chainentry object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('bsert');
const bio = require('bufio');
const BN = require('bcrypto/lib/bn.js');
const consensus = require('../protocol/consensus');
const hash256 = require('bcrypto/lib/hash256');
const util = require('../utils/util');
const Headers = require('../primitives/headers');
const InvItem = require('../primitives/invitem');
const {inspectSymbol} = require('../utils');
/*
* Constants
*/
const ZERO = new BN(0);
/**
* Chain Entry
* Represents an entry in the chain. Unlike
* other bitcoin fullnodes, we store the
* chainwork _with_ the entry in order to
* avoid reading the entire chain index on
* boot and recalculating the chainworks.
* @alias module:blockchain.ChainEntry
* @property {Hash} hash
* @property {Number} version
* @property {Hash} prevBlock
* @property {Hash} merkleRoot
* @property {Number} time
* @property {Number} bits
* @property {Number} nonce
* @property {Number} height
* @property {BN} chainwork
* @property {Hash} rhash
*/
class ChainEntry {
/**
* Create a chain entry.
* @constructor
* @param {Object?} options
*/
constructor(options) {
this.hash = consensus.ZERO_HASH;
this.version = 1;
this.prevBlock = consensus.ZERO_HASH;
this.merkleRoot = consensus.ZERO_HASH;
this.time = 0;
this.bits = 0;
this.nonce = 0;
this.height = 0;
this.chainwork = ZERO;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
*/
fromOptions(options) {
assert(options, 'Block data is required.');
assert(Buffer.isBuffer(options.hash));
assert((options.version >>> 0) === options.version);
assert(Buffer.isBuffer(options.prevBlock));
assert(Buffer.isBuffer(options.merkleRoot));
assert((options.time >>> 0) === options.time);
assert((options.bits >>> 0) === options.bits);
assert((options.nonce >>> 0) === options.nonce);
assert((options.height >>> 0) === options.height);
assert(!options.chainwork || BN.isBN(options.chainwork));
this.hash = options.hash;
this.version = options.version;
this.prevBlock = options.prevBlock;
this.merkleRoot = options.merkleRoot;
this.time = options.time;
this.bits = options.bits;
this.nonce = options.nonce;
this.height = options.height;
this.chainwork = options.chainwork || ZERO;
return this;
}
/**
* Instantiate chainentry from options.
* @param {Object} options
* @param {ChainEntry} prev - Previous entry.
* @returns {ChainEntry}
*/
static fromOptions(options, prev) {
return new this().fromOptions(options, prev);
}
/**
* Calculate the proof: (1 << 256) / (target + 1)
* @returns {BN} proof
*/
getProof() {
const target = consensus.fromCompact(this.bits);
if (target.isNeg() || target.isZero())
return new BN(0);
return ChainEntry.MAX_CHAINWORK.div(target.iaddn(1));
}
/**
* Calculate the chainwork by
* adding proof to previous chainwork.
* @returns {BN} chainwork
*/
getChainwork(prev) {
const proof = this.getProof();
if (!prev)
return proof;
return proof.iadd(prev.chainwork);
}
/**
* Test against the genesis block.
* @returns {Boolean}
*/
isGenesis() {
return this.height === 0;
}
/**
* Test whether the entry contains an unknown version bit.
* @param {Network} network
* @returns {Boolean}
*/
hasUnknown(network) {
const TOP_MASK = consensus.VERSION_TOP_MASK;
const TOP_BITS = consensus.VERSION_TOP_BITS;
const bits = (this.version & TOP_MASK) >>> 0;
if (bits !== TOP_BITS)
return false;
return (this.version & network.unknownBits) !== 0;
}
/**
* Test whether the entry contains a version bit.
* @param {Number} bit
* @returns {Boolean}
*/
hasBit(bit) {
return consensus.hasBit(this.version, bit);
}
/**
* Get little-endian block hash.
* @returns {Hash}
*/
rhash() {
return util.revHex(this.hash);
}
/**
* Inject properties from block.
* @private
* @param {Block|MerkleBlock} block
* @param {ChainEntry} prev - Previous entry.
*/
fromBlock(block, prev) {
this.hash = block.hash();
this.version = block.version;
this.prevBlock = block.prevBlock;
this.merkleRoot = block.merkleRoot;
this.time = block.time;
this.bits = block.bits;
this.nonce = block.nonce;
this.height = prev ? prev.height + 1: 0;
this.chainwork = this.getChainwork(prev);
return this;
}
/**
* Instantiate chainentry from block.
* @param {Block|MerkleBlock} block
* @param {ChainEntry} prev - Previous entry.
* @returns {ChainEntry}
*/
static fromBlock(block, prev) {
return new this().fromBlock(block, prev);
}
/**
* Serialize the entry to internal database format.
* @returns {Buffer}
*/
toRaw() {
const bw = bio.write(116);
bw.writeU32(this.version);
bw.writeHash(this.prevBlock);
bw.writeHash(this.merkleRoot);
bw.writeU32(this.time);
bw.writeU32(this.bits);
bw.writeU32(this.nonce);
bw.writeU32(this.height);
bw.writeBytes(this.chainwork.toArrayLike(Buffer, 'le', 32));
return bw.render();
}
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
fromRaw(data) {
const br = bio.read(data, true);
const hash = hash256.digest(br.readBytes(80));
br.seek(-80);
this.hash = hash;
this.version = br.readU32();
this.prevBlock = br.readHash();
this.merkleRoot = br.readHash();
this.time = br.readU32();
this.bits = br.readU32();
this.nonce = br.readU32();
this.height = br.readU32();
this.chainwork = new BN(br.readBytes(32), 'le');
return this;
}
/**
* Deserialize the entry.
* @param {Buffer} data
* @returns {ChainEntry}
*/
static fromRaw(data) {
return new this().fromRaw(data);
}
/**
* Serialize the entry to an object more
* suitable for JSON serialization.
* @returns {Object}
*/
toJSON() {
return {
hash: util.revHex(this.hash),
version: this.version,
prevBlock: util.revHex(this.prevBlock),
merkleRoot: util.revHex(this.merkleRoot),
time: this.time,
bits: this.bits,
nonce: this.nonce,
height: this.height,
chainwork: this.chainwork.toString('hex', 64)
};
}
/**
* Inject properties from json object.
* @private
* @param {Object} json
*/
fromJSON(json) {
assert(json, 'Block data is required.');
assert(typeof json.hash === 'string');
assert((json.version >>> 0) === json.version);
assert(typeof json.prevBlock === 'string');
assert(typeof json.merkleRoot === 'string');
assert((json.time >>> 0) === json.time);
assert((json.bits >>> 0) === json.bits);
assert((json.nonce >>> 0) === json.nonce);
assert(typeof json.chainwork === 'string');
this.hash = util.fromRev(json.hash);
this.version = json.version;
this.prevBlock = util.fromRev(json.prevBlock);
this.merkleRoot = util.fromRev(json.merkleRoot);
this.time = json.time;
this.bits = json.bits;
this.nonce = json.nonce;
this.height = json.height;
this.chainwork = new BN(json.chainwork, 'hex');
return this;
}
/**
* Instantiate block from jsonified object.
* @param {Object} json
* @returns {ChainEntry}
*/
static fromJSON(json) {
return new this().fromJSON(json);
}
/**
* Convert the entry to a headers object.
* @returns {Headers}
*/
toHeaders() {
return Headers.fromEntry(this);
}
/**
* Convert the entry to an inv item.
* @returns {InvItem}
*/
toInv() {
return new InvItem(InvItem.types.BLOCK, this.hash);
}
/**
* Return a more user-friendly object.
* @returns {Object}
*/
[inspectSymbol]() {
const json = this.toJSON();
json.version = json.version.toString(16);
return json;
}
/**
* Test whether an object is a {@link ChainEntry}.
* @param {Object} obj
* @returns {Boolean}
*/
static isChainEntry(obj) {
return obj instanceof ChainEntry;
}
}
/**
* The max chainwork (1 << 256).
* @const {BN}
*/
ChainEntry.MAX_CHAINWORK = new BN(1).ushln(256);
/*
* Expose
*/
module.exports = ChainEntry;