Author: brucefeng
創(chuàng)新互聯(lián)主要從事做網(wǎng)站、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)浮梁,10余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
Email: brucefeng@brucefeng.com
編程語言:Golang
Bolt是一個純粹Key/Value模型的程序。該項(xiàng)目的目標(biāo)是為不需要完整數(shù)據(jù)庫服務(wù)器(如Postgres或MySQL)的項(xiàng)目提供一個簡單,快速,可靠的數(shù)據(jù)庫。
BoltDB只需要將其鏈接到你的應(yīng)用程序代碼中即可使用BoltDB提供的API來高效的存取數(shù)據(jù)。而且BoltDB支持完全可序列化的ACID事務(wù),讓應(yīng)用程序可以更簡單的處理復(fù)雜操作。
其源碼地址為:https://github.com/boltdb/bolt
BoltDB設(shè)計(jì)源于LMDB,具有以下特點(diǎn):
支持?jǐn)?shù)據(jù)結(jié)構(gòu)
BoltDB是一個Key/Value(鍵/值)存儲,這意味著沒有像SQL RDBMS(MySQL,PostgreSQL等)中的表,沒有行,沒有列。相反,數(shù)據(jù)作為鍵值對存儲(如在Golang Maps中)。鍵值對存儲在Buckets中,它們旨在對相似的對進(jìn)行分組(這與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程序運(yùn)行創(chuàng)建的my.db會保存在
GOPATH /src/Project目錄下
如果通過go build main.go ; ./main 執(zhí)行生成的my.db,會保存在當(dāng)前目錄GOPATH /src/Project/package下
//1. 調(diào)用Update方法進(jìn)行數(shù)據(jù)的寫入
err = db.Update(func(tx *bolt.Tx) error {
//2.通過CreateBucket()方法創(chuàng)建BlockBucket(表),初次使用創(chuàng)建
b, err := tx.CreateBucket([]byte("BlockBucket"))
if err != nil {
return fmt.Errorf("Create bucket :%s", err)
}
//3.通過Put()方法往表里面存儲一條數(shù)據(jù)(key,value),注意類型必須為[]byte
if b != nil {
err := b.Put([]byte("l"), []byte("Send $100 TO Bruce"))
if err != nil {
log.Panic("數(shù)據(jù)存儲失敗..")
}
}
return nil
})
//數(shù)據(jù)Update失敗,退出程序
if err != nil {
log.Panic(err)
}
//1.打開數(shù)據(jù)庫
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()方法往表里面存儲數(shù)據(jù)
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("數(shù)據(jù)存儲失敗..")
}
}
return nil
})
//更新失敗
if err != nil {
log.Panic(err)
}
//1.打開數(shù)據(jù)庫
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
//2.通過View方法獲取數(shù)據(jù)
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的數(shù)據(jù)庫創(chuàng)建,表創(chuàng)建,區(qū)塊添加,區(qū)塊查詢操作
//1.創(chuàng)建一個區(qū)塊對象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. 打印區(qū)塊對象相關(guān)信息
fmt.Printf("區(qū)塊的Hash信息為:\t%x\n", block.Hash)
fmt.Printf("區(qū)塊的數(shù)據(jù)信息為:\t%v\n", string(block.Data))
fmt.Printf("區(qū)塊的隨機(jī)數(shù)為:\t%d\n", block.Nonce)
//3. 打開數(shù)據(jù)庫
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
//4. 更新數(shù)據(jù)
err = db.Update(func(tx *bolt.Tx) error {
//4.1 打開BlockBucket表對象
b := tx.Bucket([]byte("blocks"))
//4.2 如果表對象不存在,創(chuàng)建表對象
if b == nil {
b, err = tx.CreateBucket([]byte("blocks"))
if err != nil {
log.Panic("Block Table Create Failed")
}
}
//4.3 往表里面存儲一條數(shù)據(jù)(key,value)
err = b.Put([]byte("l"), block.Serialize())
if err != nil {
log.Panic("數(shù)據(jù)存儲失敗..")
}
return nil
})
//更新失敗,返回錯誤
if err != nil {
log.Panic("數(shù)據(jù)更新失敗")
}
//5. 查看數(shù)據(jù)
err = db.View(func(tx *bolt.Tx) error {
//5.1打開BlockBucket表對象
b := tx.Bucket([]byte("blocks"))
if b != nil {
//5.2 取出key=“l(fā)”對應(yīng)的value
blockData := b.Get([]byte("l"))
//5.3反序列化
block := BLC.DeserializeBlock(blockData)
//6. 打印區(qū)塊對象相關(guān)信息
fmt.Printf("區(qū)塊的Hash信息為:\t%x\n", block.Hash)
fmt.Printf("區(qū)塊的數(shù)據(jù)信息為:\t%v\n", string(block.Data))
fmt.Printf("區(qū)塊的隨機(jī)數(shù)為:\t%d\n", block.Nonce)
}
return nil
})
//數(shù)據(jù)查看失敗
if err != nil {
log.Panic("數(shù)據(jù)更新失敗")
}
北京時(shí)間2009年1月4日2時(shí)15分5秒,比特幣的第一個區(qū)塊誕生了。隨著時(shí)間往后推移,不斷有新的區(qū)塊被添加到鏈上,所有后續(xù)區(qū)塊都可以追溯到第一個區(qū)塊。第一個區(qū)塊就被人們稱為創(chuàng)世區(qū)塊。
在比特幣世界中,獲取區(qū)塊記賬權(quán)的過程稱之為挖礦,一個礦工成功后,他會把之前打包好的網(wǎng)絡(luò)上的交易記錄到一頁賬本上,同步給其他人。因?yàn)檫@個礦工能夠最先計(jì)算出超難數(shù)學(xué)題的正確答案,說明這個礦工付出了工作量,是一個有權(quán)利記賬的人,因此其他人也會同意這一頁賬單。這種依靠工作量來證明記賬權(quán),大家來達(dá)成共識的機(jī)制叫做“工作量證明”,簡而言之結(jié)果可以證明你付出了多少工作量。Proof Of Work簡稱“PoW”,關(guān)于其原理跟代碼實(shí)現(xiàn),我們在后面的代碼分析中進(jìn)行講解說明。
type ProofOfWork struct {
Block *Block //要驗(yàn)證的block
Target *big.Int //目標(biāo)hash
}
const TargetBit = 16 //目標(biāo)哈希的0個個數(shù),16,20,24,28
func NewProofOfWork(block *Block) *ProofOfWork {
//1.創(chuàng)建pow對象
pow := &ProofOfWork{}
//2.設(shè)置屬性值
pow.Block = block
target := big.NewInt(1) // 目標(biāo)hash,初始值為1
target.Lsh(target, 256-TargetBit) //左移256-16
pow.Target = target
return pow
}
我們首先設(shè)定一個難度系數(shù)值為16,即目標(biāo)哈希前導(dǎo)0的個數(shù),0的個數(shù)越多,挖礦難度越大,此處我們創(chuàng)建一個函數(shù)NewProofOfWork用于返回Pow對象。
目標(biāo)Hash的長度為256bit,通過64個16進(jìn)制byte進(jìn)行展示,如下所示為前導(dǎo)0為16/4=4的哈希
0000c01d342fc51cb030f93979343de70ab771855dd8ca28e6f5888737759747
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
//將二進(jìn)制數(shù)據(jù)寫入w
//
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
//轉(zhuǎn)為[]byte并返回
return buff.Bytes()
}
通過
func Write(w io.Writer, order ByteOrder, data interface{}) error
方法將一個int64的整數(shù)轉(zhuǎn)為二進(jìn)制后,每8bit一個byte,轉(zhuǎn)為[]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方法將區(qū)塊相關(guān)屬性進(jìn)行拼接成字節(jié)數(shù)組
func (pow *ProofOfWork) Run() ([]byte, int64) {
var nonce int64 = 0
var hash [32]byte
for {
//1.根據(jù)nonce獲取數(shù)據(jù)
data := pow.prepareData(nonce)
//2.生成hash
hash = sha256.Sum256(data) //[32]byte
fmt.Printf("\r%d,%x", nonce, hash)
//3.驗(yàn)證:和目標(biāo)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的值,計(jì)算hash,直到小于目標(biāo)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.高度:區(qū)塊在區(qū)塊鏈中的編號,第一個區(qū)塊也叫創(chuàng)世區(qū)塊,一般設(shè)定為0
Height int64
//2.上一個區(qū)塊的Hash值
PrevBlockHash []byte
//3.數(shù)據(jù):Txs,交易數(shù)據(jù)
Txs []*Transaction
//4.時(shí)間戳
TimeStamp int64
//5.自己的hash
Hash []byte
//6.Nonce
Nonce int64
}
關(guān)于屬性的定義,在代碼的注釋中比較清晰了,需要提一下的就是創(chuàng)世區(qū)塊的PrevBlockHash一般設(shè)定為0 ,高度也一般設(shè)定為0
func CreateGenesisBlock(txs []*Transaction) *Block{
return NewBlock(txs,make([]byte,32,32),0)
}
設(shè)定創(chuàng)世區(qū)塊的PrevBlockHash為0,區(qū)塊高度為0
func (block *Block) Serialize()[]byte{
//1.創(chuàng)建一個buff
var buf bytes.Buffer
//2.創(chuàng)建一個編碼器
encoder:=gob.NewEncoder(&buf)
//3.編碼
err:=encoder.Encode(block)
if err != nil{
log.Panic(err)
}
return buf.Bytes()
}
通過gob庫的Encode方法將Block對象序列化成字節(jié)數(shù)組,用于持久化存儲
func DeserializeBlock(blockBytes [] byte) *Block{
var block Block
//1.先創(chuàng)建一個reader
reader:=bytes.NewReader(blockBytes)
//2.創(chuàng)建×××
decoder:=gob.NewDecoder(reader)
//3.解碼
err:=decoder.Decode(&block)
if err != nil{
log.Panic(err)
}
return &block
}
定義一個函數(shù),用于將[]byte反序列化為block對象
type BlockChain struct {
DB *bolt.DB //對應(yīng)的數(shù)據(jù)庫對象
Tip [] byte //存儲區(qū)塊中最后一個塊的hash值
}
定義區(qū)塊鏈結(jié)構(gòu)體屬性DB用于存儲對應(yīng)的數(shù)據(jù)庫對象,Tip用于存儲區(qū)塊中最后一個塊的Hash值
const DBName = "blockchain.db" //數(shù)據(jù)庫的名字
const BlockBucketName = "blocks" //定義bucket
定義數(shù)據(jù)庫名字以及定義用于存儲區(qū)塊數(shù)據(jù)的bucket(表)名
func dbExists() bool {
if _, err := os.Stat(DBName); os.IsNotExist(err) {
return false //表示文件不存在
}
return true //表示文件存在
}
需要注意
IsNotExist
返回true
,則表示不存在成立,返回值為true
,則dbExists
函數(shù)的返回值則需要返回false
,否則,返回true
func CreateBlockChainWithGenesisBlock(address string) {
/*
1.判斷數(shù)據(jù)庫如果存在,直接結(jié)束方法
2.數(shù)據(jù)庫不存在,創(chuàng)建創(chuàng)世區(qū)塊,并存入到數(shù)據(jù)庫中
*/
if dbExists() {
fmt.Println("數(shù)據(jù)庫已經(jīng)存在,無法創(chuàng)建創(chuàng)世區(qū)塊")
return
}
//數(shù)據(jù)庫不存在
fmt.Println("數(shù)據(jù)庫不存在")
fmt.Println("正在創(chuàng)建創(chuàng)世區(qū)塊")
/*
1.創(chuàng)建創(chuàng)世區(qū)塊
2.存入到數(shù)據(jù)庫中
*/
//創(chuàng)建一個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 {
//創(chuàng)世區(qū)塊序列化后,存入到數(shù)據(jù)庫中
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) 判斷數(shù)據(jù)庫是否存在,如果不存在,證明還沒有創(chuàng)建創(chuàng)世區(qū)塊,如果存在,則提示創(chuàng)世區(qū)塊已存在,直接返回
if dbExists() {
fmt.Println("數(shù)據(jù)庫已經(jīng)存在,無法創(chuàng)建創(chuàng)世區(qū)塊")
return
}
(2) 如果數(shù)據(jù)庫不存在,則提示開始調(diào)用相關(guān)函數(shù)跟方法創(chuàng)建創(chuàng)世區(qū)塊
fmt.Println("數(shù)據(jù)庫不存在")
fmt.Println("正在創(chuàng)建創(chuàng)世區(qū)塊")
(3) 創(chuàng)建一個交易數(shù)組Txs
關(guān)于交易這一部分內(nèi)容,將在后面一個章節(jié)中進(jìn)行詳細(xì)說明,篇幅會非常長,這也是整個課程體系中最為繁瑣,知識點(diǎn)最廣的地方,屆時(shí)慢慢分析
txCoinBase := NewCoinBaseTransaction(address)
通過函數(shù)NewCoinBaseTransaction創(chuàng)建一個CoinBase交易
func NewCoinBaseTransaction(address string) *Transaction {
txInput := &TxInput{[]byte{}, -1, nil, nil}
txOutput := NewTxOutput(10, address)
txCoinBaseTransaction := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}}
//設(shè)置交易ID
txCoinBaseTransaction.SetID()
return txCoinBaseTransaction
}
(4) 生成創(chuàng)世區(qū)塊
genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase})
(5) 打開/創(chuàng)建數(shù)據(jù)庫
db, err := bolt.Open(DBName, 0600, nil)
if err != nil {
log.Panic(err)
}
defer db.Close()
通過bolt.Open
方法打開(如果不存在則創(chuàng)建)數(shù)據(jù)庫文件,注意數(shù)據(jù)庫關(guān)閉操作不能少,用defer實(shí)現(xiàn)延遲關(guān)閉。
(6) 將數(shù)據(jù)寫入數(shù)據(jù)庫
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
方法進(jìn)行數(shù)據(jù)更新操作
func (cli *CLI) CreateBlockChain(address string) {
CreateBlockChainWithGenesisBlock(address)
}
測試命令
$ ./mybtc createblockchain -address 1DHPNHKfk9uUdog2f2xBvx9dq4NxpF5Q4Q
返回結(jié)果
數(shù)據(jù)庫不存在
正在創(chuàng)建創(chuàng)世區(qū)塊
32325,00005c7b4246aa88bd1f9664c665d6424d1522f569d981691ac2b5b5d15dd8d9
本章節(jié)介紹了如何創(chuàng)建一個帶有創(chuàng)世區(qū)塊的區(qū)塊鏈,并持久化存儲至數(shù)據(jù)庫blockchain.db
$ ls
BLC Wallets.dat blockchain.db main.go mybtc
分享名稱:從0到1簡易區(qū)塊鏈開發(fā)手冊V0.3-數(shù)據(jù)持久化與創(chuàng)世區(qū)塊
當(dāng)前鏈接:http://muchs.cn/article34/gedgse.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗(yàn)、建站公司、網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、網(wǎng)站營銷、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)