3.3.JS手写一个迷你区块链实战(3)

154 阅读3分钟

新区块校验

当挖矿挖出来了新的区块,我们需要把这个区块进行校验,既然要校验这个区块,那么就需要先把这个区块的结构给拼接出来,因此,在上一个小节的基础上,我们先把生成区块的逻辑先抽离出来

index.js

const crypto = require('crypto')

class BlockChain {
  constructor() {
    // 定义一个创世区块,即第一个区块的数据结构
    this.firstBlock = {
      index: 0,
      data: 'hello world',
      prevHash: '0',
      timestamp: 1679234322447,
      hash: 'f0c40a6c9ea4b9ae794c89744b45a2aa10af6d6d561046d039db981f94a5073b'
    }
    // 区块链数组
    this.blockchain = [this.firstBlock]
    // 难度值
    this.difficulty = 4
  }
  // 生成区块的hash值
  createHash({ index, prevHash, timestap, data, nonce }) {
    return crypto
      .createHash('sha256')
      .update(index + prevHash + timestap + data + nonce)
      .digest('hex')
  }
  // 挖矿
  mine() {
    // 这里需要对新区块进行校验
    let newBlock = this.createNewBlock()
    // 接下来的逻辑就是校验这个新区块,然后加入到区块链中
  }
  // 创建新区块的逻辑
  createNewBlock() {
    // 记录挖矿次数
    let nonce = 0
    // 记录当前是第几个区块
    let index = this.blockchain.length
    // 交易数据
    let data = 'A -> B: 100'
    // 最后一个区块
    let prevHash = this.blockchain[this.blockchain.length - 1].hash
    let timestamp = new Date().getTime()
    let hash = this.createHash({ index, prevHash, timestamp, data, nonce })
    while (hash.slice(0, this.difficulty) !== '0'.repeat(this.difficulty)) {
      nonce++
      hash = this.createHash({ index, prevHash, timestamp, data, nonce })
    }
    return {
      index,
      data,
      prevHash,
      timestamp,
      hash,
      nonce
    }
  }
}

let b = new BlockChain()
b.mine()

先分析校验新区块的思路,我们需要校验哪些点

1.生成的新区块的时间戳,是否是大于区块链中最后的那个区块
2.生成的新区块的prevHash,是否等于区块链中最后的那个区块的hash
3.生成的新区块的难度值,是否符合我们设定的难度值
4.生成的新区块的index,是否等于区块链中最后的那个区块的index + 1
5.生成的新区块的随机值,是否被篡改

注意:以上校验点124都是和区块链中最后一个区块进行对比验证

接下来,我们就去具体实现代码, 我们单独写一个方法来验证

   validBlock(newBlock, lastBlock) {
    // 校验判断
    if (
      newBlock.timestamp < lastBlock.timestamp ||
      newBlock.index !== lastBlock.index + 1 ||
      newBlock.prevHash !== lastBlock.hash ||
      newBlock.hash.slice(0, this.difficulty) !== "0".repeat(this.difficulty) ||
      newBlock.hash !== this.createHash(newBlock)
    ) {
      return false;
    }
    return true;
  }

新区块校验完了以后,我们还需要校验整个区块链是否被篡改过

接下来,还需要封装一个函数,对整个区块链和新区块进行校验

  validBlockChain(blockchain) {
    // 从后往前验证
    for (let i = blockchain.length - 1; i >= 1; i--) {
      if (!this.validBlock(blockchain[i], blockchain[i - 1])) {
        return false;
      }
    }
    // 校验创世区块
    if (JSON.stringify(blockchain[0]) !== JSON.stringify(this.firstBlock)) {
      return false;
    }
    return true;
  }

