第9章 UTXO集优化

交易(Transactions)(2)

在这个系列文章的一开始,我们就提到了,区块链是一个分布式数据库。不过在之前的文章中,我们选择性地跳过了“分布式”这个部分,而是将注意力都放到了“数据库”部分。到目前为止,我们几乎已经实现了一个区块链数据库的所有元素(基本原型,工作量证明,持久化,命令行接口,交易,地址,数字签名等)。今天,我们将会分析之前跳过的一些机制。而在下一篇文章中,我们将会开始讨论区块链的分布式特性。

本文的代码实现变化很大。

1. 课程目标

  1. 知道什么是奖励
  2. 知道什么是UTXO集
  3. 学会UTXO集优化的原理
  4. 项目中修改代码实现UTXO集查询余额
  5. 项目中修改代码实现UTXO集进行转账交易

2. 项目代码及效果展示

2.1 项目代码结构

http://img.kongyixueyuan.com/0901_%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png

2.2 项目运行结果

http://img.kongyixueyuan.com/0902_%E8%BF%90%E8%A1%8C%E6%95%88%E6%9E%9C.gif

3. 创建项目

3.1 创建工程

首先打开Goland开发工具

打开工程:mypublicchain

创建项目:将上一次的项目代码,day05_07_signature,复制为day06_08_Update

说明:我们每一章节的项目代码,都是在上一个章节上进行添加。所以拷贝上一次的项目代码,然后进行新内容的添加或修改。

3.2 代码实现

3.2.1 创建go文件:TxOutputs.go

打开day06_08_Update目录里的BLC包,创建TxOutputs.go文件。在TxOutputs.go文件中编写代码如下:

package BLC

import (
    "bytes"
    "encoding/gob"
    "log"
)

type TxOutputs struct {
    UTXOS []*UTXO
}


//序列化
func (outs *TxOutputs) Serilalize()[]byte{
    //1.创建一个buffer
    var result bytes.Buffer
    //2.创建一个编码器
    encoder := gob.NewEncoder(&result)
    //3.编码--->打包
    err := encoder.Encode(outs)
    if err != nil {
        log.Panic(err)
    }
    return result.Bytes()
}

//反序列化
func DeserializeTXOutputs(txOutputsBytes [] byte) *TxOutputs {
    var txOutputs TxOutputs
    var reader = bytes.NewReader(txOutputsBytes)
    //1.创建一个解码器
    decoder := gob.NewDecoder(reader)
    //解包
    err := decoder.Decode(&txOutputs)
    if err != nil {
        log.Panic(err)
    }
    return &txOutputs
}

3.2.2 创建UTXO_Set.go文件

打开day06_08_Update目录里的BLC包,创建UTXO_Set.go文件。

添加代码如下:

package BLC

import (
    "github.com/boltdb/bolt"
    "log"
    "encoding/hex"
    "fmt"
    "os"
    "bytes"
)

type UTXOSet struct {
    BlockChain *BlockChain
}

const utxoTableName = "utxoTable"

//重置数据库表
func (utxoSet *UTXOSet) ResetUTXOSet() {
    err := utxoSet.BlockChain.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            err := tx.DeleteBucket([]byte(utxoTableName))
            if err != nil {
                log.Panic("重置中,删除表失败。。")
            }

        }
        b, err := tx.CreateBucket([]byte(utxoTableName))
        if err != nil {
            log.Panic("重置中,创建新表失败。。")
        }
        if b != nil {
            txOutputMap := utxoSet.BlockChain.FindUnSpentOutputMap()
            //fmt.Println("未花费outputmap:",txOutputMap)
            for txIDStr, outputs := range txOutputMap {
                txID, _ := hex.DecodeString(txIDStr)
                b.Put(txID, outputs.Serilalize())
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
}

func (utxoSet *UTXOSet) GetBalance(address string) int64 {
    utxos := utxoSet.FindUnspentOutputsForAddress(address)
    var amount int64
    for _, utxo := range utxos {
        amount += utxo.Output.Value
        fmt.Println(address,"余额:",utxo.Output.Value)
    }
    //fmt.Printf("%s账户,有%d个Token\n",address,amount)
    return amount

}

func (utxoSet *UTXOSet) FindUnspentOutputsForAddress(address string) []*UTXO {
    var utxos [] *UTXO
    //查询数据,遍历所有的未花费
    err := utxoSet.BlockChain.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            c := b.Cursor()
            for k, v := c.First(); k != nil; k, v = c.Next() {
                //fmt.Printf("key=%s,value=%v\n", k, v)
                txOutputs := DeserializeTXOutputs(v)
                for _, utxo := range txOutputs.UTXOS {
                    if utxo.Output.UnLockWithAddress(address) {
                        utxos = append(utxos, utxo)
                    }
                }
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return utxos
}

//添加一个方法:用于查询给定地址下的,要转账使用的可以使用的utxo
func (utxoSet *UTXOSet) FindSpendableUTXOs(from string, amount int64, txs [] *Transaction) (int64, map[string][]int) {
    spentableUTXO := make(map[string][]int)
    var total int64 = 0
    //1.找出未打包的Transaction中未花费的
    unPackageUTXOs := utxoSet.FindUnPackageSpentableUTXOs(from, txs)
    for _, utxo := range unPackageUTXOs {
        total += utxo.Output.Value
        txIDStr := hex.EncodeToString(utxo.TxID)
        spentableUTXO[txIDStr] = append(spentableUTXO[txIDStr], utxo.Index)
        fmt.Println(amount,",未打包,转账花费:",utxo.Output.Value)
        if total >= amount {
            return total, spentableUTXO
        }
    }

    //钱不够
    //2.找出已经存在数据库中的未花费的
    err := utxoSet.BlockChain.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            c := b.Cursor()
        dbLoop:
            for k, v := c.First(); k != nil; k, v = c.Next() {
                txOutpus := DeserializeTXOutputs(v)
                for _, utxo := range txOutpus.UTXOS {
                    if utxo.Output.UnLockWithAddress(from) {
                        total += utxo.Output.Value
                        txIDStr := hex.EncodeToString(utxo.TxID)
                        spentableUTXO[txIDStr] = append(spentableUTXO[txIDStr], utxo.Index)
                        fmt.Println(amount,",数据库,转账花费:",utxo.Output.Value)
                        if total >= amount {
                            break dbLoop
                        }
                    }

                }
            }
        }
        return nil
    })
    if err != nil {
        log.Panic(err)
    }

    if total < amount {
        fmt.Printf("%s,账户余额不足,不能转账。。", from)
        os.Exit(1)
    }
    return total, spentableUTXO

}

func (utxoSet *UTXOSet) FindUnPackageSpentableUTXOs(from string, txs []*Transaction) []*UTXO {
    var unUTXOs []*UTXO
    //存储已经花费
    spentTxOutput := make(map[string][]int)

    for i := len(txs) - 1; i >= 0; i-- {
        unUTXOs = caculate(txs[i], from, spentTxOutput, unUTXOs)
    }
    return unUTXOs
}

