Toy BlockChain with GoLang

啊,這個只是一個玩具類型的區塊鏈而已~主要關注在區塊鏈的一部分實現上,暫時沒有涉及到分布式相關的問題。這個項目的文件組織如下~

.
 ├── block.go       // Block struct 的聲明以及相關函數
 ├── blockchain.go  // BlockChain struct 的聲明以及相關函數
 ├── cli.go         // 與 command line 相關的代碼
 ├── pow.go         // 一個簡單的 Proof of Work 實現
 └── utils.go       // 輔助函數

首先是 utils.go,這裏聲明了兩個輔助函數,一個是將 int64 類型的數字轉為其對應的字節表示,另一個是檢查 err 的函數

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "os"
)

//  int64 類型轉為其 Byte Respresentation
//
// Parameter
// ---------
//   num: 要轉換的 int64 數字
//
// Return
// ------
//   該數字對應的 Byte Respresentation
func IntToByte(num int64) []byte {
    var buffer bytes.Buffer
    err := binary.Write(&buffer, binary.BigEndian, num)
    CheckErr("IntToByte(num int64) []byte", err)
    return buffer.Bytes()
}

// 檢查是否出錯
// 
// Parameter
// ---------
//   info: 用戶定義的信息
//   err: error 類的實例
func CheckErr(info string, err error) {
    if err != nil {
        fmt.Printf("[ERROR] %s:%s\n", info, err)
        os.Exit(1)
    }
}

接下來是一個簡單的 Proof of Work 的實現,pow.go,這裡我們假定要求的是 SHA256 的最高 16 個 bit 都為 0 才行

package main

import (
    "crypto/sha256"
    "fmt"
    "math/big"
)

// Proof of Work
type ProofOfWork struct {
    block  *Block   // Block to be calculated
    target *big.Int // Complexity
}

// The most significant bits should be 16 consecutive zeros
const kTARGETBITS = 16

func NewProofOfWork(block *Block) *ProofOfWork {
    target := big.NewInt(1)

    // left shifting so that we can get a 1 at `kTARGETBITS + 1`th bit
    target.Lsh(target, 256-kTARGETBITS)

    // set bits for block
    block.Bits = kTARGETBITS

    return &ProofOfWork{block, target}
}

// Find the nonce which satisfies the target
func (pow *ProofOfWork) Run(num int64) ([]byte, int64) {
    // init nonce to num
    var nonce int64 = num
    var hashInt big.Int
    var hash [32]byte

    for {
        // get byte representation of the block with new nonce
        bytes := pow.block.GetBytesWithNonce(nonce)

        // calculate the corresponding SHA256
        hash = sha256.Sum256(bytes)

        // convert the SHA256 result to big.Int
        hashInt.SetBytes(hash[:])

        // check satisfication
        if pow.target.Cmp(&hashInt) == 1 {
            // break if satisfies
            fmt.Printf("[INFO] Block %d: Hash:[%x] Nonce:[%d]\n", num, hash, nonce)
            break
        }

        // otherwise try next nonce
        nonce++
    }

    // return corresponding hash and nonce
    return hash[:], nonce
}

// Check whether a block is valid or not
func (pow *ProofOfWork) IsValid() bool {
    var hashInt big.Int

    // get byte representation of the given block
    data := pow.block.GetBytes()

    // calculate the corresponding SHA256
    hash := sha256.Sum256(data)

    // convert the SHA256 result to big.Int
    hashInt.SetBytes(hash[:])

    // check satisfication
    return pow.target.Cmp(&hashInt) == 1
}

接下來就是 Block 的聲明還有它相關的實例函數了~block.go 如下~

package main

import (
    "bytes"
    "crypto/sha256"
    "encoding/gob"
    "time"
)

type Block struct {
    Version      int64  // 版本
    PreBlockHash []byte // 前一個區塊的 Hash 
    Hash         []byte // 這個區塊的 Hash
    Height       int64  // 高度
    TimeStamp    int64  // 生成的時間
    Bits         int64  // 難度
    Nonce        int64  // 隨機數
    Data         []byte // 保存的數據
}

