go语言学习(四)区块链文件存储与命令行接口

//block.go
package main

import (
	"bytes"
	"encoding/gob"
	"time"
	"log"
)

//定义区块
type Block struct{
	Timestamp int64      //时间线,1970年1月1日00.00.00
	Data []byte    //交易数据
	PrevBlockHash []byte  //上一块数据的哈希
	Hash []byte   //当前块数据的哈希
	Nonce int   //工作量证明
}

/*//设定结构体对象哈希
func (block *Block)SetHash(){
	//处理当前的时间,转化为10进制的字符串,再转化为字节集合
	timestamp:=[]byte(strconv.FormatInt(block.Timestamp,10))
	//叠加要哈希的数据
	headers:=bytes.Join([][]byte{block.PrevBlockHash,block.Data,timestamp},[]byte{})
	//计算出哈希地址
	hash:=sha256.Sum256(headers)
	block.Hash=hash[:]//设置哈希
}*/
//创建一个区块
func NewBlock(data string, prevBlockHash []byte) *Block{
	//block是一个指针,取得一个对象初始化之后的地址
	block:=&Block{time.Now().Unix(),[]byte(data),prevBlockHash,[]byte{},0}
	pow:=NewProofOfWork(block)//挖矿附加这个区块
	nonce,hash:=pow.Run()//开始挖矿
	block.Hash=hash[:]
	block.Nonce=nonce
	//block.SetHash()//设置当前哈希
	return block
}
//创建创世区块,
func NewGenesisBlock() *Block{
	return NewBlock("hello, welcome to my second BlockChain",[]byte{})

}
//对象转化为二进制字节集,可以写入文件
func (block *Block)Serialize() []byte{
	var result bytes.Buffer  //开辟内存,存放字节集合
	encoder:=gob.NewEncoder(&result)//编码对象创建
	err:=encoder.Encode(block)//编码操作
	if err!=nil{
		log.Panic(err)//处理错误
	}
	return result.Bytes()//返回字节
}
//读取文件,读到二进制字节集,二进制字节集和转化为对象
func DeserializeBlock(data []byte) *Block{
	var block Block//对象存储用于字节转化的对象
	decoder:=gob.NewDecoder(bytes.NewReader(data))//解码
	err:=decoder.Decode(&block)//尝试解码
	if err!=nil{
		log.Panic(err)//处理错误
	}
	return &block
}
//blockchain.go
package main

import (
	"fmt"
	"github.com/boltdb/bolt"
	"log"
)

const dbFile="blockchain.db"  //数据库文件名当前目录下
const blockBucket="blocks"  //名称

type BlockChain struct{
	tip []byte //二进制数据
	db *bolt.DB //数据库
}
type BlockChainIterator struct{
	currentHash []byte  //当前的哈希
	db *bolt.DB //数据库
}

//增加一个区块
func (block *BlockChain)AddBlock(data string ){
	var lastHash []byte  //上一块哈希
	err:=block.db.View(func(tx *bolt.Tx) error{
		block:=tx.Bucket([]byte(blockBucket)) //取得数据
		lastHash=block.Get([]byte("1"))   //取得第一块
		return nil
	})
	if err!=nil{
		log.Panic(err)
	}
	newBlock:=NewBlock(data,lastHash)//创建一个新的区块
	err=block.db.Update(func (tx *bolt.Tx)error{
		bucket:=tx.Bucket([]byte(blockBucket))//取出
		err:=bucket.Put(newBlock.Hash,newBlock.Serialize())//压入数据
		if err!=nil{
			log.Panic(err)  //处理压入错误
		}
		err =bucket.Put([]byte("1"),newBlock.Hash)//压入数据
		if err!=nil{
			log.Panic(err)  //处理压入错误
		}
		block.tip=newBlock.Hash  //处理
		return nil
	})
}
//迭代器
func (block *BlockChain)Iterator()*BlockChainIterator{
	bcit:=&BlockChainIterator{block.tip,block.db}
	return bcit //根据区块链创建区块链迭代器
}
//取得下一个区块
func (it *BlockChainIterator)next()*Block{
	var block *Block
	err:=it.db.View(func (tx *bolt.Tx)error{
		bucket:=tx.Bucket([]byte(blockBucket))
		encodeBlock:=bucket.Get(it.currentHash)//抓取二进制数据
		block=DeserializeBlock(encodeBlock)//解码
		return nil
	})
	if err!=nil{
		log.Panic(err)
	}
	it.currentHash=block.PrevBlockHash//哈希赋值
	return block
}
//新建一个区块链
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{
		bucket:=tx.Bucket([]byte(blockBucket)) //按照名称打开数据库的表格
		if bucket==nil{
			fmt.Println("当前数据库没有区块链,没有创建一个新的")
			genesis:=NewGenesisBlock()//创建创世区块
			bucket,err:=tx.CreateBucket([]byte(blockBucket))//创建一个数据库表格
			if err!=nil{
				log.Panic(err) //处理创建错误
			}
			err=bucket.Put(genesis.Hash,genesis.Serialize())//存入数据
			if err!=nil{
				log.Panic(err) //处理存入错误
			}
			err=bucket.Put([]byte("1"),genesis.Hash)//存入数据
			if err!=nil{
				log.Panic(err) //处理存入错误
			}
			tip=genesis.Hash//取得哈希
		}else{
			tip=bucket.Get([]byte("1"))
		}

		return nil
	})
	if err!=nil{
		log.Panic(err)//处理数据库更新错误
	}
	bc:=BlockChain{tip,db}//创建一个区块链
	return &bc
}
//cli.go
package main

