摘要

本文构建了一个使用工作量证明机制(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()

//定义一个区块的结构Block
type Block struct {
//版本号
Version int64
//父区块头哈希值
PreBlockHash []byte
//当前区块的Hash值, 为了简化代码
Hash []byte
//Merkle根
MerkleRoot []byte
//时间戳
TimeStamp int64
//难度值
Bits int64
//随机值
Nonce int64 //交易信息
Data []byte
} //提供一个创建区块的方法
func NewBlock(data string, preBlockHash []byte) *Block {
var block Block
block = Block{
Version: ,
PreBlockHash: preBlockHash,
//Hash TODO
MerkleRoot: []byte{},
TimeStamp: time.Now().Unix(),
Bits: targetBits,
Nonce: ,
Data: []byte(data)}
//block.SetHash()
pow := NewProofOfWork(&block)
nonce, hash := pow.Run()
block.Nonce = nonce
block.Hash = hash return &block
} // 将 Block 序列化为一个字节数组
func (block *Block) Serialize() []byte {
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer) err := encoder.Encode(block)
CheckErr("Serialize", err) return buffer.Bytes()
} // 将字节数组反序列化为一个 Block
func Deserialize(data []byte) *Block { if len(data) == {
return nil
} var block Block decoder := gob.NewDecoder(bytes.NewReader(data))
err := decoder.Decode(&block)
CheckErr("Deserialize", err) return &block
} //创世块
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}

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)
  • 迭代器对区块进行遍历。
const dbFile = "blockchain.db"
const blocksBucket = "bucket"
const lastHashKey = "key" //定义一个区块链结构BlockChain
type BlockChain struct {
//blocks []*Block
//数据库的操作句柄
db *bolt.DB
//tail尾巴,表示最后一个区块的哈希值
//在链的末端可能出现短暂分叉的情况,所以选择tail其实也就是选择了哪条链
tail []byte
} //提供一个创建BlockChain的方法
func NewBlockChain() *BlockChain {
// 打开一个 BoltDB 文件
//func Open(path string, mode os.FileMode, options *Options) (*DB, error)
db, err := bolt.Open(dbFile, , nil)
//utils中的校验函数,校验错误
CheckErr("NewBlockChain1", err) var lastHash []byte err = db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blocksBucket)) // 如果数据库中不存在bucket,要去创建创世区块,将数据填写到数据库的bucket中
if bucket == nil {
fmt.Println("No existing blockchain found. Creating a new one...")
genesis := NewGenesisBlock() bucket, err := tx.CreateBucket([]byte(blocksBucket))
CheckErr("NewBlockChain2", err) err = bucket.Put(genesis.Hash, genesis.Serialize())
CheckErr("NewBlockChain3", err) err = bucket.Put([]byte(lastHashKey), genesis.Hash)
CheckErr("NewBlockChain4", err)
lastHash = genesis.Hash
} else {
//直接读取最后区块的哈希值
lastHash = bucket.Get([]byte(lastHashKey))
} return nil
}) CheckErr("db.Update", err) return &BlockChain{db, lastHash}
} //提供一个添加区块的方法
func (bc *BlockChain) AddBlock(data string) {
var preBlockHash []byte err := bc.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blocksBucket))
if bucket == nil {
os.Exit()
} preBlockHash = bucket.Get([]byte(lastHashKey))
return nil
})
CheckErr("AddBlock-View", err) block := NewBlock(data, preBlockHash)
err = bc.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blocksBucket))
if bucket == nil {
os.Exit()
} err = bucket.Put(block.Hash, block.Serialize())
CheckErr("AddBlock1", err) err = bucket.Put([]byte(lastHashKey), block.Hash)
CheckErr("AddBlock2", err)
bc.tail = block.Hash
return nil
})
CheckErr("AddBlock-Update", err)
} //迭代器,就是一个对象,它里面包含了一个游标,一直向前/后移动,完成整个容器的遍历
type BlockChainIterator struct {
currentHash []byte
db *bolt.DB
} //创建迭代器,同时初始化为指向最后一个区块
func (bc *BlockChain) NewIterator() *BlockChainIterator {
return &BlockChainIterator{bc.tail, bc.db}
} // 返回链中的下一个块
func (it *BlockChainIterator) Next() (block *Block) { err := it.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blocksBucket))
if bucket == nil {
return nil
}
data := bucket.Get(it.currentHash)
block = Deserialize(data)
it.currentHash = block.PreBlockHash
return nil
})
CheckErr("Next", err)
return
}

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

