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