import (
	"flag"
	"fmt"
	"github.com/labstack/gommon/log"
	"os"
	"strconv"
)

//命令行接口
type CLI struct{
	blockchain *BlockChain
}
//用法
func (cli *CLI)printUsage(){
	fmt.Println("用法如下")
	fmt.Println("addblock 向区块链增加块")
	fmt.Println("showchain 显示区块链")
}
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("区块增加成功")
}
func (cli *CLI)showBlockChain(){
	bci:=cli.blockchain.Iterator()//创建循环迭代器
	for{
		block:=bci.next()//取得下一个区块
		fmt.Printf("上一块哈希%x\n",block.PrevBlockHash)
		fmt.Printf("数据:%s\n",block.Data)
		fmt.Printf("当前哈希%x\n",block.Hash)
		pow:=NewProofOfWork(block)
		fmt.Printf("pow %s",strconv.FormatBool(pow.Validate()))
		fmt.Println("\n")

		if len(block.PrevBlockHash)==0{//遇到创世区块终止
			break
		}
	}
}
func (cli *CLI)Run(){
	cli.validateArgs()//校验

	//处理命令行参数
	addblockcmd:=flag.NewFlagSet("addblock",flag.ExitOnError)
	showchaincmd:=flag.NewFlagSet("showchain",flag.ExitOnError)
	addBlockData:=addblockcmd.String("data","","Block Data")
	switch os.Args[1]{
	case "addblock":
		err:=addblockcmd.Parse(os.Args[2:])//解析参数
		if err!=nil{
			log.Panic(err)//处理错误
		}
	case "showchain":
		err:=showchaincmd.Parse(os.Args[2:])//解析参数
		if err!=nil{
			log.Panic(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()//显示区块链
	}
}
//proofofwork.go
package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"math"
	"math/big"
)

var(
	maxNonce=math.MaxInt64 //最大的64位整数
)
const targetBits=18//对比的位数

type ProofOfWork struct{
	block *Block  //区块
	target * big.Int  //存储计算哈希对比的特定整数
}

//创建一个工作量证明的挖矿对象
func NewProofOfWork(block *Block)*ProofOfWork{
	target:=big.NewInt(1)     //初始化目标整数
	target.Lsh(target,uint(256-targetBits))  //数据转换
	pow:=&ProofOfWork{block,target}  //创建对象
	return pow
}
//准备数据进行挖矿计算
func (pow * ProofOfWork) prepareData(nonce int)[]byte{
	data:=bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,//上一块哈希
			pow.block.Data,//当前数据
			IntToHex(pow.block.Timestamp),//时间十六进制
			IntToHex(int64(targetBits)),//位数十六进制
			IntToHex(int64(nonce)),//保存工作量的nonce
		},[]byte{},
		)
	return data
}
//挖矿执行
func (pow * ProofOfWork) Run()(int,[]byte){
	var hashInt big.Int
	var hash [32]byte
	nonce:=0
	fmt.Printf("当前挖矿计算的区块数据%s",pow.block.Data)
	for nonce<maxNonce{
		data:=pow.prepareData(nonce)//准备好的数据
		hash=sha256.Sum256(data)//计算出哈希
		fmt.Printf("\r%x",hash)//打印显示哈希
		hashInt.SetBytes(hash[:])//获取要对比的数据
		if hashInt.Cmp(pow.target)==-1{//挖矿的校验
			break
		}else{
			nonce++
		}
	}
	fmt.Println("\n\n")
	return nonce,hash[:]//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"
)

func IntToHex(num int64)[]byte{
	buff:=new(bytes.Buffer)  //开辟内存,存储字节集
	err:=binary.Write(buff,binary.BigEndian,num)//num转化字节集写入
	if err!=nil{
		log.Panic(err)
	}
	return buff.Bytes() //返回字节集合
}
//main.go
package main

func main(){
	block:=NewBlockChain()//创建区块链
	defer block.db.Close()//延迟关闭数据
	cli:=CLI{block}//创建命令行
	cli.Run()//开启
}

猜你喜欢

转载自blog.csdn.net/weixin_39359584/article/details/82957568
今日推荐