Go语言开发区块链只需180行代码(推荐)

2020-01-28 13:04:47刘景俊

我们接着写一个函数,用来计算给定的数据的 SHA256 散列值:


func calculateHash(block Block) string {
 record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
 h := sha256.New()
 h.Write([]byte(record))
 hashed := h.Sum(nil)
 return hex.EncodeToString(hashed)
}

这个 calculateHash 函数接受一个块,通过块中的 Index,Timestamp,BPM,以及 PrevHash 值来计算出 SHA256 散列值。接下来我们就能编写一个生成块的函数:


func generateBlock(oldBlock Block, BPM int) (Block, error) {
 var newBlock Block
 t := time.Now()
 newBlock.Index = oldBlock.Index + 1
 newBlock.Timestamp = t.String()
 newBlock.BPM = BPM
 newBlock.PrevHash = oldBlock.Hash
 newBlock.Hash = calculateHash(newBlock)
 return newBlock, nil
}

其中,Index 是从给定的前一块的 Index 递增得出,时间戳是直接通过 time.Now() 函数来获得的,Hash 值通过前面的 calculateHash 函数计算得出,PrevHash 则是给定的前一个块的 Hash 值。

校验块

搞定了块的生成,接下来我们需要有函数帮我们判断一个块是否有被篡改。检查 Index 来看这个块是否正确得递增,检查 PrevHash 与前一个块的 Hash 是否一致,再来通过 calculateHash 检查当前块的 Hash 值是否正确。通过这几步我们就能写出一个校验函数:


func isBlockValid(newBlock, oldBlock Block) bool {
 if oldBlock.Index+1 != newBlock.Index {
  return false
 }
 if oldBlock.Hash != newBlock.PrevHash {
  return false
 }
 if calculateHash(newBlock) != newBlock.Hash {
  return false
 }
 return true
}

除了校验块以外,我们还会遇到一个问题:两个节点都生成块并添加到各自的链上,那我们应该以谁为准?这里的细节我们留到下一篇文章,这里先让我们记住一个原则:始终选择最长的链:

通常来说,更长的链表示它的数据(状态)是更新的,所以我们需要一个函数能帮我们将本地的过期的链切换成最新的链:


func replaceChain(newBlocks []Block) {
 if len(newBlocks) > len(Blockchain) {
  Blockchain = newBlocks
 }
}

到这一步,我们基本就把所有重要的函数完成了。接下来,我们需要一个方便直观的方式来查看我们的链,包括数据及状态。通过浏览器查看 web 页面可能是最合适的方式!

Web 服务

我猜你一定对传统的 web 服务及开发非常熟悉,所以这部分你肯定一看就会。

借助 Gorilla/mux 包,我们先写一个函数来初始化我们的 web 服务:


func run() error {
 mux := makeMuxRouter()
 httpAddr := os.Getenv("ADDR")
 log.Println("Listening on ", os.Getenv("ADDR"))
 s := &http.Server{
  Addr:   ":" + httpAddr,
  Handler:  mux,
  ReadTimeout: 10 * time.Second,
  WriteTimeout: 10 * time.Second,
  MaxHeaderBytes: 1 << 20,
 }
 if err := s.ListenAndServe(); err != nil {
  return err
 }
 return nil
}