//每次创建区块后,更新未花费的表
func (utxoSet *UTXOSet) Update() {
    /*
    每当创建新区块后,都会花掉一些原来的utxo,产生新的utxo。
    删除已经花费的,增加新产生的未花费
    表中存储的数据结构:
    key:交易ID
    value:TxInputs
        TxInputs里是UTXO数组

     */

    //1.获取最新的区块,由于该block的产生,
    newBlock := utxoSet.BlockChain.Iterator().Next()

    //2.遍历该区块的交易
    inputs := []*TXInput{}
    //spentUTXOMap := make(map[string]*TxOutputs)
    //未花费
    outsMap := make(map[string]*TxOutputs)

    //获取已经花费的
    for _, tx := range newBlock.Txs {
        if tx.IsCoinbaseTransaction(){
            continue
        }
        for _, in := range tx.Vins {
            inputs = append(inputs, in)
        }
    }
    fmt.Println("inputs的长度:", len(inputs), inputs)
    //以上是找出新添加的区块中的所有的Input

    //以下是找到新添加的区块中的未花费了的Output
    for _, tx := range newBlock.Txs {

        utoxs := []*UTXO{}

    outLoop:
        for index, out := range tx.Vouts {
            isSpent := false
            for _, in := range inputs {
                if bytes.Compare(in.TxID, tx.TxID) == 0 && in.Vout == index && bytes.Compare(out.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 {
                    isSpent = true
                    continue outLoop
                }
            }
            if isSpent == false {
                utxo := &UTXO{tx.TxID, index, out}
                utoxs = append(utoxs, utxo)
                fmt.Println("outsMaps,",out.Value)
            }
        }
        if len(utoxs) > 0 {
            txIDStr := hex.EncodeToString(tx.TxID)
            outsMap[txIDStr] = &TxOutputs{utoxs}
        }

    }
    fmt.Println("outsMap的长度:", len(outsMap), outsMap)
    /*
    outsMap[7788] = utxo
    outsMap[5555] = utxo,utxo
    outsMap[4444] = utxo

     */

    //删除已经花费了的
    err := utxoSet.BlockChain.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            //删除 ins中
            for i:=0;i<len(inputs);i++{

                in:=inputs[i]
                fmt.Println(i,"=========================")

                //txIDStr:=hex.EncodeToString(in.TxID)
                //outputs:=outsMap[txIDStr]
                //if len(outputs.UTXOS) > 0{
                //    utxos:=[]*UTXO{}
                //    for _,utxo:=range outputs.UTXOS{
                //        if bytes.Compare(utxo.Output.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 && in.Vout ==utxo.Index {
                //
                //        }else{
                //            utxos = append(utxos,utxo)
                //        }
                //    }
                //
                //    outsMap[txIDStr] = &TxOutputs{utxos}
                //
                //
                //}else{
                txOutputsBytes := b.Get(in.TxID)
                if len(txOutputsBytes) == 0 {
                    //fmt.Println("break",i)
                    continue
                }

                txOutputs := DeserializeTXOutputs(txOutputsBytes)

                //根据IxID,如果该txOutputs中已经有output被新区块花掉了,那么将未花掉的添加到utxos里,并标记该txouputs要删除
                // 判断是否需要
                isNeedDelete := false
                utxos := []*UTXO{} //存储未花费
                for _, utxo := range txOutputs.UTXOS {
                    if bytes.Compare(utxo.Output.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 && in.Vout == utxo.Index {
                        isNeedDelete = true
                    } else {
                        utxos = append(utxos, utxo)
                    }
                }
                fmt.Println(len(utxos))
                if isNeedDelete == true {
                    b.Delete(in.TxID)
                    if len(utxos) > 0 {
                        //该删除的txoutputs中还有其他未花费的
                        //preTXOutputs := outsMap[hex.EncodeToString(in.TxID)]
                        //preTXOutputs.UTXOS = append(preTXOutputs.UTXOS,utxos...)
                        //outsMap[hex.EncodeToString(in.TxID)] = preTXOutputs

                        txOutputs := &TxOutputs{utxos}
                        b.Put(in.TxID, txOutputs.Serilalize())
                        fmt.Println("删除时:map:", len(outsMap), outsMap)
                    }

                }

                //}

            }

            //增加
            for keyID, outPuts := range outsMap {
                keyHashBytes, _ := hex.DecodeString(keyID)
                b.Put(keyHashBytes, outPuts.Serilalize())
            }

        }

        return nil
    })

    if err != nil {
        log.Panic(err)
    }
}

3.2.3 修改CLI_createBlockChain.go

打开day06_08_Update目录里的BLC包。修改CLI_createBlockChain.go文件。

修改步骤:

修改步骤:
step1:添加createGenesisBlockchain()方法,创建完创世区块后,重置utxoSet。

修改完后代码如下:

package BLC

func (cli *CLI) createGenesisBlockchain(address string){
    //fmt.Println(data)
    CreateBlockChainWithGenesisBlock(address)


    bc:=GetBlockchainObject()
    defer bc.DB.Close()
    if bc != nil{
        utxoSet:=&UTXOSet{bc}
        utxoSet.ResetUTXOSet()
    }

}

3.2.4 修改CLI_getBalance.go文件

打开day06_08_Update目录里的BLC包。修改CLI_getBalance.go文件。

修改步骤:

修改步骤:
step1:修改getBalance()方法,查询余额从utxoSet中查询。

修改完后代码如下:

package BLC

import (
    "fmt"
    "os"
)

//查询余额
func (cli *CLI)getBalance(address string){
    fmt.Println("查询余额:",address)
    bc := GetBlockchainObject()

    if bc == nil{
        fmt.Println("数据库不存在,无法查询。。")
        os.Exit(1)
    }
    defer bc.DB.Close()
    //txOutputs:= bc.UnUTXOs(address)
    //for i,out:=range txOutputs{
    //    fmt.Println(i,"---->",out)
    //}
    //balance:=bc.GetBalance(address,[]*Transaction{})
    utxoSet:=&UTXOSet{bc}
    balance:= utxoSet.GetBalance(address)
    fmt.Printf("%s,一共有%d个Token\n",address,balance)
}

3.2.5 修改Transaction.go文件

打开day06_08_Update目录里的BLC包。修改Transaction.go文件。

修改步骤:

修改步骤:
step1:修改NewSimpleTransaction()方法,从utxoSet中查找转账要使用的utxo。

修改完后代码如下:


package BLC

import (
    "bytes"
    "encoding/gob"
    "log"
    "crypto/sha256"
    "encoding/hex"
    "crypto/ecdsa"
    "crypto/rand"
    "crypto/elliptic"
    "math/big"
    "time"
)

//step1:创建Transaction结构体
type Transaction struct {
    //1.交易ID
    TxID []byte
    //2.输入
    Vins []*TXInput
    //3.输出
    Vouts [] *TXOuput
}

//step2:
/*
Transaction 创建分两种情况
1.创世区块创建时的Transaction

2.转账时产生的Transaction

 */
func NewCoinBaseTransaction(address string) *Transaction {
    txInput := &TXInput{[]byte{}, -1, nil, []byte{}}
    //txOutput := &TXOuput{10, address}
    txOutput := NewTXOuput(10, address)
    txCoinbase := &Transaction{[]byte{}, []*TXInput{txInput}, []*TXOuput{txOutput}}
    //设置hash值
    //txCoinbase.HashTransaction()
    txCoinbase.SetTxID()
    return txCoinbase
}

//设置交易ID,其实就是hash
func (tx *Transaction) SetTxID() {
    var buff bytes.Buffer
    encoder := gob.NewEncoder(&buff)
    err := encoder.Encode(tx)
    if err != nil {
        log.Panic(err)
    }

    buffBytes:=bytes.Join([][]byte{IntToHex(time.Now().Unix()),buff.Bytes()},[]byte{})

    hash := sha256.Sum256(buffBytes)
    tx.TxID = hash[:]
}

func NewSimpleTransaction(from, to string, amount int64, utxoSet *UTXOSet, txs []*Transaction) *Transaction {
    var txInputs [] *TXInput
    var txOutputs [] *TXOuput

    //balance, spendableUTXO := bc.FindSpendableUTXOs(from, amount, txs)
    balance, spendableUTXO := utxoSet.FindSpendableUTXOs(from, amount, txs)

    //代表消费

    //bytes,_:=hex.DecodeString("558e697b0f0f7adfcb6b8f8b542fb5980cd78a58a103a7a9a19e274aabefed5e")
    //bytes,_:=hex.DecodeString("0704aabb159bd1b276dc819b85e49581d80eb53aba69a48c84dc12442b799d8e")

    //txInput := &TXInput{bytes, 0, from}
    //txInputs = append(txInputs, txInput)

    //获取钱包
    wallets := NewWallets()
    wallet := wallets.WalletsMap[from]

    for txID, indexArray := range spendableUTXO {
        txIDBytes, _ := hex.DecodeString(txID)
        for _, index := range indexArray {
            txInput := &TXInput{txIDBytes, index, nil, wallet.PublicKey}
            txInputs = append(txInputs, txInput)
        }
    }

    //转账
    //txOutput1 := &TXOuput{amount, to}
    txOutput1 := NewTXOuput(amount, to)
    txOutputs = append(txOutputs, txOutput1)

    //找零
    //txOutput2 := &TXOuput{10 - amount, from}
    //txOutput2 := &TXOuput{4 - amount, from}
    //txOutput2 := &TXOuput{balance - amount, from}
    txOutput2 := NewTXOuput(balance-amount, from)

    txOutputs = append(txOutputs, txOutput2)

    tx := &Transaction{[]byte{}, txInputs, txOutputs}
    //设置hash值
    tx.SetTxID()

    //进行签名
    utxoSet.BlockChain.SignTransaction(tx, wallet.PrivateKey,txs)

    return tx
}

//判断当前交易是否是Coinbase交易
func (tx *Transaction) IsCoinbaseTransaction() bool {
    return len(tx.Vins[0].TxID) == 0 && tx.Vins[0].Vout == -1
}

//签名
//正如上面提到的,为了对一笔交易进行签名,我们需要获取交易输入所引用的输出,因为我们需要存储这些输出的交易。
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]*Transaction) {
    //1.如果时coinbase交易,无需签名
    if tx.IsCoinbaseTransaction() {
        return
    }

    //2.input没有对应的transaction,无法签名
    for _, vin := range tx.Vins {
        if prevTXs[hex.EncodeToString(vin.TxID)].TxID == nil {
            log.Panic("当前的input没有对应的transaction")
        }
    }

    //3.获取Transaction的部分数据的副本
    txCopy:=tx.TrimmedCopy()

    //4.
    for index,input:=range txCopy.Vins{
        prevTx := prevTXs[hex.EncodeToString(input.TxID)]
        //为txCopy设置新的交易ID:txID->[]byte{},Vout,sign-->nil, publlicKey-->对应输出的公钥哈希
        input.Signature = nil//双保险
        input.PublicKey = prevTx.Vouts[input.Vout].PubKeyHash//设置input的公钥为对应输出的公钥哈希
        data := txCopy.getData()//设置新的txID

        input.PublicKey = nil//再将publicKey置为nil

        //签名
        /*
        通过 privKey 对 txCopy.ID 进行签名。
        一个 ECDSA 签名就是一对数字,我们对这对数字连接起来,并存储在输入的 Signature 字段。
         */
        r,s,err := ecdsa.Sign(rand.Reader,&privKey,data)
        if err != nil{
            log.Panic(err)
        }
        signature:=append(r.Bytes(),s.Bytes()...)
        tx.Vins[index].Signature = signature

    }
}

