Go语言实现区块链(三)

一、保存区块链

为了能够永久保存区块链数据,这里引入bolt数据库。

(1)改写BlockChain结构体

type BlockChain struct {
	//Blocks []*Block // 保存所有区块
	Db   *bolt.DB
	Tail []byte // 最后区块的hash
}

(2)改写NewBlockChain方法,把创世块数据以及最后区块的哈希写入数据库中。

const dbName = "blockchain.db"
const bucketName = "bucket"
const last = "lastBlockHash"

// 创建方法
func NewBlockChain() *BlockChain {
	/*// 创建创世块
	genericBlock := NewBlock([]byte(genesisInfo), []byte{})
	// 创建BlockChain
	bc := BlockChain{[]*Block{genericBlock}}
	return &bc*/

	var db *bolt.DB
	var lastHash []byte

	// 1. 打开数据库
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		panic("bolt.Open err!")
	}
	db.Update(func(tx *bolt.Tx) error {
		// 2. 打开抽屉Bucket
		bucket := tx.Bucket([]byte(bucketName))
		// 3. 如果Bucket是否为nil,则创建一个Bucket
		if bucket == nil {
			bucket, err = tx.CreateBucket([]byte(bucketName))
			if err != nil {
				panic("tx.CreateBucket err!")
			}
			// 创建创世块
			genericBlock := NewBlock([]byte(genesisInfo), []byte{})
			// 把创世块保存在bucket中
			bucket.Put(genericBlock.Hash, genericBlock.Serialize())
			// 把创世块的hash保存在last中
			bucket.Put([]byte(last), genericBlock.Hash)
			// 记录lastHash
			lastHash = genericBlock.Hash
		} else {
			// 4. 如果Bucket不为nil,记录lastHash
			lastHash = bucket.Get([]byte(last))
		}
		return nil
	})
	return &BlockChain{db, lastHash}
}

(3)定义序列化和反序列化函数

// 序列化block
func (block *Block) Serialize() []byte {
	// 创建Encoder
	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)
	// 加密操作
	err := encoder.Encode(block)
	if err != nil {
		panic("encoder.Encode err!")
	}
	return buffer.Bytes()
}

// 反序列化
func Deserialize(data []byte) *Block {
	var block *Block
	var buffer bytes.Buffer
	_, err := buffer.Write(data)
	if err != nil {
		panic("buffer.Write err!")
	}
	// 创建Decoder
	decoder := gob.NewDecoder(&buffer)
	// 加密操作
	err = decoder.Decode(&block)
	if err != nil {
		panic("decoder.Decode err!")
	}
	return block
}

(4)修改AddBlock方法,把新区块序列化后保存到数据库中,并且更新最后区块的哈希。

// 添加区块
func (bc *BlockChain) AddBlock(data string) {
	/*// 获取最后区块
	lastBlock := bc.Blocks[len(bc.Blocks)-1]
	// 创建一个新区块
	block := NewBlock([]byte(data), lastBlock.Hash)
	// 添加新区块
	bc.Blocks = append(bc.Blocks, block)*/

	// 获取最后区块hash
	lastHash := bc.Tail
	// 创建区块
	block := NewBlock([]byte(data), lastHash)
	// 更新操作
	bc.Db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			panic("bucket should not be nil!")
		}
		// 向bolt数据库添加新区块
		bucket.Put(block.Hash, block.Serialize())
		// 更新数据库的last
		bucket.Put([]byte(last), block.Hash)
		// 更新bc.Tail
		bc.Tail = block.Hash
		return nil
	})
}

(5)修改main函数,把打印语句注释。

func main()  {
	bc := NewBlockChain()
	bc.AddBlock("1111")
	bc.AddBlock("2222")

	/*for i, block := range bc.Blocks {
		fmt.Println("======== block height : ", i, "=======")
		fmt.Printf("Version : %d\n", block.Version)
		fmt.Printf("PreHash : %x\n", block.PrevHash)
		fmt.Printf("MerKleRoot : %x\n", block.MerKleRoot)
		fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
		fmt.Printf("Difficulty : %d\n", block.Difficulty)
		fmt.Printf("Nonce : %d\n", block.Nonce)
		fmt.Printf("Hash : %x\n", block.Hash)
		fmt.Printf("Data : %s\n", block.Data)
	}*/
}

