分享
  1. 首页
  2. 文章

golang-区块链学习03永久存储

embedsky · · 1298 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

前面两篇简单的实现了区块链的创建和工作量证明,但是都是在内存中进行的。实际的区块链应该是可以永久存储的,这样才有意义。下面开始做永久性区块链存储。

知识点

1、github项目引用
2、github.com/boltdb/bolt项目的简单使用
3、命令行使用
4、go常用的数据转换

golang-区块链永久性存储

1、创建区块链

方法:func NewBlockChain() *BlockChain

// 创建区块链
// 返回一个区块链实例
func NewBlockChain() *BlockChain {
 var tip []byte
 // 打开存储区块链的文件blockchian.db、不存在则创建
 db, err := bolt.Open(dbFile, 0600, nil)
 if err != nil {
 log.Panic(err)
 }
 // 可读写的方式访问区块链文件blockchian.db
 err = db.Update(func(tx *bolt.Tx) error {
 b := tx.Bucket([]byte(blockBucket))
 if b == nil {
 fmt.Println("No existing blockchain. Creating a new one...")
 // 创建创世纪块
 genesis := NewGenesisBlock()
 b, err := tx.CreateBucket([]byte(blockBucket))
 if err != nil {
 log.Panic(err)
 }
 // 键值对的方式存储区块在blockchian.db里
 err = b.Put(genesis.Hash, genesis.Serialize())
 if err != nil {
 log.Panic(err)
 }
 // 以一个特殊的key保存最新的区块的hash,便于整个链的检索
 err = b.Put([]byte("l"), genesis.Hash)
 if err != nil {
 log.Panic(err)
 }
 tip = genesis.Hash
 } else {
 tip = b.Get([]byte("l"))
 }
 return nil
 })
 if err != nil {
 log.Panic(err)
 }
 // 返回区块链的实例
 bc := &BlockChain{tip, db}
 return bc
}

bolt是一种通过键值对的方式来存储数据的。具体的介绍和使用参考github.com/boltdb/bolt。程序启动时候,调用NewBlockChain函数,打开blockchian.db文件实例化一个区块链对象BlockChain。如果是第一次运行程序会创建blockchian.db文件,并生成一个创世纪区块,存储进blockchian.db文件中,然后返回一个区块链对象。

2、添加新区块到链上

方法:func (bc *BlockChain) AddBlock(data string)

// 添加新区块到链上
// 参数:data,区块要保存的数据
func (bc *BlockChain) AddBlock(data string) {
 var lastHash []byte
 // 只读的方式打开blockchian.db,获取最新区块的hash值
 err := bc.Db.View(func(tx1 *bolt.Tx) error {
 b := tx1.Bucket([]byte(blockBucket))
 lastHash = b.Get([]byte("l"))
 return nil
 })
 if err != nil {
 log.Panic(err)
 }
 // 计算新的区块
 newBlock := NewBlock(data, lastHash)
 bc.tip = newBlock.Hash
 // 读写的方式打开lockchian.db,写入新区块到blockchian.db中。
 bc.Db.Update(func(tx *bolt.Tx) error {
 b := tx.Bucket([]byte(blockBucket))
 if b == nil {
 log.Panic("bucket is nil !")
 }
 err := b.Put(newBlock.Hash, newBlock.Serialize())
 if err != nil {
 log.Panic(err)
 }
 //更新最新区块的hash
 err = b.Put([]byte("l"), newBlock.Hash)
 if err != nil {
 log.Panic(err)
 }
 return nil
 })
}

添加新区块到链上,首先要获取当前链上最新区块的hash,然后计算新的区块,计算出新的区块后存储新区块数据到blockchian.db中。

3、区块数据序列化转换

将区块block实例转换成byte数组方法:func (b *Block)Serialize()[]byte

// 序列化一个区块实例为byte数组
func (b *Block)Serialize()[]byte {
 var result bytes.Buffer
 // 以一个byte的buf实例化一个编码实例encoder
 encoder:=gob.NewEncoder(&result)
 
 err:=encoder.Encode(b)
 if err!=nil {
 log.Panic(err)
 }
 return result.Bytes()
}

将byte数组转换成block对象方法:func Deserialize(b []byte)*Block

// 反序列化byte数组,生成block实例。
func Deserialize(b []byte)*Block{
 var block Block
 decoder:=gob.NewDecoder(bytes.NewReader(b))
 err:=decoder.Decode(&block)
 if err!=nil{
 log.Panic(err)
 }
 return &block
}
4、命令行flag使用
// 命令行执行程序
func (cli *CLI) Run() {
 cli.validateArgs()
 // 创建命令行对象
 addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
 printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
 // 命令行对象添加参数,
 addBlockData := addBlockCmd.String("data", "", "区块数据不能为空!")
 switch os.Args[1] {
 case "addblock":
 err := addBlockCmd.Parse(os.Args[2:])
 if err != nil {
 log.Panic(err)
 }
 case "printchain":
 err:=printChianCmd.Parse(os.Args[2:])
 if err!=nil{
 log.Panic(err)
 }
 default:
 cli.printUsage()
 os.Exit(1)
 }
 if addBlockCmd.Parsed(){
 if *addBlockData==""{
 addBlockCmd.Usage()
 os.Exit(1)
 }
 cli.addBlock(*addBlockData)
 }
 if printChianCmd.Parsed(){
 cli.printChain()
 //cli.printUsage()
 }
}
5、github项目引用

