【重点】BTC区块链余额查询和转账自总结
还是借助于这张交易的图,顺一下余额查询的逻辑,这里需要涉及到几个知识点
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 }