(6)编译运行,并查看数据库文件大小。

> go build -o block.exe .
> block
> dir/a blockchain.db

 

二、遍历区块链

(1)定义迭代器结构体

type BlockChainIterator struct {
	Db    *bolt.DB
	Index []byte // 迭代器指针的索引值
}

(2)提供创建方法

// 创建迭代器
func (bc *BlockChain) NewIterator() *BlockChainIterator {
	itr := BlockChainIterator{
		bc.Db,
		bc.Tail,
	}
	return &itr
}

(3)定义迭代函数

// 遍历上一个区块
func (itr *BlockChainIterator) Prev() *Block {
	var block *Block
	itr.Db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			panic("bucket shoud not be nil!")
		}
		// 获取最后区块
		blockBytes := bucket.Get(itr.Index)
		// 反序列化
		block = Deserialize(blockBytes)
		// 移动指针
		itr.Index = block.PrevHash
		return nil
	})
	return block
}

(4)改写main函数

func main() {
	bc := NewBlockChain()
	bc.AddBlock("1111")
	bc.AddBlock("2222")

	// 定义迭代器
	itr := bc.NewIterator()
	for {
		// 调用迭代器函数,返回当前block,并且向左移动
		block := itr.Prev()
		fmt.Println("========================")
		fmt.Printf("Version : %d\n", block.Version)
		fmt.Printf("PreHash : %x\n", block.PrevHash)
		fmt.Printf("MerKleRoot : %x\n", block.MerKleRoot)
		fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
		fmt.Printf("Difficulty : %d\n", block.Difficulty)
		fmt.Printf("Nonce : %d\n", block.Nonce)
		fmt.Printf("Hash : %x\n", block.Hash)
		fmt.Printf("Data : %s\n", block.Data)

		// 退出条件
		if len(block.PrevHash)  == 0 {
			break
		}
	}
}

 

三、命令行

(1)定义命令行结构体

type Cli struct {
	bc *BlockChain
}

(2)定义Run函数,用于与用户进行交互。

const Usage  = `
   block addBlock --data DATA "添加新区块"
   block printChain "打印区块链" 
   block getBalance --address ADDRESS "查询账户余额"
   block send FROM TO AMOUNT MINER DATA "由FROM给TO转AMOUNT个比特币,并指定MINER为矿工"
   block newWallet "创建一个钱包"
   block listAddress "列出所有钱包地址"
`

func (cli *Cli) Run() {
	args := os.Args
	if len(args) < 2 {
		fmt.Println(Usage)
		return
	}
	// 获取命令名称
	command := args[1]
	switch command {
		case "addBlock":
			if len(os.Args) == 4 && os.Args[2] == "--data" {
				data := os.Args[3]
				if data == "" {
					fmt.Println("data should not be empty!")
					return
				}
				cli.addBlock(data)
			}
		case "printChain":
			cli.PrintChain()
		default:
			fmt.Println(Usage)
	}
}

(3)提供AddBlock函数

// 添加区块
func (cli *Cli) addBlock(data string) {
	cli.bc.AddBlock(data)
}

(4)提供PrintChain函数

// 打印区块
func (cli *Cli) printChain() {
	bc := cli.bc
	// 定义迭代器
	itr := bc.NewIterator()
	for {
		// 调用迭代器函数,返回当前block,并且向左移动
		block := itr.Prev()
		fmt.Println("========================")
		fmt.Printf("Version : %d\n", block.Version)
		fmt.Printf("PreHash : %x\n", block.PrevHash)
		fmt.Printf("MerKleRoot : %x\n", block.MerKleRoot)
		fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
		fmt.Printf("Difficulty : %d\n", block.Difficulty)
		fmt.Printf("Nonce : %d\n", block.Nonce)
		fmt.Printf("Hash : %x\n", block.Hash)
		fmt.Printf("Data : %s\n", block.Data)

		// 退出条件
		if len(block.PrevHash)  == 0 {
			break
		}
	}
}

(5)修改main函数

func main() {
	bc := NewBlockChain()
	cli := Cli{bc}
	cli.Run()
}

(6)创建运行脚本

@del blockchain.db
@go build -o block.exe .
@block

 

猜你喜欢

转载自blog.csdn.net/zhongliwen1981/article/details/89636308