DEV Community

Jonathan Thomas
Jonathan Thomas

Posted on • Updated on

MY Journey Through Tech:Fullstack Blockchain Course Section 2

In the last post here, we managed to create a block object with hashing functionality. We also identified the tests that must be passed for valid blocks. Today, we are adding the blockchain.test.js and blockchain.js as such:

Adding the blockchain.test.js:

const Blockchain = require('./blockchain');
const Block = require('./block');

describe('Blockchain', () => {
const blockchain = new Blockchain();

ii('contains a `chain` Array instance', () => {
    expect(blockchain.chain instanceof Array).toBe(true);

});
it('starts with the genesis block', () =>{
    expect(blockchain.chain[0]).toEqual(Block.genesis());
});
it('adds a new block to the chain', () =>{
    const newData = 'foo-bar';
    blockchain.addBlock({data : newData });

    expect(blockchain.chain[blockchain.chain.length-1].data).toEqual(newData);

});

})

In this test we import the blockchain.js and block.js files for the program. Next in our description for the test, we're expecting the object values to be start with a genesis block as the first item in the array.Then is looks for the addBlock method in the blockchain file as an instance of a block itself and calling the "{data:newData}" object does this as arguments for the addBlock function. Then finally we expect the most recent value to be the last item in the chain.

Add blockchain.js:

const Block = require('./block');
class Blockchain{
constructor(){
this.chain = [Block.genesis()];
}

addBlock({ data }){
    const newBlock = Block.mineBlock({
        lastBlock: this.chain[this.chain.length-1],
        data
    });

    this.chain.push(newBlock);
}

}

module.exports = Blockchain;

Here we created the blockchain class by importing the block.js file. In the constructor, we declared a genesis block is the first item in the array. Then we created the addBlock method that takes a data object as an argument. You'll recall that the mineBlock method has data as it's own argument from the block.js file when creating an instance. It then looks for the last item in the array and pushes it into the blockchain array as a data value. Finally to be seen for the tests, we export it it locally.

Completing the Chain Validation tests

To complete our blockchain.test file we now refactor it as show:

const Blockchain = require('./blockchain');
const Block = require('./block');

describe('Blockchain', () => {
let blockchain;

beforeEach( () => {
    blockchain = new Blockchain();
});

it('contains a `chain` Array instance', () => {
    expect(blockchain.chain instanceof Array).toBe(true);

});
it('starts with the genesis block', () =>{
    expect(blockchain.chain[0]).toEqual(Block.genesis());
});
it('adds a new block to the chain', () =>{
    const newData = 'foo-bar';
    blockchain.addBlock({data : newData });

    expect(blockchain.chain[blockchain.chain.length-1].data).toEqual(newData);

});

describe('isValidChain()', () => {
    describe('when the chain does not start with a genesis block', () =>{
        it('returns false', () => {
            blockchain.chain[0] = { data: 'fake-genesis'};

            expect(Blockchain.isValidChain(blockchain.chain)).toBe(false);
        });

    });

        describe('when the chain starts with the genesis block and has multiple blocks', () => {
            beforeEach( () => {
                blockchain.addBlock({ data: 'Bears'});
                blockchain.addBlock({data: 'Beets'});
                blockchain.addBlock({data: 'Battlestar Galactica' });

            });
            describe('and lastHash reference has changed', () =>{
                it('returns false', () => {


                    blockchain.chain[2].lastHash = 'broken-lastHash';

                    expect(blockchain.isValidChain(blockchain.chain)).toBe(false);
                });
            });

            describe('when the chain starts with the genesis block and has multiple blocks', () => {
                describe('and a lastHash reference has changed', () => {
                    it('returns false', () => {});
                });

                describe('and the chain contains a block with an invalid field', () => {
                    it('returns false', () => {

                        blockchain.chain[2].data = 'some-bad-and-evil-data';

                        expect(blockchain.isValidChain(blockchain.chain)).toBe(false);
                    });
                });

                describe('and the chain does not contain any invalid blocks', () => {
                    it('returns true', () => {

                        expect(blockchain.isValidChain(blockchain.chain)).toBe(true);

                    });
                });
            });

        });
    });
}); 

});

Immediately in the first describe block, we change it so that there is a new block is formed every time using the beforeEach function. We then added out validation test to see if the data matches the arguments needed before adding the a new block to the chain.Again we also used beforeEach within the multiple block tests with dummy data.

Chain Validation code//blockchain.js

static isValidChain(chain) {
if(JSON.stringify(chain[0]) !== JSON.stringify(Block.genesis())) {

    return false;
    };

    for (let i=1; i<chain.length; i++){
        const { timestamp, lastHash, hash, data } = chain[i];

        const actualLastHash = chain[i-1].hash;


        if(lastHash !== actualLastHash) return false;

        const validatedHash = cryptoHash(timestamp, lastHash, data);

        if(hash !== validatedHash) return false;
    }

    return true;
}

Here, we've completed the is valid chain function.Since the instance of a block object can be called in the blockchain test we use the JSON.stringify method so that the data inside the block can be read for testing.We iterate through the data of the block instance that get pushed into an arry. It checks the hash of the last block is valid before continuing on to chain replacement.

Chain replacement tests:

describe('replaceChain()', () => {
    describe('when the new chain is not longer', () => {
        it('does not replace the chain', () => {
            newChain.chain[0] = { new : 'chain' };

            blockchain.replaceChain(newChain.chain);

            expect(blockchain.chain).toEqual(originalChain);
        });

        describe('when the chain is longer', () => {
            beforeEach(() => {
                newChain.addBlock({ data: 'Bears'});
                newChain.addBlock({data: 'Beets'});
                newChain.addBlock({data: 'Battlestar Galactica' });
            });

            describe('and the chain is invalid', () => {});
                it('does not replace the chain', () => {
                    newChain.chain[2].hash = 'some-fake-hash';

                    blockchain.replaceChain(newChain.chain);

                    expect(blockchain.chain).toEqual(originalChain);

                });
            describe('and the chain is valid', () => {
                it('replaces the chain', () => {
                    blockchain.replaceChain(newChain.chain);

                    expect(blockchain.chain).toEqual(newChain.chain);
                });
            });
        });
    });
})

In completing the tests for chain replacement, we check to see if the chain length matches what needs to be taken.If the chain length doesn't match up, it creates a new instance at the end of the array.In the next description, we check if the chain is longer but populated with invalid data, it doesn't replace the last block, rather, it calls the replaceChain function to make a new instance of a valid block.

Chain replacement function

replaceChain(chain) {
if(chain.length <= this.chain.length){
return;
}
if(!Blockchain.isValidChain(chain)){
return;
}
this.chain = chain;
}
Finally we add the replaceChain functionality anf having our tests pass.

Personal Reflections:
It took a bit longer to complete this section. So far I'm averaging a section a week and I'm probably going to stick to that schedule. I'm also in the third week of the IT Analyst program and I'm well on my way to being read for the Comptia Core 1 Exam in 3 weeks time.Wrting the test decriptions was abit confusing, but ultimately made sense with practice. All that's left now is to add stubs for error checking and I can move on to the next section.

Thanks for reading and hapy coding, constructive criticism always welcome

Top comments (0)