BlockChain (V3)

董俊晖
2023-12-01
rem add.bat
@echo off &setlocal enabledelayedexpansion
color 0a & title autoAddData
set /a namelen=10
set /a strlen=62
set /a number=100

set str=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
rem the length is 26*2+10=62

set "person1="
set "person2="

for /l %%a in  ( 1 1  !number! ) do (
    call :run
    set person1=!s!
    call :run
    set person2=!s!
    set /a BTCN=!random!%%1000
set  "last=!person1!--SEND--!person2!--!BTCN!--BTC"
echo  Data:	!last!
V2.exe  addBlock  --data   !last!
echo.
)

cls
echo 交易信息处理完成......
echo.
echo.
echo.

V2.exe printchain

echo.
echo.
echo.

pause





:run
set /a len=1+!random!%%!namelen!
set   "s="
for /l %%a in ( 1 1 !len!  ) do (
    set /a b=!random!%%!strlen!
    call set "v=%%str:~!b!,1%%"
    set  "s=!s!!v!"
)
// block.go
package main

import "time"
import "encoding/gob"
import "os"
import "fmt"

//import "crypto/sha256"
import "bytes"

type Block struct {
	Version       int64  //版本信息
	PrevBlockHash []byte //前一个区块的hash值
	Hash          []byte //本区块的hash值,为了方便而做了一些简化,正常比特币区块不包含自己的hash值
	TimeStamp     int64  //时间戳,用于标记产生的时间
	TargetBits    int64  //难度值
	Nonce         int64  //随机值
	MerKelRoot    []byte //默克尔根

	Data []byte //区块体,简化,正常来说是一个交易
	//正常分区块头和区块体,这里方便编写,所以写在了一起
}

func (block *Block) Serialize() []byte { //编码  序列化  序列化和反序列化配对

	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)

	err := encoder.Encode(block)
	CheckErr("Serialize", err)

	return buffer.Bytes()
}

func Deserialize(data []byte) *Block { //反序列化
	if len(data) == 0 {
		fmt.Println("data is empty !")
		os.Exit(1)
	}

	decoder := gob.NewDecoder(bytes.NewReader(data))
	var block Block
	err := decoder.Decode(&block) //decode成block类型
	CheckErr("Deserialize", err)

	return &block
}

func NewBlock(data string, prevBlockHash []byte) *Block { //创建交易信息
	//data 是交易,prev是前一个区块的hash值
	block := &Block{ //初始化区块各个字段的数据
		Version:       1,
		PrevBlockHash: prevBlockHash,
		//Hash:
		TimeStamp:  time.Now().Unix(), //时间戳
		TargetBits: targetBits,        //pow证明加入
		Nonce:      0,                 //工作量证明的,后边会自动调整
		MerKelRoot: []byte{},          //根据交易计算出的
		Data:       []byte(data)}
	//现有的内容做一个hash运算
	//block.SetHash() //设置各个字段的hash值
	pow := NewProofOfWork(block)
	nonce, hash := pow.run()
	//丰富block
	block.Nonce = nonce
	block.Hash = hash
	return block
}

/*
func (block *Block) SetHash() {
	//比特币用的是sha256算法

	tmp := [][]byte{
		//实现Int类型转换为byte类型的工具函数
		IntToByte(block.Version), //
		block.PrevBlockHash,
		//hash不用放了
		IntToByte(block.TimeStamp), //
		block.MerKelRoot,
		IntToByte(block.Nonce), //三个类型不对,把int转换成byte,提供工具包,来解决这个问题。
		block.Data}

	//join 将区块的各个字段连接成一个切片,使用[]byte{}空切片连接,目的是避免污染原区块的信息
	data := bytes.Join(tmp, []byte{}) //把所有指定的切片用分割符分割
	//对区块进行sha256算法,返回值为[32]byte数组
	hash := sha256.Sum256(data)
	block.Hash = hash[:] //由数组转化为切片
}
*/