// 產生一個新的區塊
//
// Parameter
// ---------
//   data: 該區塊將保存之數據
//   height: 該區塊的高度
//   preBlockHash: 前一個區塊的 Hash 
//
// Return
// ------
//   Block 類型實例
func NewBlock(data string, height int64, preBlockHash []byte) *Block {
    var block Block
    block = Block{
        Version:      1,                 //  1 
        PreBlockHash: preBlockHash,      // 前一個區塊的 Hash 
        Height:       height,            // 高度
        TimeStamp:    time.Now().Unix(), // 當前 UNIX 時間戳
        Bits:         0,                 // 難度
        Nonce:        0,                 // 隨機數
        Data:         []byte(data),      // 保存的數據
    }

    // 準備 PoW
    pow := NewProofOfWork(&block)
    // 計算 PoW 獲得 nonce  Hash
    hash, nonce := pow.Run(block.Height)
    // 保存 Hash
    block.Hash = hash[:]
    // 以及對應的 nonce
    block.Nonce = nonce

    return &block
}

// 將一個 Block 類型實例轉為其對應的 Byte Representation
//
// Return
// ------
//   該區塊對應的 Byte Representation
func (block *Block) GetBytes() []byte {
    // 將區塊各 field 拼接成 [][]byte
    tmp := [][]byte{
        IntToByte(block.Version),
        block.PreBlockHash,
        IntToByte(block.Height),
        IntToByte(block.TimeStamp),
        IntToByte(block.Bits),
        IntToByte(block.Nonce),
        block.Data,
    }

    // 合併成一個 []byte
    data := bytes.Join(tmp, []byte{})

    return data
}

// 使用給定的 nonce 計算一個 Block 類型實例對應的 Byte Representation
//
// Parameter
// ---------
//   nonce: 給定的 nonce
//
// Return
// ------
//   該區塊在使用給定的 nonce 之後對應的 Byte Representation
func (block *Block) GetBytesWithNonce(nonce int64) []byte {
    tmp := [][]byte{
        IntToByte(block.Version),
        block.PreBlockHash,
        IntToByte(block.Height),
        IntToByte(block.TimeStamp),
        IntToByte(block.Bits),
        IntToByte(nonce),
        block.Data,
    }
    data := bytes.Join(tmp, []byte{})
    return data
}

// 計算並設置一個區塊的 Hash
func (block *Block) SetHash() {
    data := block.GetBytes()
    hash := sha256.Sum256(data)
    block.Hash = hash[:]
}

// 創建創世區塊
func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", 1, []byte{})
}

// 序列化一個 Block 實例
//
// Return
// ------
//   對應的序列化之後的 Byte Representation
func (block *Block) Serialize() []byte {
    var buffer bytes.Buffer
    // 使用 encoding/gob 序列化對象
    encoder := gob.NewEncoder(&buffer)
    err := encoder.Encode(block)
    CheckErr("Serialize", err)
    return buffer.Bytes()
}

//  Byte Representation 反序列化對象
//
// Parameter
// ---------
//   data: Byte Representation
//
// Return
// ------
//   如果成功反序列化,則返回對應的 Block 實例
//   否則直接 os.Exit(1)
func Deserialize(data []byte) *Block {
    if len(data) == 0 {
        return nil
    }

    var block Block
    decoder := gob.NewDecoder(bytes.NewReader(data))
    err := decoder.Decode(&block)
    CheckErr("Deserialize", err)
    return &block
}

在有了 Block 的聲明之後,就可以搭建起 BlockChain 了~這裏就直接選擇了 Bolt 作為區塊鏈保存的數據庫,當然也可以用別的數據庫(^O^)/

package main

import (
    "fmt"
    "github.com/boltdb/bolt"
    "os"
)

// 保存區塊鏈的數據庫文件名
const kBLOCKCHAIN = "blockchain.db"

// 區塊所使用的 bucket 的名字
const kBLOCKBUCKET = "block_bucket"

