区块链UTXO集源码分析
资源
前提
在未实现UTXO集之前,假设系统需要查询某个钱包地址的余额,系统需要遍历区块链的所有区块,当区块链非常长时,这种做法的成本太高了。
UTXO集是未花费交易输出的集合,它存储区块链中所有未花费交易输出。在实现UTXO集后,系统只需要遍历UTXO集就可以得到某个钱包地址的余额(或者其他操作)。
它的结构体如下:
// UTXOSet represents UTXO set
type UTXOSet struct {
Blockchain *Blockchain
}
可以看到,UTXOSet的结构体中有一个区块链的指针,实际上我们需要通过这个区块链的指针来获得数据库连接(因为UTXO集合的内容是存储在数据库中的)。
源码分析
Reindex
下面是UTXOSet的Reindex方法,该方法将为区块链中所有的未花费交易输出建立Key-Value的索引。(其中,Key是交易的ID,Value是该交易ID下未花费交易输出的集合)
func (u UTXOSet) Reindex() {
db := u.Blockchain.db
bucketName := []byte(utxoBucket)
err := db.Update(func(tx *bolt.Tx) error {
err := tx.DeleteBucket(bucketName)
_, err = tx.CreateBucket(bucketName)
})
UTXO := u.Blockchain.FindUTXO()
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketName)
for txID, outs := range UTXO {
key, err := hex.DecodeString(txID)
err = b.Put(key, outs.Serialize())
}
})
}
FindSpendableOutputs
FindSpendableOutputs这个方法是在发币的时候调用的,代码的逻辑比较清晰直接看源码即可。
func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
accumulated := 0
db := u.Blockchain.db
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
txID := hex.EncodeToString(k)
outs := DeserializeOutputs(v)
for outIdx, out := range outs.Outputs {
if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
}
}
}
})
return accumulated, unspentOutputs
}
FindUTXO
FindUTXO方法用来查询指定钱包地址的余额,它是通过遍历UTXO集的方式来完成余额查询的,代码的逻辑比较清晰直接看源码即可。
func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput {
var UTXOs []TXOutput
db := u.Blockchain.db
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
outs := DeserializeOutputs(v)
for _, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}
return nil
})
return UTXOs
}
Update
Update方法是用来更新UTXO集的,这个方法在新区块上链后被调用,以保证区块链和UTXO的数据同步更新。
func (cli *CLI) send(from, to string, amount int) {
...
newBlock := bc.MineBlock(txs)
UTXOSet.Update(newBlock)
}
Update方法的执行步骤可分为两步:
- 将被交易输入中引用的未花费交易输出从UTXO集中删除
- 将新产生的交易输出更新到UTXO集中
func (u UTXOSet) Update(block *Block) {
db := u.Blockchain.db
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
for _, tx := range block.Transactions {
if tx.IsCoinbase() == false {
for _, vin := range tx.Vin {
updatedOuts := TXOutputs{}
outsBytes := b.Get(vin.Txid)
outs := DeserializeOutputs(outsBytes)
for outIdx, out := range outs.Outputs {
if outIdx != vin.Vout {
updatedOuts.Outputs = append(updatedOuts.Outputs, out)
}
}
if len(updatedOuts.Outputs) == 0 {
err := b.Delete(vin.Txid)
} else {
err := b.Put(vin.Txid, updatedOuts.Serialize())
}
}
}
newOutputs := TXOutputs{}
for _, out := range tx.Vout {
newOutputs.Outputs = append(newOutputs.Outputs, out)
}
err := b.Put(tx.ID, newOutputs.Serialize())
}
})
}