Author: brucefenggit
Email: brucefeng@brucefeng.comgithub
編程語言:Golang
數據庫
Bolt是一個純粹Key/Value模型的程序。該項目的目標是爲不須要完整數據庫服務器(如Postgres或MySQL)的項目提供一個簡單,快速,可靠的數據庫。編程
BoltDB只須要將其連接到你的應用程序代碼中便可使用BoltDB提供的API來高效的存取數據。並且BoltDB支持徹底可序列化的ACID事務,讓應用程序能夠更簡單的處理複雜操做。數組
其源碼地址爲:https://github.com/boltdb/bolt服務器
BoltDB設計源於LMDB,具備如下特色:網絡
支持數據結構數據結構
BoltDB是一個Key/Value(鍵/值)存儲,這意味着沒有像SQL RDBMS(MySQL,PostgreSQL等)中的表,沒有行,沒有列。相反,數據做爲鍵值對存儲(如在Golang Maps中)。鍵值對存儲在Buckets中,它們旨在對類似的對進行分組(這與RDBMS中的表相似)。所以,爲了得到Value(值),須要知道該Value所在的桶和鑰匙。併發
//經過go get下載並import import "github.com/boltdb/bolt"
db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close()
若是經過goland程序運行建立的my.db會保存在編程語言
GOPATH /src/Project目錄下 若是經過go build main.go ; ./main 執行生成的my.db,會保存在當前目錄GOPATH /src/Project/package下
//1. 調用Update方法進行數據的寫入 err = db.Update(func(tx *bolt.Tx) error { //2.經過CreateBucket()方法建立BlockBucket(表),初次使用建立 b, err := tx.CreateBucket([]byte("BlockBucket")) if err != nil { return fmt.Errorf("Create bucket :%s", err) } //3.經過Put()方法往表裏面存儲一條數據(key,value),注意類型必須爲[]byte if b != nil { err := b.Put([]byte("l"), []byte("Send $100 TO Bruce")) if err != nil { log.Panic("數據存儲失敗..") } } return nil }) //數據Update失敗,退出程序 if err != nil { log.Panic(err) }
//1.打開數據庫 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() err = db.Update(func(tx *bolt.Tx) error { //2.經過Bucket()方法打開BlockBucket表 b := tx.Bucket([]byte("BlockBucket")) //3.經過Put()方法往表裏面存儲數據 if b != nil { err := b.Put([]byte("l"), []byte("Send $200 TO Fengyingcong")) err = b.Put([]byte("ll"), []byte("Send $100 TO Bruce")) if err != nil { log.Panic("數據存儲失敗..") } } return nil }) //更新失敗 if err != nil { log.Panic(err) }
//1.打開數據庫 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() //2.經過View方法獲取數據 err = db.View(func(tx *bolt.Tx) error { //3.打開BlockBucket表,獲取表對象 b := tx.Bucket([]byte("BlockBucket")) //4.Get()方法經過key讀取value if b != nil { data := b.Get([]byte("l")) fmt.Printf("%s\n", data) data = b.Get([]byte("ll")) fmt.Printf("%s\n", data) } return nil }) if err != nil { log.Panic(err) }
該代碼包含對BoltDB的數據庫建立,表建立,區塊添加,區塊查詢操做
//1.建立一個區塊對象block block := BLC.NewBlock("Send $500 to Tom", 1, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) //2. 打印區塊對象相關信息 fmt.Printf("區塊的Hash信息爲:\t%x\n", block.Hash) fmt.Printf("區塊的數據信息爲:\t%v\n", string(block.Data)) fmt.Printf("區塊的隨機數爲:\t%d\n", block.Nonce) //3. 打開數據庫 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() //4. 更新數據 err = db.Update(func(tx *bolt.Tx) error { //4.1 打開BlockBucket表對象 b := tx.Bucket([]byte("blocks")) //4.2 若是表對象不存在,建立表對象 if b == nil { b, err = tx.CreateBucket([]byte("blocks")) if err != nil { log.Panic("Block Table Create Failed") } } //4.3 往表裏面存儲一條數據(key,value) err = b.Put([]byte("l"), block.Serialize()) if err != nil { log.Panic("數據存儲失敗..") } return nil }) //更新失敗,返回錯誤 if err != nil { log.Panic("數據更新失敗") } //5. 查看數據 err = db.View(func(tx *bolt.Tx) error { //5.1打開BlockBucket表對象 b := tx.Bucket([]byte("blocks")) if b != nil { //5.2 取出key=「l」對應的value blockData := b.Get([]byte("l")) //5.3反序列化 block := BLC.DeserializeBlock(blockData) //6. 打印區塊對象相關信息 fmt.Printf("區塊的Hash信息爲:\t%x\n", block.Hash) fmt.Printf("區塊的數據信息爲:\t%v\n", string(block.Data)) fmt.Printf("區塊的隨機數爲:\t%d\n", block.Nonce) } return nil }) //數據查看失敗 if err != nil { log.Panic("數據更新失敗") }
北京時間2009年1月4日2時15分5秒,比特幣的第一個區塊誕生了。隨着時間日後推移,不斷有新的區塊被添加到鏈上,全部後續區塊均可以追溯到第一個區塊。第一個區塊就被人們稱爲創世區塊。
在比特幣世界中,獲取區塊記帳權的過程稱之爲挖礦,一個礦工成功後,他會把以前打包好的網絡上的交易記錄到一頁帳本上,同步給其餘人。由於這個礦工可以最早計算出超難數學題的正確答案,說明這個礦工付出了工做量,是一個有權利記帳的人,所以其餘人也會贊成這一頁帳單。這種依靠工做量來證實記帳權,你們來達成共識的機制叫作「工做量證實」,簡而言之結果能夠證實你付出了多少工做量。Proof Of Work簡稱「PoW」,關於其原理跟代碼實現,咱們在後面的代碼分析中進行講解說明。
type ProofOfWork struct { Block *Block //要驗證的block Target *big.Int //目標hash }
const TargetBit = 16 //目標哈希的0個個數,16,20,24,28 func NewProofOfWork(block *Block) *ProofOfWork { //1.建立pow對象 pow := &ProofOfWork{} //2.設置屬性值 pow.Block = block target := big.NewInt(1) // 目標hash,初始值爲1 target.Lsh(target, 256-TargetBit) //左移256-16 pow.Target = target return pow }
咱們首先設定一個難度係數值爲16,即目標哈希前導0的個數,0的個數越多,挖礦難度越大,此處咱們建立一個函數NewProofOfWork用於返回Pow對象。
目標Hash的長度爲256bit,經過64個16進制byte進行展現,以下所示爲前導0爲16/4=4的哈希
0000c01d342fc51cb030f93979343de70ab771855dd8ca28e6f5888737759747
func IntToHex(num int64) []byte { buff := new(bytes.Buffer) //將二進制數據寫入w // err := binary.Write(buff, binary.BigEndian, num) if err != nil { log.Panic(err) } //轉爲[]byte並返回 return buff.Bytes() }
經過
func Write(w io.Writer, order ByteOrder, data interface{}) error
方法將一個int64的整數轉爲二進制後,每8bit一個byte,轉爲[]byte
func (pow *ProofOfWork) prepareData(nonce int64) []byte { data := bytes.Join([][]byte{ IntToHex(pow.Block.Height), pow.Block.PrevBlockHash, IntToHex(pow.Block.TimeStamp), pow.Block.HashTransactions(), IntToHex(nonce), IntToHex(TargetBit), }, []byte{}) return data }
經過bytes.Join方法將區塊相關屬性進行拼接成字節數組
func (pow *ProofOfWork) Run() ([]byte, int64) { var nonce int64 = 0 var hash [32]byte for { //1.根據nonce獲取數據 data := pow.prepareData(nonce) //2.生成hash hash = sha256.Sum256(data) //[32]byte fmt.Printf("\r%d,%x", nonce, hash) //3.驗證:和目標hash比較 /* func (x *Int) Cmp(y *Int) (r int) Cmp compares x and y and returns: -1 if x < y 0 if x == y +1 if x > y 目的:target > hashInt,成功 */ hashInt := new(big.Int) hashInt.SetBytes(hash[:]) if pow.Target.Cmp(hashInt) == 1 { break } nonce++ } fmt.Println() return hash[:], nonce }
代碼思路
不斷更改nonce的值,計算hash,直到小於目標hash。
func (pow *ProofOfWork) IsValid() bool { hashInt := new(big.Int) hashInt.SetBytes(pow.Block.Hash) return pow.Target.Cmp(hashInt) == 1 }
判斷方式同挖礦中的策略
type Block struct { //字段屬性 //1.高度:區塊在區塊鏈中的編號,第一個區塊也叫創世區塊,通常設定爲0 Height int64 //2.上一個區塊的Hash值 PrevBlockHash []byte //3.數據:Txs,交易數據 Txs []*Transaction //4.時間戳 TimeStamp int64 //5.本身的hash Hash []byte //6.Nonce Nonce int64 }
關於屬性的定義,在代碼的註釋中比較清晰了,須要提一下的就是創世區塊的PrevBlockHash通常設定爲0 ,高度也通常設定爲0
func CreateGenesisBlock(txs []*Transaction) *Block{ return NewBlock(txs,make([]byte,32,32),0) }
設定創世區塊的PrevBlockHash爲0,區塊高度爲0
func (block *Block) Serialize()[]byte{ //1.建立一個buff var buf bytes.Buffer //2.建立一個編碼器 encoder:=gob.NewEncoder(&buf) //3.編碼 err:=encoder.Encode(block) if err != nil{ log.Panic(err) } return buf.Bytes() }
經過gob庫的Encode方法將Block對象序列化成字節數組,用於持久化存儲
func DeserializeBlock(blockBytes [] byte) *Block{ var block Block //1.先建立一個reader reader:=bytes.NewReader(blockBytes) //2.建立××× decoder:=gob.NewDecoder(reader) //3.解碼 err:=decoder.Decode(&block) if err != nil{ log.Panic(err) } return &block }
定義一個函數,用於將[]byte反序列化爲block對象
type BlockChain struct { DB *bolt.DB //對應的數據庫對象 Tip [] byte //存儲區塊中最後一個塊的hash值 }
定義區塊鏈結構體屬性DB用於存儲對應的數據庫對象,Tip用於存儲區塊中最後一個塊的Hash值
const DBName = "blockchain.db" //數據庫的名字 const BlockBucketName = "blocks" //定義bucket
定義數據庫名字以及定義用於存儲區塊數據的bucket(表)名
func dbExists() bool { if _, err := os.Stat(DBName); os.IsNotExist(err) { return false //表示文件不存在 } return true //表示文件存在 }
須要注意
IsNotExist
返回true
,則表示不存在成立,返回值爲true
,則dbExists
函數的返回值則須要返回false
,不然,返回true
func CreateBlockChainWithGenesisBlock(address string) { /* 1.判斷數據庫若是存在,直接結束方法 2.數據庫不存在,建立創世區塊,並存入到數據庫中 */ if dbExists() { fmt.Println("數據庫已經存在,沒法建立創世區塊") return } //數據庫不存在 fmt.Println("數據庫不存在") fmt.Println("正在建立創世區塊") /* 1.建立創世區塊 2.存入到數據庫中 */ //建立一個txs--->CoinBase txCoinBase := NewCoinBaseTransaction(address) genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase}) db, err := bolt.Open(DBName, 0600, nil) if err != nil { log.Panic(err) } defer db.Close() err = db.Update(func(tx *bolt.Tx) error { //創世區塊序列化後,存入到數據庫中 b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName)) if err != nil { log.Panic(err) } if b != nil { err = b.Put(genesisBlock.Hash, genesisBlock.Serialize()) if err != nil { log.Panic(err) } b.Put([]byte("l"), genesisBlock.Hash) } return nil }) if err != nil { log.Panic(err) } //return &BlockChain{db, genesisBlock.Hash} }
代碼分析
(1) 判斷數據庫是否存在,若是不存在,證實尚未建立創世區塊,若是存在,則提示創世區塊已存在,直接返回
if dbExists() { fmt.Println("數據庫已經存在,沒法建立創世區塊") return }
(2) 若是數據庫不存在,則提示開始調用相關函數跟方法建立創世區塊
fmt.Println("數據庫不存在") fmt.Println("正在建立創世區塊")
(3) 建立一個交易數組Txs
關於交易這一部份內容,將在後面一個章節中進行詳細說明,篇幅會很是長,這也是整個課程體系中最爲繁瑣,知識點最廣的地方,屆時慢慢分析
txCoinBase := NewCoinBaseTransaction(address)
經過函數NewCoinBaseTransaction建立一個CoinBase交易
func NewCoinBaseTransaction(address string) *Transaction { txInput := &TxInput{[]byte{}, -1, nil, nil} txOutput := NewTxOutput(10, address) txCoinBaseTransaction := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}} //設置交易ID txCoinBaseTransaction.SetID() return txCoinBaseTransaction }
(4) 生成創世區塊
genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase})
(5) 打開/建立數據庫
db, err := bolt.Open(DBName, 0600, nil) if err != nil { log.Panic(err) } defer db.Close()
經過bolt.Open
方法打開(若是不存在則建立)數據庫文件,注意數據庫關閉操做不能少,用defer實現延遲關閉。
(6) 將數據寫入數據庫
err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName)) if err != nil { log.Panic(err) } if b != nil { err = b.Put(genesisBlock.Hash, genesisBlock.Serialize()) if err != nil { log.Panic(err) } b.Put([]byte("l"), genesisBlock.Hash) } return nil }) if err != nil { log.Panic(err) }
經過db.Upadate
方法進行數據更新操做
func (cli *CLI) CreateBlockChain(address string) { CreateBlockChainWithGenesisBlock(address) }
測試命令
$ ./mybtc createblockchain -address 1DHPNHKfk9uUdog2f2xBvx9dq4NxpF5Q4Q
返回結果
數據庫不存在 正在建立創世區塊 32325,00005c7b4246aa88bd1f9664c665d6424d1522f569d981691ac2b5b5d15dd8d9
本章節介紹瞭如何建立一個帶有創世區塊的區塊鏈,並持久化存儲至數據庫blockchain.db
$ ls BLC Wallets.dat blockchain.db main.go mybtc