//获取签名所需要的Transaction的副本
//创建tx的副本:需要剪裁数据
/*
TxID,
[]*TxInput,
    TxInput中,去除sign,publicKey
[]*TxOutput

这个副本包含了所有的输入和输出,但是 TXInput.Signature 和 TXIput.PubKey 被设置为 nil。
 */
func (tx *Transaction) TrimmedCopy() Transaction {
    var inputs [] *TXInput
    var outputs [] *TXOuput
    for _, input := range tx.Vins {
        inputs = append(inputs, &TXInput{input.TxID, input.Vout, nil, nil})
    }
    for _, output := range tx.Vouts {
        outputs = append(outputs, &TXOuput{output.Value, output.PubKeyHash})
    }
    txCopy := Transaction{tx.TxID, inputs, outputs}
    return txCopy

}

func (tx *Transaction) Serialize() []byte {
    jsonByte,err := json.Marshal(tx)
    if err != nil{
        //fmt.Println("序列化失败:",err)
        log.Panic(err)
    }
    return jsonByte
}

func (tx Transaction)getData()[]byte{
    txCopy :=tx
    txCopy.TxID=[]byte{}
    hash:=sha256.Sum256(txCopy.Serialize())
    return hash[:]
}

//验证数字签名
func (tx *Transaction) Verify(prevTXs map[string]*Transaction)bool{
    if tx.IsCoinbaseTransaction() {
        return true
    }

    //2.input没有对应的transaction,无法签名
    for _, vin := range tx.Vins {
        if prevTXs[hex.EncodeToString(vin.TxID)].TxID == nil {
            log.Panic("当前的input没有对应的transaction,无法验证。。")
        }
    }
    txCopy:=tx.TrimmedCopy()

    curve:=elliptic.P256()
    for index,input:=range tx.Vins{
        prevTx:=prevTXs[hex.EncodeToString(input.TxID)]
        txCopy.Vins[index].Signature = nil
        txCopy.Vins[index].PublicKey = prevTx.Vouts[input.Vout].PubKeyHash
        data := txCopy.getData()
        txCopy.Vins[index].PublicKey = nil

        //签名中的s和r
        r:=big.Int{}
        s:=big.Int{}
        sigLen:=len(input.Signature)
        r.SetBytes(input.Signature[:sigLen/2])
        s.SetBytes(input.Signature[sigLen/2:])

        //通过公钥,产生新的s和r,与原来的进行对比
        x:=big.Int{}
        y:=big.Int{}
        keyLen:=len(input.PublicKey)
        x.SetBytes(input.PublicKey[:keyLen/2])
        y.SetBytes(input.PublicKey[keyLen/2:])

        //根据椭圆曲线,以及x,y获取公钥
        //我们使用从输入提取的公钥创建了一个 ecdsa.PublicKey
        rawPubKey:=ecdsa.PublicKey{curve,&x,&y}//
        //这里我们解包存储在 TXInput.Signature 和 TXInput.PubKey 中的值,
        // 因为一个签名就是一对数字,一个公钥就是一对坐标。
        // 我们之前为了存储将它们连接在一起,现在我们需要对它们进行解包在 crypto/ecdsa 函数中使用。

        //验证
        //在这里:我们使用从输入提取的公钥创建了一个 ecdsa.PublicKey,通过传入输入中提取的签名执行了 ecdsa.Verify。
        // 如果所有的输入都被验证,返回 true;如果有任何一个验证失败,返回 false.
        if ecdsa.Verify(&rawPubKey,data,&r,&s) ==false{
            //公钥,要验证的数据,签名的r,s
            return false
        }
    }
    return true

}

3.2.6 修改CLI_send.go文件

打开day06_08_Update目录里的BLC包。修改CLI_send.go文件。

修改步骤:

修改步骤:
step1:修改send()方法,转账之后更新utxoSet。

修改完后代码如下:

package BLC

import (
    "fmt"
    "os"
)

//转账
func (cli *CLI) send(from, to, amount [] string) {
    if !dbExists() {
        fmt.Println("数据库不存在。。。")
        os.Exit(1)
    }
    blockchain := GetBlockchainObject()

    blockchain.MineNewBlock(from, to, amount)
    defer blockchain.DB.Close()

    utxoSet:=&UTXOSet{blockchain}
    //转账成功以后,需要更新
    //utxoSet.ResetUTXOSet()
    utxoSet.Update()
}

3.2.7 修改BlockChain.go文件

打开day06_08_Update目录里的BLC包。修改BlockChain.go文件。

修改步骤:

修改步骤:
step1:添加FindUnSpentOutputMap()方法
    查询未花费的Output
step2:修改MineNewBlock(),
    添加utxoset对象

修改完后代码如下:

package BLC

import (
    "github.com/boltdb/bolt"
    "os"
    "fmt"
    "log"
    "math/big"
    "time"
    "strconv"
    "encoding/hex"
    "crypto/ecdsa"
    "bytes"
)

type BlockChain struct {
    //Blocks []*Block //存储有序的区块
    Tip [] byte  // 最近的取快递Hash值
    DB  *bolt.DB //数据库对象
}

//修改该方法
/*
1.仅仅用来创建区块链
如果数据库存在,证明区块链存在,直接结束该方法
否则进行创建创世区块,并存入数据库中
 */
func CreateBlockChainWithGenesisBlock(address string) {
    if dbExists() {
        fmt.Println("数据库已经存在。。。")
        return
    }

    //
    fmt.Println("创建创世区块:")
    //2.数据库不存在,说明第一次创建,然后存入到数据库中
    fmt.Println("数据库不存在。。")
    //A:创建创世区块
    //创建创世区块
    //先创建coinbase交易
    txCoinBase := NewCoinBaseTransaction(address)
    genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase})
    //B:打开数据库
    db, err := bolt.Open(DBNAME, 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
    //defer db.Close()
    //C:存入数据表
    err = db.Update(func(tx *bolt.Tx) error {
        b, err := tx.CreateBucket([]byte(BLOCKTABLENAME))
        if err != nil {
            log.Panic(err)
        }
        if b != nil {
            err = b.Put(genesisBlock.Hash, genesisBlock.Serilalize())
            if err != nil {
                log.Panic("创世区块存储有误。。。")
            }
            //存储最新区块的hash
            b.Put([]byte("l"), genesisBlock.Hash)
        }
        return nil
    })
    if err != nil {
        log.Panic(err)
    }

    //返回区块链对象
    //return &BlockChain{genesisBlock.Hash, db}
}

