blockchain-ex2
实验二:使用Go语言构造区块链
实验概述
21 世纪最具先峰性的代表性技术之一,就是区块链。目前,它仍然处于,并将长期处于不断成长的时期,而且,在他的身上,还有很多潜在的力量,没有完全展露出来。从本质上来讲,区块链的核心,可以说是一个分布式数据库而已。不过,在区块链中,与传统的分布式数据库,最为独一无二的地方在于,区块链的数据库是公开的,而不是一个私人数据库。也就是说,每个使用它的人,都将在自己的机器上,拥有一个或部分,或完整的副本。而向数据库中添加新的记录,必须经过其他“矿工”的同意,才可以。除此以外,也是因为区块链的兴起,才使得加密货币和智能合约这一新兴技术,成为正在发生的事情。
本实验将在Go语言的环境下,实现一个简化版的区块链。
实验目标
-
熟练掌握用Go语言的语法。
-
在实践中学会构造区块链的区块(实验2-1),将区块链接为链(实验2-2),为该区块链添加工作量证明(实验2-3),
-
举一反三,发散思维,尝试实现链上数据的持久化存储(拓****展实验2-4) ,为该区块链添加命令行接口(拓展实验2-5)。
实验内容
实验2-1:构建区块
将ex2-1文件夹下的blockchain_demo文件夹下的代码补充完整:
-
Block类结构体
1
2
3
4
5
6
7type Block struct {
Time int64
Nonce int64
PrevHash []byte
Hash []byte
Data []byte
} -
Block.SetHash(),实现对Block的Hash计算
1
2
3
4
5func (b *Block) SetHash() {
montage := append(b.PrevHash, IntToHex(b.Time)...)
montage = append(montage, b.Data...)
b.Hash = calHash(montage)
} -
NewBlock(),创建新区块
1
2
3
4
5func NewBlock(data string, prevHash []byte) *Block {
block := &Block{time.Now().Unix(), 0, prevHash, []byte{}, []byte(data)}
block.SetHash()
return block
}
运行结果:
实验2-2:实现一条链
补全blockchain.go文件:
-
添加区块函数Blockchain.AddBlock()
1
2
3
4
5
6
7
8func (bc *Blockchain) AddBlock(data string) {
//可能用到的函数:
// len(array):获取数组长度
lenBC := len(bc.blocks)
lastBlock := NewBlock(data, bc.blocks[lenBC-1].Hash)
// append(array,b):将元素b添加至数组array末尾
bc.blocks = append(bc.blocks, lastBlock)
} -
创世区块生成函数GenesisBlock()
1
2
3
4
5func NewGenesisBlock() *Block {
//创世区块前置哈希为空,Data为"Genesis Block"
block := NewBlock("Genesis Block", []byte{})
return block
} -
修改main函数
1
2
3
4
5
6
7
8
9
10
11
12
13func main() {
t := time.Now()
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
for _, block := range bc.blocks {
fmt.Printf("PrevHash: %x\n", block.PrevHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
fmt.Println("Time using: ", time.Since(t))
}运行结果:
实验2-3:添加工作量证明模块
补全ProofOfWork.go文件
-
ProofOfWork.Mine()函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func (pow *ProofOfWork) Mine() (int64, []byte) {
var hashInt big.Int
var hash []byte
nonce := int64(0)
fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
// 寻找符合条件的hash
for {
hash = calHash(pow.prepareData(nonce))
hashInt.SetBytes(hash)
cmp := hashInt.Cmp(pow.target)
if cmp < 0 {
break
}
nonce += 1
}
fmt.Printf("\r%x", hash)
fmt.Printf("\n\n")
return nonce, hash[:]
} -
ProofOfWork.Validate()函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
var isVaild = false
// 检查hash正确性
hash := calHash(pow.prepareData(pow.block.Nonce))
if bytes.Compare(hash, pow.block.Hash) != 0 {
return false
}
// 检查hash满足target
hashInt.SetBytes(hash)
cmp := hashInt.Cmp(pow.target)
if cmp < 0 {
isVaild = true
}
return isVaild
} -
修改Block类,使其哈希计算方法变为工作量证明算法在main函数中添加对区块哈希的PoW验证
1
2
3
4
5// SetHash 通过PoW计算区块hash
func (b *Block) SetHash() {
pow := NewProofOfWork(b)
pow.block.Nonce, pow.block.Hash = pow.Mine()
}
回答问题:工作量证明中的difficulty值的大小会怎样影响PoW计算时间?
-
difficulty越大,PoW计算时间越长,的指数形式复杂度
-
具体数据测试如下表
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | avg | |
| ------ | ------- | ------ | ------- | ------ | ------ | ------- | ------- | ------- | ------ | ------- | ---- |
| 565.84 | 1127.76 | 976.77 | 1377.62 | 911.12 | 951.12 | 1295.62 | 1769.02 | 1320.01 | 827.29 | 1112.22 | ms |
| 21.37 | 36.29 | 14.41 | 20.65 | 22.69 | 14.24 | 36.19 | 3.06 | 8.99 | 24.61 | 20.25 | s |
运行结果:
扩展实验2-4:阅读代码,添加数据库
将程序调通:
-
修改Block结构:增添Nonce(必须大写,否则无法序列化,巨坑!!!)
1
2
3
4
5
6
7type Block struct {
Time int64
Nonce int64
PrevHash []byte
Hash []byte
Data []byte
} -
增添序列化和反序列化函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// Serialize 序列化:block->[]byte
func (b *Block) Serialize() []byte {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
err := encoder.Encode(b)
if err != nil {
return nil
}
return result.Bytes()
}
// DeserializeBlock 反序列化:[]byte->block
func DeserializeBlock(d []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
err := decoder.Decode(&block)
if err != nil {
return nil
}
return &block
} -
修改main函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20func main() {
t := time.Now()
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
bci := bc.Iterator()
for {
block := bci.Next()
fmt.Printf("PrevHash: %x\n", block.PrevHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %t\n", pow.Validate())
fmt.Println()
if len(block.PrevHash) == 0 {
break
}
}
fmt.Println("Time using: ", time.Since(t))
}
运行结果:
回答问题:
-
为什么需要在block类中添加Serialize()和DeserializeBlock()两个函数?他们主要做了什么? 描述一下NewBlockchain()和NewBlock()的执行逻辑
-
区块链信息在代码中是以结构体的形式存在的,但是存储到数据库则只能用[]byte形式。所以在存储和读取的过程中,序列化和反序列化是必不可少的。
-
二者进行struct和[]byte两种形式的转换
-
NewBlockchain()执行逻辑:
-
打开数据库文件,进行错误处理
-
检查是否存在一个区块链:
-
如果存在:
-
对其创建Blockchain 实例,将 Blockchain中的tip设置为从数据库key l读取到的最后一个区块hash
-
-
如果不存在:
-
创建创世区块
-
存储至数据库
-
把key l对应的value设为创世区块的hash
-
将上述区块链创建 Blockchain 实例,设置tip为创世区块的hash
-
-
-
NewBlock()执行逻辑:
-
取出数据库key l,读取最后一个区块hash
-
根据末尾区块hash,计算并创建新区块
-
将新区块序列化后存入数据库
-
更新数据库key l的value为新区块hash
-
-
-
Blockchain类中的tip变量是做什么用的?
-
在迭代器中,作为存放当前读取区块hash的索引变量,或者说做一个标记记录,避免key l被改变or使用
-
-
迭代器Interator是如何工作使得我们能够从数据库中遍历出区块信息的?
-
从tip变量开始,通过取出最新区块,并更新pre区块,重新标记hash,可以倒序遍历所有区块。
-
扩展实验2-5:添加命令行接口
-
编写命令行参数文件arg.go:
-
InitArg()函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20func InitArgs() {
args := os.Args[1:]
flagadd := flag.NewFlagSet("add", flag.ExitOnError)
addData := flagadd.String("data", "nil", "添加数据区块")
if len(args) < 1 {
fmt.Println("expected subcommands")
os.Exit(1)
}
bc := NewBlockchain()
switch args[0] {
case "list":
bc.ListBlock()
case "add":
err := flagadd.Parse(args[1:])
if err != nil {
return
}
bc.AddBlock(*addData)
}
}
修改main函数:
-
func main() { t := time.Now() InitArgs() fmt.Println("Time using: ", time.Since(t)) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#### 运行结果:
### 


### 附录:工具函数
#### utils.go
##### Int64 to []byte(hex)
+ ```go
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()
}
计算sha256
-
func calHash(inputData []byte) []byte { sha256Example := sha256.New() sha256Example.Write(inputData) sha256Result := sha256Example.Sum(nil) return sha256Result }
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 lzhのBLOG!