【重点】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
}