func (bc *BlockChain) AddBlockToBlockChain(txs [] *Transaction) {
    //创建新区块
    //newBlock := NewBlock(data,prevHash,height)
    //添加到切片中
    //bc.Blocks = append(bc.Blocks,newBlock)
    //1.更新数据库
    err := bc.DB.Update(func(tx *bolt.Tx) error {
        //2.打开表
        b := tx.Bucket([]byte(BLOCKTABLENAME))
        if b != nil {
            //2.根据最新块的hash读取数据,并反序列化最后一个区块
            blockBytes := b.Get(bc.Tip)
            lastBlock := DeserializeBlock(blockBytes)
            //3.创建新的区块
            newBlock := NewBlock(txs, lastBlock.Hash, lastBlock.Height+1)
            //4.将新的区块序列化并存储
            err := b.Put(newBlock.Hash, newBlock.Serilalize())
            if err != nil {
                log.Panic(err)
            }
            //5.更新最后一个哈希值,以及blockchain的tip
            b.Put([]byte("l"), newBlock.Hash)
            bc.Tip = newBlock.Hash
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }

}

//判断数据库是否存在
func dbExists() bool {
    if _, err := os.Stat(DBNAME); os.IsNotExist(err) {
        return false
    }
    return true
}

/*
func (bc *BlockChain) PrintChains() {
    //1.根据bc的tip,获取最新的hash值,表示当前的hash
    var currentHash = bc.Tip
    //2.循环,根据当前hash读取数据,反序列化得到最后一个区块
    var count = 0
    block := new(Block) // var block *Block
    for {
        err := bc.DB.View(func(tx *bolt.Tx) error {
            b := tx.Bucket([]byte(BLOCKTABLENAME))

            if b != nil {
                count++
                fmt.Printf("第%d个区块的信息:\n", count)
                //获取当前hash对应的数据,并进行反序列化
                blockBytes := b.Get(currentHash)
                block = DeserializeBlock(blockBytes)
                fmt.Printf("\t高度:%d\n", block.Height)
                fmt.Printf("\t上一个区块的hash:%x\n", block.PrevBlockHash)
                fmt.Printf("\t当前的hash:%x\n", block.Hash)
                fmt.Printf("\t数据:%s\n", block.Data)
                //fmt.Printf("\t时间:%v\n", block.TimeStamp)
                fmt.Printf("\t时间:%s\n",time.Unix(block.TimeStamp,0).Format("2006-01-02 15:04:05"))
                fmt.Printf("\t次数:%d\n", block.Nonce)
            }

            return nil
        })
        if err != nil {
            log.Panic(err)
        }
        //3.直到父hash值为0
        hashInt := new(big.Int)
        hashInt.SetBytes(block.PrevBlockHash)
        if big.NewInt(0).Cmp(hashInt) == 0 {
            break
        }
        //4.更新当前区块的hash值
        currentHash = block.PrevBlockHash
    }
}
*/

//2.获取一个迭代器的方法
func (bc *BlockChain) Iterator() *BlockChainIterator {
    return &BlockChainIterator{bc.Tip, bc.DB}
}

func (bc *BlockChain) PrintChains() {
    //1.获取迭代器对象
    bcIterator := bc.Iterator()

    //2.循环迭代
    for {
        block := bcIterator.Next()
        fmt.Printf("第%d个区块的信息:\n", block.Height+1)
        //获取当前hash对应的数据,并进行反序列化
        fmt.Printf("\t高度:%d\n", block.Height)
        fmt.Printf("\t上一个区块的hash:%x\n", block.PrevBlockHash)
        fmt.Printf("\t当前的hash:%x\n", block.Hash)
        //fmt.Printf("\t数据:%v\n", block.Txs)
        fmt.Println("\t交易:")
        for _, tx := range block.Txs {
            fmt.Printf("\t\t交易ID:%x\n", tx.TxID)
            fmt.Println("\t\tVins:")
            for _, in := range tx.Vins {
                fmt.Printf("\t\t\tTxID:%x\n", in.TxID)
                fmt.Printf("\t\t\tVout:%d\n", in.Vout)
                fmt.Printf("\t\t\tPublicKey:%v\n", in.PublicKey)
            }
            fmt.Println("\t\tVouts:")
            for _, out := range tx.Vouts {
                fmt.Printf("\t\t\tvalue:%d\n", out.Value)
                fmt.Printf("\t\t\tPubKeyHash:%v\n", out.PubKeyHash)
            }
        }

        //fmt.Printf("\t时间:%v\n", block.TimeStamp)
        fmt.Printf("\t时间:%s\n", time.Unix(block.TimeStamp, 0).Format("2006-01-02 15:04:05"))
        fmt.Printf("\t次数:%d\n", block.Nonce)

        //3.直到父hash值为0
        hashInt := new(big.Int)
        hashInt.SetBytes(block.PrevBlockHash)
        if big.NewInt(0).Cmp(hashInt) == 0 {
            break
        }
    }
}

//新增方法,用于获取区块链
func GetBlockchainObject() *BlockChain {
    /*
    1.如果数据库不存在,直接返回nil
    2.读取数据库
     */
    if !dbExists() {
        fmt.Println("数据库不存在,无法获取区块链。。")
        return nil
    }

    db, err := bolt.Open(DBNAME, 0600, nil)
    if err != nil {
        log.Fatal(err)
    }

    //defer db.Close()
    var blockchain *BlockChain
    //B:读取数据库
    err = db.View(func(tx *bolt.Tx) error {
        //C:打开表
        b := tx.Bucket([]byte(BLOCKTABLENAME))
        if b != nil {
            //D:读取最后一个hash
            hash := b.Get([]byte("l"))
            //E:创建blockchain
            blockchain = &BlockChain{hash, db}
        }
        return nil
    })
    if err != nil {
        log.Fatal(err)
    }
    return blockchain
}

//挖掘新的区块
func (bc *BlockChain) MineNewBlock(from, to, amount []string) {
    /*
    ./bc send -from '["wangergou"]' -to '["lixiaohua"]' -amount '["4"]'
["wangergou"]
["lixiaohua"]
["4"]

     */
    //fmt.Println(from)
    //fmt.Println(to)
    //fmt.Println(amount)
    //1.新建交易
    //2.新建区块
    //3.将区块存入到数据库
    var txs []*Transaction

    //奖励
    tx := NewCoinBaseTransaction(from[0])
    txs = append(txs, tx)

    utxoSet:=&UTXOSet{bc}


    for i := 0; i < len(from); i++ {

        amountInt, _ := strconv.ParseInt(amount[i], 10, 64)
        tx := NewSimpleTransaction(from[i], to[i], amountInt, utxoSet, txs)

        txs = append(txs, tx)
    }



    var block *Block    //数据库中的最后一个block
    var newBlock *Block //要创建的新的block
    bc.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(BLOCKTABLENAME))
        if b != nil {
            hash := b.Get([] byte("l"))
            blockBytes := b.Get(hash)
            block = DeserializeBlock(blockBytes) //数据库中的最后一个block
        }
        return nil
    })

    //在建立新区块钱,对txs进行签名验证
    _txs :=[]*Transaction{}
    for _, tx := range txs {
        if bc.VerifyTransaction(tx,_txs) !=true{
            log.Panic("签名验证失败。。")
        }
        _txs = append(_txs,tx)
    }



    newBlock = NewBlock(txs, block.Hash, block.Height+1)

    bc.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(BLOCKTABLENAME))
        if b != nil {
            b.Put(newBlock.Hash, newBlock.Serilalize())
            b.Put([]byte("l"), newBlock.Hash)
            bc.Tip = newBlock.Hash
        }
        return nil
    })

}

//找到所有未花费的交易输出
func (bc *BlockChain) UnUTXOs(address string, txs []*Transaction) []*UTXO {
    /*
    1.遍历数据库,获取每个块中的Transaction
    2.判断是否被
     */
    var unUTXOs []*UTXO                      //未花费
    spentTxOutputs := make(map[string][]int) //存储已经花费

    //添加先从txs遍历,查找未花费
    //for i, tx := range txs {
    for i:=len(txs)-1;i>=0;i--{
        unUTXOs = caculate(txs[i], address, spentTxOutputs, unUTXOs)
    }

    bcIterator := bc.Iterator()
    for {
        block := bcIterator.Next()
        //统计未花费
        //1.获取block中的每个Transaction
        //for _, tx := range block.Txs {
        //    unUTXOs = caculate(tx, address, spentTxOutputs, unUTXOs)
        //}
        for i := len(block.Txs) - 1; i >= 0; i-- {
            unUTXOs = caculate(block.Txs[i], address, spentTxOutputs, unUTXOs)
        }

        //结束迭代
        hashInt := new(big.Int)
        hashInt.SetBytes(block.PrevBlockHash)
        if big.NewInt(0).Cmp(hashInt) == 0 {
            break
        }
    }
    return unUTXOs
}

func (bc *BlockChain) GetBalance(address string, txs []*Transaction) int64 {
    //txOutputs:=bc.UnUTXOs(address)
    unUTXOs := bc.UnUTXOs(address, txs)
    fmt.Println(address, unUTXOs)
    var amount int64
    for _, utxo := range unUTXOs {
        amount = amount + utxo.Output.Value
    }
    return amount

}

//转账时查获在可用的UTXO
func (bc *BlockChain) FindSpendableUTXOs(from string, amount int64, txs []*Transaction) (int64, map[string][]int) {
    /*
    1.获取所有的UTXO
    2.遍历UTXO

    返回值:map[hash]{index}
     */

    var balance int64
    utxos := bc.UnUTXOs(from, txs)
    fmt.Println(from,utxos)
    spendableUTXO := make(map[string][]int)
    for _, utxo := range utxos {
        balance += utxo.Output.Value
        hash := hex.EncodeToString(utxo.TxID)
        spendableUTXO[hash] = append(spendableUTXO[hash], utxo.Index)
        if balance >= amount {
            break
        }
    }
    if balance < amount {
        fmt.Printf("%s 余额不足。。总额:%d,需要:%d\n", from,balance,amount)
        os.Exit(1)
    }
    return balance, spendableUTXO

}

func caculate(tx *Transaction, address string, spentTxOutputs map[string][]int, unUTXOs []*UTXO) []*UTXO {
    //2.先遍历TxInputs,表示花费
    if !tx.IsCoinbaseTransaction() {
        for _, in := range tx.Vins {
            //如果解锁
            fullPayloadHash:=Base58Decode([]byte(address))
            pubKeyHash:=fullPayloadHash[1:len(fullPayloadHash)-addressChecksumLen]

            if in.UnLockWithAddress(pubKeyHash) {
                key := hex.EncodeToString(in.TxID)
                spentTxOutputs[key] = append(spentTxOutputs[key], in.Vout)
            }
        }
    }

    //fmt.Println("===>", spentTxOutputs)
    //3.遍历TxOutputs
outputs:
    for index, out := range tx.Vouts {
        if out.UnLockWithAddress(address) {
            //fmt.Println("height,", block.Height, ",index---", index, out, "map-->", spentTxOutputs, len(spentTxOutputs))
            //如果对应的花费容器中长度不为0,
            if len(spentTxOutputs) != 0 {
                var isSpentUTXO bool

                for txID, indexArray := range spentTxOutputs {
                    for _, i := range indexArray {
                        if i == index && txID == hex.EncodeToString(tx.TxID) {
                            isSpentUTXO = true
                            continue outputs
                        }
                    }
                }
                if !isSpentUTXO {
                    utxo := &UTXO{tx.TxID, index, out}
                    unUTXOs = append(unUTXOs, utxo)
                    //unSpentTxOutputs = append(unSpentTxOutputs, out)
                }

            } else {
                utxo := &UTXO{tx.TxID, index, out}
                unUTXOs = append(unUTXOs, utxo)
                //unSpentTxOutputs = append(unSpentTxOutputs, out)
            }
            //fmt.Println(block.Height, "   ", index, "----....", unUTXOs)
        }
    }
    return unUTXOs
}

