经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 区块链 » 查看文章
[区块链\理解BTCD源码]GO语言实现一个区块链原型
来源:cnblogs  作者:勋爵  时间:2019/4/24 9:48:16  对本文有异议

摘要

本文构建了一个使用工作量证明机制(POW)的类BTC的区块链。将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作。这篇文章目的是希望帮助大家理解BTC源码的架构,所以主要专注于的实现原理及存储上,暂时忽略了 “分布式” 这个部分。严格来说还不能算是一个完全意义上的区块链系统。

开发环境

语言:GO;

数据库:BoltDB;

IDE: Goland或其他工具都可以;

系统:不限,本文使用windows。

BoltDB数据库

实际上,选择任何一个数据库都可以,本文先用的是BoltDB。在比特币白皮书中,并没有提到要使用哪一个具体的数据库,它完全取决于开发者如何选择。现在是比特币的一个参考实现,Bitcoin core使用的是是LevelDB。

BoltDB安装及使用可以参考《BoltDB简单使用教程》

BoltDB有如下优点:

  1. 非常简单和简约
  2. 用 Go 实现
  3. 不需要运行一个服务器
  4. 能够允许我们构造想要的数据结构

由于 Bolt 意在用于提供一些底层功能,简洁便成为其关键所在。它的API 并不多,并且仅关注值的获取和设置。仅此而已。

Bolt 使用键值存储,数据被存储为键值对(key-value pair,就像 Golang 的 map)。键值对被存储在 bucket 中,这是为了将相似的键值对进行分组(类似 RDBMS 中的表格)。因此,为了获取一个值,你需要知道一个 bucket 和一个键(key)。

注意:Bolt 数据库没有数据类型:键和值都是字节数组(byte array)。鉴于需要在里面存储 Go 的结构(准确来说,也就是存储(块)Block),我们需要对它们进行序列化,也就说,实现一个从 Go struct 转换到一个 byte array 的机制,同时还可以从一个 byte array 再转换回 Go struct。虽然我们将会使用 encoding/gob 来完成这一目标,但实际上也可以选择使用 JSON, XML, Protocol Buffers 等等。之所以选择使用 encoding/gob, 是因为它很简单,而且是 Go 标准库的一部分。

区块链原型的函数架构

 

 

系统实现

1.区块文件block.go

该部分主要包括:

对区块结构的定义;创建区块的方法NewBlock();区块的序列化Serialize()与反序列化Deserialize()函数;以及创世区块的生成NewGenesisBlock()

  1. //定义一个区块的结构Block
  2. type Block struct {
  3. //版本号
  4. Version int64
  5. //父区块头哈希值
  6. PreBlockHash []byte
  7. //当前区块的Hash值, 为了简化代码
  8. Hash []byte
  9. //Merkle根
  10. MerkleRoot []byte
  11. //时间戳
  12. TimeStamp int64
  13. //难度值
  14. Bits int64
  15. //随机值
  16. Nonce int64
  17. //交易信息
  18. Data []byte
  19. }
  20. //提供一个创建区块的方法
  21. func NewBlock(data string, preBlockHash []byte) *Block {
  22. var block Block
  23. block = Block{
  24. Version: 1,
  25. PreBlockHash: preBlockHash,
  26. //Hash TODO
  27. MerkleRoot: []byte{},
  28. TimeStamp: time.Now().Unix(),
  29. Bits: targetBits,
  30. Nonce: 0,
  31. Data: []byte(data)}
  32. //block.SetHash()
  33. pow := NewProofOfWork(&block)
  34. nonce, hash := pow.Run()
  35. block.Nonce = nonce
  36. block.Hash = hash
  37. return &block
  38. }
  39. // 将 Block 序列化为一个字节数组
  40. func (block *Block) Serialize() []byte {
  41. var buffer bytes.Buffer
  42. encoder := gob.NewEncoder(&buffer)
  43. err := encoder.Encode(block)
  44. CheckErr("Serialize", err)
  45. return buffer.Bytes()
  46. }
  47. // 将字节数组反序列化为一个 Block
  48. func Deserialize(data []byte) *Block {
  49. if len(data) == 0 {
  50. return nil
  51. }
  52. var block Block
  53. decoder := gob.NewDecoder(bytes.NewReader(data))
  54. err := decoder.Decode(&block)
  55. CheckErr("Deserialize", err)
  56. return &block
  57. }
  58. //创世块
  59. func NewGenesisBlock() *Block {
  60. return NewBlock("Genesis Block", []byte{})
  61. }

 