该部分主要包括:

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

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

//定义一个工作量证明的结构ProofOfWork
type ProofOfWork struct {
block *Block
//目标值
target *big.Int
} //难度值常量
const targetBits = //创建POW的方法
func NewProofOfWork(block *Block) *ProofOfWork { //000000000000000... 01
target := big.NewInt()
//0x1000000000000...00
target.Lsh(target, uint(-targetBits)) pow := ProofOfWork{block: block, target: target}
return &pow
} //给Run()准备数据
func (pow *ProofOfWork) PrepareData(nonce int64) []byte {
block := pow.block
tmp := [][]byte{
/*
需要将block中的不同类型都转化为byte,以便进行连接
*/
IntToByte(block.Version),
block.PreBlockHash,
block.MerkleRoot,
IntToByte(block.TimeStamp),
IntToByte(nonce),
IntToByte(targetBits),
block.Data}
//func Join(s [][]byte, sep []byte) []byte
data := bytes.Join(tmp, []byte{})
return data
} //计算哈希值的方法
func (pow *ProofOfWork) Run() (int64, []byte) {
/*伪代码
for nonce {
hash := sha256(block数据 + nonce)
if 转换(Hash)< pow.target{
找到了
}else{
nonce++
}
}
return nonce,hash{:}
*/
//1.拼装数据
//2.哈希值转成big.Int类型
var hash []byte
var nonce int64 =
var hashInt big.Int fmt.Println("Begin Minding...")
fmt.Printf("target hash : %x\n", pow.target.Bytes()) for nonce < math.MaxInt64 {
data := pow.PrepareData(nonce)
hash = sha256.Sum256(data) hashInt.SetBytes(hash[:])
// Cmp compares x and y and returns:
//
// -1 if x < y
// 0 if x == y
// +1 if x > y
//
//func (x *Int) Cmp(y *Int) (r int) {
if hashInt.Cmp(pow.target) == - {
fmt.Printf("found hash :%x,nonce :%d\n,", hash, nonce)
break
} else {
//fmt.Printf("not found nonce,current nonce :%d,hash : %x\n", nonce, hash)
nonce++
}
}
return nonce, hash[:]
} //校验函数
func (pow *ProofOfWork) IsValid() bool {
var hashInt big.Int data := pow.PrepareData(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:]) return hashInt.Cmp(pow.target) == -
}

4.命令函交互CLI.go

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

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

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

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

具体如下:

//因为是多行的,所以用反引号`···`包一下,可以实现多行字符串的拼接,不需要转义!
//命令行提示
const usage = `
Usage:
addBlock -data BLOCK_DATA "add a block to the blockchain"
printChain "print all the blocks of the blockchain"
`
const AddBlockCmdString = "addBlock"
const PrintChainCmdString = "printChain" //输出提示函数
func (cli *CLI) printUsage() {
fmt.Println("Invalid input!")
fmt.Println(usage)
os.Exit()
} //参数检查函数
func (cli *CLI) validateArgs() {
if len(os.Args) < {
fmt.Println("invalid input!")
cli.printUsage()
}
} func (cli *CLI) Run() {
cli.validateArgs() addBlockCmd := flag.NewFlagSet(AddBlockCmdString, flag.ExitOnError)
printChainCmd := flag.NewFlagSet(PrintChainCmdString, flag.ExitOnError)
//func (f *FlagSet) String(name string, value string, usage string) *string
addBlocCmdPara := addBlockCmd.String("data", "", "Block data") switch os.Args[] {
case AddBlockCmdString:
//添加动作
err := addBlockCmd.Parse(os.Args[:])
CheckErr("Run()1", err)
if addBlockCmd.Parsed() {
if *addBlocCmdPara == "" {
fmt.Println("addBlock data not should be empty!")
cli.printUsage()
}
cli.AddBlock(*addBlocCmdPara)
}
case PrintChainCmdString:
//打印输出
err := printChainCmd.Parse(os.Args[:])
CheckErr("Run()2", err)
if printChainCmd.Parsed() {
cli.PrintChain()
}
default:
//命令不符合规定,输出提示信息
cli.printUsage()
}
}