//创建比特币的创世块,即第一个区块,他的前一个区块的hash为空
func NewGenesisBlock() *Block {
	return NewBlock("Genesis Block!", []byte{})
}
//blockchain.go
package main

import "os"

import "fmt"
import "github.com/bolt"

const dbfile = "blockChainDb.db" //数据库文件名
const blockBucket = "block"      //桶名
const LastHash = "lastHash"      //用于寻址

type BlockChain struct { //构造区块链结构,使用数组来存储所有区块
	//blocks []*Block
	db       *bolt.DB //把信息写到数据库中,数据库的句柄
	lastHash []byte   //存放最后一个区块的hash值,便于迭代
}

//创建区块链实例,并且添加第一个创世块
func NewBlockChain() *BlockChain {
	//new一个创世块
	//return &BlockChain{[]*Block{NewGenesisBlock()}} //填第一个元素
	//func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
	//1.文件名,2读写方式 3不用管,填空就行  返回值,db的句柄和一个error(成功失败)
	//path用常量写,比较规范,用常量写,,,0600代表读写
	//如果存在,打开数据库,打开桶,把数据取出来,存进去就可以,如果没有,需要创建桶

	db, err := bolt.Open(dbfile, 0600, nil)
	CheckErr("NewBlockChain1", err)

	var lastHash []byte //临时存储数据库里的lasthash
	//db.View()//只读不写
	err = db.Update(func(tx *bolt.Tx) error { //Tx参数事务
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket != nil {
			//读取lastHash即可

			lastHash = bucket.Get([]byte(LastHash))
			//fmt.Println("执行过了")

		} else {
			//1.创建bucket
			//2.写数据
			genesis := NewGenesisBlock()
			bucket, err := tx.CreateBucket([]byte(blockBucket))
			CheckErr("NewBlockChain2", err)

			err = bucket.Put(genesis.Hash, genesis.Serialize()) //TODO
			CheckErr("NewBlockChain3", err)                     //填数据,值是区块的值,todo可以帮助我们找到未完成的工作
			err = bucket.Put([]byte(LastHash), genesis.Hash)    //这个lasthash是外部的全局变量
			CheckErr("NewBlockChain4", err)

			lastHash = genesis.Hash //把lasthash返出去

			//fmt.Printf("### %s ###", string(lastHash))
			// fmt.Println("执行过了")
			// fmt.Println(lastHash)
		}
		return nil
	})
	CheckErr("db.Update", err)

	return &BlockChain{db, lastHash} //没有信息则填第一个元素
} //到此,完成了区块链的构造

//添加新区块操作
func (bc *BlockChain) AddBlock(data string) {

	var prevBlockHash []byte
	err := bc.db.View(func(tx *bolt.Tx) error { //view取操作
		bucket := tx.Bucket([]byte(blockBucket))
		lastHash := bucket.Get([]byte(LastHash))
		prevBlockHash = lastHash

		return nil
	})

	CheckErr("AddBlock", err)

	block := NewBlock(data, prevBlockHash) //前一个区块的hash就是最后一个区块的hash

	err = bc.db.Update(func(tx *bolt.Tx) error { //写到数据库中

		bucket := tx.Bucket([]byte(blockBucket))
		err := bucket.Put(block.Hash, block.Serialize())
		CheckErr("AddBlock2", err)

		err = bucket.Put([]byte(LastHash), block.Hash)
		CheckErr("AddBlock3", err)

		bc.lastHash = block.Hash
		return nil
	})
	CheckErr("AddBlock4", err)
}

//遍历数据库文件,通过最后一块的hash,找到前一块的hash
//通过key把hash放进去的
//迭代器,有一个指向当前区块的指针

type BlockChainIterator struct {
	db          *bolt.DB
	currentHash []byte //当前区块的hash
}

func (bc *BlockChain) Iterator() *BlockChainIterator {

	return &BlockChainIterator{bc.db, bc.lastHash}
}