//添加方法
func (bc *BlockChain) SignTransaction(tx *Transaction,privKey ecdsa.PrivateKey,txs []*Transaction){
    if tx.IsCoinbaseTransaction(){
        return
    }
    prevTxs:=make(map[string]*Transaction)
    for _,vin :=range tx.Vins{
        prevTx:=bc.FindTransactionByTxID(vin.TxID,txs)
        prevTxs[hex.EncodeToString(prevTx.TxID)] = prevTx
    }

    tx.Sign(privKey,prevTxs)
}

//根据交易ID查找对应的Transaction
func (bc *BlockChain) FindTransactionByTxID(txID[]byte,txs []*Transaction)*Transaction{
    itertaor:=bc.Iterator()
    //先遍历txs
    for _,tx:=range txs{
        if bytes.Compare(txID,tx.TxID) ==0{
            return tx
        }
    }


    for{
        block:=itertaor.Next()
        for _,tx:=range block.Txs{
            if bytes.Compare(txID,tx.TxID) == 0{
                return tx
            }
        }

        var hashInt big.Int
        hashInt.SetBytes(block.PrevBlockHash)
        if big.NewInt(0).Cmp(&hashInt) == 0{
            break
        }
    }
    return &Transaction{}
}



//验证数字签名:
func (bc *BlockChain) VerifyTransaction(tx *Transaction,txs []*Transaction)bool{
    prevTXs :=make(map[string] *Transaction)
    for _,vin:=range tx.Vins{
        prevTx := bc.FindTransactionByTxID(vin.TxID,txs)
        prevTXs[hex.EncodeToString(prevTx.TxID)] = prevTx
    }
    return tx.Verify(prevTXs)
}



//新增方法
/*
查询未花费的Output
[string] *TxOutputs
 */
func (bc *BlockChain) FindUnSpentOutputMap() map[string]*TxOutputs {
    iterator := bc.Iterator()

    //存储已经花费:·[txID], txInput
    spentUTXOsMap := make(map[string][]*TXInput)

    //存储未花费
    unSpentOutputMaps := make(map[string]*TxOutputs)

    for {
        block := iterator.Next()

        for i := len(block.Txs) - 1; i >= 0; i-- {
            txOutputs := &TxOutputs{[]*UTXO{}}
            tx := block.Txs[i]

            if !tx.IsCoinbaseTransaction() {
                for _, txInput := range tx.Vins {
                    key := hex.EncodeToString(txInput.TxID)
                    spentUTXOsMap[key] = append(spentUTXOsMap[key], txInput)
                }
            }

            txID := hex.EncodeToString(tx.TxID)
        work:
            for index, out := range tx.Vouts {
                txInputs := spentUTXOsMap[txID]
                if len(txInputs) > 0 {
                    var isSpent bool
                    for _, input := range txInputs {
                        inputPubKeyHash := PubKeyHash(input.PublicKey)
                        if bytes.Compare(inputPubKeyHash, out.PubKeyHash) == 0 {
                            if input.Vout == index {
                                isSpent = true
                                continue work
                            }
                        }
                    }
                    if isSpent == false {
                        utxo:=&UTXO{tx.TxID,index,out}
                        txOutputs.UTXOS = append(txOutputs.UTXOS, utxo)
                    }

                } else {
                    utxo:=&UTXO{tx.TxID,index,out}
                    txOutputs.UTXOS = append(txOutputs.UTXOS, utxo)
                }
            }
            //设置
            unSpentOutputMaps[txID] = txOutputs
        }

        //停止迭代
        var hashInt big.Int
        hashInt.SetBytes(block.PrevBlockHash)

        if hashInt.Cmp(big.NewInt(0)) == 0 {
            break
        }
    }
    return unSpentOutputMaps
}

3.2.8 main.go无需修改

main.go中代码不变,依然如下:

package main

import (
    "./BLC"

)

func main() {
    //1.测试Block
    //block:=BLC.NewBlock("I am a block",make([]byte,32,32),1)
    //fmt.Println(block)
    //2.测试创世区块
    //genesisBlock :=BLC.CreateGenesisBlock("Genesis Block..")
    //fmt.Println(genesisBlock)

    //3.测试区块链
    //genesisBlockChain := BLC.CreateBlockChainWithGenesisBlock()
    //fmt.Println(genesisBlockChain)
    //fmt.Println(genesisBlockChain.Blocks)
    //fmt.Println(genesisBlockChain.Blocks[0])

    //4.测试添加新区块
    //blockChain:=BLC.CreateBlockChainWithGenesisBlock()
    //blockChain.AddBlockToBlockChain("Send 100RMB To Wangergou",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
    //blockChain.AddBlockToBlockChain("Send 300RMB To lixiaohua",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
    //blockChain.AddBlockToBlockChain("Send 500RMB To rose",blockChain.Blocks[len(blockChain.Blocks)-1].Height+1,blockChain.Blocks[len(blockChain.Blocks)-1].Hash)
    //
    //fmt.Println(blockChain)

    //5.测试序列化和反序列化
    //block:=BLC.NewBlock("helloworld",make([]byte,32,32),0)
    //data:=block.Serilalize()
    //fmt.Println(block)
    //fmt.Println(data)
    //block2:=BLC.DeserializeBlock(data)
    //fmt.Println(block2)

    //6.创建区块,存入数据库
    //打开数据库
    //block:=BLC.NewBlock("helloworld",make([]byte,32,32),0)
    //db,err := bolt.Open("my.db",0600,nil)
    //if err != nil{
    //    log.Fatal(err)
    //}
    //
    //defer db.Close()
    //
    //err = db.Update(func(tx *bolt.Tx) error {
    //    //获取bucket,没有就创建新表
    //    b := tx.Bucket([]byte("blocks"))
    //    if b == nil{
    //        b,err = tx.CreateBucket([] byte("blocks"))
    //        if err !=nil{
    //            log.Panic("创建表失败")
    //        }
    //    }
    //    //添加数据
    //    err  = b.Put([]byte("l"),block.Serilalize())
    //    if err !=nil{
    //        log.Panic(err)
    //    }
    //
    //    return nil
    //})
    //if err != nil{
    //    log.Panic(err)
    //}
    //err = db.View(func(tx *bolt.Tx) error {
    //    b := tx.Bucket([]byte("blocks"))
    //    if b !=nil{
    //        data := b.Get([]byte("l"))
    //        //fmt.Printf("%s\n",data)//直接打印会乱码
    //        //反序列化
    //        block2:=BLC.DeserializeBlock(data)
    //        //fmt.Println(block2)
    //        fmt.Printf("%v\n",block2)
    //
    //    }
    //    return nil
    //})

    //7.测试创世区块存入数据库
    //blockchain:=BLC.CreateBlockChainWithGenesisBlock("Genesis Block..")
    //fmt.Println(blockchain)
    //defer blockchain.DB.Close()

    //8.测试新添加的区块
    //blockchain.AddBlockToBlockChain("Send 100RMB to wangergou")
    //blockchain.AddBlockToBlockChain("Send 100RMB to lixiaohua")
    //blockchain.AddBlockToBlockChain("Send 100RMB to rose")
    //fmt.Println(blockchain)
    //blockchain.PrintChains()

    //9.CLI操作
    cli:=BLC.CLI{}
    cli.Run()


}

4. 优化内容讲解

4.1 奖励Reward

在上一篇文章中,我们略过的一个小细节是挖矿奖励。现在,我们已经可以来完善这个细节了。

挖矿奖励,实际上就是一笔coinbase 交易。当一个挖矿节点开始挖出一个新块时,它会将交易从队列中取出,并在前面附加一笔coinbase交易。coinbase 交易只有一个输出,里面包含了矿工的公钥哈希。

实现奖励,非常简单,更新 MineNewBlock() 即可:

//挖掘新的区块
func (bc *BlockChain) MineNewBlock(from, to, amount []string) {

    var txs []*Transaction

    //奖励
    tx := NewCoinBaseTransaction(from[0])
    txs = append(txs, tx)

    utxoSet:=&UTXOSet{bc}


    for i := 0; i < len(from); i++ {

        amountInt, _ := strconv.ParseInt(amount[i], 10, 64)
        tx := NewSimpleTransaction(from[i], to[i], amountInt, utxoSet, txs)

        txs = append(txs, tx)
    }


...

}

在我们的实现中,创建交易的第一个人同时挖出了新块,所以会得到一笔奖励。

在项目添加了奖励之后,我们进行测试,首先在终端创建3个地址:

hanru:day06_08_Update ruby$ go build -o bc main.go
hanru:day06_08_Update ruby$ ls
hanru:day06_08_Update ruby$ ./bc
hanru:day06_08_Update ruby$ ./bc createwallet
hanru:day06_08_Update ruby$ ./bc createwallet
hanru:day06_08_Update ruby$ ./bc createwallet
hanru:day06_08_Update ruby$ ./bc addresslists

