DEV Community

Jonathan Thomas
Jonathan Thomas

Posted on • Edited on

My Journey through Tech: FullStack Blockchain course Section 3

Continuing on from the last section, it appears that I needed to add a few more describes to our replaceChain description in the blockchain.test.js file like so:

describe('replaceChain()', () => {
let errorMock, logMock;

  beforeEach(() => {
      errorMock = jest.fn();
      logMock = jest.fn();

      global.console.error = errorMock;
      global.console.log = logMock;
  });

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

            blockchain.replaceChain(newChain.chain);
    });
      it('does not replace the chain', () => {

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

        it('logs an error', () => {
            expect(errorMock).toHaveBeenCalled();
        });



        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', () => {});
                beforeEach(() => {
                    newChain.chain[2].hash = 'some-fake-hash';

                    blockchain.replaceChain(newChain.chain);
                });
                it('does not replace the chain', () => {

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

                });

                it('logs an error', () => {
                    expect(errorMock).toHaveBeenCalled();
                });

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

                it('replaces the chain', () => {
                    expect(blockchain.chain).toEqual(newChain.chain);
                });

                it('logs about chain replacement', () =>{
                    expect(logMock).toHaveBeenCalled();
                })
            });
        });
    });
})

});

We set the global values of the error output to be stubbed using the jest.fn() built-in. We set the variables, errorCheck and logCheck to be globally accessible. The idea is that, this module will quiet the error outputs on the command line. As of writing, the current replaceChain test fails because the erroMock, is expecting to be called but not found. Currently I have reached out to fellow learners and the course instructor for solutions. As well, I've taken the initiative to read up on the jestJS documentaion for a solution.

From what I've gathered from my readings, this is a bug that seems to be common in versions of jest <= 23.0.6, which is currently part of my dependcy file.I'll test with a current version of jest later tonight.

Proof of Work portion

Before getting back into code we got introduced to the proof of work theory behind cryptocurrency. The proof of work system was based on hashcash a concept designed back in 1997. The premise of which is, that any hashes generated from data has a difficulty rating assigned to it. The higher the difficulty, the more computing power needed to decrypt the hash.
In the case of block hashes, we are looking for hashes with leading zeroes that that can match up to a number used once(nonce) that is generated from our data.

The idea that anyone who wants to solve thes hash problems uses their computer's resources to match the hash.Using bitcoin as an example, the network of bitcoin is set to release a new block every 10 minutes. During that time, people who are trying to solve a hash on a newly released block contribute their computing power. The more that you can contribute, the greater the chance that a portion of the reward goes to you for decrypting it.

However, this could lead to being exploited by a 51% attack. The concept behind this attack is that a malicious block is inserted into the chain that has a greater amount of resources contributing to decrypting th hash.

Updating the block.test.js

While back inside out block test file we add two more tests they are nonce and difficulty tests to the block.test.js file:

describe('Block',() => {
const timestamp = 'a-date';
const lastHash = 'foo-hash';
const hash = 'bar-hash';
const data = ['blockchain', 'data'];
const nonce = 1;
const difficulty = 1;

const block = new Block({timestamp,lastHash,hash, data, nonce, difficulty});

We updated the block object as well to refelct the new values.

We then updated the SHA 256 has test to refelct the nonce and difficulty like so:

it('creates a sha-256 hash based on proper inputs', () => {
expect(minedBlock.hash)
.toEqual(
cryptoHash(
minedBlock.timestamp,
minedBlock.nonce,
minedBlock.difficulty,
lastBlock.hash,
data
)
);
});
And then we finally added a hash matching test to match for the difficulty of the block:

it('it sets a hash that matches the difficulty criteria', () => {
expect(minedBlock.hash.substring(0, minedBlock.difficulty))
.toEqual('0'.repeat(minedBlock.difficulty));
});
});

We then set an initial difficulty within the config.js with the added nonce and diffictulty values:
const INITIAL_DIFFICULTY = 3;

const GENESIS_DATA = {
timestamp: 1,
lastHash : '-----',
hash :'hash-one',
difficulty: INITIAL_DIFFICULTY,
nonce: 0,
data: []
};

module.exports = { GENESIS_DATA} ;

Then in the blockchain file, we updated according to the new values as well:

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, nonce, difficulty,data } = chain[i];

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


        if(lastHash !== actualLastHash) return false;

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

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

    return true;
}

}

We also updated the minedBlock fucntion like so:

static mineBlock({ lastBlock, data }) {
let hash, timestamp;
const lastHash = lastBlock.hash;
const { difficulty } = lastBlock;
let nonce = 0;

    do {
        nonce++;
        timestamp = Date.now();
        hash = cryptoHash(timestamp, lastHash, data, nonce, difficulty);
    } while (hash.substring(0, difficulty) !== '0'.repeat(difficulty));

    return new this({ timestamp,lastHash,data,difficulty,nonce,hash });

Next we need to find a way to dynamically change the difficulty of the new blocks on the chain by establishiing a mine rate. So we update out config.js file:

cont MINE_RATE = 1000;

const INITIAL_DIFFICULTY = 3;

const GENESIS_DATA = {
timestamp: 1,
lastHash : '-----',
hash :'hash-one',
difficulty: INITIAL_DIFFICULTY,
nonce: 0,
data: []
};

module.exports = { GENESIS_DATA, MINE_RATE} ;

Afterward we add a tests for asjusting difficulty in block.test.js:
describe('adjustDifficulty()', ()=> {
it('raises the difficulty for a quickly mined block', () =>{
expect(Block.adjustDifficulty({
originalBlock: block, timestamp: block.timestamp + MINE_RATE - 100
})).toEqual(block.difficulty+1);
});

    it('lowers the difficulty for a slowly mined block', () => {
        expect(Block.adjustDifficulty({
            originalBlock: block, timestamp: block.timestamp + MINE_RATE +100
        })).toEqual(block.difficulty-1);
    });
});

});

Here if the block was mined to fast, we increase the difficulty and if mined to long we decrease the difficulty.From here we add the adjustDifficuly() function to our block.js file:

static adjustDifficulty({ originalBlock, timestamp }){
const { difficulty } = originalBlock;

    const difference = timestamp - originalBlock.timestamp;

    if(difference > MINE_RATE) return difficulty -1;

    return difficulty + 1;
}

Next we improve the proof of work system by implementing an average work script.The reason being is that we want to mine blocks with leading zeros in hexeadecimal. So we need to add parameters that will also be able to solve the binary hashes with leading zeros.We going to write a script that takes tha average pace of mining when we try to add meany blocks to the chain.

average-work.js
const Blockchain = require('./blockchain');

const blockchain = new Blockchain();

blockchain.addBlock({data: 'inital'});

let prevTimestamp, nextTimestamp, nextBlock, timeDiff,average;

const times = [];

for(let i=0; i<10000; i++){
prevTimestamp = blockchain.chain[blockchain.chain.length-1].timestamp;

blockchain.addBlock({ data: `block ${i}`});
nextBlock = blockchain.chain[blockchain.chain.length-1];

nextTimestamp = nextBlock.timestamp;
timeDiff = nextTimestamp - prevTimestamp;
times.push(timeDiff);

average = times.reduce((total, num) => (total + num))/times.length;

console.log(`Time to mine block: ${timeDiff}ms.Difficulty: ${nextBlock}.Average time: ${average}ms`)

}

Here we've initiated a new instance of a block for our average work script. This way, the new block's timestamp will be different from the genesis data's timetamp and we keep track of times in a times array.Finally we ensure that the difficulty cannot be abritrarily changed by adding a description for a jumped block.
blockchain.test.js:

const cryptoHash = require('./crypto-hash');

describe('and the chain contains a block with a jumped difficulty', () => {
it('return false', () => {
const lastBlock = blockchain.chain[blockchain.chain.length-1];
const lastHash = lastBlock.hash;
const timestamp = Date.now();
const nonce = 0;
const data = [];
const difficulty = lastBlock.difficulty - 3;
const hash = cryptoHash(timestamp, lastHash, difficulty, nonce, data);
const badBlock = new Block({
timestamp, lastHash, hash, nonce, data

                    });
                    blockchain.chain.push(badBlock);

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

And we finally update the isValidChain in 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, nonce, difficulty,data } = chain[i];
        const actualLastHash = chain[i-1].hash;
        const lastDifficulty = chain[i-1].difficulty;

        if(lastHash !== actualLastHash) return false;

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

        if(hash !== validatedHash) return false;

        if(Math.abs(lastDifficulty - difficulty) > 1) return false 
    }

    return true;
}

Personal reflection:
This took me a week longer to implement and follow along because the tests for error checking wasn't working. I finally got feed back from the coures instructor and it turns out that I hadn't properly nested the beforeEach block in the blockchain test file. While refactoring some code and checking the difference, I can get the test checking properly it seems.

Still at this part of the course we've completed the block and test needed to work on the blockchain network. Before moving onto the API and Network implementation I'll review everything I've done up to this point. I'm getting more comfortable with porject but need to take it one section at a time. I've already statred thinking about how this could be implemented in future cloud infrastructure but for now just relax, review and get ready for the next section.

Thanks for reading :)

Top comments (1)

Collapse
 
marianodacosta profile image
Mariano D'Acosta

Hi Jonathan.
Thank you for posting out this.
I had to install Jest v.-26.0.1 and Yarn v- 1.22.4, as of the node version I have installed on my machine, v12.16.3. I am trouble with the Mock log instance, as Jest doesn't seam to receive the chain replacement, and so it retrieves an error.
" FAIL ./blockchain.test.js (6.121 s)
● Blockchain › replaceChain() › and the chain is valid › logs about the chain replacement

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

   98 |             });
   99 |             it('logs about the chain replacement', () => {
> 100 |                 expect(logMock).toHaveBeenCalled();
      |                                 ^
  101 |             });
  102 |         });
  103 |     });"

Did it worked well for you, or did you face the same issue?