通过blockchain_go分析区块链交易原理 (2)

因为每一个output都包括一个value和一个公钥身份,所以遍历所有区块中的交易,找出其中所有“未花费”的输出,就可以计算出用户的账户余额。

4.查找未花费的Output

如果一个账户需要进行一次交易,把自己的代币转给别人,由于没有一个账号系统可以直接查询余额和变更,而在utxo模型里面一个用户账户余额就是这个用户的所有utxo(未花费的输出)记录的合集,因此需要查询用户的转账额度是否足够,以及本次转账需要消耗哪些output(将“未花费”的output变成”已花费“的output),通过遍历区块链中每个区块中的每个交易中的output来得到结果。

下面看看怎么查找一个特定用户的utxo,utxo_set.go相关代码如下:

// FindSpendableOutputs finds and returns unspent outputs to reference in inputs 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 nil }) if err != nil { log.Panic(err) } return accumulated, unspentOutputs }

FindSpendableOutputs查找区块链上pubkeyHash账户的utxo集合,直到这些集合的累计未花费金额达到需求的amount为止。

blockchain_go中使用嵌入式key-value数据库boltdb存储区块链和未花费输出等信息,其中utxoBucket是所有用户未花费输出的bucket,其中的key表示交易ID,value是这个交易中未被引用的所有output的集合。所以通过遍历查询本次交易需要花费的output,得到Transaction的txID和这个output在Transaction中的输出数组中的下标组合unspentOutputs。

另外一个重点是utxobucket中保存的未花费输出结合是关于所有账户的,要查询特定账户需要对账户进行判断,因为TXOutput中有pubkeyhash字段,用来表示该输出属于哪个用户,此处采用out.IsLockedWithKey(pubkeyHash)判断特定output是否是属于给定用户。

5.新建Transaction

需要发起一笔交易的时候,需要新建一个Transaction,通过交易发起人的钱包得到足够的未花费输出,构建出交易的输入和输出,完成签名即可,blockchain_go中的实现如下:

// NewUTXOTransaction creates a new transaction func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction { var inputs []TXInput var outputs []TXOutput pubKeyHash := HashPubKey(wallet.PublicKey) acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) if acc < amount { log.Panic("ERROR: Not enough funds") } // Build a list of inputs for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) if err != nil { log.Panic(err) } for _, out := range outs { input := TXInput{txID, out, nil, wallet.PublicKey} inputs = append(inputs, input) } } // Build a list of outputs from := fmt.Sprintf("%s", wallet.GetAddress()) outputs = append(outputs, *NewTXOutput(amount, to)) if acc > amount { outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change } tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey) return &tx }

函数参数:

wallet : 用户钱包参数,存储用户的公私钥,用于交易的签名和验证。

to : 交易转账的目的地址(转账给谁)。

amount : 需要交易的代币额度。

UTXOSet : uxto集合,查询用户的未花费输出。

查询需要的未花费输出:

acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)

因为用户的总金额是通过若干未花费输出累计起来的,而每个output所携带金额不一而足,所以每次转账可能需要消耗多个不同的output,而且还可能涉及找零问题。以上查询返回了一批未花费输出列表validOutputs和他们总共的金额acc. 找出来的未花费输出列表就是本次交易的输入,并将输出结果构造output指向目的用户,并检查是否有找零,将找零返还。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjzyz.html