Home Reference Source Test

src/main/generic/consensus/base/block/BlockHeader.js

class BlockHeader {
    /**
     * @param {Hash} prevHash
     * @param {Hash} interlinkHash
     * @param {Hash} bodyHash
     * @param {Hash} accountsHash
     * @param {number} nBits
     * @param {number} height
     * @param {number} timestamp
     * @param {number} nonce
     * @param {number} version
     */
    constructor(prevHash, interlinkHash, bodyHash, accountsHash, nBits, height, timestamp, nonce, version = BlockHeader.CURRENT_VERSION) {
        if (!NumberUtils.isUint16(version)) throw 'Malformed version';
        if (!Hash.isHash(prevHash)) throw 'Malformed prevHash';
        if (!Hash.isHash(interlinkHash)) throw 'Malformed interlinkHash';
        if (!Hash.isHash(bodyHash)) throw 'Malformed bodyHash';
        if (!Hash.isHash(accountsHash)) throw 'Malformed accountsHash';
        if (!NumberUtils.isUint32(nBits) || !BlockUtils.isValidCompact(nBits)) throw 'Malformed nBits';
        if (!NumberUtils.isUint32(height)) throw 'Invalid height';
        if (!NumberUtils.isUint32(timestamp)) throw 'Malformed timestamp';
        if (!NumberUtils.isUint32(nonce)) throw 'Malformed nonce';

        /** @type {number} */
        this._version = version;
        /** @type {Hash} */
        this._prevHash = prevHash;
        /** @type {Hash} */
        this._interlinkHash = interlinkHash;
        /** @type {Hash} */
        this._bodyHash = bodyHash;
        /** @type {Hash} */
        this._accountsHash = accountsHash;
        /** @type {number} */
        this._nBits = nBits;
        /** @type {number} */
        this._height = height;
        /** @type {number} */
        this._timestamp = timestamp;
        /** @type {number} */
        this._nonce = nonce;
    }

    /**
     * @param {SerialBuffer} buf
     * @returns {BlockHeader}
     */
    static unserialize(buf) {
        const version = buf.readUint16();
        if (!BlockHeader.SUPPORTED_VERSIONS.includes(version)) throw new Error(`Unsupported block version ${version}`);
        const prevHash = Hash.unserialize(buf);
        const interlinkHash = Hash.unserialize(buf);
        const bodyHash = Hash.unserialize(buf);
        const accountsHash = Hash.unserialize(buf);
        const nBits = buf.readUint32();
        const height = buf.readUint32();
        const timestamp = buf.readUint32();
        const nonce = buf.readUint32();
        return new BlockHeader(prevHash, interlinkHash, bodyHash, accountsHash, nBits, height, timestamp, nonce, version);
    }

    /**
     * @param {SerialBuffer} [buf]
     * @returns {SerialBuffer}
     */
    serialize(buf) {
        buf = buf || new SerialBuffer(this.serializedSize);
        buf.writeUint16(this._version);
        this._prevHash.serialize(buf);
        this._interlinkHash.serialize(buf);
        this._bodyHash.serialize(buf);
        this._accountsHash.serialize(buf);
        buf.writeUint32(this._nBits);
        buf.writeUint32(this._height);
        buf.writeUint32(this._timestamp);
        buf.writeUint32(this._nonce);
        return buf;
    }

    /** @type {number} */
    get serializedSize() {
        return /*version*/ 2
            + this._prevHash.serializedSize
            + this._interlinkHash.serializedSize
            + this._bodyHash.serializedSize
            + this._accountsHash.serializedSize
            + /*nBits*/ 4
            + /*height*/ 4
            + /*timestamp*/ 4
            + /*nonce*/ 4;
    }

    /**
     * @param {SerialBuffer} [buf]
     * @return {Promise.<boolean>}
     */
    async verifyProofOfWork(buf) {
        const pow = await this.pow(buf);
        return BlockUtils.isProofOfWork(pow, this.target);
    }

    /**
     * @param {BlockHeader} prevHeader
     * @returns {boolean}
     */
    isImmediateSuccessorOf(prevHeader) {
        // Check that the height is one higher than the previous height.
        if (this.height !== prevHeader.height + 1) {
            return false;
        }

        // Check that the timestamp is greater or equal to the predecessor's timestamp.
        if (this.timestamp < prevHeader.timestamp) {
            return false;
        }

        // Check that the hash of the predecessor block equals prevHash.
        const prevHash = prevHeader.hash();
        if (!this.prevHash.equals(prevHash)) {
            return false;
        }

        // Everything checks out.
        return true;
    }

    /**
     * @param {SerialBuffer} [buf]
     * @return {Hash}
     */
    hash(buf) {
        this._hash = this._hash || Hash.light(this.serialize(buf));
        return this._hash;
    }
    
    /**
     * @param {SerialBuffer} [buf]
     * @return {Promise.<Hash>}
     */
    async pow(buf) {
        this._pow = this._pow || await Hash.hard(this.serialize(buf));
        return this._pow;
    }

    /**
     * @param {BlockHeader|*} o
     * @returns {boolean}
     */
    equals(o) {
        return o instanceof BlockHeader
            && this._prevHash.equals(o.prevHash)
            && this._interlinkHash.equals(o.interlinkHash)
            && this._bodyHash.equals(o.bodyHash)
            && this._accountsHash.equals(o.accountsHash)
            && this._nBits === o.nBits
            && this._height === o.height
            && this._timestamp === o.timestamp
            && this._nonce === o.nonce;
    }

    /**
     * @returns {string}
     */
    toString() {
        return 'BlockHeader{'
            + `prevHash=${this._prevHash}, `
            + `interlinkHash=${this._interlinkHash}, `
            + `bodyHash=${this._bodyHash}, `
            + `accountsHash=${this._accountsHash}, `
            + `nBits=${this._nBits.toString(16)}, `
            + `height=${this._height}, `
            + `timestamp=${this._timestamp}, `
            + `nonce=${this._nonce}`
            + '}';
    }

    /** @type {number} */
    get version() {
        return this._version;
    }

    /** @type {Hash} */
    get prevHash() {
        return this._prevHash;
    }

    /** @type {Hash} */
    get interlinkHash() {
        return this._interlinkHash;
    }

    /** @type {Hash} */
    get bodyHash() {
        return this._bodyHash;
    }

    /** @type {Hash} */
    get accountsHash() {
        return this._accountsHash;
    }

    /** @type {number} */
    get nBits() {
        return this._nBits;
    }

    /** @type {number} */
    get target() {
        return BlockUtils.compactToTarget(this._nBits);
    }

    /** @type {number} */
    get difficulty() {
        return BlockUtils.compactToDifficulty(this._nBits);
    }

    /** @type {number} */
    get height() {
        return this._height;
    }

    /** @type {number} */
    get timestamp() {
        return this._timestamp;
    }

    /** @type {number} */
    get nonce() {
        return this._nonce;
    }

    // XXX The miner changes the nonce of an existing BlockHeader during the
    // mining process.
    /** @type {number} */
    set nonce(n) {
        this._nonce = n;
        this._hash = null;
        this._pow = null;
    }
}
// FIXME: Clean up for mainnet.
BlockHeader.Version = {
    V1: 1
};
BlockHeader.CURRENT_VERSION = BlockHeader.Version.V1;
BlockHeader.SUPPORTED_VERSIONS = [
    BlockHeader.Version.V1
];
BlockHeader.SERIALIZED_SIZE = 146;
Class.register(BlockHeader);