在此只对bolt进行简单说明使用,深入学习可以去bolt的github网站,有更加详细的使用说明。bolt github 链接
//函数原型:func Open(path string, mode os.FileMode, options *Options) (*DB, error) {}
//参数说明:
// path :要打开的数据库文件,没有就会创建一个
// mode :打开数据库文件时的权限,0600为读写
// options :;连接数据库的一些设置,可选
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
}
//函数原型:func (db *DB) Update(fn func(*Tx) error) error {}
//参数说明:
// 函数:作为参数的函数可以看作一个事务,当返回值为nil时,会自动提交事务。当返回值为err时,会回滚事务。
err := db.Update(func(tx *bolt.Tx) error {
...
return nil
})
//函数原型:func (db *DB) View(fn func(*Tx) error) error {}
//参数说明:
// 在参数函数中,只能在只读事务中检索存储区,检索值并复制数据库
err := db.View(func(tx *bolt.Tx) error {
...
return nil
})
bucket的作用:bucket里面存放键值对,就是我们要存放进数据库的数据
//函数原型:func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {}
//参数说明:
// []byte: 要创建bucket的名称。
b, err := tx.CreateBucket([]byte("MyBucket"))
//put和get之前一定要有bucket
b := tx.Bucket([]byte("MyBucket"))
//put函数原型:func (b *Bucket) Put(key []byte, value []byte) error {}
err := b.Put([]byte("answer"), []byte("42"))
//get函数原型:func (b *Bucket) Get(key []byte) []byte {}
v := b.Get([]byte("answer"))
type BlockChain struct {
//使用数据库代替数组
//区块链中有两个属性:
//数据库文件,key是区块的hash值,value为区块的字节流
db *bolt.DB
//存储最后一个区块的哈希,方便找到最后一个区块哈希
tail []byte
}
修改区块链存储方式后,每次添加区块要做两件事:
func NewBlockChain () *BlockChain{
//最后一个区块的哈希,从数据库中读出来的
var lastHash []byte
//打开数据库
db, err := bolt.Open(blockChianDb, 0600, nil)
defer db.Close()
if err != nil{
log.Panic(err)
}
//写数据
db.Update(func(tx *bolt.Tx) error {
//找到bucket,(如果没有就创建,没有要找的bucket就代表要对一个新链进行操作,否则就是已有的链,进行追加即可)
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
//没有bucket,创建
bucket, err = tx.CreateBucket([]byte(blockBucket))
if err != nil {
log.Panic("创建bucket(blockBucket)失败")
}
//定义创世块
genesisBlock := GenesisBlock()
//把创世块加入到数据库文件中
//block的哈希作为key,block的字节流作为value
bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
//修改最后一个区块的哈希
bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
lastHash = genesisBlock.Hash
}else {
//有数据库文件就直接引用
lastHash = bucket.Get([]byte("LastHashKey"))
}
//return nil代表整个事务操作完成,不需要回滚
return nil
})
//返回刚刚操作的区块链
return &BlockChain{
db: db,
tail: lastHash,
}
}
func (bc *BlockChain) AddBlock (data string) {
//获取区块链
db := bc.db
//获取最后一个区块哈希
lastHash := bc.tail
db.Update(func(tx *bolt.Tx) error {
//完成区块添加
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
log.Panic("bucket 不应该为空,请检查!")
}
//1. 创建新区块
block := NewBlock(data, lastHash)
//2. 添加区块到数据库中
//hash作为key, block的字节流作为value
bucket.Put(block.Hash, block.Serialize())
bucket.Put([]byte("LastHashKey"), block.Hash)
//3. 更新内存中的区块链
bc.tail = block.Hash
return nil
})
}
type BlockChainIterator struct {
db *bolt.DB
//游标
currentHashPointer []byte
}
func (bc *BlockChain) NewIterator() *BlockChainIterator{
return &BlockChainIterator{
db: bc.db,
//最初指向区块链的最后一个区块,随着Next的调用,不断变化
currentHashPointer: bc.tail,
}
}
func (it *BlockChainIterator) Next() *Block{
var block Block
it.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
log.Panic("迭代器遍历时,bucket不应该为空!")
}
blockTmp := bucket.Get(it.currentHashPointer)
//解码动作
block = Deserialize(blockTmp)
//游标左移
it.currentHashPointer = block.PrevHash
return nil
})
return &block
}
bc := NewBlockChain()
it := bc.NewIterator()
for{
//返回区块并左移
block := it.Next()
fmt.Println("==========================\n")
fmt.Printf("前区块哈希: %x\n", block.PrevHash)
fmt.Printf("区块哈希: %x\n", block.Hash)
fmt.Printf("区块数据: %s\n", block.Data)
if len(block.PrevHash) == 0 {
fmt.Printf("区块遍历完成!\n")
break
}
}
type CLI struct {
bc *BlockChain
}
const Usage = `
addBlock --data DATA "add data to blockchain"
printChain "print all blockchain data"
`
func (cli *CLI) Run() {
//1. 获取命令
args := os.Args
// 校验参数是否准确
if len(args) < 2 {
fmt.Printf(Usage)
return
}
//2. 分析命令
cmd := args[1]
switch cmd {
case "addBlock":
//添加区块
fmt.Printf("添加区块")
//命令校验,验确保参数为4,并且第三个参数为--data
if len(args) == 4 && args[2] == "--data"{
//获取数据
data := args[3]
//添加区块
cli.AddBlock(data)
}else {
fmt.Printf("添加区块参数使用不当,请检查!")
}
case "printChain":
//打印区块
fmt.Printf("打印区块\n")
cli.PrintBlockChain()
default:
fmt.Printf("无效命令,请检查!")
fmt.Printf(Usage)
}
}
func (cli *CLI) AddBlock(data string){
cli.bc.AddBlock(data)
fmt.Printf("添加区块成功!\n")
}
func (cli *CLI) PrintBlockChain(){
bc := cli.bc
it := bc.NewIterator()
for{
//返回区块并左移
block := it.Next()
fmt.Println("==========================\n")
fmt.Printf("版本号: %d\n", block.Version)
fmt.Printf("前区块哈希: %x\n", block.PrevHash)
fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
fmt.Printf("时间戳: %d\n", block.TimeStamp)
fmt.Printf("难度值: %d\n", block.Difficulty)
fmt.Printf("随机数: %d\n", block.Nonce)
fmt.Printf("当前区块哈希: %x\n", block.Hash)
fmt.Printf("区块数据: %s\n", block.Data)
if len(block.PrevHash) == 0 {
fmt.Printf("区块遍历完成!\n")
break
}
}
}
源码:https://gitee.com/xiaoshengdada/go_bitcoin/tree/master/v3
如果有任何问题可以来微信群交流,另外群里有学习资料,可以自行下载。一起学习进步。
最最后,推荐一位大佬的公众号:区块链技术栈。