首页 > 其他分享 >使用Go语言构造区块链

使用Go语言构造区块链

时间:2024-01-20 20:00:16浏览次数:39  
标签:err 构造 block Go byte 区块 data Block

使用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))
}

实验结果截图

image

实验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()
	}
}

实验结果截图

image

实验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]
	}
}

实验结果截图

image

实验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]
	}
}

实验结果截图

image

问题回答

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()
}

实验结果截图

image

标签:err,构造,block,Go,byte,区块,data,Block
From: https://www.cnblogs.com/Silverplan/p/17977051

相关文章

  • UI测试脚本录制器已上线,RunnerGo :UI自动化测试平台
    想快速配置可视化UI自动化测试脚本?RunnerGo近期上线脚本录制器,根据你的测试操作直接生成UI自动化测试脚本,下面是使用方法Step1:下载录制器点击RunnerGo上方插件按钮下载录制器Step2:录制器使用将插件文件拖入浏览器扩展程序点击打开录制器,在浏览器中进行操作时录制器会将操作录制为......
  • UI测试脚本录制器已上线,RunnerGo :UI自动化测试平台
    想快速配置可视化UI自动化测试脚本?RunnerGo近期上线脚本录制器,根据你的测试操作直接生成UI自动化测试脚本,下面是使用方法Step1:下载录制器点击RunnerGo上方插件按钮下载录制器 Step2:录制器使用将插件文件拖入浏览器扩展程序 点击打开录制器,在浏览器中进行操作时录制器......
  • Google用AI替代广告销售工作只是开始……
    关注卢松松,会经常给你分享一些我的经验和观点。前几天Google不是裁员3万人吗,其中有一个信息值得关注:就是Google的广告部门的部分员工,也被裁员了。当然这不新鲜的,主要原因是Google的广告业务正在转向AI驱动了,AI是裁员广告部门的最重要原因。比如GoogleAds产品这个大家都知道,是一款......
  • Go io包的一些api的用法
    io.Pipe()返回reader和writer,这种机制使得可以进行并发的数据交换;写入到这个writer中的数据会被同步到reader中;io.TeeReader()可以给原始的TeeReader进行分流,另外一个writer可以同时获取到写入的数据;我是不是可以理解成:TeeReader可以包装一个原始的reader,将......
  • 2024-01-20:用go语言,小扣在探索丛林的过程中,无意间发现了传说中“落寞的黄金之都“, 而
    2024-01-20:用go语言,小扣在探索丛林的过程中,无意间发现了传说中"落寞的黄金之都",而在这片建筑废墟的地带中,小扣使用探测仪监测到了存在某种带有「祝福」效果的力场,经过不断的勘测记录,小扣将所有力场的分布都记录了下来,forceField[i]=[x,y,side],表示第i片力场将覆盖以坐标......
  • go-计算器
    为了实践一下go语言,弄一个+-*/和有小括号的计算器,其中小括号的嵌套可以任意多个。代码如下:packagemainimport( "fmt" "regexp" "sort" "strconv" "strings")funcmain(){ str:="6-4*(1+(2*((3+4)*2)+2)-(9-5*4)*2)-(2*3-(2+5+3-2*2))+6&......
  • Go中的整数到字符串的转换
    在Go语言中,我们经常需要将整数转换为字符串。然而,直接使用string()函数进行转换可能会导致意想不到的结果。这是因为string()函数会将整数解释为Unicode字符的代码点,而不是将其转换为对应的数字字符串。错误的转换方式例如,如果我们尝试将整数65转换为字符串:s := stri......
  • GYM102596L Yosupo's Algorithm【分治,支配对】
    给定平面上\(2n\)个点,每个点有坐标\((x_i,y_i)\),权值\(w_i\)及颜色\(c_i\)。所有点满足:若\(c_i=0\),则\(x_i<0\);若\(c_i=1\),则\(x_i>0\)。\(q\)次查询,每次给定\(L_i,R_i\),你需要选择两个点\(i,j\)满足如下条件:\(c_i=0,c_j=1\)。\(x_i<L,x_j>R\)或\(x_......
  • FZOJ省选模拟构造合集
    FZOJ省选模拟构造合集FZOJ5404[2023NOIP前模拟]机械之心/CF600FEdgecoloringofbipartitegraph小Y拥有着机械之心。小Y拥有\(k\)种不同的心脏,排列在一个\(n×m\)的网格中。我们用\(c_{i,j}\)​代表第\(i\)行第\(j\)列的心脏的类型:\(1.\)如果\(c_{i,j}......
  • 【踩了一个坑】为什么 golang struct 中的 slice 无法原子赋值
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯有这样一个结构体:typeMyStstruct{Field[]byte}我在数组排序中想要交换值:funcSwap(arr[]MySt,i,jint){arr[i],arr[j]=arr[j],arr[i]}我猜......