// 保存最後一個區塊的 Hash  Key
const kLASTHASHKEY = "last_hash_key"

type BlockChain struct {
    db            *bolt.DB // 區塊鏈的數據庫引用
    lastBlockHash []byte   // 最後一個區塊的 Hash
}

// 創建新的區塊鏈
//
// Return
// ------
//   如果對應的區塊鏈的數據庫存在
//     且裡面包含至少一個 Block
//     那麼則返回對應的 BlockChain 實例
//   否則將會創建對應的數據庫以及 bucket
//     並且創建創世區塊
//     隨後返回 BlockChain 實例
func NewBlockChain() *BlockChain {
    // open blockchain database
    db, err := bolt.Open(kBLOCKCHAIN, 0600, nil)
    CheckErr("NewBlockChain() *BlockChain: bolt.Open(kBLOCKCHAIN, 0600, nil)", err)

    var lastHash []byte

    err = db.Update(func(tx *bolt.Tx) error {
        // get the bucket where blcoks store
        bucket := tx.Bucket([]byte(kBLOCKBUCKET))

        // if no such bucket
        // there is no existing blockchain
        if bucket == nil {
            fmt.Println("[INFO] NewBlockChain() *BlockChain: No existing blockchain found. Creating a new one...")

            // create the bucket
            bucket, err := tx.CreateBucket([]byte(kBLOCKBUCKET))
            CheckErr("NewBlockChain() *BlockChain: tx.CreateBucket([]byte(kBLOCKBUCKET))", err)

            // and the genesis block
            genesis := NewGenesisBlock()
            err = bucket.Put(genesis.Hash, genesis.Serialize())
            CheckErr("NewBlockChain() *BlockChain: bucket.Put(genesis.Hash, genesis.Serialize())", err)

            // store the hash of genesis block as known last hash
            err = bucket.Put([]byte(kLASTHASHKEY), genesis.Hash)
            CheckErr("NewBlockChain() *BlockChain: bucket.Put([]byte(kLASTHASHKEY), genesis.Hash)", err)
            lastHash = genesis.Hash
        } else {
            // if bucket exists
            // retrive the last hash
            lastHash = bucket.Get([]byte(kLASTHASHKEY))
        }

        return nil
    })
    CheckErr("NewBlockChain() *BlockChain: db.Update(func(tx *bolt.Tx) error", err)

    return &BlockChain{
        db,
        lastHash,
    }
}

// add a new block to the chain
func (bc *BlockChain) AddBlock(data string) {
    // get the hash and height of the last block
    var lastBlockHash []byte
    var lastBlockHeight int64
    err := bc.db.View(func(tx *bolt.Tx) error {
        // select the bucket
        bucket := tx.Bucket([]byte(kBLOCKBUCKET))
        if bucket == nil {
            // if bucket not exists
            // it may suggest that the database is corrupted
            fmt.Println("[ERROR] (bc *BlockChain) AddBlock(data string): bc.db.View(func(tx *bolt.Tx) error: bucket == nil")
            os.Exit(1)
        }

        // get the hash of the last block
        lastBlockHash = bucket.Get([]byte(kLASTHASHKEY))
        if lastBlockHash == nil {
            // it may suggest that the database is corrupted
            fmt.Println("[ERROR] (bc *BlockChain) AddBlock(data string): bc.db.View(func(tx *bolt.Tx) error: lastBlockHash == nil")
            os.Exit(1)
        }

        lastBlockData := bucket.Get(lastBlockHash)
        if lastBlockData == nil {
            // it may suggest that the database is corrupted
            fmt.Println("[ERROR] (bc *BlockChain) AddBlock(data string): bc.db.View(func(tx *bolt.Tx) error: lastBlockData == nil")
            os.Exit(1)
        }

        // try to deserialize the data
        lastBlock := Deserialize(lastBlockData)

        // retrive the height
        lastBlockHeight = lastBlock.Height

        return nil
    })
    CheckErr("(bc *BlockChain) AddBlock(data string): bc.db.View(func(tx *bolt.Tx) error", err)

    // generate new block
    block := NewBlock(data, lastBlockHeight+1, lastBlockHash)

    // update database
    err = bc.db.Update(func(tx *bolt.Tx) error {
        bucket := tx.Bucket([]byte(kBLOCKBUCKET))
        if bucket == nil {
            // if bucket not exists
            // it may suggest that the database is corrupted
            fmt.Println("[ERROR] (bc *BlockChain) AddBlock(data string): bc.db.Update(func(tx *bolt.Tx) error: bucket == nil")
            os.Exit(1)
        }

        err = bucket.Put(block.Hash, block.Serialize())
        CheckErr("(bc *BlockChain) AddBlock(data string): bc.db.Update(func(tx *bolt.Tx) error: bucket.Put(block.Hash, block.Serialize())", err)

        err = bucket.Put([]byte(kLASTHASHKEY), block.Hash)
        CheckErr("(bc *BlockChain) AddBlock(data string): bc.db.Update(func(tx *bolt.Tx) error: bucket.Put([]byte(kLASTHASHKEY), genesis.Hash)", err)
        bc.lastBlockHash = block.Hash

        return nil
    })
    CheckErr("(bc *BlockChain) AddBlock(data string): bc.db.Update(func(tx *bolt.Tx) error", err)
}

