啊,這個只是一個玩具類型的區塊鏈而已~主要關注在區塊鏈的一部分實現上,暫時沒有涉及到分布式相關的問題。這個項目的文件組織如下~
. ├── block.go // Block struct 的聲明以及相關函數 ├── blockchain.go // BlockChain struct 的聲明以及相關函數 ├── cli.go // 與 command line 相關的代碼 ├── pow.go // 一個簡單的 Proof of Work 實現 └── utils.go // 輔助函數
![](/wp-content/uploads/2019/11/toy-blockchain.webp)
首先是 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() } } }