美文网首页
跟我一起写区块链:区块数据持久化与遍历

跟我一起写区块链:区块数据持久化与遍历

作者: 柏链教育 | 来源:发表于2019-12-25 10:16 被阅读0次

之前我们介绍过如何通过Go语言实现pow挖矿,今天我们继续来自己动手编写区块链。之前我们的区块链即使运行起来,停下来之后数据就会丢失,那么数据怎样做到持久化呢?这和我们之前学到的原理是一样的,在进程中的数据会随着进程的结束而消失,若想持久化,那必须把数据保存在磁盘,大家很容易就联想到数据库。

为了让我们的区块链数据能够持久化,我们也使用一款数据库,这款数据库不能像oracle和mysql那样麻烦,还需要安装、配置等等,我们只需要将数据保留在本地就可以了,因此我们使用一款小巧型的数据库-boltdb,初看这个名字,竟然是与短跑名将齐名!

先不着急把数据存放到数据库中,首先我们要解决一个小安全问题,因为数据一旦存储到数据库中,就代表着可以被其他任何第三方读取到,为了保护我们的数据,我们将数据做一些小小的处理,之前我们提到过的,做一下数据序列化。在这里我们使用golang中的gob库来进行序列化处理,当然序列化之后我们自己也要有解析的能力。

// Serialize serializes the block
func (b *Block) Serialize() []byte {
    var result bytes.Buffer
    encoder := gob.NewEncoder(&result)

    err := encoder.Encode(b)
    if err != nil {
        log.Panic(err)
    }

    return result.Bytes()
}

Serialize() 可以将一个区块的数据变成我们无法识别的符号。

// DeserializeBlock deserializes a block
func DeserializeBlock(d []byte) *Block {
    var block Block
    //构造解码器
    decoder := gob.NewDecoder(bytes.NewReader(d))
    //解码
    err := decoder.Decode(&block)
    if err != nil {
        log.Panic(err)
    }

    return &block
}

DeserializeBlock则完全是逆向操作,帮助我们把数据还原为区块信息。

接下来我们考虑改造之前的区块链,将区块与数据库相结合。

// Blockchain keeps a sequence of Blocks
type Blockchain struct {
    tip []byte //记录当前块的hash值
    db  *bolt.DB //保存区块完整数据
}

对于boltdb来说,简单的2个操作就够了,不过它内部有一个bucket的概念,我们首先决定把数据装到哪个bucket里,然后再考虑是存还是取的操作。

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
    var tip []byte
    //打开数据库,相当于指定了一个数据库文件
    db, err := bolt.Open(dbFile, 0600, nil)
    if err != nil {
        log.Panic(err)
    }
    //涉及到数据修改,要用到update方法
    err = db.Update(func(tx *bolt.Tx) error {
        //取指定的bucket
        b := tx.Bucket([]byte(blocksBucket))

        if b == nil {
            fmt.Println("No existing blockchain found. Creating a new one...")
            genesis := NewGenesisBlock()
            //创世块需要创建新的bucket
            b, err := tx.CreateBucket([]byte(blocksBucket))
            if err != nil {
                log.Panic(err)
            }
            //放创世块的hash与序列化数据
            err = b.Put(genesis.Hash, genesis.Serialize())
            if err != nil {
                log.Panic(err)
            }
            //l代表之前块的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
}

这样的函数可以帮助我们完成创世块的创建,同时区块链运行起来。接下来我们需要考虑增加一个区块:

// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
    var lastHash []byte
    //增加区块,意味着之前肯定有数据了,所以此时用view方法查看历史数据,以免误操作
    err := bc.db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        lastHash = b.Get([]byte("l"))

        return nil
    })

    if err != nil {
        log.Panic(err)
    }
    
    newBlock := NewBlock(data, lastHash)
    //用pow构造一个新块后,我们将数据写入到数据库中,现在不再是依靠我们的数据结构来形成区块信息
    err = bc.db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        err := b.Put(newBlock.Hash, newBlock.Serialize())
        if err != nil {
            log.Panic(err)
        }

        err = b.Put([]byte("l"), newBlock.Hash)
        if err != nil {
            log.Panic(err)
        }

        bc.tip = newBlock.Hash

        return nil
    })
}

当这些事情搞定后,我们还需要知道如何去遍历已经产生的区块,首先我们先定义一个迭代器结构:

// BlockchainIterator is used to iterate over blockchain blocks
type BlockchainIterator struct {
    currentHash []byte
    db          *bolt.DB
}

// Iterator ...
func (bc *Blockchain) Iterator() *BlockchainIterator {
    bci := &BlockchainIterator{bc.tip, bc.db}

    return bci
}

实现遍历就是通过当前块能找到下一块,这时候反序列化就有它的用武之地了!

// Next returns next block starting from the tip
func (i *BlockchainIterator) Next() *Block {
    var block *Block

    err := i.db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        encodedBlock := b.Get(i.currentHash)
        block = DeserializeBlock(encodedBlock)

        return nil
    })

    if err != nil {
        log.Panic(err)
    }

    i.currentHash = block.PrevBlockHash

    return block
}

