前言
初步(仅仅是初步,等到完善后再回来修改文章)实现UTXO模型,我在学习区块链的理论知识时,就对UTXO这一模型很疑惑,这玩意在技术上究竟是如何实现的,今天终于初步实现了UTXO的制作。
可能中间有一些知识以及代码会断层(例如:数据库的存储采用了boltdb,这个我也是刚刚看完了github的快速入手,还没深入了解,所以就没有写文章),在博客中没有体现,但是我会定期回去修改文章,完善先前的文章。等到我全部完工后,将会把整个代码上传至github,并且试着在csdn用流程图来讲解整个区块链源代码。
UTXO模型概念以及图示
UTXO:UnspentTransactionOutputs,即未花费的交易输出。这概念十分抽象!直白讲就是某人的余额!
那么我这里就定义了这么几个实体:一个区块Block里面有多笔交易Transaction,一笔交易Transaction中有多笔输入TXInput(钱的来源)以及多笔输出TXOutput(将这笔钱转出去) 如下两图所示:
代码实现及其说明
//该函数UTXO的作用是查询某个账户的余额
func (bc *BlockChain) UTXO(address string) []*TXOutput {
//用来存某账户的UTXO,即查询给定address的UTXO
var UTXOs []*TXOutput
//字典存储已花费的交易 字典中存的是 所属 交易 实体Transaction的TXhash:所属 输出 实体TXinput的所在索引(位置)
//若看不懂先略过即可
spentTXOutputs := make(map[string][]int)
//获取区块链的迭代器
itr := bc.Iterator()
for {
//获取当前迭代器中游标(指针)对应的区块
block := itr.Next()
//遍历区块链中的交易Transaction (一个区块拥有多个交易)
for _, tx := range block.Txs {
//判断是否为创世区块中的交易,不是才可继续,因为创世区块压根就没有输入,但是有输出(一位创世神 输出给了 中本聪)
if !tx.IsCoinBaseTransaction() {
//遍历交易中的输入(钱的来源,一个交易拥有多个输入)
for _, in := range tx.TXins {
//UnlockScriptSignWithAddress是看看这一 输入 是否是这个账户的;是的话 就继续
if in.UnlockScriptSignWithAddress(address) {
//这一步就是hash字符串化,变成字典的key
key := hex.EncodeToString(in.TXHash)
//添加进字典,将这一输入放进已花费的输出的字典
spentTXOutputs[key] = append(spentTXOutputs[key], in.Vout)
//该代码段的功能说白了就是:通过input找到已花费的输出;都成为了input,那肯定就花费掉了!
}
}
}
txout:
//一言以蔽之:第一个大的for循环是用来判断哪些输出已被使用,就是哪些钱已经被花掉了。
//遍历交易中的输出(余额转账,一个交易拥有多个输出)
for index, out := range tx.TXouts {
//判断某输出的转账获得者是否为我们当前所要查询的人,说白了:这一笔钱是不是我的?
if out.UnlockScriptPubKeyWithAddress(address) {
if spentTXOutputs != nil {
//如果已花费的交易不为空,说明这些钱未必就是我的,因为可能已经被我花掉了
if len(spentTXOutputs) != 0 {
//遍历已花费的输出 这一字典,从而获得某一交易内的多笔输入的下标indexArray以及该交易的hash值txHash
for txHash, indexArray := range spentTXOutputs {
//遍历这些输入的下标
for _, i := range indexArray {
//若该消费已被花费,则不能放入未花费的交易UTXO(通过对比输入所在的输出的下标和当前遍历的输出的下标是否一致以及判断所属交易hash值是否相同)
if index == i && txHash == hex.EncodeToString(tx.TxHash) {
continue txout
}
}
//能顺利执行到这一步,说明已花费的输出并没有它,那么就可以放进去UTXO
UTXOs = append(UTXOs, out)
}
} else {
//如果没有已花费的交易。那么证明这些钱都是我的。说明我很拮据,没有乱花这里的一分钱!
unUTXOs = append(UTXOs, out)
}
}
}
}
}
//一言以蔽之:第二个大的for循环是用来判断哪些输出已被使用,从而在得到的输出中扣除这些已经使用的,那么就是未使用的交易输出。
var hashInt big.Int
hashInt.SetBytes(block.PrevBlockHash)
//判断是否当前为创世区块,若已经为创世区块,则不再继续向前寻找区块
if hashInt.Cmp(big.NewInt(0)) == 0 {
break
}
}
return UTXOs
}
type TXInput struct {
TXHash []byte
Vout int
ScriptSign string
}
// 判断当前是谁的钱,谁的余额
func (txInput *TXInput) UnlockScriptSignWithAddress(address string) bool {
return txInput.ScriptSign == address
}
type TXOutput struct {
Value int64
ScriptPubKey string
}
func (txOnput *TXOutput) UnlockScriptPubKeyWithAddress(address string) bool {
return txOnput.ScriptPubKey == address
}
以上思想大致(有些不必要的省略了)如下: