定义交易结构

package main

//定义交易结构
//定义input
//定义output
//设置交易ID

type TXInput struct {
	TXID []byte //交易ID
	Index int64 //output的索引
	Address string //解锁脚本,先使用地址模拟
}

type TXOutput struct {
	Value float64 //转账金额
	Address string //锁定脚本
}

type Transaction struct {
	TXid []byte //交易id
	TXInputs []TXInput //所有的inputs
	TXOutputs []TXOutput //所有的outputs
}

setTXID函数

func (tx *Transaction) setTXID() {
	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)

	err := encoder.Encode(tx)
	if err != nil{
		log.Panic(err)
	}
	hash := sha256.Sum256(buffer.Bytes())
	tx.TXid = hash[:]
}

实现挖矿交易

//特点:只有输出,没有有效的输入(不需要ID索引签名)
func NewCoinbaseTx(miner string) *Transaction {
	//挖矿交易没输入
	inputs := []TXInput{TXInput{nil,-1,"coinbase"}}

	//输出给矿工奖励
	outputs := []TXOutput{TXOutput{12.5,miner}}

	tx := Transaction{nil,inputs,outputs}
	tx.setTXID()

	return &tx
}

使用Transaction改写程序
1、改写block结构
2、逐个文件根据错误提示逐个处理,把程序编译成功
3、使用strings db查看

模拟梅克尔根
更正:比特币做哈希值,并不是对整个区块做哈希,而是对区块头做哈希

//模拟梅克尔根,简单处理
func (block *Block) HashTransactions() {
	//将交易的ID拼接起来
	var hashes []byte
	for _,tx := range block.Transactions{
		txid := tx.TXid
		hashes = append(hashes,txid...)
	}

	hash := sha256.Sum256(hashes)
	block.MarKleRoot = hash[:]
}

在Newblock中调用

我有多少可用的比特币
1、遍历账本,找到能够支配的UTXO
2、剔除已经花费过的output

实现思路:
1、遍历账本
2、遍历交易
3、遍历output
4、找到属于我所有的output
此时会找到所有历史上属于我的output,这个是不准确的,需要把消耗过的剔除
在添加Output之前,遍历input,过滤掉已经消耗的output

UTXO理解参照 http://c.biancheng.net/view/1895.html

通过UTXO获取余额框架搭建

//实现思路
//1、遍历账本
//2、遍历交易
//3、遍历output
//4、找到属于我的所有output

func (bc *BlockChain) FindMyUtxos(address string) []TXOutput {
	//TODO
	fmt.Println("FUNC FindMyUtxos")
	return []TXOutput{}
}

func (bc *BlockChain) GetBalance(address string)  {
	utxos := bc.FindMyUtxos(address)

	var total = 0.0
	for _,tx := range utxos{
		total += tx.Value
	}

	fmt.Printf("%s 的余额为%f \n",address,total)
}

在cli.go中添加getBalance命令,调用GetBalance函数
cli.bc.GetBalance(cmds[2])

遍历交易输出TXOutputs

func (bc *BlockChain) FindMyUtxos(address string) []TXOutput {
	fmt.Println("FUNC FindMyUtxos")
	it := bc.NewIterator()

	var myOutput []TXOutput

	for{ //遍历账本
		block := it.Next()
		for _,tx := range block.Transactions{ //遍历交易
			for i,output := range tx.TXOutputs{ //遍历output
				if output.Address == address{ //找到属于我的所有output
					fmt.Printf("找到了属于 %s 的output,i:%d\n",address,i)
					myOutput = append(myOutput, output)
				}
			}
		}

		//这里一定要跳出
		if len(block.PrevHash) == 0{
			fmt.Printf("遍历区块链结束\n")
			break
		}
	}

	return myOutput
}

func (bc *BlockChain) GetBalance(address string)  {
	utxos := bc.FindMyUtxos(address)

	var total = 0.0
	for _,tx := range utxos{
		total += tx.Value
	}

	fmt.Printf("%s 的余额为%f \n",address,total)
}

遍历交易的inputs

创建普通交易
参数
1、付款人 2、收款人 3、转账金额 4、bc

内部逻辑
1、遍历账本,找到属于付款人的合适的金额,把这个outputs找到
2、如果找到的钱不足以转账,创建交易失败
3、将outputs转成inputs
4、创建输出,创建一个属于收款人的output
5、如果有找零,创建属于付款人的output
6、设置交易ID
7、返回交易结构

//创建普通交易
func NewTransaction(from,to string,amount float64,bc *BlockChain) *Transaction{
	utxos := make(map[string][]int64) //标识能用的utxo
	var resValue float64 //这些utxo存储的金额
	//例如李四转赵六4,返回的信息为
	//utxos[0x333] = int64{0,1}
	//resValue = 5

	//1、遍历账本,找到属于付款人的合适金额,把这个outputs找到
	utxos,resValue = bc.FindNeedUtxos(from,amount)

	//2、如果找到钱不足以转账,创建交易失败
	if resValue < amount{ fmt.Printf("余额不足,交易失败\n") return nil } var inputs []TXInput var outputs []TXOutput //3.将Outputs转成inputs for txid,indexes := range utxos{ for _,i := range indexes{ input := TXInput{[]byte(txid),i,from} inputs = append(inputs,input) } } //4、创建输出,创建一个属于收款人的output output := TXOutput{amount,to} outputs = append(outputs,output) //5、如果有找零,创建属于付款人output if resValue > amount{
		output1 := TXOutput{resValue-amount,from}
		outputs = append(outputs,output1)
	}

	tx := Transaction{nil,inputs,outputs}
	//6、设置交易ID
	tx.setTXID()

	//7、返回交易结构
	return &tx
}

FindNeedUtxos 空实现

//1、遍历账本,找到属于付款人的合适金额,把这个outputs找到
//utxos,resValue = bc.FindNeedUtxos(from,amount)
func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) {
	//TODO 找到合理的utxos集合
	utxos := make(map[string][]int64) //标识能用的utxo
	return utxos,0.0
}

FindNeedUtxos第一版本实现

func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) {
	needUtxos := make(map[string][]int64) //标识能用的utxo
	var resValue float64 //返回统计的金额
	it := bc.NewIterator()

	//这是标识已经消耗过的utxo结构key是交易ID,value是这个id里的output索引的数组
	spentUTXOs := make(map[string][]int64)

	for{ //遍历账本
		block := it.Next()
		for _,tx := range block.Transactions{ //遍历交易
			//遍历交易输入inputs
			for _,input := range tx.TXInputs{
				if input.Address == from{
					fmt.Printf("找到了消耗过的output! index:%d\n",input.Index)
					key := string(input.TXID)
					spentUTXOs[key] = append(spentUTXOs[key],input.Index)
				}
			}

			//遍历output
		OUTPUT:
			for i,output := range tx.TXOutputs{
				key := string(tx.TXid)
				indexes /*[]int64{0,1}*/ := spentUTXOs[key]

				if len(indexes) != 0{
					fmt.Printf("当前这币交易中又被消耗过的output\n")
					for _,j /*0,1*/ := range indexes{
						if int64(i) == j{
							fmt.Printf("i == j ,当前的output已经被消耗过了,跳过不统计\n")
							continue OUTPUT //跳出两层循环
						}
					}
				}

				//找到属于我的所有output
				if output.Address == from{
					fmt.Printf("找到了属于 %s 的output,i:%d\n",from,i)
					//myOutput = append(myOutput, output)

					//在这里实现二次修改的控制逻辑 找到符合条件的output
					//1、添加到返回结构中needUtxos
					needUtxos[key] = append(needUtxos[key],int64(i))
					resValue += output.Value

					//2、判断一下金额是否足够
					if resValue >= amount{
						//a,足够,直接返回
						return needUtxos,resValue
					}
					//b,不足,继续遍历,无需处理
				}
			}
		}

		//这里一定要跳出
		if len(block.PrevHash) == 0{
			fmt.Printf("遍历区块链结束\n")
			break
		}
	}
	return needUtxos,resValue
}

添加Send命令

case "send":
			fmt.Printf("转账命令被调用\n")
			//./blockchain send FROM TO AMOUNT MINER DATA 转账命令
			if len(cmds) != 7{
				fmt.Printf("send命令发现无效参数,请检查\n")
				fmt.Printf(Usage)
				os.Exit(1)
			}
			from := cmds[2]
			to := cmds[3]
			amount,_ := strconv.ParseFloat(cmds[4],64)
			miner := cmds[5]
			data := cmds[6]
			cli.Send(from,to,amount,miner,data)

Send命令实现

//发送交易cli.Send(from,to,amount,miner,data)
func (cli *CLI) Send (from,to string,amount float64,miner string,data string)  {
	//1、创建挖矿交易
	coinbase := NewCoinbaseTx(miner,data)

	txs := []*Transaction{coinbase}

	//2、创建普通交易
	tx := NewTransaction(from,to,amount,cli.bc)
	if tx != nil{
		txs = append(txs, tx)
	}else{
		fmt.Printf("发现无效交易,过滤!\n")
	}

	//3、添加到区块
	cli.bc.addBlock(txs)

	fmt.Printf("挖矿成功!")
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
注意:以下主要是对代码的优化和改写,主要运用代码知识,无区块链知识 不作为学习区块链原理的重点

补充:改写和优化

我们想把`FindMyUtoxs`和`FindNeedUTXO`进行整合

1. FindMyUtoxs: 找到所有utxo(只要output就可以了)
2. FindNeedUTXO:找到需要的utxo(要output的定位)

我们可以定义一个结构,同时包含output已经定位信息

UTXOInfo
1. TXID
2. index
3. output

FindMyUtoxs()[]UTXOInfo

//实现思路:
func (bc *BlockChain) FindMyUtoxs(address string) []UTXOInfo {
	fmt.Printf("FindMyUtoxs\n")
	//var UTXOs []TXOutput //返回的结构
	var UTXOInfos []UTXOInfo //新的返回结构

	it := bc.NewIterator()

	//这是标识已经消耗过的utxo的结构,key是交易id,value是这个id里面的output索引的数组
	spentUTXOs := make(map[string][]int64)

	//1. 遍历账本
	for {

		block := it.Next()

		//2. 遍历交易
		for _, tx := range block.Transactions {
			//遍历交易输入:inputs

			for _, input := range tx.TXInputs {
				if input.Address == address {
					fmt.Printf("找到了消耗过的output! index : %d\n", input.Index)
					key := string(input.TXID)
					spentUTXOs[key] = append(spentUTXOs[key], input.Index)
					//spentUTXOs[0x222] = []int64{0}
					//spentUTXOs[0x333] = []int64{0}  //中间状态
					//spentUTXOs[0x333] = []int64{0, 1}
				}
			}

			key := string(tx.TXid)
			indexes /*[]int64{0,1}*/ := spentUTXOs[key]

		OUTPUT:
		//3. 遍历output
			for i, output := range tx.TXOutputs {

				if len(indexes) != 0 {
					fmt.Printf("当前这笔交易中有被消耗过的output!\n")
					for _, j /*0, 1*/ := range indexes {
						if int64(i) == j {
							fmt.Printf("i == j, 当前的output已经被消耗过了,跳过不统计!\n")
							continue OUTPUT
						}
					}
				}

				//4. 找到属于我的所有output
				if address == output.Address {
					fmt.Printf("找到了属于 %s 的output, i : %d\n", address, i)
					//UTXOs = append(UTXOs, output)
					utxoinfo := UTXOInfo{tx.TXid, int64(i), output}
					UTXOInfos = append(UTXOInfos, utxoinfo)
				}
			}
		}

		if len(block.PrevBlockHash) == 0 {
			fmt.Printf("遍历区块链结束!\n")
			break
		}
	}

	return UTXOInfos
}

改写GetBalance

func (bc *BlockChain) GetBalance(address string) {
	utxoinfos := bc.FindMyUtoxs(address)

	var total = 0.0
	//所有的output都在utxoinfos内部
	//获取余额时,遍历utxoinfos获取output即可
	for _, utxoinfo := range utxoinfos {
		total += utxoinfo.Output.Value //10, 3, 1
	}

	fmt.Printf("%s 的余额为: %f\n", address, total)
}

改写FindNeedUTXO

//1. 遍历账本,找到属于付款人的合适的金额,把这个outputs找到
//utxos, resValue = bc.FindNeedUtxos(from, amount)
func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) {
	needUtxos := make(map[string][]int64) //标识能用的utxo
	var resValue float64 //返回统计的金额

	//复用FindMyUtxo函数,这个函数已经包含了所有信息
	utxoinfos := bc.FindMyUtxos(from)

	for _,utxoinfo := range utxoinfos{
		key := string(utxoinfo.TXID)
		needUtxos[key] = append(needUtxos[key],int64(utxoinfo.Index)) //这里定位了UTXO->TXID和索引
		resValue += utxoinfo.Output.Value

		//判断一下金额是否足够
		if resValue >= amount{
			//足够就跳出
			break
		}
	}
	return needUtxos,resValue
}

收尾工作

1. 写一个函数IsCoinbase,判断一个交易是否为挖矿交易
2. 把创建区块链这个命令单独实现一下
./blockchain createBlockChain 班花

IsCoinbase实现

func (tx *Transaction) IsCoinbase() bool {
	//特点:1. 只有一个input 2. 引用的id是nil 3. 引用的索引是-1
	inputs := tx.TXInputs
	if len(inputs) == 1 && inputs[0].TXID == nil && inputs[0].Index == -1 {
		return true
	}

	return false
}

在遍历inputs时使用

			if tx.IsCoinbase() == false {
				//如果不是coinbase,说明是普通交易,才有必要进行遍历
				for _, input := range tx.TXInputs {
					if input.Address == address {
						fmt.Printf("找到了消耗过的output! index : %d\n", input.Index)
						key := string(input.TXID)
						spentUTXOs[key] = append(spentUTXOs[key], input.Index)
						//spentUTXOs[0x222] = []int64{0}
						//spentUTXOs[0x333] = []int64{0}  //中间状态
						//spentUTXOs[0x333] = []int64{0, 1}
					}
				}
			}

