本文構建了一個使用工做量證實機制(POW)的類BTC的區塊鏈。將區塊鏈持久化到一個Bolt數據庫中,而後會提供一個簡單的命令行接口,用來完成一些與區塊鏈的交互操做。這篇文章目的是但願幫助你們理解BTC源碼的架構,因此主要專一於的實現原理及存儲上,暫時忽略了 「分佈式」 這個部分。嚴格來講還不能算是一個徹底意義上的區塊鏈系統。html
語言:GO;git
數據庫:BoltDB;github
IDE: Goland或其餘工具均可以;golang
系統:不限,本文使用windows。數據庫
實際上,選擇任何一個數據庫均可以,本文先用的是BoltDB。在比特幣白皮書中,並無提到要使用哪個具體的數據庫,它徹底取決於開發者如何選擇。如今是比特幣的一個參考實現,Bitcoin core使用的是是LevelDB。windows
BoltDB安裝及使用能夠參考《BoltDB簡單使用教程》。數組
BoltDB有以下優勢:服務器
因爲 Bolt 意在用於提供一些底層功能,簡潔便成爲其關鍵所在。它的API 並很少,而且僅關注值的獲取和設置。僅此而已。
數據結構
Bolt 使用鍵值存儲,數據被存儲爲鍵值對(key-value pair,就像 Golang 的 map)。鍵值對被存儲在 bucket 中,這是爲了將類似的鍵值對進行分組(相似 RDBMS 中的表格)。所以,爲了獲取一個值,你須要知道一個 bucket 和一個鍵(key)。架構
注意:Bolt 數據庫沒有數據類型:鍵和值都是字節數組(byte array)。鑑於須要在裏面存儲 Go 的結構(準確來講,也就是存儲(塊)Block),咱們須要對它們進行序列化,也就說,實現一個從 Go struct 轉換到一個 byte array 的機制,同時還能夠從一個 byte array 再轉換回 Go struct。雖然咱們將會使用 encoding/gob 來完成這一目標,但實際上也能夠選擇使用 JSON, XML, Protocol Buffers 等等。之因此選擇使用 encoding/gob, 是由於它很簡單,並且是 Go 標準庫的一部分。
該部分主要包括:
對區塊結構的定義;建立區塊的方法NewBlock();區塊的序列化Serialize()與反序列化Deserialize()函數;以及創世區塊的生成NewGenesisBlock()。
//定義一個區塊的結構Block type Block struct { //版本號 Version int64 //父區塊頭哈希值 PreBlockHash []byte //當前區塊的Hash值, 爲了簡化代碼 Hash []byte //Merkle根 MerkleRoot []byte //時間戳 TimeStamp int64 //難度值 Bits int64 //隨機值 Nonce int64 //交易信息 Data []byte } //提供一個建立區塊的方法 func NewBlock(data string, preBlockHash []byte) *Block { var block Block block = Block{ Version: 1, PreBlockHash: preBlockHash, //Hash TODO MerkleRoot: []byte{}, TimeStamp: time.Now().Unix(), Bits: targetBits, Nonce: 0, Data: []byte(data)} //block.SetHash() pow := NewProofOfWork(&block) nonce, hash := pow.Run() block.Nonce = nonce block.Hash = hash return &block } // 將 Block 序列化爲一個字節數組 func (block *Block) Serialize() []byte { var buffer bytes.Buffer encoder := gob.NewEncoder(&buffer) err := encoder.Encode(block) CheckErr("Serialize", err) return buffer.Bytes() } // 將字節數組反序列化爲一個 Block 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 } //創世塊 func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{}) }
該部份內容主要包括:
定義一個區塊鏈結構BlockChain結構體;
提供一個建立BlockChain的方法NewBlockChain();
咱們但願
NewBlockchain實現的功能有
:
- 打開一個數據庫文件
- 檢查文件裏面是否已經存儲了一個區塊鏈
- 若是已經存儲了一個區塊鏈:
- 建立一個新的
Blockchain
實例- 設置
Blockchain
實例的 tip 爲數據庫中存儲的最後一個塊的哈希- 若是沒有區塊鏈:
- 建立創世塊
- 存儲到數據庫
- 將創世塊哈希保存爲最後一個塊的哈希
- 建立一個新的
Blockchain
實例,初始時 tail 指向創世塊( tail存儲的是最後一個塊的哈希值)
const dbFile = "blockchain.db" const blocksBucket = "bucket" const lastHashKey = "key" //定義一個區塊鏈結構BlockChain type BlockChain struct { //blocks []*Block //數據庫的操做句柄 db *bolt.DB //tail尾巴,表示最後一個區塊的哈希值 //在鏈的末端可能出現短暫分叉的狀況,因此選擇tail其實也就是選擇了哪條鏈 tail []byte } //提供一個建立BlockChain的方法 func NewBlockChain() *BlockChain { // 打開一個 BoltDB 文件 //func Open(path string, mode os.FileMode, options *Options) (*DB, error) db, err := bolt.Open(dbFile, 0600, nil) //utils中的校驗函數,校驗錯誤 CheckErr("NewBlockChain1", err) var lastHash []byte err = db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(blocksBucket)) // 若是數據庫中不存在bucket,要去建立創世區塊,將數據填寫到數據庫的bucket中 if bucket == nil { fmt.Println("No existing blockchain found. Creating a new one...") genesis := NewGenesisBlock() bucket, err := tx.CreateBucket([]byte(blocksBucket)) CheckErr("NewBlockChain2", err) err = bucket.Put(genesis.Hash, genesis.Serialize()) CheckErr("NewBlockChain3", err) err = bucket.Put([]byte(lastHashKey), genesis.Hash) CheckErr("NewBlockChain4", err) lastHash = genesis.Hash } else { //直接讀取最後區塊的哈希值 lastHash = bucket.Get([]byte(lastHashKey)) } return nil }) CheckErr("db.Update", err) return &BlockChain{db, lastHash} } //提供一個添加區塊的方法 func (bc *BlockChain) AddBlock(data string) { var preBlockHash []byte err := bc.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(blocksBucket)) if bucket == nil { os.Exit(1) } preBlockHash = bucket.Get([]byte(lastHashKey)) return nil }) CheckErr("AddBlock-View", err) block := NewBlock(data, preBlockHash) err = bc.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(blocksBucket)) if bucket == nil { os.Exit(1) } err = bucket.Put(block.Hash, block.Serialize()) CheckErr("AddBlock1", err) err = bucket.Put([]byte(lastHashKey), block.Hash) CheckErr("AddBlock2", err) bc.tail = block.Hash return nil }) CheckErr("AddBlock-Update", err) } //迭代器,就是一個對象,它裏面包含了一個遊標,一直向前/後移動,完成整個容器的遍歷 type BlockChainIterator struct { currentHash []byte db *bolt.DB } //建立迭代器,同時初始化爲指向最後一個區塊 func (bc *BlockChain) NewIterator() *BlockChainIterator { return &BlockChainIterator{bc.tail, bc.db} } // 返回鏈中的下一個塊 func (it *BlockChainIterator) Next() (block *Block) { err := it.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(blocksBucket)) if bucket == nil { return nil } data := bucket.Get(it.currentHash) block = Deserialize(data) it.currentHash = block.PreBlockHash return nil }) CheckErr("Next", err) return }
該部分主要包括:
建立POW的方法NewProofOfWork(block *Block) ;
計算哈希值的方法 Run() (int64, []byte);
//定義一個工做量證實的結構ProofOfWork type ProofOfWork struct { block *Block //目標值 target *big.Int } //難度值常量 const targetBits = 20 //建立POW的方法 func NewProofOfWork(block *Block) *ProofOfWork { //000000000000000... 01 target := big.NewInt(1) //0x1000000000000...00 target.Lsh(target, uint(256-targetBits)) pow := ProofOfWork{block: block, target: target} return &pow } //給Run()準備數據 func (pow *ProofOfWork) PrepareData(nonce int64) []byte { block := pow.block tmp := [][]byte{ /* 須要將block中的不一樣類型都轉化爲byte,以便進行鏈接 */ IntToByte(block.Version), block.PreBlockHash, block.MerkleRoot, IntToByte(block.TimeStamp), IntToByte(nonce), IntToByte(targetBits), block.Data} //func Join(s [][]byte, sep []byte) []byte data := bytes.Join(tmp, []byte{}) return data } //計算哈希值的方法 func (pow *ProofOfWork) Run() (int64, []byte) { /*僞代碼 for nonce { hash := sha256(block數據 + nonce) if 轉換(Hash)< pow.target{ 找到了 }else{ nonce++ } } return nonce,hash{:} */ //1.拼裝數據 //2.哈希值轉成big.Int類型 var hash [32]byte var nonce int64 = 0 var hashInt big.Int fmt.Println("Begin Minding...") fmt.Printf("target hash : %x\n", pow.target.Bytes()) for nonce < math.MaxInt64 { data := pow.PrepareData(nonce) hash = sha256.Sum256(data) hashInt.SetBytes(hash[:]) // Cmp compares x and y and returns: // // -1 if x < y // 0 if x == y // +1 if x > y // //func (x *Int) Cmp(y *Int) (r int) { if hashInt.Cmp(pow.target) == -1 { fmt.Printf("found hash :%x,nonce :%d\n,", hash, nonce) break } else { //fmt.Printf("not found nonce,current nonce :%d,hash : %x\n", nonce, hash) nonce++ } } return nonce, hash[:] } //校驗函數 func (pow *ProofOfWork) IsValid() bool { var hashInt big.Int data := pow.PrepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) return hashInt.Cmp(pow.target) == -1 }
注意這部分須要使用標準庫裏面的 flag 包來解析命令行參數;
首先,建立兩個子命令: addblock
和 printchain
, 而後給 addblock
添加 --data
標誌。printchain
沒有標誌;
而後,檢查用戶輸入的命令並解析相關的 flag
子命令;
最後檢查解析是哪個子命令,並調用相關函數執行。
具體以下:
//由於是多行的,因此用反引號`···`包一下,能夠實現多行字符串的拼接,不須要轉義! //命令行提示 const usage = ` Usage: addBlock -data BLOCK_DATA "add a block to the blockchain" printChain "print all the blocks of the blockchain" ` const AddBlockCmdString = "addBlock" const PrintChainCmdString = "printChain" //輸出提示函數 func (cli *CLI) printUsage() { fmt.Println("Invalid input!") fmt.Println(usage) os.Exit(1) } //參數檢查函數 func (cli *CLI) validateArgs() { if len(os.Args) < 2 { fmt.Println("invalid input!") cli.printUsage() } } func (cli *CLI) Run() { cli.validateArgs() addBlockCmd := flag.NewFlagSet(AddBlockCmdString, flag.ExitOnError) printChainCmd := flag.NewFlagSet(PrintChainCmdString, flag.ExitOnError) //func (f *FlagSet) String(name string, value string, usage string) *string addBlocCmdPara := addBlockCmd.String("data", "", "Block data") switch os.Args[1] { case AddBlockCmdString: //添加動做 err := addBlockCmd.Parse(os.Args[2:]) CheckErr("Run()1", err) if addBlockCmd.Parsed() { if *addBlocCmdPara == "" { fmt.Println("addBlock data not should be empty!") cli.printUsage() } cli.AddBlock(*addBlocCmdPara) } case PrintChainCmdString: //打印輸出 err := printChainCmd.Parse(os.Args[2:]) CheckErr("Run()2", err) if printChainCmd.Parsed() { cli.PrintChain() } default: //命令不符合規定,輸出提示信息 cli.printUsage() } }
首先 go build 編譯程序;輸入不帶--data參數的錯誤命令,查看提示。
輸入交易信息,查看pow運算:
打印區塊鏈已有區塊信息:
Reference:
最後要感謝Ivan Kuznetsov在GitHub社區的貢獻!