最后,在挖矿函数中,对新出的区块和整个区块链进行校验

  // 挖矿
  mine() {
    // 挖出新区块
    let newBlock = this.createNewBlock();
    // 获取到区块链中最后的区块
    let lastBlock = this.getLastBlock();
    // 校验区块和区块链的合法性
    if (
      this.validBlock(newBlock, lastBlock) &&
      this.validBlockChain(this.blockchain)
    ) {
      this.blockchain.push(newBlock);
    } else {
      console.log("错误:区块链被篡改 ", this.blockchain);
    }
  }

上面函数中,我们封装了一个方法来获取区块链中最后一个区块

  getLastBlock() {
    return this.blockchain[this.blockchain.length - 1];
  }

接下来,测试效果

let b = new BlockChain()
b.mine()
b.blockchain[1].nonce = 99
b.mine()
console.log(b.blockchain)

结果如下

image.png

结果符合预期

到目前为止,index.js 里面的完整代码如下,如果你写代码的过程中有报错,查看下面完整的代码,看看是否有遗漏

const crypto = require('crypto')

class BlockChain {
  constructor() {
    // 定义一个创世区块,即第一个区块的数据结构
    this.firstBlock = {
      index: 0,
      data: 'hello world',
      prevHash: '0',
      timestamp: 1679234322447,
      hash: 'f0c40a6c9ea4b9ae794c89744b45a2aa10af6d6d561046d039db981f94a5073b'
    }
    // 区块链数组
    this.blockchain = [this.firstBlock]
    // 难度值
    this.difficulty = 4
  }
  // 生成区块的hash值
  createHash({ index, prevHash, timestap, data, nonce }) {
    return crypto
      .createHash('sha256')
      .update(index + prevHash + timestap + data + nonce)
      .digest('hex')
  }
  // 挖矿
  mine() {
    // 挖出新区块
    let newBlock = this.createNewBlock()
    // 获取到区块链中最后的区块
    let lastBlock = this.getLastBlock()
    // 校验区块和区块链的合法性
    if (
      this.validBlock(newBlock, lastBlock) &&
      this.validBlockChain(this.blockchain)
    ) {
      this.blockchain.push(newBlock)
    } else {
      console.log('错误:区块链被篡改 ', this.blockchain)
    }
  }
  createNewBlock() {
    // 记录挖矿次数
    let nonce = 0
    // 记录当前是第几个区块
    let index = this.blockchain.length
    // 交易数据
    let data = 'A -> B: 100'
    // 最后一个区块
    let prevHash = this.blockchain[this.blockchain.length - 1].hash
    let timestamp = new Date().getTime()
    let hash = this.createHash({ index, prevHash, timestamp, data, nonce })
    while (hash.slice(0, this.difficulty) !== '0'.repeat(this.difficulty)) {
      nonce++
      hash = this.createHash({ index, prevHash, timestamp, data, nonce })
    }
    return {
      index,
      data,
      prevHash,
      timestamp,
      hash, 
      nonce
    }
  }
  validBlock(newBlock, lastBlock) {
    // 校验判断
    if (
      newBlock.timestamp < lastBlock.timestamp ||
      newBlock.index !== lastBlock.index + 1 ||
      newBlock.prevHash !== lastBlock.hash ||
      newBlock.hash.slice(0, this.difficulty) !== '0'.repeat(this.difficulty) ||
      newBlock.hash !== this.createHash(newBlock)
    ) {
      return false
    }
    return true
  }
  validBlockChain(blockchain) {
    // 从后往前验证
    for (let i = blockchain.length - 1; i >= 1; i--) {
      if (!this.validBlock(blockchain[i], blockchain[i - 1])) {
        return false
      }
    }
    // 校验创世区块
    if (JSON.stringify(blockchain[0]) !== JSON.stringify(this.firstBlock)) {
      return false
    }
    return true
  }

  getLastBlock() {
    return this.blockchain[this.blockchain.length - 1]
  }
}

let b = new BlockChain()
b.mine()
b.blockchain[1].nonce = 99
b.mine()
console.log(b.blockchain)