func (it *BlockChainIterator) Next() *Block {
	var block *Block
	err := it.db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			CheckErr("View ,", fmt.Errorf("bucket is empty"))
			os.Exit(1)
		}

		blockTmp := bucket.Get(it.currentHash)
		block = Deserialize(blockTmp) //反序列化

		it.currentHash = block.PrevBlockHash //迭代器指针向前移动

		return nil

	})
	CheckErr("Next", err)
	return block
}

//添加其他的区块的操作

// func (bc *BlockChain) AddBlock(data string) {

// 	//校验数组的元素个数,避免出现访问越界情况!!!直接操作下标有风险
// 	if len(bc.blocks) <= 0 {
// 		os.Exit(1)
// 	}

// 	//取出最后一个区块,目的是得到其hash值
// 	lastBlock := bc.blocks[len(bc.blocks)-1] //找到最后一个区块的下标,因为是数组存储
// 	prevBlockHash := lastBlock.Hash
// 	//构造新区块,且添加至整个数组中
// 	block := NewBlock(data, prevBlockHash) //交易信息和上一个区块的hash
// 	bc.blocks = append(bc.blocks, block)
// }
//cli.go
package main

import (
	"flag"
	"fmt"
	"os"
)

//使用说明
// const Usage = `

//       ./block addBlock --data DATA   "add a block to block chain"
//       ./block printchain             "print all blocks"

// `

const Usage = `

      addBlock --data DATA   "add a block to block chain"
      printchain             "print all blocks"

`

type CLI struct { //和命令行进行交互
	bc *BlockChain
}

func (cli *CLI) Run() { //等待接受信息

	if len(os.Args) < 2 {
		fmt.Println("too few parameters!\n(参数不够)\n", Usage)
		os.Exit(1)
	} //完成了校验

	addBlockCmd := flag.NewFlagSet("addBlock", flag.ExitOnError)
	printCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
	//1命令名字 2.返回

	//printchain 不需要接收参数     addBlock需要接收 --data 参数
	addBlockCmdPara := addBlockCmd.String("data", "", "block info") //获取指定参数
	//1 参数data  2默认值 空 3它的描述(意义)
	//返回值是参数,解析命令时会把参数返回来

	switch os.Args[1] { //检查用户传入参数

	case "addBlock":
		err := addBlockCmd.Parse(os.Args[2:]) //解参数 0,1,2,从第二个开始解出来
		CheckErr("addBlock", err)

		if addBlockCmd.Parsed() { //解析过了
			if *addBlockCmdPara == "" { //校验
				fmt.Println("data is empty ,please check!")
				os.Exit(1)
			}
			//cli.bc.AddBlock(*addBlockCmdPara)
			cli.AddBlock(*addBlockCmdPara)
		}

	case "printchain":
		err := printCmd.Parse(os.Args[2:])
		CheckErr("printchain", err)
		if printCmd.Parsed() {
			cli.PrintChain()
		}
	default:
		fmt.Println("invalid cmd!\n (你输入的参数不正确)\n", Usage)
		os.Exit(1)
	}

}
//commands.go
package main

import "fmt"

func (cli *CLI) AddBlock(data string) {
	cli.bc.AddBlock(data)
	fmt.Println("AddBlock Succeed!")
}

func (cli *CLI) PrintChain() {
	bc := cli.bc
	it := bc.Iterator() //迭代
	for {               //从后向前迭代区块链
		block := it.Next() //取回当前hash指向的block,将hash值前移动

		//fmt.Println("==========block num:", i)
		fmt.Println("Data:\t", string(block.Data))
		fmt.Println("Version:\t", block.Version)
		fmt.Printf("PrevBlockHash:\t %x\n", block.PrevBlockHash)
		fmt.Printf("Hash:\t\t %x\n", block.Hash)
		fmt.Printf("TimeStamp:\t %d\n", block.TimeStamp)
		fmt.Printf("MerKel:\t %x\n", block.MerKelRoot)
		fmt.Printf("Nonce:\t %d\n", block.Nonce)
		fmt.Println("\n\n")

		pow := NewProofOfWork(block)
		fmt.Printf("Isvalid:%v\n\n", pow.IsValid())

		if len(block.PrevBlockHash) == 0 { //到前一个区块hash是空结束
			break
		}
	}

}
//main.go
package main

