Before due to the course requirements, based on Nodejs to do a simple implementation of block chain. The requirements are very simple, the structure records the block structure, incidentally can insert new blocks into the chain.

But if you want to support multiple users, you need to consider the issue of “reliability.” According to blockchain requirements, the data on the chain cannot be tampered with unless the computing power exceeds that of all other machines except the attacker.

I thought about it, and I did it.

🔍 View the full tutorial/read the original article 🔍

Technology research

I googled it and found a great project: github.com/lhartikk/na… . It’s only about 200 lines, but dozens of them are about setting up WS and HTTP servers, and the fly in the wall is that there’s no way to bulk insert blockchain and compute reliability.

Together with this project, it is almost certain that each block will be encapsulated into a class (structured representation), and the blockchain will also be encapsulated into a class that exposes the interface.

Block definition

To make it easier to represent a block, encapsulate it as a class, which has no methods:

/** * Structured definition of block information */
class Block {
  /** * constructor * @param {Number} index * @param {String} previousHash * @param {Number} timestamp * @param {*} data * @param  {String} hash */
  constructor(index, previousHash, timestamp, data, hash) {
    this.index = index // Block location
    this.previousHash = previousHash + ' ' // Hash of the previous block
    this.timestamp = timestamp // The timestamp when the block was generated
    this.data = data // The data carried by the block itself
    this.hash = hash + ' ' // The block generates a hash based on its own information and rules}}Copy the code

As for how to generate hashes, the rules used here are simple:

  1. Concatenate index, previouHash, TIMESTAMP and data to string them
  2. Using the SHA256 algorithm, the calculated demerits are hash

For convenience, a cryptographic library will be introduced:

const CryptoJS = require('crypto-js')
Copy the code

Chain structure definition

Many blocks are linked together to form a chain. This chain, I’ll call it class. And it implements many methods:

  1. Generates hash based on encryption rules
  2. Insert new block and check operation
  3. Batch insert block and check operation and reliability calculation

1. The origin

The origin block is “hard-coded” because it has no data in front of it. And it says it can’t be tampered with, that is, it can’t be overwritten. We place the generated origin block directly into the chain in the constructor.

class BlockChain {
  constructor() {
    this.blocks = [this.getGenesisBlock()]
  }

  /** * Creates a block of blockchain origin, which is hard-coded */
  getGenesisBlock() {
    return new Block(0.'0'.1552801194452.'genesis block'.'810f9e854ade9bb8730d776ea02622b65c02b82ffa163ecfe4cb151a14412ed4')}}Copy the code

2. Calculate the next block

BlockChain objects can automatically calculate the next block based on the current chain. And compared with the block information sent by the user, if the same, it is legal and can be inserted; Otherwise, the user’s block is illegal and cannot be inserted.

// Methods are all BlockChain object methods
  /** * Computes the hash value based on the information */
  calcuteHash(index, previousHash, timestamp, data) {
    return CryptoJS.SHA256(index + previousHash + timestamp + data) + ' '
  }

  /** * returns the last block node in the blockchain */
  getLatestBlock() {
    return this.blocks[this.blocks.length - 1]}/** * Calculates the next block of the current list * @param {*} blockData */
  generateNextBlock(blockData) {
    const previousBlock = this.getLatestBlock()
    const nextIndex = previousBlock.index + 1
    const nextTimeStamp = new Date().getTime()
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
  }
Copy the code

3. Insert the block

When inserting a block, check whether the current block is valid. If so, insert and return true. Otherwise return false.

  /** * Add a new node to the blockchain * @param {Block} newBlock */
  addBlock(newBlock) {
    // Valid block
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
      this.blocks.push(newBlock)
      return true  
    }
    return false
  }
Copy the code

The logic for checking is in the isValidNewBlock method, which does three things:

  1. Check whether the index of the new block is incremented
  2. Determines whether previousHash is equal to the hash of the previous block
  3. Determines whether the hash of the new block is generated according to a constrained rule
  @param {Block} newBlock * @param {Block} previousBlock */
  isValidNewBlock(newBlock, previousBlock) {
    if(
      !(newBlock instanceofBlock) || ! (previousBlockinstanceof Block)
    ) {
      return false
    }

    / / determine the index
    if(newBlock.index ! == previousBlock.index +1) { 
      return false
    }

    // Determine the hash value
    if(newBlock.previousHash ! == previousBlock.hash) {return false
    }

    // Computes the hash value of the new block to see if it matches the rule
    if(this.calcuteHash(newBlock.index, newBlock.previousHash, newBlock.timestamp, newBlock.data) ! == newBlock.hash) {return false
    }

    return true
  }
Copy the code

4. Batch insert

The logic of batch inserts is complicated, for example, there are four blocks in the current chain with subscripts: 0->1->2->3. Except that origin block 0 cannot be overridden, the original block can be replaced when a new chain with subscripts of “1->2->3->4” is inserted. The final result is: 0->1->2->3->4.

In terms of the subscript index processing, assuming the same situation as above, if the subscript of the chain passed in starts from an integer greater than 4, it obviously cannot join the subscript of the original blockchain and is directly thrown away.

But how to ensure credibility? When the new chain (B chain) replaces the original chain (A chain), A new chain (C chain) is formed. If length(C) > length(A), then the part to be replaced is overwritten. This ensures that the chain can only be tampered with if the computational force exceeds 50% of the total computational force.

Insert a new chain as follows:

  @param {Array} newChain */
  addChain(newChain) {
    if(this.isValidNewChain(newChain)) {
      const index = newChain[0].index
      this.blocks.splice(index)
      this.blocks = this.blocks.concat(newChain)
      return true
    }
    return false
  }
Copy the code

The above logic can be implemented as follows:

  /** * determine if the newly inserted blockchain is valid and overrides the original node * @param {Array} newChain */
  isValidNewChain(newChain) {
    if(Array.isArray(newChain) === false || newChain.length === 0) {
      return false
    }

    let newChainLength = newChain.length,
      firstBlock = newChain[0]

    // Hardcoded origin blocks cannot be changed
    if(firstBlock.index === 0) {
      return false
    }

    // Migrate the length of the new chain <= the length of the existing chain
    // The new chain is not trusted
    if(newChainLength + firstBlock.index <= this.blocks.length) {
      return false
    }

    // Check whether the new chain can be ported
    // And whether each node of the new chain conforms to the rules
    if(!this.isValidNewBlock(firstBlock, this.blocks[firstBlock.index - 1]) {return false
    }

    for(let i = 1; i < newChainLength; ++i) {
      if(!this.isValidNewBlock(newChain[i], newChain[i - 1]) {return false}}return true
  }
Copy the code

5. Why do I need batch inserts?

I wondered why “batch insert” was needed. Then I figured it out (I hope I’m right). Assume server S, and two users A and B.

A and B simultaneously pull the data of the known chain and generate them separately. A has A fast network speed but low computing power, so A block is generated and put into S. Note: At this point the block on S has been updated.

B is worse. It generates 2 blocks locally, but is limited by the network speed. It has to wait for the network speed to recover the incoming blocks. In this case, by rule, it can be covered. So in this case, server S receives 2 blocks from B, the updated chain length is 3 (including the origin block), and the block from A has been overwritten.

Effect of the test

There is no write server, but the fifth case described above is simulated. The code is in the test.js file, so just run it. Take a look at the screenshot below:

The upper red line is calculated first, and the lower red line is the blockchain tampered with by the higher-power client. Specific simulation process can see the code, here is no longer redundant.

All the code is placed at: github.com/dongyuanxin…

More articles in series

⭐ Bookmark/subscribe at GitHub ⭐

Front-end Knowledge System

  • Basic Knowledge of JavaScript (PART 1)
  • Basic Knowledge of JavaScript (Part 2)
  • ES6
  • Talk about the promise/async/await execution order and V8 bugs
  • Front-end interview often test the source code implementation
  • Flex hands-on and practical
  • .

Design Patterns Manual

  • The singleton pattern
  • The strategy pattern
  • The proxy pattern
  • Iterator pattern
  • Subscription-publish model
  • The bridge model
  • Memo mode
  • Template pattern
  • Abstract Factory pattern
  • .

Webpack4 progressive tutorial

  • Build ES6 with webpack4
  • Multi-page solution — Extract common code
  • Single page solution — code splitting and lazy loading
  • Webpack4 tutorial (5): handling CSS
  • Webpack4 教程 : JS Tree Shaking
  • Working with third-party JavaScript libraries
  • Development mode and Webpack-dev-server
  • .

⭐ Bookmark/subscribe at GitHub ⭐