为了让我们的区块链运行起来舒服一点点,我们来实现一个命令行的客户端。

type CLI struct {
    bc *Blockchain
}

客户端的第一件事先提供一个帮助手册

func (cli *CLI) printUsage() {
    fmt.Println("Usage:")
    fmt.Println("  addblock -data BLOCK_DATA - add a block to the blockchain")
    fmt.Println("  printchain - print all the blocks of the blockchain")
}
//检查参数
func (cli *CLI) validateArgs() {
    if len(os.Args) < 2 {
        cli.printUsage()
        os.Exit(1)
    }
}

我们暂时将客户端只增加2个操作,添加区块与打印区块链

·添加区块

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("Prev. 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
        }
    }
}

接下来,完成我们的客户端调用,run起来。

// Run parses command line arguments and processes commands
func (cli *CLI) Run() {
    cli.validateArgs()
    //参数设置
    addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)
    printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)

    addBlockData := addBlockCmd.String("data", "", "Block data")
    
    //根据参数进行分支处理
    switch os.Args[1] {
    case "addblock":
        err := addBlockCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "printchain":
        err := printChainCmd.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 printChainCmd.Parsed() {
        cli.printChain()
    }
}

接下来的事情,在main函数里,把代码联系起来就可以了!

package main

func main() {
    bc := NewBlockchain()
    defer bc.db.Close()

    cli := CLI{bc}
    cli.Run()
}

大功告成,接下来我们需要来敲一些命令来看看效果了!

localhost:level3 yekai$ go run *.go 
No existing blockchain found. Creating a new one...
Mining the block containing "Genesis Block"
00000005303bf13700fa0ec64153e8fb6c0a5978fbcc262c7c2e0b477510a5c1

Usage:
  addblock -data BLOCK_DATA - add a block to the blockchain
  printchain - print all the blocks of the blockchain
exit status 1

再加一个区块

localhost:level3 yekai$ go run *.go addblock -data "yekai's 100 btc to alice"
Mining the block containing "yekai's 100 btc to alice"
000000c28b0e005142d2494b4a5819d086c2180c76fbaa31d9fa23f98ede2ed3

Success!

打印一下看看

localhost:level3 yekai$ go run *.go printchain
Prev. hash: 00000005303bf13700fa0ec64153e8fb6c0a5978fbcc262c7c2e0b477510a5c1
Data: yekai's 100 btc to alice
Hash: 000000c28b0e005142d2494b4a5819d086c2180c76fbaa31d9fa23f98ede2ed3
PoW: true

Prev. hash: 
Data: Genesis Block
Hash: 00000005303bf13700fa0ec64153e8fb6c0a5978fbcc262c7c2e0b477510a5c1
PoW: true

总结一下,我们使用了gob序列化技术将区块数据序列化,然后利用boltdb将hash与区块数据进行了一对一key-val存储,并用一个客户端通过命令行的形式进行展现。

相关文章

  • 跟我一起写区块链:区块数据持久化与遍历

    之前我们介绍过如何通过Go语言实现pow挖矿,今天我们继续来自己动手编写区块链。之前我们的区块链即使运行起来,停下...

  • 打造公链-造轮子(3)

    持久化和命令行接口 已经构建出一个PoW机制的区块链,但区块链的数据需要持久化到一个数据库,还需要提供一个简单的命...

  • 打造公链(3)

    持久化和命令行接口 已经构建出一个PoW机制的区块链,但区块链的数据需要持久化到一个数据库,还需要提供一个简单的命...

  • 区块链入门与去中心化应用实战

    区块链入门与去中心化应用实战 区块链 – 原始区块链 ,是一种去中心化的数据库,它包含一张被称为区块的列表,有着持...

  • 区块链与比特币

    区块链 区块链使用去中心化的数据安全技术,可提升数据安全性、降低...

  • 兄弟连区块链培训教程分享持久化

    兄弟连区块链培训教程分享持久化:数据库选型、直到现在,我们的区块链实现中还没有用到数据库,我们只是把每次启动程序计...

  • BTCC Global:区块链与传统数据库有何不同?

    区块链与传统数据库有何不同? 区块链是什么?本质上,区块链是一种分布式、去中心化的数据库系统,这个系统会让数据的存...

  • 区块链,你了解多少

    什么是区块链技术 去中心化的新型数据库、信任的机器……在网上搜索“区块链”,定义不一。究竟什么是区块链?区块链技术...

  • 区块链

    简介 区块链:是一种特殊的分布式数据存储系统。 区块与区块链? 区块头与区块体 区块头:block:101, 上一...

  • 区块链基础知识问答

    什么是区块链 区块链是由区块组成的链。 本质上讲区块链是一个记账数据库。 这个数据库有两个特点: 1,去中心化。 ...

网友评论

      本文标题:跟我一起写区块链:区块数据持久化与遍历

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