//import "fmt"

func main() {

	bc := NewBlockChain()
	cli := CLI{bc}
	cli.Run()
}

//  bc.AddBlock("班长转给老师一枚BTC")
// 	bc.AddBlock("班长又转给老师一枚BTC")
// 	bc.AddBlock("小明转给老师一枚BTC")
// 	bc.AddBlock("小刚愣了一秒钟,然后给老师转了10000BTC")

// for i, block := range bc.blocks {

// 	fmt.Println("==========block num:", i)
// 	fmt.Println("Data:\t", string(block.Data))
// 	fmt.Println("Version:\t", block.Version)
// 	fmt.Printf("PrevBlockHash:\t %x\n", block.PrevBlockHash)
// 	fmt.Printf("Hash:\t\t %x\n", block.Hash)
// 	fmt.Printf("TimeStamp:\t %d\n", block.TimeStamp)
// 	fmt.Printf("MerKel:\t %x\n", block.MerKelRoot)
// 	fmt.Printf("Nonce:\t %d\n", block.Nonce)
// 	fmt.Println("\n\n")

// 	pow := NewProofOfWork(block)
// 	fmt.Printf("Isvalid:%v\n", pow.IsValid())
// }

//}
//proofofwork.go

package main

//想工作量证明:首先要运算,数据来源于区块链,所以说,我们这个结构里边肯定要包含区块,不然数据没有来源
import "math/big"
import "bytes"
import "crypto/sha256"
import "math"
import "fmt"

const targetBits = 24 //转成十六进制后前边有4个0,便于运算
type ProofOfWork struct {
	block *Block
	//有一个难度值,有一个目标值,和它对比
	//hash 256位的一个数
	targetBit *big.Int
}

func NewProofOfWork(block *Block) *ProofOfWork { //本身需要两个字段
	var IntTarget = big.NewInt(1)
	//先创建一个大的值
	//00000000000000000000000000000000001
	//初始化为1,把他进行向左移动256位-24位
	//10000000000000000000000000000000000十进制
	//往回移动24位
	//00000000000001000000000000000000000十进制
	//对于一个十六进制的数,24是6个0
	//z:0000010000000000000000000000000000十六进制,长度先不管
	//最终要对比的hash值就是 十六进制的数,前导0

	//假如算出来的hash值是
	//y:000000a000123560000000000000000000
	//和上边的十六进制对比,,通过不断添加遍历nac值改变
	//假如找到了y值比z值小,y满足条件,那么就认为当前noc值就是目标值

	//做一些值的运算
	//根据难度值算hash值,算完以后跟我的hash值对比
	IntTarget.Lsh(IntTarget, uint(256-targetBits)) //向左移位

	return &ProofOfWork{block, IntTarget}
} //工作量证明结构,实例
//根据难度值构建出一个hash值,这个hash值就是将来我们要对block做hash运算出来的值和那个相比
//工作量证明肯定做一个hash,上一个版本对block直接做hash了,对各个字段字符串连接,做hash运算
//做工作量证明时,也要重复这些步骤,为了代码复用,写一个函数,把block拼装成字符串,写成一个函数