2.区块链blockChain.go

该部分内容主要包括:

  • 定义一个区块链结构BlockChain结构体

  • 提供一个创建BlockChain的方法NewBlockChain()

我们希望NewBlockchain实现的功能有

  1. 打开一个数据库文件
  2. 检查文件里面是否已经存储了一个区块链
  3. 如果已经存储了一个区块链:
    1. 创建一个新的 Blockchain 实例
    2. 设置 Blockchain 实例的 tip 为数据库中存储的最后一个块的哈希
  4. 如果没有区块链:
    1. 创建创世块
    2. 存储到数据库
    3. 将创世块哈希保存为最后一个块的哈希
    4. 创建一个新的 Blockchain 实例,初始时 tail 指向创世块( tail存储的是最后一个块的哈希值)

 

  • 提供一个添加区块的方法AddBlock(data string)
  • 迭代器对区块进行遍历。
  1. const dbFile = "blockchain.db"
  2. const blocksBucket = "bucket"
  3. const lastHashKey = "key"
  4.  
  5. //定义一个区块链结构BlockChain
  6. type BlockChain struct {
  7. //blocks []*Block
  8. //数据库的操作句柄
  9. db *bolt.DB
  10. //tail尾巴,表示最后一个区块的哈希值
  11. //在链的末端可能出现短暂分叉的情况,所以选择tail其实也就是选择了哪条链
  12. tail []byte
  13. }
  14. //提供一个创建BlockChain的方法
  15. func NewBlockChain() *BlockChain {
  16. // 打开一个 BoltDB 文件
  17. //func Open(path string, mode os.FileMode, options *Options) (*DB, error)
  18. db, err := bolt.Open(dbFile, 0600, nil)
  19. //utils中的校验函数,校验错误
  20. CheckErr("NewBlockChain1", err)
  21. var lastHash []byte
  22. err = db.Update(func(tx *bolt.Tx) error {
  23. bucket := tx.Bucket([]byte(blocksBucket))
  24. // 如果数据库中不存在bucket,要去创建创世区块,将数据填写到数据库的bucket中
  25. if bucket == nil {
  26. fmt.Println("No existing blockchain found. Creating a new one...")
  27. genesis := NewGenesisBlock()
  28. bucket, err := tx.CreateBucket([]byte(blocksBucket))
  29. CheckErr("NewBlockChain2", err)
  30. err = bucket.Put(genesis.Hash, genesis.Serialize())
  31. CheckErr("NewBlockChain3", err)
  32. err = bucket.Put([]byte(lastHashKey), genesis.Hash)
  33. CheckErr("NewBlockChain4", err)
  34. lastHash = genesis.Hash
  35. } else {
  36. //直接读取最后区块的哈希值
  37. lastHash = bucket.Get([]byte(lastHashKey))
  38. }
  39. return nil
  40. })
  41. CheckErr("db.Update", err)
  42. return &BlockChain{db, lastHash}
  43. }
  44. //提供一个添加区块的方法
  45. func (bc *BlockChain) AddBlock(data string) {
  46. var preBlockHash []byte
  47. err := bc.db.View(func(tx *bolt.Tx) error {
  48. bucket := tx.Bucket([]byte(blocksBucket))
  49. if bucket == nil {
  50. os.Exit(1)
  51. }
  52. preBlockHash = bucket.Get([]byte(lastHashKey))
  53. return nil
  54. })
  55. CheckErr("AddBlock-View", err)
  56. block := NewBlock(data, preBlockHash)
  57. err = bc.db.Update(func(tx *bolt.Tx) error {
  58. bucket := tx.Bucket([]byte(blocksBucket))
  59. if bucket == nil {
  60. os.Exit(1)
  61. }
  62. err = bucket.Put(block.Hash, block.Serialize())
  63. CheckErr("AddBlock1", err)
  64. err = bucket.Put([]byte(lastHashKey), block.Hash)
  65. CheckErr("AddBlock2", err)
  66. bc.tail = block.Hash
  67. return nil
  68. })
  69. CheckErr("AddBlock-Update", err)
  70. }
  71. //迭代器,就是一个对象,它里面包含了一个游标,一直向前/后移动,完成整个容器的遍历
  72. type BlockChainIterator struct {
  73. currentHash []byte
  74. db *bolt.DB
  75. }
  76. //创建迭代器,同时初始化为指向最后一个区块
  77. func (bc *BlockChain) NewIterator() *BlockChainIterator {
  78. return &BlockChainIterator{bc.tail, bc.db}
  79. }
  80. // 返回链中的下一个块
  81. func (it *BlockChainIterator) Next() (block *Block) {
  82. err := it.db.View(func(tx *bolt.Tx) error {
  83. bucket := tx.Bucket([]byte(blocksBucket))
  84. if bucket == nil {
  85. return nil
  86. }
  87. data := bucket.Get(it.currentHash)
  88. block = Deserialize(data)
  89. it.currentHash = block.PreBlockHash
  90. return nil
  91. })
  92. CheckErr("Next", err)
  93. return
  94. }

 