添加创建区块链命令
1. 当前情况
在NewBlockCHain中实现了两个功能
– 没有区块链时创建区块链,并且返回区块链实例
– 如果区块链存在,直接返回区块链实例

2. 我们要做的事情
* 添加CreateBlockChain命令,只创建区块链(只能调用一次)
* 把返回区块链的实例使用一个新的函数实现(并不单独使用,而是在每次操作区块链之前调用一下)

创建区块链函数

func CreateBlockChain(miner string) *BlockChain {

	//功能分析:
	//1. 获得数据库的句柄,打开数据库,读写数据

	db, err := bolt.Open(blockChainName, 0600, nil)
	//向数据库中写入数据
	//从数据库中读取数据

	if err != nil {
		log.Panic(err)
	}

	//defer db.Close()

	var tail []byte

	db.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucket([]byte(blockBucketName))

		if err != nil {
			log.Panic(err)
		}

		//抽屉准备完毕,开始添加创世块
		//创世块中只有一个挖矿交易,只有Coinbase
		coinbase := NewCoinbaseTx(miner, genesisInfo)
		genesisBlock := NewBlock([]*Transaction{coinbase}, []byte{})

		b.Put(genesisBlock.Hash, genesisBlock.Serialize() /*将区块序列化,转成字节流*/)
		b.Put([]byte(lastHashKey), genesisBlock.Hash)

		//为了测试,我们把写入的数据读取出来,如果没问题,注释掉这段代码
		//blockInfo := b.Get(genesisBlock.Hash)
		//block := Deserialize(blockInfo)
		//fmt.Printf("解码后的block数据:%s\n", block)

		tail = genesisBlock.Hash

		return nil
	})

	return &BlockChain{db, tail}
}

获取区块链实例函数

//返回区块链实例
func NewBlockChain() *BlockChain {

	//功能分析:
	//1. 获得数据库的句柄,打开数据库,读写数据

	db, err := bolt.Open(blockChainName, 0600, nil)

	if err != nil {
		log.Panic(err)
	}

	//defer db.Close()

	var tail []byte

	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blockBucketName))

		if b == nil {
			fmt.Printf("区块链bucket为空,请检查!\n")
			os.Exit(1)
		}

		tail = b.Get([]byte(lastHashKey))

		return nil
	})

	return &BlockChain{db, tail}
}

添加CreateBlockChain命令 在cli.go中添加命令

	switch cmds[1] {
	case "createBlockChain":
		if len(cmds) != 3 {
			fmt.Printf(Usage)
			os.Exit(1)
		}

		fmt.Printf("创建区块链命令被调用!\n")

		addr := cmds[2]
		cli.CreateBlockChain(addr)

	case "printChain":

在commands.go中实现

func (cli *CLI) CreateBlockChain(addr string) {
	bc := CreateBlockChain(addr)
	bc.db.Close()
	fmt.Printf("创建区块链成功!\n")
}

主函数

package main

func main() {
	cli := CLI{}
	cli.Run()
}

在commands.go中用NewBlockChain函数
例如,获取余额:

func (cli *CLI)GetBalance(addr string)  {
	bc := NewBlockChain()
	defer bc.db.Close()

	bc.GetBalance(addr)
}

判断区块链文件是否存在
1. 如果存在,不允许再次创建
2. 如果没有,做所有操作之前,必须先创建区块链

判断文件`blockChain.db`是否存在??

在utils.go中添加IsFileExist函数,代码如下:

//判断文件是否存在
func IsFileExist(fileName string) bool {
	//使用os.Stat来判断
	//func Stat(name string) (FileInfo, error) {
	_, err := os.Stat(fileName)

	if os.IsNotExist(err) {
		return false
	}

	return true
}

在NewBlockChain中调用

func NewBlockChain() *BlockChain {

	if !IsFileExist(blockChainName) {
		fmt.Printf("区块链不存在,请先创建!\n")
		return nil
	}
   // xxxxx
}

在CreateBlockChain中调用

func CreateBlockChain(miner string) *BlockChain {

	if IsFileExist(blockChainName) {
		fmt.Printf("区块链已经存在,不需要重复创建!\n")
		return nil
	}
    //xxxx
}

在commands.go进行有效性判断

func (cli *CLI) Send(from, to string, amount float64, miner string, data string) {

	bc := NewBlockChain()

	if bc == nil {
		return
	}

	defer bc.db.Close()
    //xxxxx
}