Building a Blockchain Using JavaScript: Part 2

Proof of Work

In this second part of this series, we will focus on implementing the proof of work mechanism in our JavaScript blockchain code. We will cover the following:

  1. What is Proof of Work?

  2. Why is Proof of Work important?

  3. Introducing the nonce and difficulty

  4. Modifying the Block class to include proof of work

  5. Adjusting the Blockchain class to incorporate proof of work

Let's get technical!

1. What is Proof of Work?

Proof of Work (PoW) is a consensus algorithm used in blockchain systems to validate and secure transactions. It involves finding a solution to a computational problem that requires significant computational power and time. This solution, known as the "proof," is then included in the block to prove that the required work has been done.

2. Why is Proof of Work important?

Proof of Work is important for several reasons:

  • Security: PoW ensures that malicious actors cannot easily manipulate the blockchain. The computational effort required to find a valid proof makes it infeasible to alter past blocks.

  • Consensus: PoW allows participants in the network to reach a consensus on the validity of transactions. Nodes in the network can independently verify the proof and agree on the order of transactions.

  • Incentives: PoW incentivizes miners to contribute their computational power to secure the network. Miners are rewarded with the blockchain's native cryptocurrency for successfully mining a block.

3. Introducing the nonce and difficulty

To implement PoW, we introduce two additional concepts: the nonce and the difficulty.

  • Nonce: The nonce is a random value that miners modify to find valid proof. Miners repeatedly change the nonce until proof is found that meets the required criteria.

  • Difficulty: The difficulty determines the complexity of the computational problem that miners must solve to find a valid proof. It is a measure of how many leading zeros the hash of a block must have to be considered valid. A higher difficulty requires more computational power and time to find a valid proof.

4. Modifying the Block class to include proof of work

To implement proof of work in our Block class, we need to make the following modifications:

  • Add a nonce property to the Block class to store the nonce value.

  • Modify the calculateHash method to include the nonce value in the hash calculation.

  • Add a mineBlock method that repeatedly changes the nonce value until a valid proof is found.

Here's an updated version of the Block class with these modifications:

class Block {
  constructor(index, timestamp, data, previousHash = "") {
    this.index = index;
    this.timestamp = timestamp;
    this.data = data;
    this.previousHash = previousHash;
    this.hash = this.calculateHash();
    this.nonce = 0;
  }

  calculateHash() {
    return SHA256(
      this.index +
        this.previousHash +
        this.timestamp +
        JSON.stringify(this.data) +
        this.nonce
    ).toString();
  }

  mineBlock(difficulty) {
    while (
      this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
    ) {
      this.nonce++;
      this.hash = this.calculateHash();
    }
    console.log("Block mined " + this.hash);
  }
}

The mineBlock method repeatedly increments the nonce value and recalculates the hash until a valid proof is found. The valid proof is determined by the required number of leading zeros defined by the difficulty parameter.

5. Adjusting the Blockchain class to incorporate proof of work

To incorporate proof of work into our Blockchain class, we need to make the following adjustments:

  • Add a difficulty property to the Blockchain class to define the difficulty level.

  • Update the addBlock method to include the mineBlock function call with the specified difficulty.

  • Adjust the isChainValid method to verify the validity of the proof of work.

Here's an updated version of the Blockchain class:

class Blockchain {
  constructor() {
    this.chain = [this.createGenesisBlock()];
    this.difficulty = 4; // Adjust the difficulty level as desired
  }

  // Other methods...

  addBlock(newBlock) {
    newBlock.previousHash = this.getLatestBlock().hash;

    newBlock.mineBlock(this.difficulty);
    this.chain.push(newBlock);
  }

  isChainValid() {
    for (let i = 1; i < this.chain.length; i++) {
      const currentBlock = this.chain[i];
      const previousBlock = this.chain[i - 1];

      if (currentBlock.hash !== currentBlock.calculateHash()) {
        return false;
      }

      if (currentBlock.previousHash !== previousBlock.hash) {
        return false;
      }

      // Verify the validity of the proof

Proof of Work with Different Difficulty Levels

In the above code, we have a Block class that represents each block in the blockchain. It has properties like index, timestamp, data, previousHash, hash, and nonce. The mineBlock method is responsible for finding a valid proof of work for each block.

The Blockchain class maintains the chain of blocks. It has a difficulty property that defines the number of leading zeros required in the hash of each block for it to be considered valid. It also has an addBlock method that adds a new block to the chain and uses the mineBlock method to find the proof of work.

Now, let's see the behavior of the code and the corresponding console logs when implementing proof of work with different difficulty levels.

1. Difficulty Level 4

In the code provided above, the default difficulty level is set to 4 (this.difficulty = 4). When running the code with this difficulty level, the console logs will show the following output:

Mining block 1...
Block mined 00001959d3038f328919f317987287f8fedcc57f6dc9f4871c2f9db1b67beabe
Mining block 2...
Block mined 0000bede65292077f5f84fe54c4f801e953650557259bc8d3b7bc25269eb70f7

In the output, we can observe that the hash of each block begins with four leading zeros (0000). This signifies that the proof of work has been successfully found with the required difficulty level.

2. Difficulty Level 2

Now, let's modify the difficulty level to 2 (this.difficulty = 7) and run the code again. The console logs will display the following output:

Mining block 1...
Block mined 009659303dc8bce648e8d4b734b082427af0b122d7010031cbb611db779604ed
Mining block 2...
Block mined 0047ab7e56f359f5b1dfc674e6683022fbe2cc3becbb94509fbabf8d5e30dc2a

Here, we can see that the hash of each block now begins with two leading zeros (00).

As the difficulty level increases, so does the complexity of finding a valid proof of work. The higher difficulty level requires more computational power and time, resulting in a longer mining process to find the correct nonce.

Understanding the Behavior

The behavior we observe is a direct result of the proof of work algorithm. By incrementing the nonce value and recalculating the hash until a valid proof is found, the miners make the computational problem more difficult to solve. The number of leading zeros required in the hash determines the level of difficulty.

With a higher difficulty level, finding a valid proof becomes more challenging, leading to a longer mining time. On the other hand, a lower difficulty level allows for faster mining as it requires fewer leading zeros in the hash.

Setting an appropriate difficulty level is crucial for maintaining the security and efficiency of the blockchain. It should be adjusted based on factors like network size, computational power, and desired block mining time.

See you in the next!