3.工作量证明机制POW.go

该部分主要包括:

创建POW的方法NewProofOfWork(block *Block)

计算哈希值的方法 Run() (int64, []byte)

  1. //定义一个工作量证明的结构ProofOfWork
  2. type ProofOfWork struct {
  3. block *Block
  4. //目标值
  5. target *big.Int
  6. }
  7. //难度值常量
  8. const targetBits = 20
  9.  
  10. //创建POW的方法
  11. func NewProofOfWork(block *Block) *ProofOfWork {
  12. //000000000000000... 01
  13. target := big.NewInt(1)
  14. //0x1000000000000...00
  15. target.Lsh(target, uint(256-targetBits))
  16. pow := ProofOfWork{block: block, target: target}
  17. return &pow
  18. }
  19. //给Run()准备数据
  20. func (pow *ProofOfWork) PrepareData(nonce int64) []byte {
  21. block := pow.block
  22. tmp := [][]byte{
  23. /*
  24. 需要将block中的不同类型都转化为byte,以便进行连接
  25. */
  26. IntToByte(block.Version),
  27. block.PreBlockHash,
  28. block.MerkleRoot,
  29. IntToByte(block.TimeStamp),
  30. IntToByte(nonce),
  31. IntToByte(targetBits),
  32. block.Data}
  33. //func Join(s [][]byte, sep []byte) []byte
  34. data := bytes.Join(tmp, []byte{})
  35. return data
  36. }
  37. //计算哈希值的方法
  38. func (pow *ProofOfWork) Run() (int64, []byte) {
  39. /*伪代码
  40. for nonce {
  41. hash := sha256(block数据 + nonce)
  42. if 转换(Hash)< pow.target{
  43. 找到了
  44. }else{
  45. nonce++
  46. }
  47. }
  48. return nonce,hash{:}
  49. */
  50. //1.拼装数据
  51. //2.哈希值转成big.Int类型
  52. var hash [32]byte
  53. var nonce int64 = 0
  54. var hashInt big.Int
  55. fmt.Println("Begin Minding...")
  56. fmt.Printf("target hash : %x\n", pow.target.Bytes())
  57. for nonce < math.MaxInt64 {
  58. data := pow.PrepareData(nonce)
  59. hash = sha256.Sum256(data)
  60. hashInt.SetBytes(hash[:])
  61. // Cmp compares x and y and returns:
  62. //
  63. // -1 if x < y
  64. // 0 if x == y
  65. // +1 if x > y
  66. //
  67. //func (x *Int) Cmp(y *Int) (r int) {
  68. if hashInt.Cmp(pow.target) == -1 {
  69. fmt.Printf("found hash :%x,nonce :%d\n,", hash, nonce)
  70. break
  71. } else {
  72. //fmt.Printf("not found nonce,current nonce :%d,hash : %x\n", nonce, hash)
  73. nonce++
  74. }
  75. }
  76. return nonce, hash[:]
  77. }
  78. //校验函数
  79. func (pow *ProofOfWork) IsValid() bool {
  80. var hashInt big.Int
  81. data := pow.PrepareData(pow.block.Nonce)
  82. hash := sha256.Sum256(data)
  83. hashInt.SetBytes(hash[:])
  84. return hashInt.Cmp(pow.target) == -1
  85. }

 