//在运算前做一个前期准备,(准备原信息)
func (pow *ProofOfWork) PrepareRowData(nonce int64) []byte { //对byte进行sha256运算
	block := pow.block

	//东西都在结构里,其实是重复前边的连接的过程
	tmp := [][]byte{
		//实现Int类型转换为byte类型的工具函数
		IntToByte(block.Version), //
		block.PrevBlockHash,
		//hash不用放了
		IntToByte(block.TimeStamp), //
		block.MerKelRoot,
		IntToByte(nonce), //block.Nonce // Nonce不能随便写了,需要传进来随机碰撞的值                 三个类型不对,把int转换成byte,提供工具包,来解决这个问题。
		IntToByte(targetBits),
		block.Data}
	//完成了对区块数据的整合

	//join 将区块的各个字段连接成一个切片,使用[]byte{}空切片连接,目的是避免污染原区块的信息
	data := bytes.Join(tmp, []byte{}) //把所有指定的切片用分割符分割

	return data
	//不断碰撞的意思是不断变化nonce值,不断循环

	//对区块进行sha256算法,返回值为[32]byte数组,不是切片
	//hash := sha256.Sum256(data)//256是真正执行pow运算的过程
	//block.Hash = hash[:]      //由数组转化为切片

}

//做运算,真正的hash碰撞,for循环,做轮循的过程,不断变化nonce,做sha256,和我的难度值z进行对比
//碰撞的过程
func (pow *ProofOfWork) run() (int64, []byte) { //找目标值,返回来
	var nonce int64 //=0
	var hash [32]byte
	var HashInt big.Int //做辅助,来承接hash值,做比较
	//把hash数组,转换成big.Int

	fmt.Println("Begin Mining....................")
	fmt.Printf("target hash:\t    %x\n", pow.targetBit.Bytes()) //难度值,前边省略了四个0
	//找到了nonce也就确定了唯一结构信息,hash值也确定了
	for nonce < math.MaxInt64 { //不断轮循,防止死循环,设置一个最大值,很大了,
		//如果找到hash值,返回,否则,nonce继续加
		data := pow.PrepareRowData(nonce)
		hash = sha256.Sum256(data) //进行对比
		HashInt.SetBytes(hash[:])  //hash字符串转换成大的数(int)
		// Cmp compares x and y and returns:
		//
		//   -1 if x <  y
		//    0 if x == y
		//   +1 if x >  y
		//
		//cmp内部注释
		//z:0000010000000000000000000000000000十六进制  target
		//y:000000a000123560000000000000000000   实际值
		//想要实际值小于target
		//fmt.Printf("THE Hash:\t%x\n", hash)
		if HashInt.Cmp(pow.targetBit) == -1 { //HashInt.自带cmp比较方法
			//找到了
			fmt.Printf("Fond Hash:\t%x\n\n", hash)
			break

		} else {
			//fmt.Printf("current Hash:\t%x\n", hash)
			//如果真的找不到,证明难度值太大了
			//进行下一个nonce寻找
			nonce++

		}

		// if hash 对比 pow.targetBit{
		// 如果现在的hash大,则不满足条件,找一个比他小的值
		// hash> targetBit
		// breal
		// } else{

		// nonce++

		// }

	}
	return nonce, hash[:] //完成pow计算

}

//所有节点在真实网络中,会校验你算的nonce对不对
//在pow里提供一个方法验证算的nonce是否有效
//校验方法很简单
//block 拿到 拿到nonce值 带进去算下值 cmp是不是=-1就完成了校验工作

func (pow *ProofOfWork) IsValid() bool {
	//给外部校验的。思路,pow提供block
	//构造区块信息,取hash和目标值进行对比

	data := pow.PrepareRowData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	var IntHash big.Int
	IntHash.SetBytes(hash[:])
	return IntHash.Cmp(pow.targetBit) == -1
}
//utls.go

package main

import "encoding/binary"
import "bytes"
import "fmt"
import "os"

func IntToByte(num int64) []byte {
	var buffer bytes.Buffer //用buffer转换

	err := binary.Write(&buffer, binary.BigEndian, num) //三个参数,传入的buffer,对齐方式,任何类型
	if err != nil {
		CheckErr("IntToByte\n", err)
		os.Exit(1)
	}
	return buffer.Bytes()
}

func CheckErr(pos string, err error) {
	if err != nil {
		fmt.Println("err occur:\t", err, "pos:", pos)
		os.Exit(1)
	}
}

 

 类似资料: