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