货币流动起来才创造价值,为了达成此目的,我们需要创建一个交易,将该交易放到一个block中,然后通过挖矿挖到(PoW)该block。目前为止,我们仅仅实现了coinbase这种特殊的交易,下面我们实现通用的交易:
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
var inputs []TXInput
var outputs []TXOutput
acc, validOutputs := bc.FindSpendableOutputs(from, 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)
for _, out := range outs {
input := TXInput{txID, out, from}
inputs = append(inputs, input)
}
}
// Build a list of outputs
outputs = append(outputs, TXOutput{amount, to})
if acc > amount {
outputs = append(outputs, TXOutput{acc - amount, from}) // a change
}
tx := Transaction{nil, inputs, outputs}
tx.SetID()
return &tx
}
创建TXO前,我们需要找到该账户之前所有交易中的UTXO(FindSpendableOutputs函数),然后对于每一个UTXO创建一个TXI引用它,直到交易额满足要求为止。此时所有被引用的UTXO变为TXO。随后,在当前交易我们需要创建TXO:
- 如果引用的UTXO的交易额小于所需,则当前交易创建失败;
- 如果引用的UTXO的交易额等于所需,则当前交易仅有一个TXO;
- 如果引用的UTXO的交易额大于所需,则当前交易有两个TXO。
FindSpendableOutputs函数基于FindUnspentTransactions实现:
func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
unspentTXs := bc.FindUnspentTransactions(address)
accumulated := 0
Work:
for _, tx := range unspentTXs {
txID := hex.EncodeToString(tx.ID)
for outIdx, out := range tx.Vout {
if out.CanBeUnlockedWith(address) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
if accumulated >= amount {
break Work
}
}
}
}
return accumulated, unspentOutputs
}
此方法遍历账户所有UTX,遍历过程中进行交易额累加,同时计算累加交易额是否满足需求,当满足需求时停止遍历,并返回UTXO列表以及累加交易额。
接下来修改Blockchain.MineBlock方法:
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
...
newBlock := NewBlock(transactions, lastHash)
...
}
最后,实现send命令:
func (cli *CLI) send(from, to string, amount int) {
bc := NewBlockchain(from)
defer bc.db.Close()
tx := NewUTXOTransaction(from, to, amount, bc)
bc.MineBlock([]*Transaction{tx})
fmt.Println("Success!")
}
消费意味着创建一个交易,然后挖一个block存储该交易,并将block添加到blockchain中。但是比特币的做法不同:比特币不会为一个新的交易马上去挖矿,而是会先将新交易缓存内存池mempool,当矿工即将挖矿时,从内存池中将所有交易取出,整体放到block中,并添加到blockchain。
让我们尝试进行一些交易:
$ blockchain_go send -from Ivan -to Pedro -amount 6
00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37
Success!
$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 4
$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 6
Ivan给了Pedro 6元,此时Ivan余4元,Pedro余6元。然后,Pedro给Helen 2元,Ivan给Helen 2元:
$ blockchain_go send -from Pedro -to Helen -amount 2
00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf
Success!
$ blockchain_go send -from Ivan -to Helen -amount 2
000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa
Success!
此时,Helen锁定两个TXO:一个来自Pedro,一个来自Ivan。Helen给Rachel 3元,此时Ivan余2元,Pedro余4元,Helen余1元,Rachel余3元:
$ blockchain_go send -from Helen -to Rachel -amount 3
000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0
Success!
$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2
$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4
$ blockchain_go getbalance -address Helen
Balance of 'Helen': 1
$ blockchain_go getbalance -address Rachel
Balance of 'Rachel': 3
一切OK!尝试一种异常情况:Pedro给Ivan5元,但是Pedro只有4元,消费失败。交易失败前后,Pedro和Ivan的余额未发生变化。
$ blockchain_go send -from Pedro -to Ivan -amount 5
panic: ERROR: Not enough funds
$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4
$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2