前言
前面兩篇簡單的實現了區塊鏈的建立和工作量證明,但是都是在記憶體中進行的。實際的區塊鏈應該是可以永久儲存的,這樣才有意義。下面開始做永久性區塊鏈儲存。
知識點
1、github項目引用
2、github.com/boltdb/bolt項目的簡單使用
3、命令列使用
4、go常用的資料轉換
golang-區塊鏈非揮發性儲存體
1、建立區塊鏈
方法:func NewBlockChain() *BlockChain
// 建立區塊鏈// 返回一個區塊鏈執行個體func NewBlockChain() *BlockChain { var tip []byte // 開啟儲存區塊鏈的檔案blockchian.db、不存在則建立 db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } // 可讀寫的方式訪問區塊鏈檔案blockchian.db err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucket)) if b == nil { fmt.Println("No existing blockchain. Creating a new one...") // 建立創世紀塊 genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blockBucket)) if err != nil { log.Panic(err) } // 索引值對的方式儲存區塊在blockchian.db裡 err = b.Put(genesis.Hash, genesis.Serialize()) if err != nil { log.Panic(err) } // 以一個特殊的key儲存最新的區塊的hash,便於整個鏈的檢索 err = b.Put([]byte("l"), genesis.Hash) if err != nil { log.Panic(err) } tip = genesis.Hash } else { tip = b.Get([]byte("l")) } return nil }) if err != nil { log.Panic(err) } // 返回區塊鏈的執行個體 bc := &BlockChain{tip, db} return bc}
bolt是一種通過索引值對的方式來儲存資料的。具體的介紹和使用參考github.com/boltdb/bolt。程式啟動時候,調用NewBlockChain函數,開啟blockchian.db檔案執行個體化一個區塊鏈對象BlockChain。如果是第一次運行程式會建立blockchian.db檔案,並產生一個創世紀區塊,儲存進blockchian.db檔案中,然後返回一個區塊鏈對象。
2、添加新區塊到鏈上
方法:func (bc *BlockChain) AddBlock(data string)
// 添加新區塊到鏈上// 參數:data,區塊要儲存的資料func (bc *BlockChain) AddBlock(data string) { var lastHash []byte // 唯讀方式開啟blockchian.db,擷取最新區塊的hash值 err := bc.Db.View(func(tx1 *bolt.Tx) error { b := tx1.Bucket([]byte(blockBucket)) lastHash = b.Get([]byte("l")) return nil }) if err != nil { log.Panic(err) } // 計算新的區塊 newBlock := NewBlock(data, lastHash) bc.tip = newBlock.Hash // 讀寫的方式開啟lockchian.db,寫入新區塊到blockchian.db中。 bc.Db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucket)) if b == nil { log.Panic("bucket is nil !") } err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } //更新最新區塊的hash err = b.Put([]byte("l"), newBlock.Hash) if err != nil { log.Panic(err) } return nil })}
添加新區塊到鏈上,首先要擷取當前鏈上最新區塊的hash,然後計算新的區塊,計算出新的區塊後儲存新區塊資料到blockchian.db中。
3、區塊資料序列化轉換
將區塊block執行個體轉換成byte數組方法:func (b *Block)Serialize()[]byte
// 序列化一個區塊執行個體為byte數組func (b *Block)Serialize()[]byte { var result bytes.Buffer // 以一個byte的buf執行個體化一個編碼執行個體encoder encoder:=gob.NewEncoder(&result) err:=encoder.Encode(b) if err!=nil { log.Panic(err) } return result.Bytes()}
將byte數群組轉換成block對象方法:func Deserialize(b []byte)*Block
// 還原序列化byte數組,產生block執行個體。func Deserialize(b []byte)*Block{ var block Block decoder:=gob.NewDecoder(bytes.NewReader(b)) err:=decoder.Decode(&block) if err!=nil{ log.Panic(err) } return &block}
4、命令列flag使用
// 命令列執行程式func (cli *CLI) Run() { cli.validateArgs() // 建立命令列對象 addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError) // 命令列對象添加參數, addBlockData := addBlockCmd.String("data", "", "區塊資料不可為空!") switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "printchain": err:=printChianCmd.Parse(os.Args[2:]) if err!=nil{ log.Panic(err) } default: cli.printUsage() os.Exit(1) } if addBlockCmd.Parsed(){ if *addBlockData==""{ addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChianCmd.Parsed(){ cli.printChain() //cli.printUsage() }}
5、github項目引用
下載github.com/boltdb/bolt項目到工程目錄,如附件項目結構圖所示。
注意設定項目路徑為gopath路徑。
附件
1、項目結構項目目錄結構
2、代碼
main.go
package mainimport ( "core")func main() { // 建立區塊鏈 bc := core.NewBlockChain() // 關閉本地庫 defer bc.Db.Close() // 執行個體命令列對象 cli := core.CLI{bc} cli.Run()}
block.go
package coreimport ( "time" "strconv" "bytes" "crypto/sha256" "encoding/gob" "log")type Block struct { TimeStamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int}func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} pow := NewProofOfWork(block) block.Nonce, block.Hash = pow.Run() return block}func (b *Block) SetHash() { strTimeStamp := []byte(strconv.FormatInt(b.TimeStamp, 10)) headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, strTimeStamp}, []byte{}) hash := sha256.Sum256(headers) b.Hash = hash[:]}func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{})}// 序列化一個區塊執行個體為byte數組func (b *Block)Serialize()[]byte { var result bytes.Buffer // 以一個byte的buf執行個體化一個編碼執行個體encoder encoder:=gob.NewEncoder(&result) err:=encoder.Encode(b) if err!=nil { log.Panic(err) } return result.Bytes()}// 還原序列化byte數組,產生block執行個體。func Deserialize(b []byte)*Block{ var block Block decoder:=gob.NewDecoder(bytes.NewReader(b)) err:=decoder.Decode(&block) if err!=nil{ log.Panic(err) } return &block}
blockchain.go
package coreimport ( "fmt" "log" "github.com/boltdb/bolt")const dbFile = "blockchian.db"const blockBucket = "blocks"type BlockChain struct { tip []byte Db *bolt.DB}type BLockchainIterator struct { currentHash []byte Db *bolt.DB}// 添加新區塊到鏈上// 參數:data,區塊要儲存的資料func (bc *BlockChain) AddBlock(data string) { var lastHash []byte // 唯讀方式開啟lockchian.db,擷取最新區塊的hash值 err := bc.Db.View(func(tx1 *bolt.Tx) error { b := tx1.Bucket([]byte(blockBucket)) lastHash = b.Get([]byte("l")) return nil }) if err != nil { log.Panic(err) } // 計算新的區塊 newBlock := NewBlock(data, lastHash) bc.tip = newBlock.Hash // 讀寫的方式開啟lockchian.db,寫入新區塊到lockchian.db中。 bc.Db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucket)) if b == nil { log.Panic("bucket is nil !") } err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } //更新最新區塊的hash err = b.Put([]byte("l"), newBlock.Hash) if err != nil { log.Panic(err) } return nil })}func (bc *BlockChain) Iterator() *BLockchainIterator { var lastHash []byte bc.Db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte(blockBucket)) lastHash = b.Get([]byte("l")) //c := b.Cursor() //for k, v := cursor.First(); k != nil; k, v = cursor.Next() { // fmt.Printf("key=%s, value=%s\n", k, v) //} return nil }) return &BLockchainIterator{lastHash, bc.Db}}func (bci *BLockchainIterator) Next() *Block { var byteBlock []byte bci.Db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucket)) byteBlock = b.Get(bci.currentHash) return nil }) block := Deserialize(byteBlock) bci.currentHash = block.PrevBlockHash return block}// 建立區塊鏈// 返回一個區塊鏈執行個體func NewBlockChain() *BlockChain { var tip []byte // 開啟儲存區塊鏈的檔案blockchian.db、不存在則建立 db, err := bolt.Open(dbFile, 0600, nil) if err != nil { log.Panic(err) } // 可讀寫的方式訪問區塊鏈檔案blockchian.db err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucket)) if b == nil { fmt.Println("No existing blockchain. Creating a new one...") // 建立創世紀塊 genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blockBucket)) if err != nil { log.Panic(err) } // 索引值對的方式儲存區塊在blockchian.db裡 err = b.Put(genesis.Hash, genesis.Serialize()) if err != nil { log.Panic(err) } // 以一個特殊的key儲存最新的區塊的hash,便於整個鏈的檢索 err = b.Put([]byte("l"), genesis.Hash) if err != nil { log.Panic(err) } tip = genesis.Hash } else { tip = b.Get([]byte("l")) } return nil }) if err != nil { log.Panic(err) } // 返回區塊鏈的執行個體 bc := &BlockChain{tip, db} return bc}
cli.go
package coreimport ( "fmt" "os" "flag" "log" "strconv")type CLI struct { Bc *BlockChain}func (cli *CLI) printUsage() { fmt.Println("Usage:") fmt.Println(" addblock -data(區塊的資料) - 添加一個區塊到區塊鏈上面去。") fmt.Println(" printchain - 列印區塊鏈上所有的區塊")}func (cli *CLI) validateArgs() { if len(os.Args)<2{ cli.printUsage() os.Exit(1) }}func (cli *CLI) addBlock(data string) { cli.Bc.AddBlock(data) fmt.Println("Success!")}func (cli *CLI)printChain() { bci:=cli.Bc.Iterator() for{ block:=bci.Next() fmt.Printf("Prive hash :%x\n",block.PrevBlockHash) fmt.Printf("Data: %s\n",block.Data) fmt.Printf("Hash: %x\n",block.Hash) pow := NewProofOfWork(block) fmt.Printf("pow:%s\n",strconv.FormatBool(pow.Validate())) fmt.Println() if len(block.PrevBlockHash)==0{ break } }}// 命令列執行程式func (cli *CLI) Run() { cli.validateArgs() // 建立命令列對象 addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError) // 命令列對象添加參數, addBlockData := addBlockCmd.String("data", "", "區塊資料不可為空!") switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "printchain": err:=printChianCmd.Parse(os.Args[2:]) if err!=nil{ log.Panic(err) } default: cli.printUsage() os.Exit(1) } if addBlockCmd.Parsed(){ if *addBlockData==""{ addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChianCmd.Parsed(){ cli.printChain() //cli.printUsage() }}
proofofwork.go
package coreimport ( "math" "math/big" "fmt" "crypto/sha256" "bytes")var ( maxNonce = math.MaxInt64)const targetBits = 12type ProofOfWork struct { block *Block target *big.Int}func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} return pow}func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join([][]byte{ pow.block.PrevBlockHash, pow.block.Data, IntToHex(pow.block.TimeStamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}) return data}func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) for nonce < maxNonce { data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } fmt.Printf("\n\n") return nonce, hash[:]}func (pow *ProofOfWork) Validate() bool { var hashInt big.Int data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) isValid := hashInt.Cmp(pow.target) == -1 return isValid}
utils.go
package coreimport ( "bytes" "encoding/binary" "log" "crypto/sha256")func IntToHex(num int64) []byte { buff := new(bytes.Buffer) err := binary.Write(buff, binary.BigEndian, num) if err != nil { log.Panic(err) } return buff.Bytes()}func DataToHash(data []byte) []byte { hash := sha256.Sum256(data) return hash[:]}