天天看點

100行代碼實作一個區塊鍊!

廢話不多說,開始跟我用golang搭建個迷你區塊鍊:

首先,引入包:

package main

import (
  "bytes"
  "crypto/sha256"
  "encoding/binary"
  "fmt"
  "math"
  "math/big"
  "time"
)      

設定一些常量,工作量證明難度系數dif,搜尋上界INT64_MAX:

const (
  dif       = 20
  INT64_MAX = math.MaxInt64
)      

然後我們設計一個簡單的區塊體和區塊鍊體,每個區塊呢有前個區塊哈希,本區塊哈希,資料,鍊長,時間戳和随機數:

type Block struct {
  PrevHash  []byte
  Hash      []byte
  Data      string
  Height    int64
  Timestamp int64
  Nonce     int
}

type BlockChain struct {
  Blocks []Block
}      

實作一段工作量證明函數,這個工作量證明呢以後聊共識算法會細講的,也就是這裡實作了不可篡改(reigns最近被raft血虐的,TAT):

func IntToHex(num int64) []byte {
  buff := new(bytes.Buffer)
  err := binary.Write(buff, binary.BigEndian, num)
  if err != nil {
    panic(err)
  }
  return buff.Bytes()
}

func ProofOfWork(b Block, dif int) ([]byte, int) {
  target := big.NewInt(1)
  target.Lsh(target, uint(256-dif))
  nonce := 0
  for ; nonce < INT64_MAX; nonce++ {
    check := bytes.Join(
      [][]byte{b.PrevHash,
        []byte(b.Data),
        IntToHex(b.Height),
        IntToHex(b.Timestamp),
        IntToHex(int64(nonce))},
      []byte{})
    hash := sha256.Sum256(check)
    var hashInt big.Int
    hashInt.SetBytes(hash[:])
    if hashInt.Cmp(target) == -1 {
      return hash[:], nonce
    }
  }
  return []byte(""), nonce
}      

然後呢這段函數實作生成創世塊,也就是鍊頭啦:

func GenesisBlock(data string) BlockChain {
  var bc BlockChain
  bc.Blocks = make([]Block, 1)
  bc.Blocks[0] = Block{
    PrevHash:  []byte(""),
    Data:      data,
    Height:    1,
    Timestamp: time.Now().Unix(),
  }
  bc.Blocks[0].Hash, bc.Blocks[0].Nonce = ProofOfWork(bc.Blocks[0], dif)
  return bc
}      

這段呢是生成新的區塊,也就是俗稱的挖礦,當然了真實的挖礦可比這個複雜多了:

func GenerateBlock(bc *BlockChain, data string) {
  prevBlock := bc.Blocks[len(bc.Blocks)-1]
  block := Block{
    PrevHash:  prevBlock.Hash,
    Data:      data,
    Height:    prevBlock.Height + 1,
    Timestamp: time.Now().Unix(),
  }
  block.Hash, block.Nonce = ProofOfWork(block, dif)
  bc.Blocks = append(bc.Blocks, block)
}      

再寫個列印區塊函數:

func Print(bc BlockChain) {
  for _, i := range bc.Blocks {
    fmt.Printf("PrevHash: %x\n", i.PrevHash)
    fmt.Printf("Hash: %x\n", i.Hash)
    fmt.Println("Block's Data: ", i.Data)
    fmt.Println("Current Height: ", i.Height)
    fmt.Println("Timestamp: ", i.Timestamp)
    fmt.Println("Nonce: ", i.Nonce)
  }
}      

好啦,各個函數都寫完了,不多不少99行

100行代碼實作一個區塊鍊!

100行代碼實作一個區塊鍊!
100行代碼實作一個區塊鍊!

寫個main函數看看吧~

來,reigns開始挖礦,先挖個創世塊,給alice轉賬2刀,然後再給alice轉3刀,看看這幾次次操作怎麼記錄在區塊鍊中吧:

func main() {
  blockchain := GenesisBlock("i am reigns")
  GenerateBlock(&blockchain, "send 2$ to alice")
  GenerateBlock(&blockchain, "send 3$ to alice")
  Print(blockchain)
}      

輸出結果:

100行代碼實作一個區塊鍊!

由于創世塊沒有前面的區塊,是以他的PrevHash就是空,記錄給alice轉賬2刀的這個區塊,它的PrevHash就是創世塊了,後面的以此類推,得到一個不可篡改鍊。

也可以發現啊,區塊生成并不是一下子執行完成的,三個區塊用了接近7秒鐘,這就是工作量證明算法起的作用了,防止惡意節點不勞而獲,每個新的區塊都需要付出一定計算力才能得到,當然這裡沒有設定很高的困難度(dif = 20),也沒有根據時間動态增加難度,比特币網絡中一般設定的挖礦難度都需要10分鐘左右的,想想2009年的10分鐘和2019的10分鐘,難度提了多少~

怎麼樣,是不是了解了區塊鍊的基本結構呢,當然了成熟的鍊使用的資料存儲結構肯定不會是一個字元串,比如比特币用的是默克爾樹,交易也不會這麼随便就成功,都需要依靠一個叫做智能合約的東東進行限制的。

繼續閱讀