下载github.com/boltdb/bolt项目到工程目录,如附件项目结构图所示。
注意设置项目路径为gopath路径。

附件

1、项目结构
项目目录结构

2、代码
main.go

package main
import (
 "core"
)
func main() {
 // 创建区块链
 bc := core.NewBlockChain()
 // 关闭本地库
 defer bc.Db.Close()
 // 实例命令行对象
 cli := core.CLI{bc}
 cli.Run()
}

block.go

package core
import (
 "time"
 "strconv"
 "bytes"
 "crypto/sha256"
 "encoding/gob"
 "log"
)
type Block struct {
 TimeStamp int64
 Data []byte
 PrevBlockHash []byte
 Hash []byte
 Nonce int
}
func NewBlock(data string, prevBlockHash []byte) *Block {
 block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
 pow := NewProofOfWork(block)
 block.Nonce, block.Hash = pow.Run()
 return block
}
func (b *Block) SetHash() {
 strTimeStamp := []byte(strconv.FormatInt(b.TimeStamp, 10))
 headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, strTimeStamp}, []byte{})
 hash := sha256.Sum256(headers)
 b.Hash = hash[:]
}
func NewGenesisBlock() *Block {
 return NewBlock("Genesis Block", []byte{})
}
// 序列化一个区块实例为byte数组
func (b *Block)Serialize()[]byte {
 var result bytes.Buffer
 // 以一个byte的buf实例化一个编码实例encoder
 encoder:=gob.NewEncoder(&result)
 err:=encoder.Encode(b)
 if err!=nil {
 log.Panic(err)
 }
 return result.Bytes()
}
// 反序列化byte数组,生成block实例。
func Deserialize(b []byte)*Block{
 var block Block
 decoder:=gob.NewDecoder(bytes.NewReader(b))
 err:=decoder.Decode(&block)
 if err!=nil{
 log.Panic(err)
 }
 return &block
}

blockchain.go

package core
import (
 "fmt"
 "log"
 "github.com/boltdb/bolt"
)
const dbFile = "blockchian.db"
const blockBucket = "blocks"
type BlockChain struct {
 tip []byte
 Db *bolt.DB
}
type BLockchainIterator struct {
 currentHash []byte
 Db *bolt.DB
}
// 添加新区块到链上
// 参数:data,区块要保存的数据
func (bc *BlockChain) AddBlock(data string) {
 var lastHash []byte
 // 只读的方式打开lockchian.db,获取最新区块的hash值
 err := bc.Db.View(func(tx1 *bolt.Tx) error {
 b := tx1.Bucket([]byte(blockBucket))
 lastHash = b.Get([]byte("l"))
 return nil
 })
 if err != nil {
 log.Panic(err)
 }
 // 计算新的区块
 newBlock := NewBlock(data, lastHash)
 bc.tip = newBlock.Hash
 // 读写的方式打开lockchian.db,写入新区块到lockchian.db中。
 bc.Db.Update(func(tx *bolt.Tx) error {
 b := tx.Bucket([]byte(blockBucket))
 if b == nil {
 log.Panic("bucket is nil !")
 }
 err := b.Put(newBlock.Hash, newBlock.Serialize())
 if err != nil {
 log.Panic(err)
 }
 //更新最新区块的hash
 err = b.Put([]byte("l"), newBlock.Hash)
 if err != nil {
 log.Panic(err)
 }
 return nil
 })
}
func (bc *BlockChain) Iterator() *BLockchainIterator {
 var lastHash []byte
 bc.Db.View(func(tx *bolt.Tx) error {
 // Assume bucket exists and has keys
 b := tx.Bucket([]byte(blockBucket))
 lastHash = b.Get([]byte("l"))
 //c := b.Cursor()
 //for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
 // fmt.Printf("key=%s, value=%s\n", k, v)
 //}
 return nil
 })
 return &BLockchainIterator{lastHash, bc.Db}
}
func (bci *BLockchainIterator) Next() *Block {
 var byteBlock []byte
 bci.Db.View(func(tx *bolt.Tx) error {
 b := tx.Bucket([]byte(blockBucket))
 byteBlock = b.Get(bci.currentHash)
 return nil
 })
 block := Deserialize(byteBlock)
 bci.currentHash = block.PrevBlockHash
 return block
}
// 创建区块链
// 返回一个区块链实例
func NewBlockChain() *BlockChain {
 var tip []byte
 // 打开存储区块链的文件blockchian.db、不存在则创建
 db, err := bolt.Open(dbFile, 0600, nil)
 if err != nil {
 log.Panic(err)
 }
 // 可读写的方式访问区块链文件blockchian.db
 err = db.Update(func(tx *bolt.Tx) error {
 b := tx.Bucket([]byte(blockBucket))
 if b == nil {
 fmt.Println("No existing blockchain. Creating a new one...")
 // 创建创世纪块
 genesis := NewGenesisBlock()
 b, err := tx.CreateBucket([]byte(blockBucket))
 if err != nil {
 log.Panic(err)
 }
 // 键值对的方式存储区块在blockchian.db里
 err = b.Put(genesis.Hash, genesis.Serialize())
 if err != nil {
 log.Panic(err)
 }
 // 以一个特殊的key保存最新的区块的hash,便于整个链的检索
 err = b.Put([]byte("l"), genesis.Hash)
 if err != nil {
 log.Panic(err)
 }
 tip = genesis.Hash
 } else {
 tip = b.Get([]byte("l"))
 }
 return nil
 })
 if err != nil {
 log.Panic(err)
 }
 // 返回区块链的实例
 bc := &BlockChain{tip, db}
 return bc
}