4.命令函交互CLI.go

注意这部分需要使用标准库里面的 flag 包来解析命令行参数;

首先,创建两个子命令: addblock 和 printchain, 然后给 addblock 添加 --data 标志。printchain 没有标志;

然后,检查用户输入的命令并解析相关的 flag 子命令;

最后检查解析是哪一个子命令,并调用相关函数执行。

具体如下:

  1. //因为是多行的,所以用反引号`···`包一下,可以实现多行字符串的拼接,不需要转义!
  2. //命令行提示
  3. const usage = `
  4. Usage:
  5. addBlock -data BLOCK_DATA "add a block to the blockchain"
  6. printChain "print all the blocks of the blockchain"
  7. `
  8. const AddBlockCmdString = "addBlock"
  9. const PrintChainCmdString = "printChain"
  10.  
  11. //输出提示函数
  12. func (cli *CLI) printUsage() {
  13. fmt.Println("Invalid input!")
  14. fmt.Println(usage)
  15. os.Exit(1)
  16. }
  17. //参数检查函数
  18. func (cli *CLI) validateArgs() {
  19. if len(os.Args) < 2 {
  20. fmt.Println("invalid input!")
  21. cli.printUsage()
  22. }
  23. }
  24. func (cli *CLI) Run() {
  25. cli.validateArgs()
  26. addBlockCmd := flag.NewFlagSet(AddBlockCmdString, flag.ExitOnError)
  27. printChainCmd := flag.NewFlagSet(PrintChainCmdString, flag.ExitOnError)
  28. //func (f *FlagSet) String(name string, value string, usage string) *string
  29. addBlocCmdPara := addBlockCmd.String("data", "", "Block data")
  30. switch os.Args[1] {
  31. case AddBlockCmdString:
  32. //添加动作
  33. err := addBlockCmd.Parse(os.Args[2:])
  34. CheckErr("Run()1", err)
  35. if addBlockCmd.Parsed() {
  36. if *addBlocCmdPara == "" {
  37. fmt.Println("addBlock data not should be empty!")
  38. cli.printUsage()
  39. }
  40. cli.AddBlock(*addBlocCmdPara)
  41. }
  42. case PrintChainCmdString:
  43. //打印输出
  44. err := printChainCmd.Parse(os.Args[2:])
  45. CheckErr("Run()2", err)
  46. if printChainCmd.Parsed() {
  47. cli.PrintChain()
  48. }
  49. default:
  50. //命令不符合规定,输出提示信息
  51. cli.printUsage()
  52. }
  53. }

 

区块链操作演示效果:

首先 go build 编译程序;输入不带--data参数的错误命令,查看提示。

 

输入交易信息,查看pow运算:

 

打印区块链已有区块信息:

 

Reference:

最后要感谢Ivan Kuznetsov在GitHub社区的贡献!

 

原文链接:http://www.cnblogs.com/X-knight/p/10759493.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号