Home Reference Source Test

src/main/generic/consensus/base/blockchain/BlockChain.js

class BlockChain {
    /**
     * @param {BlockChain} chain1
     * @param {BlockChain} chain2
     * @returns {BlockChain}
     */
    static merge(chain1, chain2) {
        const merged = [];
        let i1 = 0, i2 = 0;
        while (i1 < chain1.length && i2 < chain2.length) {
            const block1 = chain1.blocks[i1];
            const block2 = chain2.blocks[i2];

            if (block1.height === block2.height) {
                Assert.that(block1.equals(block2), 'Encountered different blocks at same height during chain merge');
                merged.push(block1);
                i1++;
                i2++;
            } else if (block1.height < block2.height) {
                merged.push(block1);
                i1++;
            } else {
                merged.push(block2);
                i2++;
            }
        }

        for (; i1 < chain1.length; i1++) {
            merged.push(chain1.blocks[i1]);
        }
        for (; i2 < chain2.length; i2++) {
            merged.push(chain2.blocks[i2]);
        }

        return new BlockChain(merged);
    }

    /**
     * @param {BlockChain} chain1
     * @param {BlockChain} chain2
     * @returns {?Block}
     */
    static lowestCommonAncestor(chain1, chain2) {
        let i1 = chain1.length - 1;
        let i2 = chain2.length - 1;
        while (i1 >= 0 && i2 >= 0) {
            const block1 = chain1.blocks[i1];
            const block2 = chain2.blocks[i2];

            if (block1.equals(block2)) {
                return block1;
            } else if (block1.height > block2.height) {
                i1--;
            } else {
                i2--;
            }
        }
        return undefined;
    }

    /**
     * @param {Array.<Block>} blocks
     * @param {Array.<BlockChain>} [superChains]
     */
    constructor(blocks, superChains) {
        if (!Array.isArray(blocks) || !NumberUtils.isUint16(blocks.length)
            || blocks.some(it => !(it instanceof Block) || !it.isLight())) throw new Error('Malformed blocks');

        /** @type {Array.<Block>} */
        this._blocks = blocks;
        /** @type {Array.<BlockChain>} */
        this._chains = superChains;
    }

    /**
     * @param {SerialBuffer} buf
     * @returns {BlockChain}
     */
    static unserialize(buf) {
        const count = buf.readUint16();
        const blocks = [];
        for (let i = 0; i < count; i++) {
            blocks.push(Block.unserialize(buf));
        }
        return new BlockChain(blocks);
    }

    /**
     * @param {SerialBuffer} [buf]
     * @returns {SerialBuffer}
     */
    serialize(buf) {
        buf = buf || new SerialBuffer(this.serializedSize);
        buf.writeUint16(this._blocks.length);
        for (const block of this._blocks) {
            block.serialize(buf);
        }
        return buf;
    }

    /** @type {number} */
    get serializedSize() {
        return /*count*/ 2
            + this._blocks.reduce((sum, block) => sum + block.serializedSize, 0);
    }

    /**
     * @returns {Promise.<boolean>}
     */
    async verify() {
        // For performance reasons, we DO NOT VERIFY the validity of the blocks in the chain here.
        // Block validity is checked by the Nano/LightChain upon receipt of a ChainProof.

        // Check that all blocks in the chain are valid successors of one another.
        for (let i = this._blocks.length - 1; i >= 1; i--) {
            if (!(await this._blocks[i].isSuccessorOf(this._blocks[i - 1]))) { // eslint-disable-line no-await-in-loop
                return false;
            }
        }

        // Everything checks out.
        return true;
    }

    /**
     * @returns {Array.<Block>}
     */
    denseSuffix() {
        // Compute the dense suffix.
        const denseSuffix = [this.head];
        let denseSuffixHead = this.head;
        for (let i = this.length - 2; i >= 0; i--) {
            const block = this.blocks[i];
            const hash = block.hash();
            if (!hash.equals(denseSuffixHead.prevHash)) {
                break;
            }

            denseSuffix.push(block);
            denseSuffixHead = block;
        }
        denseSuffix.reverse();
        return denseSuffix;
    }

    /**
     * @returns {Promise.<Array.<BlockChain>>}
     */
    async getSuperChains() {
        if (!this._chains) {
            this._chains = [];
            for (let i = 0; i < this.length; i++) {
                const block = this.blocks[i];
                const depth = BlockUtils.getHashDepth(await block.pow());

                if (this._chains[depth]) {
                    this._chains[depth].blocks.push(block);
                } else if (!this._chains[depth]) {
                    this._chains[depth] = new BlockChain([block]);
                }

                for (let j = depth - 1; j >= 0; j--) {
                    if (this._chains[j]) {
                        this._chains[j].blocks.push(block);
                    }
                }
            }

            for (let i = 0; i < this._chains.length; i++) {
                if (!this._chains[i]) {
                    this._chains[i] = new BlockChain([]);
                }
            }
        }
        return this._chains;
    }

    /**
     * @returns {boolean}
     */
    isAnchored() {
        return GenesisConfig.GENESIS_HASH.equals(this.tail.hash());
    }

    /**
     * @returns {string}
     */
    toString() {
        return `BlockChain{length=${this.length}}`;
    }

    /** @type {number} */
    get length() {
        return this._blocks.length;
    }

    /** @type {Array.<Block>} */
    get blocks() {
        return this._blocks;
    }

    /** @type {Block} */
    get head() {
        return this._blocks[this.length - 1];
    }

    /** @type {Block} */
    get tail() {
        return this._blocks[0];
    }

    /**
     * @returns {number}
     */
    totalDifficulty() {
        return this._blocks.reduce((sum, block) => sum + BlockUtils.targetToDifficulty(block.target), 0);
    }
}
Class.register(BlockChain);