使用Go语言构造区块链
实验概述
21 世纪最具先峰性的代表性技术之一,就是区块链。目前,它仍然处于,并将长期处于不断成长的时期,而且,在他的身上,还有很多潜在的力量,没有完全展露出来。从本质上来讲,区块链的核心,可以说是一个分布式数据库而已。不过,在区块链中,与传统的分布式数据库,最为独一无二的地方在于,区块链的数据库是公开的,而不是一个私人数据库。也就是说,每个使用它的人,都将在自己的机器上,拥有一个或部分,或完整的副本。而向数据库中添加新的记录,必须经过其他“矿工”的同意,才可以。除此以外,也是因为区块链的兴起,才使得加密货币和智能合约这一新兴技术,成为正在发生的事情。
本实验将在Go语言的环境下,实现一个简化版的区块链。
预备知识
工作量证明:在区块链系统中,其中的一个核心关键点就是,一个人想要将数据放入到区块链中,必须经过一系列困难的工作。区块链的奖励也由此而来,正是由于完成了这种困难的工作,才使得区块链的安全性和一致性得到了保证,而完成这个工作的人,也会获得相应奖励(这也就是通过挖矿获得token的过程)。
这个机制称为激励机制,没有人愿意义务劳动。在区块链中,是通过矿工(网络中的实际参与人)连续不停的工作,进而支撑起整个系统网络。矿工不断地向区块链中加入新块,然后获得相应的奖励。在这种机制的作用下,新生成的区块能够被安全地加入到区块链中,它维护了整个区块链数据库的稳定性。值得注意的是,完成了这个工作的人必须要证明这一点,即他必须要证明他的确完成了这些工作。
整个机制,就叫做工作量证明(Proof-of-Work)。事实上,要想快速的完成工作,是一件非常不容易的事情,因为这需要大量的计算能力:即便是一台高性能计算机,也无法在短时间内快速完成。另外,随着时间不断增长,这件工作会变得越来越困难,以保持平均每一块10分钟的生成速度。在比特币系统中,这个工作就是要设法找到一个块的哈希,同时这个哈希满足了一些必要条件。而这个哈希,也就充当了证明的角色。因此,快速寻求到有效的哈希值,就是矿工实际在做的事情。
BoltDB: Bolt是一个 Go编写的kv数据库,这一项目启发自 HowardChu的 LMDB。它旨在为那些不需要维护一个庞大的,像 Postgres 和 MySQL 这样的,完整的数据库服务器的项目,提供一个简单、快速和可靠的kv数据库。并且由于 Bolt 主要目的在于提供一些底层基础的数据库功能,因此简洁便成为其关键所在。
Bolt 使用键值存储,这意味着它没有像 SQL RDBMS (MySQL,PostgreSQL 等等)的表,也就是意味着他没有行和列。相反,数据在SQL中被存储为键值对的性质(key-value pair,就像 Golang 的 map)。键值对被存储在若干个bucket 中,这是为了将相似的键值对进行分组(类似 RDBMS 中的表格)。因此,为了获取一个值,你需要知道一个 bucket 和他的key值。
需要注意的一个事情是,Bolt 数据库没有数据类型:键和值都是字节数组(bytearray)。鉴于需要在里面存储 Go 的结构(准确来说,也就是存储Block(块)),我们需要对它们进行序列化, 也就说,实现一个从 Gostruct转换到一个 bytearray的机制,同时还可以从一个 bytearray再转换回 Go struct。虽然我们将会使用 https://golang.org/pkg/encoding/gob/ 来完成这一目标,但实际上也可以选择使用 JSON, XML, Protocol Buffers 等等。之所以选择使用encoding/gob, 是因为它很简单,而且是 Go 标准库的一部分。
实验2-1:构建区块
将Block类中元素补充完整;
将Block.CalHash()函数补全,实现对Block的Hash计算。其中 Hash= SHA256(PrevHash+Time+ Data)。
实验流程
- 在block.go文件中:
- 先定义区块结构Block,Block结构中包含时间戳Time、交易数据Data、上一块数据的哈希PrevHash、当前数据的哈希Hash四部分。
- 然后定义创建新区块函数NewBlock,输入上一个区块的哈希值,和本个区块要添加的内容,生成一个新的区块。
- 最后设置结构体对象哈希函数SetHash。
- 在main.go文件中:
- 新建一个时间戳,创建一个区块,并输出该区块的四部分并输出当前时间。
源代码
block.go
package main
import (
"crypto/sha256"
"time"
)
type Block struct {
Time int64
Data []byte
PrevHash []byte
Hash []byte
}
func NewBlock(data string, prevHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevHash, []byte{}}
block.SetHash()
return block
}
func (b *Block) SetHash() {
//为Block生成hash,使用sha256.Sum256(data []byte)函数
sha := sha256.New()
sha.Write(b.Data)
b.Hash = sha.Sum(nil)
}
main.go
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
block := NewBlock("Genesis Block", []byte{})
fmt.Printf("Prev. hash: %x\n", block.PrevHash)
fmt.Printf("Time: %s\n", time.Unix(block.Time, 0).Format("2006-01-02 15:04:05"))
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println("Time using: ", time.Since(t))
}
实验结果截图
实验2-2:实现一条链
有了区块,下面将实现区块链。本质上,区块链就是一个有着特定结构的数据库,是一个有序、每一个块都连接到前一个块的链表。也就是说,区块按照插入的顺序进行存储,每个块都与前一个块相连。这样的结构,能够让我们快速地获取链上的最新块,并且高效地通过哈希来检索一个块。
从ex2-2文件夹下将blockchain.go文件复制至$GOPATH\src\blockchain_demo文件夹下,补全以下代码:
- 添加区块函数Blockchain.NewBlock()
- 创世区块生成函数GenesisBlock()
实验流程
- block.go文件与实验2-1完全相同;
- 在blockchain.go文件中:
- 定义区块链BlockChain结构,包含一个数组类型的变量,存储block区块指针。
- 定义创建创世区块NewGenesisBlock函数,在创建一个新的区块链时调用。
- 定义创建区块链NewBlockchain函数,返回区块链对象。
- 定义新增区块函数,新增一个区块并将其添加到区块链末端。
- 在main.go文件中:
- 创建一条区块链,并添加两个数据块,然后输出该区块链中的所有区块的属性值。
源代码
blockchain.go
package main
type Blockchain struct {
blocks []*Block
}
func (bc *Blockchain) AddBlock(data string) {
//可能用到的函数:
// len(array):获取数组长度
// append(array,b):将元素b添加至数组array末尾
bc_length := len(bc.blocks)
new_block := NewBlock(data, bc.blocks[bc_length-1].Hash)
bc.blocks = append(bc.blocks, new_block)
}
func NewGenesisBlock() *Block {
//创世区块前置哈希为空,Data为"Genesis Block"
New_block := NewBlock("Genesis Block", []byte{})
return New_block
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
main.go
package main
import (
"fmt"
)
func main() {
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()
}
}
实验结果截图
实验2-3:添加工作量证明模块
从ex2-3文件夹下将里面文件复制至$GOPATH\src\blockchain_demo文件夹下,补全以下代码:
- ProofOfWork.Mine()函数
- ProofOfWork.Validate()函数
修改Block类,使其哈希计算方法变为工作量证明算法在main函数中添加对区块哈希的PoW验证。
回答问题:工作量证明中的difficulty值的大小会怎样影响PoW计算时间?
实验流程
-
对Block类进行修改,将nonce添加至Block类的结构中。
-
设置一个全局的难度值targetBits,用来限制平均挖矿时间,要求矿工不断运行工作量证明算法,直到找到一个输入,使该算法输出的哈希值的前targetBits比特必须全为0。
-
构建ProofOfWork类。
-
准备用来进行哈希运算的数据:将区块的一些数据与nonce进行合并形成一个大的byte数组。
-
构造Hashcase算法:
-
选择部分公开数据;
-
在该公开数据后连接一个nonce,nonce的值为一个计数器从0开始不断计数;
-
将data+nonce用hash函数进行计算;
-
检查hash值是否在给定的范围内;如果在,则证明结束;否则重复上一步骤和该步骤。
-
-
实现PoW算法的核心:利用Mine()函数进行PoW算法,Validate()函数用以进行对区块中的哈希值进行验证。
-
修改SetHash()函数使其调用ProofOfWork算法获得哈希值。
源代码
main.go
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
bc := NewBlockchain()
t := time.Now()
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)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
fmt.Println()
}
fmt.Println("Time using: ", time.Since(t))
}
block.go
package main
import (
"crypto/sha256"
"time"
)
type Block struct {
Time int64
Data []byte
PrevHash []byte
Hash []byte
nonce int
}
func NewBlock(data string, prevHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevHash, []byte{}, 0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.nonce = nonce
return block
}
func (b *Block) SetHash() {
//为Block生成hash,使用sha256.Sum256(data []byte)函数
sha := sha256.New()
sha.Write(b.Data)
b.Hash = sha.Sum(nil)
}
blockchain.go
package main
type Blockchain struct {
blocks []*Block
}
func (bc *Blockchain) AddBlock(data string) {
//可能用到的函数:
// len(array):获取数组长度
// append(array,b):将元素b添加至数组array末尾
bc_length := len(bc.blocks)
new_block := NewBlock(data, bc.blocks[bc_length-1].Hash)
bc.blocks = append(bc.blocks, new_block)
}
func NewGenesisBlock() *Block {
//创世区块前置哈希为空,Data为"Genesis Block"
New_block := NewBlock("Genesis Block", []byte{})
return New_block
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
proofofWork.go
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"math/big"
)
const targetBits = 20
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.PrevHash,
pow.block.Data,
IntToHex(pow.block.Time),
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)
/*
实现 Hashcash 算法:对 nonce 从 0 开始进行遍历,计算每一次哈希是否满足条件
可能会用到的包及函数:big.Int.Cmp(),big.Int.SetBytes()
*/
for {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Printf("\r%x", hash)
fmt.Print("\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 main
import (
"bytes"
"encoding/binary"
"log"
)
// IntToHex converts an int64 to a byte array
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()
}
// ReverseBytes reverses a byte array
func ReverseBytes(data []byte) {
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
data[i], data[j] = data[j], data[i]
}
}
实验结果截图
实验2-4:阅读代码:添加数据库
从ex2-4文件夹下将里面文件复制至工作目录,并将github.com文件夹复制至$GOPATH/src/目录下。将程序调通后,阅读代码回答以下问题:
为什么需要在block类中添加Serialize()和DeserializeBlock()两个函数?他们主要做了什么?
描述一下NewBlockchain()和NewBlock()的执行逻辑。
Blockchain类中的tip变量是做什么用的?
迭代器Interator是如何工作使得我们能够从数据库中遍历出区块信息的?
实验流程
准备工作:
- 定义数据库名称和数据库表名称。
- 修改Blockchain struct,增加BlockChainIterator struct用于迭代读取数据。
- 序列化和反序列化,将区块链存入数据库,以及从数据库中读取数据。
对区块链的操作:
- 运行程序时首先打开数据库,找到数据库显示区块链的所有区块数据,如果库不存在则创建数据库和数据库表。
- 添加新的区块时,先找到数据库表项第一条记录,即上一个区块的哈希值用于生成新的区块。
- 利用迭代器查询区块链数据,创建循环迭代器,通过循环迭代器获取下一个区块,获取数据时将哈希值作为“键获取序列化之后的字节集。
源代码
main.go
package main
import (
"fmt"
"strconv"
"time"
)
func 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("Prev. hash: %x\n", block.PrevHash)
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.PrevHash) == 0 {
break
}
}
fmt.Println("Time using: ", time.Since(t))
}
block.go
package main
import (
"bytes"
"crypto/sha256"
"encoding/gob"
"log"
"time"
)
type Block struct {
Time int64
Data []byte
PrevHash []byte
Hash []byte
nonce int
}
func NewBlock(data string, prevHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevHash, []byte{}, 0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.nonce = nonce
return block
}
func (b *Block) SetHash() {
//为Block生成hash,使用sha256.Sum256(data []byte)函数
sha := sha256.New()
sha.Write(b.Data)
b.Hash = sha.Sum(nil)
}
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()
}
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
}
blockchain.go
package main
import (
"log"
"github.com/boltdb/bolt"
)
const dbFile = "blockchain_demo.db"
const blocksBucket = "blocks"
type Blockchain struct {
tip []byte
db *bolt.DB
}
func (bc *Blockchain) AddBlock(data string) {
var lastHash []byte
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)
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
})
if err != nil {
log.Panic(err)
}
}
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
func NewBlockchain() *Blockchain {
var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
log.Panic(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
if b == nil {
genesis := NewGenesisBlock()
b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
} else {
tip = b.Get([]byte("l"))
}
return nil
})
bc := Blockchain{tip, db}
return &bc
}
blockchain_iterator
package main
import (
"log"
"github.com/boltdb/bolt"
)
type BlockchainIterator struct {
currentHash []byte
db *bolt.DB
}
func (bc *Blockchain) Iterator() *BlockchainIterator {
bci := &BlockchainIterator{bc.tip, bc.db}
return bci
}
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.PrevHash
return block
}
proofofWork
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"math/big"
)
const targetBits = 20
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.PrevHash,
pow.block.Data,
IntToHex(pow.block.Time),
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)
/*
实现 Hashcash 算法:对 nonce 从 0 开始进行遍历,计算每一次哈希是否满足条件
可能会用到的包及函数:big.Int.Cmp(),big.Int.SetBytes()
*/
for {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Printf("\r%x", hash)
fmt.Print("\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 main
import (
"bytes"
"encoding/binary"
"log"
)
// IntToHex converts an int64 to a byte array
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()
}
// ReverseBytes reverses a byte array
func ReverseBytes(data []byte) {
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
data[i], data[j] = data[j], data[i]
}
}
实验结果截图
问题回答
1.为什么需要在block类中添加Serialize()和DeserializeBlock()两个函数?他们主要做了什么?
答:区块链信息在代码中是以结构体的形式存在的,但是存储到数据库则只能用[]byte形式。所以在存储和读取的过程中,序列化和反序列化是必不可少的。序列化将结构体类型转换为字节数组类型,将区块链存入数据库;反序列化将字节数组类型转为结构体类型,从数据库中读取数据。
2.描述一下NewBlockchain()和NewBlock()的执行逻辑。
答:NewBlockchain():
-
打开一个BoltDB数据库文件,如果出现错误则记录并终止程序。
-
在数据库事务内执行以下操作:
- 检查是否已经存在一个名为
blocksBucket
的桶(bucket)。 - 如果不存在,创建一个创世区块(Genesis Block)并将其序列化后存储在
blocksBucket
桶中,同时将创世区块的哈希值存储在键名为"l"
的位置。 - 如果已经存在
blocksBucket
桶,从键名为"l"
的位置获取最新的区块哈希值,表示当前区块链的尖端。
- 检查是否已经存在一个名为
-
创建一个新的区块链对象
bc
,其中包括尖端区块的哈希值和打开的数据库连接。 -
返回新创建的区块链对象的指针。
NewBlock():
- 接收一个数据字符串和前一区块的哈希值作为参数。
- 创建一个新的区块实例,其中包含当前时间戳、数据、前一区块的哈希、自身哈希以及初始 nonce 值。
- 通过
NewProofOfWork
函数创建了一个工作证明的实例pow
,该实例与当前区块相关联。 - 通过
pow.Run()
运行工作证明算法,得到正确的 nonce 值和对应的哈希。这个正确的哈希值和 nonce 被设置到当前区块的属性中。 - 最后函数返回创建好的区块实例。
3.Blockchain类中的tip变量是做什么用的?
答:在Blockchain类中,tip变量通常用来表示当前区块链的最新区块。tip指向链中的最后一个区块,也就是最新添加到区块链中的区块。tip变量的存在可以方便地获取最新的区块,进行区块链的操作和更新。
4.迭代器Interator是如何工作使得我们能够从数据库中遍历出区块信息的?
-
从数据库中获取数据:通过数据库连接
db
,可以执行查询或操作来获取区块链数据记录。 -
初始化迭代器:创建一个迭代器对象,并将其与获取的数据相关联。使用数据库查询结果初始化迭代器,以便逐个访问区块链数据。
-
遍历数据:使用迭代器,逐个访问数据库中的区块数据。通过迭代器提供的方法,获取每个区块的信息。
-
移动指针:迭代器跟踪当前遍历的位置,以便在需要时移动到下一个数据记录。
-
完成遍历:当遍历完成后,迭代器提供一种方式来标志遍历的结束状态,以便程序知道已经完成了数据的处理。
2-5:添加命令行接口
实验流程
- 校验用户输入的参数合法性;
- 根据用户选择调用相应的函数。
- 目前可以进行的操作: 查看区块链当前区块和添加新的区块。
源代码
CLI.go
package main
import (
"flag"
"fmt"
"log"
"os"
"strconv"
)
// 命令行接口
type CLI struct {
blockchain *Blockchain
}
// 打印用法
func (cli *CLI) printUsage() {
fmt.Println("Usage:")
fmt.Println("newblock -data ' ' 向区块链增加块")
fmt.Println("listblocks 显示区块链")
}
// 校验参数合法性
func (cli *CLI) validateArgs() {
if len(os.Args) < 2 {
cli.printUsage() //显示用法
os.Exit(1)
}
}
// 增加区块
func (cli *CLI) addBlock(data string) {
cli.blockchain.AddBlock(data) //增加一个区块
fmt.Println("Success!")
}
// 展示当前区块链数据
func (cli *CLI) showBlockChain() {
bci := cli.blockchain.Iterator() //创建循环迭代器
for {
block := bci.Next() //取得下一个区块
fmt.Printf("Prev. hash: %x\n", block.PrevHash)
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.PrevHash) == 0 { //遇到创世区块终止
break
}
}
}
// 启动命令行
func (cli *CLI) Run() {
cli.validateArgs() //校验
//处理命令行参数
addblockcmd := flag.NewFlagSet("newblock", flag.ExitOnError)
showchaincmd := flag.NewFlagSet("listblocks", flag.ExitOnError)
addBlockData := addblockcmd.String("data", "", "Block data")
switch os.Args[1] {
case "newblock":
err := addblockcmd.Parse(os.Args[2:]) //解析参数
if err != nil {
log.Panic("newblock", err) //处理错误
}
case "listblocks":
err := showchaincmd.Parse(os.Args[2:]) //解析参数
if err != nil {
log.Panic("listblocks", err) //处理错误
}
default:
cli.printUsage()
os.Exit(1)
}
if addblockcmd.Parsed() {
if *addBlockData == "" {
addblockcmd.Usage()
os.Exit(1)
} else {
cli.addBlock(*addBlockData) //增加区块
}
}
if showchaincmd.Parsed() {
cli.showBlockChain() //显示区块链
}
}
main.go
package main
func main() {
block := NewBlockchain()
defer block.db.Close()
cli := CLI{block}
cli.Run()
}