区块链操作演示效果:

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

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

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

Reference:

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

[区块链\理解BTCD源码]GO语言实现一个区块链原型的更多相关文章

  1. 深入理解OkHttp源码(二)——获取响应

    首先先看一张流程图,该图是从拆轮子系列:拆 OkHttp 中盗来的,如下: 在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChai ...

  2. 七、Spring之深入理解AOP源码

    Spring之深入理解AOP源码 ​ 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. ​ 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...

  3. 深入理解OkHttp源码(一)——提交请求

    本篇文章主要介绍OkHttp执行同步和异步请求的大体流程.主要流程如下图: 主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面 ...

  4. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

  5. 33、[源码]-AOP原理-获取拦截器链-MethodInterceptor

    33.[源码]-AOP原理-获取拦截器链-MethodInterceptor

  6. 你与优秀源码之间只差一个 Star

    fir.im Weekly - 你与优秀源码之间只差一个 Star   说起开源社区,Github 是一个不可缺少的存在.作为全球最大的同性交友网站,上面有太多优秀的开源代码库和编程大神,让无数开发者 ...

  7. vue 源码详解(一):原型对象和全局 `API`的设计

    vue 源码详解(一):原型对象和全局 API的设计 1. 从 new Vue() 开始 我们在实际的项目中使用 Vue 的时候 , 一般都是在 main.js 中通过 new Vue({el : ' ...

  8. 这一次,彻底理解Promise源码思想

    关于Promise的源码实现,网上有太多答案,我也看过很多资料,但都不是很明白.直到有一天我学完函数式编程之函子的概念,才对Promise源码有了更深刻的认识.今天,就让我们来重新认识一下Promis ...

  9. 我对java String的理解 及 源码浅析

    摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! ...

随机推荐

  1. Yii2访问自定义模块下的controller

    之前,由于所要访问的controller都是位于根目录下的controllers目录下,就像下面这样: 此时,我们可以直接通过 localhost/basic/web/index.php?r=dao/ ...

  2. Flask自带的常用组件介绍

    Flaskrender_templatesessionurl_forredirectflashmake_responsejsonifyblueprintrequestabortgsend_from_d ...

  3. Pascal Triangle

    Description: Given numRows, generate the first numRows of Pascal's triangle. For example, given numR ...

  4. react native 1跳2 2跳3 3跳4 4pop回2

    网上有介绍导航的很多了 就不一一说了   直接说一个小功能 popToRoute pop回指定页面  第一次写 组织能力不是特别好 直接贴代码 例如 我们有四个页面  从第四个pop到第二个页面  先 ...

  5. 设计模式之策略模式(Strategy Pattern)

    模板方法是通过继承实现的,在父类中定义出算法的骨架,将不同点在子类中实现.而策略模式是通过接口实现的,策略中定义了完整的算法.它们有点像啊-- 策略模式的定义 策略模式(Strategy Patter ...

  6. Flask开发微电影网站(一)

    1.用到的Flask知识 1.使用整形,浮点型,路径型,字符串型下正则表达式路由转化器 2.使用GET与POST请求,上传文件,cookie获取与响应,404处理 3.使用模板自动转义,定义过滤器,定 ...

  7. 自动化测试--protractor

    前戏 面向模型编程: 测试驱动开发: 先保障交互逻辑,再调整细节.---by 雪狼. 为什么要自动化测试? 1,提高产出质量. 2,减少重构时的痛.反正我最近重构多了,痛苦经历多了. 3,便于新人接手 ...

  8. Shell 起停脚本 专题

    To list any process listening to the port 8080: lsof -i:8080To kill any process listening to the por ...

  9. HoloLens开发手记-实现3D应用启动器

    一直有人问我,第三方应用在HoloLens上可以实现3D启动图标吗?就像微软官方出的应用那样.在不久之前,这个问题的答案还是No. 但是随着最新的Windows build 1803版本的发布,现在我 ...

  10. async 和 await 之异步编程的学习

    async修改一个方法,表示其为异步方法.而await表示等待一个异步任务的执行.js方面,在es7中开始得以支持:而.net在c#5.0开始支持.本文章将分别简单介绍他们在js和.net中的基本用法 ...