type BlockChainIterator struct {
    currentHash []byte   // 當前 Iterator 指向的區塊的 Hash
    db          *bolt.DB // 區塊鏈數據庫的引用
}

// 创建區塊鏈迭代器
//
// Return
// ------
//  區塊鏈迭代器
func (bc *BlockChain) NewIterator() *BlockChainIterator {
    return &BlockChainIterator{
        bc.lastBlockHash, // 初始化为指向最后一个区块
        bc.db,            // 再次引用該區塊鏈的數據庫引用
    }
}

// 返回迭代器的下一個區塊
//
// Return
// ------
//   如果有的話, 返回下一個區塊
//   否則返回 nil
func (it *BlockChainIterator) Next() (block *Block) {
    err := it.db.View(func(tx *bolt.Tx) error {
        bucket := tx.Bucket([]byte(kBLOCKBUCKET))
        if bucket == nil {
            return nil
        }
        data := bucket.Get(it.currentHash)
        if data == nil {
            block = nil
            return nil
        }

        block = Deserialize(data)
        it.currentHash = block.PreBlockHash
        return nil
    })

    CheckErr("(it *BlockChainIterator) Next() (block *Block)", err)
    return block
}

最後組裝一個簡單的 cli.go 就可以啦

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 添加新的數據到區塊鏈上
    addPtr := flag.String("add", "", "data for new block")
    // dump 出所有的區塊數據
    dumpPtr := flag.Bool("dump", false, "dump blockchain")
    // parse command line 的參數
    flag.Parse()

    // 實例化 BlockChain
    bc := NewBlockChain()

    // 判斷用戶是否要添加新的區塊
    if len(*addPtr) > 0 {
        // 如果字符串長度大於 0 的話
        // 則將用戶的輸入添加到區塊鏈上
        bc.AddBlock(*addPtr)
    }

    // 判斷用戶是否要 dump 出整個區塊鏈
    if *dumpPtr {
        // 如果是的話
        // 則使用迭代器從後向前 dump 出所有區塊
        iter := bc.NewIterator()
        next_block := iter.Next()
        for {
            if next_block == nil {
                break
            }
            fmt.Printf("[INFO] block[%d]:\n", next_block.Height)
            fmt.Printf("         PreBlockHash: %x\n", next_block.PreBlockHash)
            fmt.Printf("         Hash: %x\n", next_block.Hash)
            fmt.Printf("         Height: %d\n", next_block.Height)
            fmt.Printf("         TimeStamp: %d\n", next_block.TimeStamp)
            fmt.Printf("         Bits: %d\n", next_block.Bits)
            fmt.Printf("         Nonce: %d\n", next_block.Nonce)
            fmt.Printf("         Data: %s\n", next_block.Data)
            next_block = iter.Next()
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

7 − 1 =