Home Reference Source Test

test/specs/generic/consensus/full/Fullchain.spec.js

describe('Blockchain', () => {
    it('verifies block size limit', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 1);
            const sender = testBlockchain.users[0];
            const numTransactions = 1500;

            const transactions = [];
            for (let i = 0; i < numTransactions; i++) {
                const recipient = Address.fromHash(Hash.blake2b(BufferUtils.fromAscii(`tx${i}`)));
                transactions.push(TestBlockchain.createTransaction(sender.publicKey, recipient, 1, 1, 1, sender.privateKey));
            }
            transactions.sort((a, b) => a.compareBlockOrder(b));

            const block = await testBlockchain.createBlock({
                transactions: transactions
            });
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done).catch(done.fail);
    });

    it('rejects orphan blocks', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);
            const zeroHash = new Hash(new Uint8Array(Hash.SIZE.get(Hash.Algorithm.BLAKE2B)));

            // Try to push a block with an invalid prevHash and check that it fails
            // hash that does NOT match the one from Genesis
            const block = await testBlockchain.createBlock({prevHash: zeroHash});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_ORPHAN);
        })().then(done, done.fail);
    });

    it('rejects blocks from the future', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Now try to push a block with a timestamp that's more than
            // Block.TIMESTAMP_DRIFT_MAX seconds into the future
            const spyObj = spyOn(testBlockchain.time, 'now').and.returnValue(0);
            const timestamp = Block.TIMESTAMP_DRIFT_MAX + 1;
            const block = await testBlockchain.createBlock({timestamp: timestamp});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
            spyObj.and.callThrough();
        })().then(done, done.fail);
    });

    it('rejects blocks with wrong difficulty', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Now try to push a block with the wrong difficulty
            const correctDifficulty = BlockUtils.targetToDifficulty(await testBlockchain.getNextTarget());
            const compactWrongDifficulty = BlockUtils.difficultyToCompact(correctDifficulty.plus(1));
            const block = await testBlockchain.createBlock({nBits: compactWrongDifficulty});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies block body hash', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);
            const zeroHash = new Hash(new Uint8Array(Hash.SIZE.get(Hash.Algorithm.BLAKE2B)));

            // Now try to push a block with an invalid body hash
            const block = await testBlockchain.createBlock({bodyHash: zeroHash});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies accounts hash', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);
            const zeroHash = new Hash(new Uint8Array(Hash.SIZE.get(Hash.Algorithm.BLAKE2B)));

            // Try to push a block that has an invalid AccountsHash
            const block = await testBlockchain.createBlock({accountsHash: zeroHash});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies transaction signatures', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 2);
            const senderPubKey = testBlockchain.users[0].publicKey;
            const receiverAddr = testBlockchain.users[1].address;

            // Now try to push a block with an invalid transaction signature
            const data = new Uint8Array(32);
            const wrongSignature = Signature.create(testBlockchain.users[0].privateKey, testBlockchain.users[0].publicKey, data);
            const transactions = [TestBlockchain.createTransaction(senderPubKey, receiverAddr, 1, 1, 0, undefined, wrongSignature)];
            const block = await testBlockchain.createBlock({transactions: transactions});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies transaction signatures in large blocks', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 2);
            const sender = testBlockchain.users[0];
            const receiver = testBlockchain.users[1];
            const numTransactions = 700;

            const transactions = [];
            for (let i = 0; i < numTransactions; i++) {
                const recipient = Address.fromHash(Hash.blake2b(BufferUtils.fromAscii(`tx${i}`)));
                transactions.push(TestBlockchain.createTransaction(sender.publicKey, recipient, 1, 1, 1, sender.privateKey));
            }

            // Now try to push a block with an invalid transaction signature
            const data = new Uint8Array(32);
            const wrongSignature = Signature.create(testBlockchain.users[0].privateKey, testBlockchain.users[0].publicKey, data);
            transactions.push(TestBlockchain.createTransaction(sender.publicKey, receiver.address, 1, 1, 0, undefined, wrongSignature));
            transactions.sort((a, b) => a.compareBlockOrder(b));

            const block = await testBlockchain.createBlock({transactions: transactions});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies that sufficient funds are available', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 2);
            const senderPubKey = testBlockchain.users[0].publicKey;
            const senderPrivKey = testBlockchain.users[0].privateKey;
            const receiverAddr = testBlockchain.users[1].address;

            // Now try to push a block with a transaction with insufficient funds.
            const transactions = [TestBlockchain.createTransaction(senderPubKey, receiverAddr, Policy.coinsToLunas(1000000), 1, 0, senderPrivKey)];
            const block = await testBlockchain.createBlock({transactions: transactions});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies transaction validityStartHeight', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 2);
            const senderPubKey = testBlockchain.users[0].publicKey;
            const senderPrivKey = testBlockchain.users[0].privateKey;
            const receiverAddr = testBlockchain.users[1].address;

            // Now try to push a block with a transaction with invalid nonce.
            const transactions = [TestBlockchain.createTransaction(senderPubKey, receiverAddr, 1, 1, 42, senderPrivKey)];
            const block = await testBlockchain.createBlock({transactions: transactions});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('prevents transaction replay across blocks', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 2);
            const senderPubKey = testBlockchain.users[0].publicKey;
            const senderPrivKey = testBlockchain.users[0].privateKey;
            const receiverAddr = testBlockchain.users[1].address;

            // Include a valid transaction.
            const transactions = [TestBlockchain.createTransaction(senderPubKey, receiverAddr, 1, 1, 0, senderPrivKey)];
            let block = await testBlockchain.createBlock({transactions: transactions});
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            // Include the same transaction again.
            block = await testBlockchain.createBlock({transactions: transactions});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies proof of work', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Now try to push a block that is not compliant with Proof of Work requirements
            const block = await testBlockchain.createBlock({nonce: 4711});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('ignores known blocks', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Push valid block.
            const block = await testBlockchain.createBlock();
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            // Try to push the same block again.
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_KNOWN);
            expect(testBlockchain.height).toBe(2);
        })().then(done, done.fail);
    });

    it('verifies that block timestamps are increasing', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Push valid block.
            let block = await testBlockchain.createBlock();
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            // Try to push a block that has a lower timestamp than the one
            // successfully pushed before and check that it fails
            const older = block.timestamp - 1;
            block = await testBlockchain.createBlock({timestamp: older});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('verifies that the block height is increasing', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0);

            // Push valid block.
            let block = await testBlockchain.createBlock();
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(testBlockchain.height).toBe(2);

            // Try to push a block that has the same height as the block before.
            block = await testBlockchain.createBlock({height: 2});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);

            // Try to push a block that has a lower height than the block before.
            block = await testBlockchain.createBlock({height: 1});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);

            // Try to push a block that has an invalid height.
            block = await testBlockchain.createBlock({height: 5});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID);
        })().then(done, done.fail);
    });

    it('can push 10 blocks with constant difficulty, then increase the difficulty over 10 more blocks', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            let nextTarget = await testBlockchain.getNextTarget();
            expect(BlockUtils.targetToCompact(nextTarget)).toBe(BlockUtils.difficultyToCompact(1));

            let timestamp;
            for (let i = 0; i < 10; ++i) {
                timestamp = testBlockchain.height * Policy.BLOCK_TIME;
                const block = await testBlockchain.createBlock({timestamp: timestamp});
                const hash = block.hash();
                const status = await testBlockchain.pushBlock(block);
                expect(status).toBe(FullChain.OK_EXTENDED);

                // Get that same block and check that they're the same
                const resultBlock = await testBlockchain.getBlock(hash, false, true);
                expect(block.equals(resultBlock)).toBe(true);
            }

            nextTarget = await testBlockchain.getNextTarget();
            expect(BlockUtils.targetToCompact(nextTarget)).toBe(BlockUtils.difficultyToCompact(1));

            // all timestamps are explicitly set to trigger an increase in difficulty after the last block
            for (let i = 0; i < 10; ++i) {
                const block = await testBlockchain.createBlock({timestamp: 10 * Policy.BLOCK_TIME + i});
                const hash = block.hash();
                const status = await testBlockchain.pushBlock(block);
                expect(status).toBe(FullChain.OK_EXTENDED);

                // Get that same block and check that they're the same
                const resultBlock = await testBlockchain.getBlock(hash, false, true);
                expect(block.equals(resultBlock)).toBe(true);
            }

            nextTarget = await testBlockchain.getNextTarget();
            expect(BlockUtils.targetToCompact(nextTarget)).toBe(520153652); // TODO
        })().then(done, done.fail);
    });

    it('can push 20 blocks and keep difficulty increasing over each block', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            let nextTarget = await testBlockchain.getNextTarget();
            expect(BlockUtils.targetToCompact(nextTarget)).toBe(BlockUtils.difficultyToCompact(1));

            let difficulty = 0;
            for (let i = 0; i < 20; ++i) {
                let timestamp = testBlockchain.height * Math.floor(Policy.BLOCK_TIME / 2);
                const block = await testBlockchain.createBlock({timestamp: timestamp});
                const hash = block.hash();
                const status = await testBlockchain.pushBlock(block);
                expect(status).toBe(FullChain.OK_EXTENDED);

                // Get that same block and check that they're the same
                const resultBlock = await testBlockchain.getBlock(hash, false, true);
                expect(block.equals(resultBlock)).toBe(true);

                expect(block.difficulty > difficulty).toBe(true);
                difficulty = block.difficulty;
            }

            nextTarget = await testBlockchain.getNextTarget();
            expect(BlockUtils.targetToCompact(nextTarget)).toBe(520153331); // TODO
        })().then(done, done.fail);
    });

    it('can handle larger chains', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(20, 20); // eslint-disable-line no-unused-vars
            expect(testBlockchain).toBeTruthy();
        })().then(done, done.fail);
    });

    it('changes balance after transaction', (done) => {
        (async () => {
            const testBlockchain = await TestBlockchain.createVolatileTest(10, 20);
            expect(testBlockchain).toBeTruthy();
            const user0 = testBlockchain.users[0];
            const user1 = testBlockchain.users[1];
            const account0 = await testBlockchain.accounts.get(user0.address, Account.Type.BASIC);
            const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 1, 9, user0.privateKey);
            const tx2 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 1, 10, user0.privateKey);
            const block = await testBlockchain.createBlock({transactions: [tx1, tx2], minerAddr: user1.address});
            await testBlockchain.pushBlock(block);
            const account1 = await testBlockchain.accounts.get(user0.address, Account.Type.BASIC);
            expect(account1.balance).toBe(account0.balance - 4);
        })().then(done, done.fail);
    });

    it('cannot push blocks with transactions to oneself', (done) => {
        (async function () {
            const first = await TestBlockchain.createVolatileTest(0);

            // Try to push a block with a transaction where sender and recipient coincide
            const user = first.users[0];
            const transaction = TestBlockchain.createTransaction(user.publicKey, user.address, 1, 1, 1, user.privateKey);
            const block = await first.createBlock({transactions: [transaction]});
            const status = await first.pushBlock(block);
            expect(status).toBe(FullChain.ERR_INVALID, 'try pushing invalid block');
        })().then(done, done.fail);
    });

    it('can rebranch to a harder fork', (done) => {
        (async function () {
            // Create first chain (4 blocks)
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);
            let block = await testBlockchain.createBlock();
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            block = await testBlockchain.createBlock();
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            block = await testBlockchain.createBlock();
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            block = await testBlockchain.createBlock();
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.height).toBe(5);

            expect((await testBlockchain.getBlocks(GenesisConfig.GENESIS_HASH, 4, true))
                .map(b => b.height)
                .every((value, i) => value === i + 2)).toBe(true);

            expect((await testBlockchain.getBlocks(block.hash(), 4, false))
                .map(b => b.height)
                .every((value, i) => value === 4 - i)).toBe(true);

            // Create second chain (5 blocks)
            const fork = await TestBlockchain.createVolatileTest(0, 2);
            block = await fork.createBlock({
                timestamp: GenesisConfig.GENESIS_BLOCK.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_FORKED);

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_FORKED);

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_FORKED);

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            // Expect the chain to rebranch here.
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_REBRANCHED);
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.height).toBe(5);

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.height).toBe(6);

            expect((await testBlockchain.getBlocks(GenesisConfig.GENESIS_HASH, 5, true))
                .map(b => b.height)
                .every((value, i) => value === i + 2)).toBe(true);

            expect((await testBlockchain.getBlocks(block.hash(), 5, false))
                .map(b => b.height)
                .every((value, i) => value === 5 - i)).toBe(true);
        })().then(done, done.fail);
    });

    it('updates transactions cache on rebranch', (done) => {
        (async function () {
            const users = TestBlockchain.getUsers(2);
            const tx1 = TestBlockchain.createTransaction(users[0].publicKey, users[1].address, 2000, 20, 1, users[0].privateKey);
            const tx2 = TestBlockchain.createTransaction(users[0].publicKey, users[1].address, 1000, 20, 1, users[0].privateKey);
            const tx3 = TestBlockchain.createTransaction(users[0].publicKey, users[1].address, 500, 20, 2, users[0].privateKey);
            const tx4 = TestBlockchain.createTransaction(users[0].publicKey, users[1].address, 1, 20, 1, users[0].privateKey);
            const tx5 = TestBlockchain.createTransaction(users[1].publicKey, users[0].address, 500, 20, 2, users[1].privateKey);
            const tx6 = TestBlockchain.createTransaction(users[1].publicKey, users[0].address, 200, 20, 2, users[1].privateKey);

            // Create first chain (2 blocks)
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);
            let block = await testBlockchain.createBlock({transactions: [tx1]});
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            const transactions = block.transactions.slice();
            for (const tx of transactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeTruthy();
            }

            block = await testBlockchain.createBlock({transactions: [tx2, tx3]});
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            block.transactions.forEach(tx => transactions.push(tx));
            for (const tx of transactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeTruthy();
            }

            // Create second chain (3 blocks)
            const fork = await TestBlockchain.createVolatileTest(0, 2);
            block = await fork.createBlock({
                timestamp: GenesisConfig.GENESIS_BLOCK.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
                transactions: [tx4]
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_FORKED);

            const forkTransactions = block.transactions.slice();
            for (const tx of forkTransactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeFalsy();
            }

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
                transactions: [tx5]
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            // Expect the chain to rebranch here.
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_REBRANCHED);
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.height).toBe(3);

            block.transactions.forEach(tx => forkTransactions.push(tx));
            for (const tx of forkTransactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeTruthy();
            }
            for (const tx of transactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeFalsy();
            }

            block = await fork.createBlock({
                timestamp: block.timestamp + Math.floor(Policy.BLOCK_TIME / 2),
                transactions: [tx6]
            });
            status = await fork.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.height).toBe(4);

            block.transactions.forEach(tx => forkTransactions.push(tx));
            for (const tx of forkTransactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeTruthy();
            }
            for (const tx of transactions) {
                expect(testBlockchain.transactionCache.containsTransaction(tx)).toBeFalsy();
            }
        })().then(done, done.fail);
    });

    it('has getters that return correct values for its properties', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            // This is needed to make sure pushBlock() went through successfully
            // and wasn't ignored later in the process
            spyOn(Log, 'w').and.callThrough();

            // Push a valid block and check that it went through successfully
            let block = await testBlockchain.createBlock();
            let status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(Log.w).not.toHaveBeenCalled();

            // Check that the getters return the expected values
            expect(testBlockchain.head.equals(block)).toBe(true);
            expect(testBlockchain.totalDifficulty).toEqual(new BigNumber(2));
            expect(testBlockchain.height).toBe(2);
            expect(testBlockchain.headHash.equals(block.hash())).toBe(true);

            // Push some more blocks
            block = await testBlockchain.createBlock();
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(Log.w).not.toHaveBeenCalled();

            block = await testBlockchain.createBlock();
            status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);
            expect(Log.w).not.toHaveBeenCalled();

            // Check that the getters return the new expected values
            expect(testBlockchain.head).toBe(block);
            expect(testBlockchain.totalDifficulty).toEqual(new BigNumber(4));
            expect(testBlockchain.height).toBe(4);
            expect(testBlockchain.headHash.equals(block.hash())).toBe(true);

            // Asynchronously test the busy getter
            testBlockchain._synchronizer.on('work-start', function () {
                expect(testBlockchain.busy).toBe(true);
            });

            testBlockchain._synchronizer.on('work-end', function () {
                expect(testBlockchain.busy).toBe(false);
            });
        })().then(done, done.fail);
    });

    it('correctly creates TransactionsProofs by address', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            const user0 = testBlockchain.users[0];
            const user1 = testBlockchain.users[1];
            const user2 = testBlockchain.users[2];
            const user3 = testBlockchain.users[3];
            const user4 = testBlockchain.users[4];

            const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 0, 1, user0.privateKey);
            const tx2 = TestBlockchain.createTransaction(user0.publicKey, user2.address, 1, 0, 1, user0.privateKey);
            const tx3 = TestBlockchain.createTransaction(user0.publicKey, user3.address, 1, 0, 1, user0.privateKey);
            const tx4 = TestBlockchain.createTransaction(user0.publicKey, user4.address, 1, 0, 1, user0.privateKey);
            const block = await testBlockchain.createBlock({transactions: [tx4, tx2, tx1, tx3], minerAddr: user1.address});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            const blockHash = block.hash();
            const bodyHash = block.bodyHash;

            let receivedTxs = new HashSet();
            // Scenario 1
            let proof = await testBlockchain.getTransactionsProofByAddresses(blockHash, [user0.address]);
            let root = await proof.root();
            let expectedTxs = [tx4, tx2, tx1, tx3];
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 2
            proof = await testBlockchain.getTransactionsProofByAddresses(blockHash, [user1.address]);
            root = await proof.root();
            expectedTxs = [tx1];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 3
            proof = await testBlockchain.getTransactionsProofByAddresses(blockHash, [user2.address, user3.address]);
            root = await proof.root();
            expectedTxs = [tx2, tx3];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 4
            proof = await testBlockchain.getTransactionsProofByAddresses(blockHash, [user0.address, user4.address]);
            root = await proof.root();
            expectedTxs = [tx4, tx2, tx1, tx3];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }
        })().then(done, done.fail);
    });

    it('correctly creates TransactionsReceipts by address', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            const user0 = testBlockchain.users[0];
            const user1 = testBlockchain.users[1];
            const user2 = testBlockchain.users[2];
            const user3 = testBlockchain.users[3];
            const user4 = testBlockchain.users[4];

            const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 0, 1, user0.privateKey);
            const tx2 = TestBlockchain.createTransaction(user0.publicKey, user2.address, 1, 0, 1, user0.privateKey);
            const tx3 = TestBlockchain.createTransaction(user0.publicKey, user3.address, 1, 0, 1, user0.privateKey);
            const tx4 = TestBlockchain.createTransaction(user0.publicKey, user4.address, 1, 0, 1, user0.privateKey);
            const block = await testBlockchain.createBlock({transactions: [tx4, tx2, tx1, tx3], minerAddr: user1.address});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            let receivedTxs = new HashSet();
            // Scenario 1
            let receipts = await testBlockchain.getTransactionReceiptsByAddress(user0.address);
            let expectedTxs = [tx4, tx2, tx1, tx3];
            receivedTxs.addAll(receipts.map(r => r.transactionHash));
            expect(receivedTxs.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx.hash())).toBe(true);
            }

            // Scenario 2
            receipts = await testBlockchain.getTransactionReceiptsByAddress(user1.address);
            expectedTxs = [tx1];
            receivedTxs.clear();
            receivedTxs.addAll(receipts.map(r => r.transactionHash));
            expect(receivedTxs.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx.hash())).toBe(true);
            }
        })().then(done, done.fail);
    });

    it('correctly creates TransactionsProofs by hash', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            const user0 = testBlockchain.users[0];
            const user1 = testBlockchain.users[1];
            const user2 = testBlockchain.users[2];
            const user3 = testBlockchain.users[3];
            const user4 = testBlockchain.users[4];

            const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 0, 1, user0.privateKey);
            const tx2 = TestBlockchain.createTransaction(user0.publicKey, user2.address, 1, 0, 1, user0.privateKey);
            const tx3 = TestBlockchain.createTransaction(user0.publicKey, user3.address, 1, 0, 1, user0.privateKey);
            const tx4 = TestBlockchain.createTransaction(user0.publicKey, user4.address, 1, 0, 1, user0.privateKey);
            const block = await testBlockchain.createBlock({transactions: [tx4, tx2, tx1, tx3], minerAddr: user1.address});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            const blockHash = block.hash();
            const bodyHash = block.bodyHash;

            let receivedTxs = new HashSet();
            // Scenario 1
            let proof = await testBlockchain.getTransactionsProofByHashes(blockHash, [tx4.hash(), tx2.hash(), tx1.hash(), tx3.hash()]);
            let root = await proof.root();
            let expectedTxs = [tx4, tx2, tx1, tx3];
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 2
            proof = await testBlockchain.getTransactionsProofByHashes(blockHash, [tx1.hash()]);
            root = await proof.root();
            expectedTxs = [tx1];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 3
            proof = await testBlockchain.getTransactionsProofByHashes(blockHash, [tx2.hash(), tx3.hash()]);
            root = await proof.root();
            expectedTxs = [tx2, tx3];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }

            // Scenario 4
            proof = await testBlockchain.getTransactionsProofByHashes(blockHash, [tx4.hash(), tx2.hash(), tx1.hash(), tx1.hash()]);
            root = await proof.root();
            expectedTxs = [tx4, tx2, tx1];
            receivedTxs.clear();
            receivedTxs.addAll(proof.transactions);
            expect(root.equals(bodyHash)).toBe(true);
            expect(proof.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx)).toBe(true);
            }
        })().then(done, done.fail);
    });

    it('correctly creates TransactionsReceipts by hashes', (done) => {
        (async function () {
            const testBlockchain = await TestBlockchain.createVolatileTest(0, 10);

            const user0 = testBlockchain.users[0];
            const user1 = testBlockchain.users[1];
            const user2 = testBlockchain.users[2];
            const user3 = testBlockchain.users[3];
            const user4 = testBlockchain.users[4];

            const tx1 = TestBlockchain.createTransaction(user0.publicKey, user1.address, 1, 0, 1, user0.privateKey);
            const tx2 = TestBlockchain.createTransaction(user0.publicKey, user2.address, 1, 0, 1, user0.privateKey);
            const tx3 = TestBlockchain.createTransaction(user0.publicKey, user3.address, 1, 0, 1, user0.privateKey);
            const tx4 = TestBlockchain.createTransaction(user0.publicKey, user4.address, 1, 0, 1, user0.privateKey);
            const block = await testBlockchain.createBlock({transactions: [tx4, tx2, tx1, tx3], minerAddr: user1.address});
            const status = await testBlockchain.pushBlock(block);
            expect(status).toBe(FullChain.OK_EXTENDED);

            let receivedTxs = new HashSet();
            // Scenario 1
            let receipts = await testBlockchain.getTransactionReceiptsByHashes([tx4.hash(), tx2.hash(), tx1.hash(), tx3.hash()]);
            let expectedTxs = [tx4, tx2, tx1, tx3];
            receivedTxs.addAll(receipts.map(r => r.transactionHash));
            expect(receivedTxs.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx.hash())).toBe(true);
            }

            // Scenario 2
            receipts = await testBlockchain.getTransactionReceiptsByHashes([tx1.hash()]);
            expectedTxs = [tx1];
            receivedTxs.clear();
            receivedTxs.addAll(receipts.map(r => r.transactionHash));
            expect(receivedTxs.length).toBe(expectedTxs.length);
            for (const tx of expectedTxs) {
                expect(receivedTxs.contains(tx.hash())).toBe(true);
            }
        })().then(done, done.fail);
    });
});