还是借助于这张交易的图,顺一下余额查询的逻辑,这里需要涉及到几个知识点

input:输入,表明钱的来源
output:输出,表明钱的流向

图中红色框就是input 第一笔是创世块是一个特殊的input,

我们可以看出图中input包含 1、TXID为output的索引对应的ID 2、对于outputID的位置索引 3、解锁脚本
output包含 1、金额 和 2、锁定脚本
还有一个非常重要的类 不能忽略 就是Transaction Transaction中的TXid为当前output的ID

也就是 Transaction 参数TXID ->包含 []input []output 三个类 构成交易 一个链内包含多个交易Transaction

type TXInput struct {
	TXID []byte //是引用output上一个区块的哈希 用于寻找上一个区块交易
	Index int64 //output的索引位置
	Address string //解锁脚本,先使用地址模拟
}

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

type Transaction struct {
	TXid []byte //交易id - 也就是当前output的ID
	TXInputs []TXInput //所有的inputs
	TXOutputs []TXOutput //所有的outputs
}

当迭代器(迭代器从后往前读取区块)

1、遍历本区块 将最新的 input (区块后面往前)读取出TXID 将已经花费的索引 记录到 spentUTXOs中 作为一个字典结构

2、再遍历本区块的output 如果 spentUTXOs 的字典内有对应花费过的记录 就剔除 output

由于是从后往前 所以都可以遍历出来 已经花费过的钱 。

当前交易的output 是消耗之前的input,而当前outputID就是Transaction中的TXid []byte 当前交易ID

实现代码

//遍历交易输出
func (bc *BlockChain) FindMyUtxos(address string) []TXOutput {
	var myOutput []TXOutput
	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 == address{
					fmt.Printf("找到了消耗过的output! index:%d\n",input.Index)
					key := string(input.TXID) // 这里是每一个input里面的ID
					spentUTXOs[key] = append(spentUTXOs[key],input.Index)
				}
			}

			//遍历output
			OUTPUT:
			for i,output := range tx.TXOutputs{
				//这里迭代器由后往前 所以已经花费过的金额 已经在新的区块捕捉
				key := string(tx.TXid) //这里是交易ID output中没有ID参数 只有外面的交易ID
				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 == address{
					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)
}

当理解上面的逻辑 转账的逻辑也轻松理解了
也就是找到合适金额的前区块的output UTXO 没有花费过的 也需要遍历出需要的UTXO

未消费OUTPUT输出(UTXO)解释:
文件夹内blocks存放区块链数据,chainstate存放UTXO的数据,因此转账时无须遍历整个区块链

普通交易流程
1、遍历账本,找到属于付款人的合适金额,把这个outputs找到 (复用之前查询余额遍历区块链的代码二次修改下 主体一样)
2、如果找到钱不足以转账,创建交易失败
3、将Outputs转成inputs
4、创建输出,创建一个属于收款人的output
5、如果有找零,创建属于付款人output
6、设置交易ID TXID
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{ //将outputID给新的input 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 遍历账本,找到属于付款人的合适金额,把这个outputs找到 的实现
也就是找UTXO没有花费的outputs(多个或者单个) 如果金额不够继续找多个 outputs直到找到合适数量的output 如果没合适的就说明金额不足

实现代码

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
}