运行结果如下:

http://img.kongyixueyuan.com/0903_%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C1.png

接下来我们创建创世区块,并进行一次转账:

hanru:day06_08_Update ruby$ ./bc createblockchain -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'
hanru:day06_08_Update ruby$ ./bc send -from '["1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w"]' -to '["1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY"]' -amount '["4"]'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'hanru:day06_08_Update ruby$ ./bc getbalance -address '1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY'

运行结果如下:

http://img.kongyixueyuan.com/0904_%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C2.png

说明:

  1. 比如创世区块中1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w地址有10个Token,
  2. 转账给1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY地址4个Token,
  3. 那么1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w的余额是16(转账找零6个,加上得到奖励10个),
  4. 1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY的余额是4。

接下来我们测试一下,一次转账多笔交易:

hanru:day06_08_Update ruby$ ./bc send -from '["1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w","1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY"]' -to '["1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY","19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL"]' -amount '["9","6"]'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY'
hanru:day06_08_Update ruby$ ./bc getbalance -address '19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL'

运行结果如下:

http://img.kongyixueyuan.com/0905_%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C3.png

说明:

  1. 1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w地址的余额是16,

  2. 1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY地址的余额是4。

  3. 本次转账,1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w地址转账给1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY地址9个,同时1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY地址转账给19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL地址6个。

  4. 余额:

    1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w地址余额:转账后剩余7,加系统奖励10,一共17个Token

    1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY地址余额:原来余额4,加上接收转来的9,转出6,剩余7个Token

    19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL地址余额:9个Token。

4.2 UTXO 集

在持久化的章节中,我们研究了 Bitcoin Core 是如何在一个数据库中存储块的,并且了解到区块被存储在 blocks 数据库,交易输出被存储在 chainstate 数据库。会回顾一下 chainstate 的结构:

  1. c + 32 字节的交易哈希 -> 该笔交易的未花费交易输出记录
  2. B + 32 字节的块哈希 -> 未花费交易输出的块哈希

在之前那篇文章中,虽然我们已经实现了交易,但是并没有使用 chainstate 来存储交易的输出。所以,接下来我们继续完成这部分。

chainstate 不存储交易。它所存储的是 UTXO 集,也就是未花费交易输出的集合。除此以外,它还存储了“数据库表示的未花费交易输出的块哈希”,不过我们会暂时略过块哈希这一点,因为我们还没有用到块高度(但是我们会在接下来的文章中继续改进)。

那么,我们为什么需要 UTXO 集呢?

来思考一下我们早先实现的 Blockchain.UnUTXOs() 方法:


//找到所有未花费的交易输出
func (bc *BlockChain) UnUTXOs(address string, txs []*Transaction) []*UTXO {
    /*
    1.遍历数据库,获取每个块中的Transaction
    2.判断是否被
     */
    var unUTXOs []*UTXO                      //未花费
    spentTxOutputs := make(map[string][]int) //存储已经花费

    //添加先从txs遍历,查找未花费
    //for i, tx := range txs {
    for i:=len(txs)-1;i>=0;i--{
        unUTXOs = caculate(txs[i], address, spentTxOutputs, unUTXOs)
    }

    bcIterator := bc.Iterator()
    for {
        block := bcIterator.Next()
        //统计未花费
        //1.获取block中的每个Transaction
        //for _, tx := range block.Txs {
        //    unUTXOs = caculate(tx, address, spentTxOutputs, unUTXOs)
        //}
        for i := len(block.Txs) - 1; i >= 0; i-- {
            unUTXOs = caculate(block.Txs[i], address, spentTxOutputs, unUTXOs)
        }

        //结束迭代
        hashInt := new(big.Int)
        hashInt.SetBytes(block.PrevBlockHash)
        if big.NewInt(0).Cmp(hashInt) == 0 {
            break
        }
    }
    return unUTXOs
}

这个函数找到有未花费输出的交易。由于交易被保存在区块中,所以它会对区块链里面的每一个区块进行迭代,检查里面的每一笔交易。截止 2017 年 9 月 18 日,在比特币中已经有 485,860 个块,整个数据库所需磁盘空间超过 140 Gb。这意味着一个人如果想要验证交易,必须要运行一个全节点。此外,验证交易将会需要在许多块上进行迭代。

整个问题的解决方案是有一个仅有未花费输出的索引,这就是 UTXO 集要做的事情:这是一个从所有区块链交易中构建(对区块进行迭代,但是只须做一次)而来的缓存,然后用它来计算余额和验证新的交易。截止 2017 年 9 月,UTXO 集大概有 2.7 Gb。

接下来,我们个通过代码实现一下:

为了在UTXO集中存储未花费的utxo,我们可以创建一个新的结构体TxOutputs,通过对象的序列化和反序列化,便于持久化存储。

在BLC包下,新建TxOutputs.go文件,并编写代码如下:

package BLC

import (
    "bytes"
    "encoding/gob"
    "log"
)

type TxOutputs struct {
    UTXOS []*UTXO
}


//序列化
func (outs *TxOutputs) Serilalize()[]byte{
    //1.创建一个buffer
    var result bytes.Buffer
    //2.创建一个编码器
    encoder := gob.NewEncoder(&result)
    //3.编码--->打包
    err := encoder.Encode(outs)
    if err != nil {
        log.Panic(err)
    }
    return result.Bytes()
}

//反序列化
func DeserializeTXOutputs(txOutputsBytes [] byte) *TxOutputs {
    var txOutputs TxOutputs
    var reader = bytes.NewReader(txOutputsBytes)
    //1.创建一个解码器
    decoder := gob.NewDecoder(reader)
    //解包
    err := decoder.Decode(&txOutputs)
    if err != nil {
        log.Panic(err)
    }
    return &txOutputs
}

接下来,在BlockChain.go文件中添加FindUnSpentOutputMap()方法,用于获取每个TxOutputs


//新增方法
/*
查询未花费的Output
[string] *TxOutputs
 */
func (bc *BlockChain) FindUnSpentOutputMap() map[string]*TxOutputs {
    iterator := bc.Iterator()

    //存储已经花费:·[txID], txInput
    spentUTXOsMap := make(map[string][]*TXInput)

    //存储未花费
    unSpentOutputMaps := make(map[string]*TxOutputs)

    for {
        block := iterator.Next()

        for i := len(block.Txs) - 1; i >= 0; i-- {
            txOutputs := &TxOutputs{[]*UTXO{}}
            tx := block.Txs[i]

            if !tx.IsCoinbaseTransaction() {
                for _, txInput := range tx.Vins {
                    key := hex.EncodeToString(txInput.TxID)
                    spentUTXOsMap[key] = append(spentUTXOsMap[key], txInput)
                }
            }

            txID := hex.EncodeToString(tx.TxID)
        work:
            for index, out := range tx.Vouts {
                txInputs := spentUTXOsMap[txID]
                if len(txInputs) > 0 {
                    var isSpent bool
                    for _, input := range txInputs {
                        inputPubKeyHash := PubKeyHash(input.PublicKey)
                        if bytes.Compare(inputPubKeyHash, out.PubKeyHash) == 0 {
                            if input.Vout == index {
                                isSpent = true
                                continue work
                            }
                        }
                    }
                    if isSpent == false {
                        utxo:=&UTXO{tx.TxID,index,out}
                        txOutputs.UTXOS = append(txOutputs.UTXOS, utxo)
                    }

                } else {
                    utxo:=&UTXO{tx.TxID,index,out}
                    txOutputs.UTXOS = append(txOutputs.UTXOS, utxo)
                }
            }
            //设置
            unSpentOutputMaps[txID] = txOutputs
        }

        //停止迭代
        var hashInt big.Int
        hashInt.SetBytes(block.PrevBlockHash)

        if hashInt.Cmp(big.NewInt(0)) == 0 {
            break
        }
    }
    return unSpentOutputMaps
}

该方法得到一个map,我们将一笔交易的TxID作为key,将该交易中的TxOutput构建的utxo,再通过utxo构建TxOutputs作为value值。

接下来,我们在BLC下再创建一个go文件,命名为:UTXO_Set.go。添加一个结构体:

type UTXOSet struct {
    BlockChain *BlockChain
}

我们将会使用一个单一数据库,但是我们会将 UTXO 集从存储在不同的 bucket 中。因此,UTXOSetBlockchain 一起。

接下类继续定义一个ResetUTXOSet()方法:


const utxoTableName = "utxoTable"

