3.4.JS手写一个迷你区块链实战(4)

152 阅读3分钟

命令行工具使用

这个小节我们需要给整个 node-chain 项目添加上命令行工具,就像使用 vue-cli 一样运行我们的项目相关功能

自定义命令流程

开发命令行工具我们这里使用一个第三方包,叫做 commander,这个包可以解析命令行参数并且执行相应的操作,下面我们先让 commander 跑起来

第 1 步,安装 commander 包

npm install commander

第 2 步,调整我们当前项目的结构,新建一个 blockchain.js 文件,把 index.js 的代码剪切过去,项目结构如下

image.png

第 3 步,在 index.js 编写如下代码

#!/usr/bin/env node
console.log('自定义命令执行了')

第 4 步,在 package.json 加入下面配置代码

  "bin":{
    "node-chain": "./index.js"
  },

image.png

第 5 步,把 node-chain 这个命令链接到全局,你需要在项目目录下执行命令

npm link

第 6 步,测试效果

node-chain

在命令行输入命令 node-chain,我们在 index.js 里面的代码被执行了

image.png

结果符合预期

commander 使用

index.js 中添加如下代码

#!/usr/bin/env node

const { Command } = require('commander')
const program = new Command()
program
  .name('node-chain')
  .description('这是一个区块链演示工具')
  .version('0.0.1')

program
  .command('mine ')
  .description('挖矿')
  .action(() => {
    console.log('执行挖矿')
  })

program.parse()

我们再次运行 node-chain 命令,执行结果如下

image.png

区块链加入命令行

第 1 步,修改 blockchain.js 代码,这里需要修改两处

第 1 处代码是,在执行完挖矿函数后,我们把得到的新区块返回出去,这样我们能在命令行代码里面使用到

// 挖矿
  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);
      return ''
    }
    // 这里返回新区块
    return newBlock;
  }

第二处需要修改的是,把整个 BlockChain 类导出

module.exports = Blockchain

修改后的代码如下:

blockchain.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)
    }
    return newBlock
  }
  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]
  }
}

module.exports = BlockChain

第 2 步,在 index.js 文件中,添加如下代码

index.js

#!/usr/bin/env node

const { Command } = require('commander')
const program = new Command()
// 引入区块链类
const BlockChain = require('./blockchain')
const blockchain = new BlockChain()
program
  .name('node-chain')
  .description('这是一个区块链演示工具')
  .version('0.0.1')

program
  .command('mine ')
  .description('挖矿')
  .action(() => {
    const newBlock = blockchain.mine()
    if (newBlock) {
      console.log(newBlock)
    }
  })

program.parse()

第 3 步,执行测试

node-chain mine

执行结果

image.png

结果符合预期

最后,我们按照上面的代码逻辑,再添加一个查看区块链的命令

#!/usr/bin/env node

const { Command } = require('commander')
const program = new Command()
// 引入区块链类
const BlockChain = require('./blockchain')
const blockchain = new BlockChain()
program
  .name('node-chain')
  .description('这是一个区块链演示工具')
  .version('0.0.1')

program
  .command('mine ')
  .description('挖矿')
  .action(() => {
    const newBlock = blockchain.mine()
    if (newBlock) {
      console.log(newBlock)
    }
  })
program
  .command('view')
  .description('查看完整区块链信息')
  .action(() => {
    console.log(blockchain.blockchain)
  })

program.parse()

我们运行命令查看结果

node-chain view

结果如下

image.png

命令已经跑通,但是结果还有问题,每次挖矿过后,区块链并没有做持久化操作,因此,当你查看的时候,区块链中只有一个区块,这个问题,我们下个小节来解决