美文网首页Dapp开发
golang-区块链学习03永久存储

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

作者: embedsky | 来源:发表于2018-08-15 01:09 被阅读55次

    前言

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

    知识点

    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[:]
    }
    

    相关文章

      网友评论

        本文标题:golang-区块链学习03永久存储

        本文链接:https://www.haomeiwen.com/subject/krobbftx.html