//重置数据库表
func (utxoSet *UTXOSet) ResetUTXOSet() {
    err := utxoSet.BlockChain.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            err := tx.DeleteBucket([]byte(utxoTableName))
            if err != nil {
                log.Panic("重置中,删除表失败。。")
            }

        }
        b, err := tx.CreateBucket([]byte(utxoTableName))
        if err != nil {
            log.Panic("重置中,创建新表失败。。")
        }
        if b != nil {
            txOutputMap := utxoSet.BlockChain.FindUnSpentOutputMap()
            //fmt.Println("未花费outputmap:",txOutputMap)
            for txIDStr, outputs := range txOutputMap {
                txID, _ := hex.DecodeString(txIDStr)
                b.Put(txID, outputs.Serilalize())
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
}

这个方法初始化了 UTXO 集。首先,如果 bucket 存在就先移除,然后从区块链中获取所有的未花费输出,最终将输出保存到 bucket 中。该方法也用于重置UTXO集。

接下来,修改CLI_createBlockChain.go文件,代码如下:

package BLC

func (cli *CLI) createGenesisBlockchain(address string){
    //fmt.Println(data)
    CreateBlockChainWithGenesisBlock(address)


    bc:=GetBlockchainObject()
    defer bc.DB.Close()
    if bc != nil{
        utxoSet:=&UTXOSet{bc}
        utxoSet.ResetUTXOSet()
    }

}

当创建创世区块后,就会立刻进行初始化UTXO集。目前,即使这里看起来有点“杀鸡用牛刀”,因为一条链开始的时候,只有一个块,里面只有一笔交易。

写到此处,我们可以先进行一次测试,修改CLI.go文件在,添加一个test命令。

代码如下:

package BLC

import (
    "os"
    "fmt"
    "flag"
    "log"
)

//step1:
//CLI结构体
type CLI struct {
    //Blockchain *BlockChain
}

//step2:添加Run方法
func (cli *CLI) Run() {
    //判断命令行参数的长度
    isValidArgs()

    //1.创建flagset标签对象
    ...
    testCmd:=flag.NewFlagSet("test",flag.ExitOnError)

    ...

    //3.解析
    switch os.Args[1] {
    ...
    case "test":
        err := testCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    default:
        printUsage()
        os.Exit(1) //退出
    }

    ...

    if testCmd.Parsed(){
        cli.TestMethod()
    }

}

...
func printUsage() {
    fmt.Println("Usage:")
    fmt.Println("\tcreatewallet -- 创建钱包")
    fmt.Println("\taddresslists -- 输出所有钱包地址")
    fmt.Println("\tcreateblockchain -address DATA -- 创建创世区块")
    fmt.Println("\tsend -from From -to To -amount Amount - 交易数据")
    fmt.Println("\tprintchain - 输出信息:")
    fmt.Println("\tgetbalance -address DATA -- 查询账户余额")
    fmt.Println("\ttest -- 测试")
}

然后在BLC包下创建一个CLI_testmethod.go文件,用于测试程序,代码如下:

package BLC

import "fmt"

func (cli *CLI) TestMethod(){
    blockchain:=GetBlockchainObject()
    //defer blockchain.DB.Close()

    unSpentOutputMap:=blockchain.FindUnSpentOutputMap()
    fmt.Println(unSpentOutputMap)
    for key,value:=range unSpentOutputMap{
        fmt.Println(key)
        for _,utxo:=range value.UTXOS{
            fmt.Println("金额:",utxo.Output.Value)
            fmt.Printf("地址:%v\n",utxo.Output.PubKeyHash)
            fmt.Println("---------------------")
        }
    }

    utxoSet:=&UTXOSet{blockchain}
    utxoSet.ResetUTXOSet()
}

注意,命名文件时,不要命名为xxxtest.go,因为这是测试文件的命名格式。

打开终端,删除之前的blockchain.db文件,并执行以下命令:

hanru:day06_08_Update ruby$ rm -rf blockchain.db 
hanru:day06_08_Update ruby$ go build -o bc main.go
hanru:day06_08_Update ruby$ ./bc
hanru:day06_08_Update ruby$ ./bc addresslists
hanru:day06_08_Update ruby$ ./bc createblockchain -address '19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL'
hanru:day06_08_Update ruby$ ./bc test

运行效果如图:

http://img.kongyixueyuan.com/0906_%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C4.png

因为创建了创世区块,有CoinBase交易的10个Token,这是一个未花费的TxOutput,找到之后可以存储到UTXO集中。

4.3 优化查询余额

有了UTXO集,我们想要查询余额,可以不用遍历整个区块链的所有区块,而是直接查找UTXO集,找出对应地址的utxo,进行累加即可。

在UtxoSet中,添加GetBalance()方法,和FindUnspentOutputsForAddress()方法,代码如下:


func (utxoSet *UTXOSet) GetBalance(address string) int64 {
    utxos := utxoSet.FindUnspentOutputsForAddress(address)
    var amount int64
    for _, utxo := range utxos {
        amount += utxo.Output.Value
        fmt.Println(address,"余额:",utxo.Output.Value)
    }
    //fmt.Printf("%s账户,有%d个Token\n",address,amount)
    return amount

}

func (utxoSet *UTXOSet) FindUnspentOutputsForAddress(address string) []*UTXO {
    var utxos [] *UTXO
    //查询数据,遍历所有的未花费
    err := utxoSet.BlockChain.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            c := b.Cursor()
            for k, v := c.First(); k != nil; k, v = c.Next() {
                //fmt.Printf("key=%s,value=%v\n", k, v)
                txOutputs := DeserializeTXOutputs(v)
                for _, utxo := range txOutputs.UTXOS {
                    if utxo.Output.UnLockWithAddress(address) {
                        utxos = append(utxos, utxo)
                    }
                }
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return utxos
}

接下来修改,修改CLI_getBalance.go文件,不再通过bc对象调用原来的查询方法了,改用utxoSet对象进行查询余额,代码如下:

package BLC

import (
    "fmt"
    "os"
)

//查询余额
func (cli *CLI)getBalance(address string){
    fmt.Println("查询余额:",address)
    bc := GetBlockchainObject()

    if bc == nil{
        fmt.Println("数据库不存在,无法查询。。")
        os.Exit(1)
    }
    defer bc.DB.Close()
    //txOutputs:= bc.UnUTXOs(address)
    //for i,out:=range txOutputs{
    //    fmt.Println(i,"---->",out)
    //}
    //balance:=bc.GetBalance(address,[]*Transaction{})
    utxoSet:=&UTXOSet{bc}
    balance:= utxoSet.GetBalance(address)
    fmt.Printf("%s,一共有%d个Token\n",address,balance)
}

接下来,我们测试一下,先进行一次转账(转账我们还没有使用UTXO集优化),然后再查询余额。在终端中输入以下命令:

hanru:day06_08_Update ruby$ ./bc send -from '["19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL"]' -to '["1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w"]' -amount '["4"]'
hanru:day06_08_Update ruby$ ./bc test

运行结果如下:

http://img.kongyixueyuan.com/0907_%E6%9F%A5%E8%AF%A2%E4%BD%99%E9%A2%9D.png

注意,转账后一定要调用test,表示重置UTXO集,否则UTXO集并没有存储最新的utxo。稍后我们事先转账优化,转账后自动更新UTXO集即可。

接下来,我们查询余额:

hanru:day06_08_Update ruby$ ./bc getbalance -address '19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'

运行效果如图:

http://img.kongyixueyuan.com/0908_%E6%9F%A5%E8%AF%A2%E4%BD%99%E9%A2%9D.png

4.4 优化转账交易

因为我们已经将区块链中的utxo都存储到UTXO集中,所以UTXO 集可以用于转账发送币。

UTXO_Set.go文件中,添加FindSpendableUTXOs()方法,和FindUnPackageSpentableUTXOs()方法。代码如下:


//添加一个方法:用于查询给定地址下的,要转账使用的可以使用的utxo
func (utxoSet *UTXOSet) FindSpendableUTXOs(from string, amount int64, txs [] *Transaction) (int64, map[string][]int) {
    spentableUTXO := make(map[string][]int)
    var total int64 = 0
    //1.找出未打包的Transaction中未花费的
    unPackageUTXOs := utxoSet.FindUnPackageSpentableUTXOs(from, txs)
    for _, utxo := range unPackageUTXOs {
        total += utxo.Output.Value
        txIDStr := hex.EncodeToString(utxo.TxID)
        spentableUTXO[txIDStr] = append(spentableUTXO[txIDStr], utxo.Index)
        fmt.Println(amount,",未打包,转账花费:",utxo.Output.Value)
        if total >= amount {
            return total, spentableUTXO
        }
    }

    //钱不够
    //2.找出已经存在数据库中的未花费的
    err := utxoSet.BlockChain.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            c := b.Cursor()
        dbLoop:
            for k, v := c.First(); k != nil; k, v = c.Next() {
                txOutpus := DeserializeTXOutputs(v)
                for _, utxo := range txOutpus.UTXOS {
                    if utxo.Output.UnLockWithAddress(from) {
                        total += utxo.Output.Value
                        txIDStr := hex.EncodeToString(utxo.TxID)
                        spentableUTXO[txIDStr] = append(spentableUTXO[txIDStr], utxo.Index)
                        fmt.Println(amount,",数据库,转账花费:",utxo.Output.Value)
                        if total >= amount {
                            break dbLoop
                        }
                    }

                }
            }
        }
        return nil
    })
    if err != nil {
        log.Panic(err)
    }

    if total < amount {
        fmt.Printf("%s,账户余额不足,不能转账。。", from)
        os.Exit(1)
    }
    return total, spentableUTXO

}
//查找未打包中的utxo
func (utxoSet *UTXOSet) FindUnPackageSpentableUTXOs(from string, txs []*Transaction) []*UTXO {
    var unUTXOs []*UTXO
    //存储已经花费
    spentTxOutput := make(map[string][]int)

    for i := len(txs) - 1; i >= 0; i-- {
        unUTXOs = caculate(txs[i], from, spentTxOutput, unUTXOs)
    }
    return unUTXOs
}