cli.go

package core
import (
 "fmt"
 "os"
 "flag"
 "log"
 "strconv"
)
type CLI struct {
 Bc *BlockChain
}
func (cli *CLI) printUsage() {
 fmt.Println("Usage:")
 fmt.Println(" addblock -data(区块的数据) - 添加一个区块到区块链上面去。")
 fmt.Println(" printchain - 打印区块链上所有的区块")
}
func (cli *CLI) validateArgs() {
 if len(os.Args)<2{
 cli.printUsage()
 os.Exit(1)
 }
}
func (cli *CLI) addBlock(data string) {
 cli.Bc.AddBlock(data)
 fmt.Println("Success!")
}
func (cli *CLI)printChain() {
 bci:=cli.Bc.Iterator()
 for{
 block:=bci.Next()
 fmt.Printf("Prive hash :%x\n",block.PrevBlockHash)
 fmt.Printf("Data: %s\n",block.Data)
 fmt.Printf("Hash: %x\n",block.Hash)
 pow := NewProofOfWork(block)
 fmt.Printf("pow:%s\n",strconv.FormatBool(pow.Validate()))
 fmt.Println()
 if len(block.PrevBlockHash)==0{
 break
 }
 }
}
// 命令行执行程序
func (cli *CLI) Run() {
 cli.validateArgs()
 // 创建命令行对象
 addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
 printChianCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
 // 命令行对象添加参数,
 addBlockData := addBlockCmd.String("data", "", "区块数据不能为空!")
 switch os.Args[1] {
 case "addblock":
 err := addBlockCmd.Parse(os.Args[2:])
 if err != nil {
 log.Panic(err)
 }
 case "printchain":
 err:=printChianCmd.Parse(os.Args[2:])
 if err!=nil{
 log.Panic(err)
 }
 default:
 cli.printUsage()
 os.Exit(1)
 }
 if addBlockCmd.Parsed(){
 if *addBlockData==""{
 addBlockCmd.Usage()
 os.Exit(1)
 }
 cli.addBlock(*addBlockData)
 }
 if printChianCmd.Parsed(){
 cli.printChain()
 //cli.printUsage()
 }
}

proofofwork.go

package core
import (
 "math"
 "math/big"
 "fmt"
 "crypto/sha256"
 "bytes"
)
var (
 maxNonce = math.MaxInt64
)
const targetBits = 12
type ProofOfWork struct {
 block *Block
 target *big.Int
}
func NewProofOfWork(b *Block) *ProofOfWork {
 target := big.NewInt(1)
 target.Lsh(target, uint(256-targetBits))
 pow := &ProofOfWork{b, target}
 return pow
}
func (pow *ProofOfWork) prepareData(nonce int) []byte {
 data := bytes.Join([][]byte{
 pow.block.PrevBlockHash,
 pow.block.Data,
 IntToHex(pow.block.TimeStamp),
 IntToHex(int64(targetBits)),
 IntToHex(int64(nonce)),
 }, []byte{})
 return data
}
func (pow *ProofOfWork) Run() (int, []byte) {
 var hashInt big.Int
 var hash [32]byte
 nonce := 0
 fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
 for nonce < maxNonce {
 data := pow.prepareData(nonce)
 hash = sha256.Sum256(data)
 fmt.Printf("\r%x", hash)
 hashInt.SetBytes(hash[:])
 if hashInt.Cmp(pow.target) == -1 {
 break
 } else {
 nonce++
 }
 }
 fmt.Printf("\n\n")
 return nonce, hash[:]
}
func (pow *ProofOfWork) Validate() bool {
 var hashInt big.Int
 data := pow.prepareData(pow.block.Nonce)
 hash := sha256.Sum256(data)
 hashInt.SetBytes(hash[:])
 isValid := hashInt.Cmp(pow.target) == -1
 return isValid
}

utils.go

package core
import (
 "bytes"
 "encoding/binary"
 "log"
 "crypto/sha256"
)
func IntToHex(num int64) []byte {
 buff := new(bytes.Buffer)
 err := binary.Write(buff, binary.BigEndian, num)
 if err != nil {
 log.Panic(err)
 }
 return buff.Bytes()
}
func DataToHash(data []byte) []byte {
 hash := sha256.Sum256(data)
 return hash[:]
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:embedsky

查看原文:golang-区块链学习03永久存储

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1298 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