以上方法用于找出转账需要用的utxo。先查找未打包交易中的utxo,然后再查找UTXO集中。

接下来,在BlockChain.go文件中,修改MineNewBlock()方法。添加utxoSet对象。修改代码如下:


//挖掘新的区块
func (bc *BlockChain) MineNewBlock(from, to, amount []string) {

    var txs []*Transaction

    //奖励
    tx := NewCoinBaseTransaction(from[0])
    txs = append(txs, tx)

    utxoSet:=&UTXOSet{bc}


    for i := 0; i < len(from); i++ {

        amountInt, _ := strconv.ParseInt(amount[i], 10, 64)
        tx := NewSimpleTransaction(from[i], to[i], amountInt, utxoSet, txs)

        txs = append(txs, tx)
    }



    ...


}

接下来需要修改Transaction.go文件中的,NewSimpleTransaction()方法,改为调用通过UTXO集获取转账需要的utxo。代码如下:


func NewSimpleTransaction(from, to string, amount int64, utxoSet *UTXOSet, txs []*Transaction) *Transaction {
    var txInputs [] *TXInput
    var txOutputs [] *TXOuput

    //balance, spendableUTXO := bc.FindSpendableUTXOs(from, amount, txs)
    balance, spendableUTXO := utxoSet.FindSpendableUTXOs(from, amount, txs)

    ...
}

通过转账产生交易,一定会花费之前的utxo,再产生新的utxo。有了 UTXO 集,也就意味着我们的数据(交易)现在已经被分开存储:实际交易被存储在区块链中,未花费输出被存储在 UTXO 集中。这样一来,我们就需要一个良好的同步机制,因为我们想要 UTXO 集时刻处于最新状态,并且存储最新交易的输出。但是我们不想每生成一个新块,就重新生成索引,因为这正是我们要极力避免的频繁区块链扫描。因此,我们需要一个机制来更新 UTXO 集。接下来,我们在UTXO_Set.go文件中,添加一个Update()方法,代码如下:


//每次创建区块后,更新未花费的表
func (utxoSet *UTXOSet) Update() {
    /*
    每当创建新区块后,都会花掉一些原来的utxo,产生新的utxo。
    删除已经花费的,增加新产生的未花费
    表中存储的数据结构:
    key:交易ID
    value:TxInputs
        TxInputs里是UTXO数组

     */

    //1.获取最新的区块,由于该block的产生,
    newBlock := utxoSet.BlockChain.Iterator().Next()

    //2.遍历该区块的交易
    inputs := []*TXInput{}
    //spentUTXOMap := make(map[string]*TxOutputs)
    //未花费
    outsMap := make(map[string]*TxOutputs)

    //获取已经花费的
    for _, tx := range newBlock.Txs {
        if tx.IsCoinbaseTransaction(){
            continue
        }
        for _, in := range tx.Vins {
            inputs = append(inputs, in)
        }
    }
    fmt.Println("inputs的长度:", len(inputs), inputs)
    //以上是找出新添加的区块中的所有的Input

    //以下是找到新添加的区块中的未花费了的Output
    for _, tx := range newBlock.Txs {

        utoxs := []*UTXO{}

    outLoop:
        for index, out := range tx.Vouts {
            isSpent := false
            for _, in := range inputs {
                if bytes.Compare(in.TxID, tx.TxID) == 0 && in.Vout == index && bytes.Compare(out.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 {
                    isSpent = true
                    continue outLoop
                }
            }
            if isSpent == false {
                utxo := &UTXO{tx.TxID, index, out}
                utoxs = append(utoxs, utxo)
                fmt.Println("outsMaps,",out.Value)
            }
        }
        if len(utoxs) > 0 {
            txIDStr := hex.EncodeToString(tx.TxID)
            outsMap[txIDStr] = &TxOutputs{utoxs}
        }

    }
    fmt.Println("outsMap的长度:", len(outsMap), outsMap)
    /*
    outsMap[7788] = utxo
    outsMap[5555] = utxo,utxo
    outsMap[4444] = utxo

     */

    //删除已经花费了的
    err := utxoSet.BlockChain.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoTableName))
        if b != nil {
            //删除 ins中
            for i:=0;i<len(inputs);i++{

                in:=inputs[i]
                fmt.Println(i,"=========================")

                //txIDStr:=hex.EncodeToString(in.TxID)
                //outputs:=outsMap[txIDStr]
                //if len(outputs.UTXOS) > 0{
                //    utxos:=[]*UTXO{}
                //    for _,utxo:=range outputs.UTXOS{
                //        if bytes.Compare(utxo.Output.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 && in.Vout ==utxo.Index {
                //
                //        }else{
                //            utxos = append(utxos,utxo)
                //        }
                //    }
                //
                //    outsMap[txIDStr] = &TxOutputs{utxos}
                //
                //
                //}else{
                txOutputsBytes := b.Get(in.TxID)
                if len(txOutputsBytes) == 0 {
                    //fmt.Println("break",i)
                    continue
                }

                txOutputs := DeserializeTXOutputs(txOutputsBytes)

                //根据IxID,如果该txOutputs中已经有output被新区块花掉了,那么将未花掉的添加到utxos里,并标记该txouputs要删除
                // 判断是否需要
                isNeedDelete := false
                utxos := []*UTXO{} //存储未花费
                for _, utxo := range txOutputs.UTXOS {
                    if bytes.Compare(utxo.Output.PubKeyHash, PubKeyHash(in.PublicKey)) == 0 && in.Vout == utxo.Index {
                        isNeedDelete = true
                    } else {
                        utxos = append(utxos, utxo)
                    }
                }
                fmt.Println(len(utxos))
                if isNeedDelete == true {
                    b.Delete(in.TxID)
                    if len(utxos) > 0 {
                        //该删除的txoutputs中还有其他未花费的
                        //preTXOutputs := outsMap[hex.EncodeToString(in.TxID)]
                        //preTXOutputs.UTXOS = append(preTXOutputs.UTXOS,utxos...)
                        //outsMap[hex.EncodeToString(in.TxID)] = preTXOutputs

                        txOutputs := &TxOutputs{utxos}
                        b.Put(in.TxID, txOutputs.Serilalize())
                        fmt.Println("删除时:map:", len(outsMap), outsMap)
                    }

                }

                //}

            }

            //增加
            for keyID, outPuts := range outsMap {
                keyHashBytes, _ := hex.DecodeString(keyID)
                b.Put(keyHashBytes, outPuts.Serilalize())
            }

        }

        return nil
    })

    if err != nil {
        log.Panic(err)
    }
}

虽然这个方法看起来有点复杂,但是它所要做的事情非常直观。当挖出一个新块时,应该更新 UTXO 集。更新意味着移除已花费输出,并从新挖出来的交易中加入未花费输出。如果一笔交易的输出被移除,并且不再包含任何输出,那么这笔交易也应该被移除。相当简单!

最后,修改CLI_send.go文件,通过utxoSet进行转账并且转账后更新。

package BLC

import (
    "fmt"
    "os"
)

//转账
func (cli *CLI) send(from, to, amount [] string) {
    if !dbExists() {
        fmt.Println("数据库不存在。。。")
        os.Exit(1)
    }
    blockchain := GetBlockchainObject()

    blockchain.MineNewBlock(from, to, amount)
    defer blockchain.DB.Close()

    utxoSet:=&UTXOSet{blockchain}
    //转账成功以后,需要更新
    //utxoSet.ResetUTXOSet()
    utxoSet.Update()
}

现在,让我们来进行最后的测试,在终端输入一下命令:

hanru:day06_08_Update ruby$ ./bc send -from '["1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w","19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL"]' -to '["1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY","1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY"]' -amount '["3","5"]'

执行结果如下:

http://img.kongyixueyuan.com/0909_%E8%BD%AC%E8%B4%A6.png

我们来查询一下余额:

hanru:day06_08_Update ruby$ ./bc getbalance -address '19qfQaPjbDJf8V1Leb7ELDMErSiCCMQ6cL'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1EAQKaYrgEsLEvpwbXcVKjsMdwSBfgkB1w'
hanru:day06_08_Update ruby$ ./bc getbalance -address '1JbzMa6gWJecePEyXNHADt9zVcYQy7wZYY'

运行结果如下:

http://img.kongyixueyuan.com/0910_%E8%BD%AC%E8%B4%A6.png

5. 总结

本章节中,我们并没有在项目中新增功能,只是做了一些优化。首先加入了奖励Reward机制。虽然程序中我们实现的比较建议,仅仅是给发起转账的人奖励10个Token,(如果一次转账多笔交易,只奖励给第一个转账人)。然后我们引入了UTXO集,进行项目代码优化。UTXO集的原理,就是我们将所有区块的未花费的utxo,单独存储到一个bucket中。无论是进行余额查询,还是转账操作,都无需遍历查询所有的区块,查询所有的交易去找未花费utxo了,只需要查询UTXO集即可。如果是转账操作,转账后需要及时更新UTXO集。

项目源代码