# 前言
前面为了转账,需要迭代整个区块链,查找未花费的输出,这要求发起交易者必须运行一个全节点,截止 2017 年 9 月 18 日,在比特币中已经有 485,860 个块,整个数据库所需磁盘空间超过 140 Gb。这是非常困难的。
整个问题的解决方案是有一个仅有未花费输出的索引,这就是 UTXO 集要做的事情:这是一个从所有区块链交易中构建(对区块进行迭代,但是只须在创建区块链时候做一次)而来的缓存,然后用它来计算余额和验证新的交易。截止 2017 年 9 月,UTXO 集大概有 2.7 Gb,远小于整个节点。
# 顺便实现下挖矿奖励
我们的区块链由交易发起者自行完成挖矿,所以奖励给到交易发起者即可。
挖矿奖励,实际上就是一笔 coinbase 交易。
~~~
func (cli *CLI) send(from, to string, amount int) {
...
bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
defer bc.db.Close()
tx := NewUTXOTransaction(from, to, amount, &UTXOSet)
cbTx := NewCoinbaseTX(from, "")//创建一个coninbase交易,给from发送一个固定数量的奖励。
txs := []*Transaction{cbTx, tx}//将coninbase交易和实际交易打包到一起
newBlock := bc.MineBlock(txs)//挖矿
fmt.Println("交易成功!")
}
~~~
>bitcoin的挖矿由专职矿工完成。交易发起者将交易丢进交易池中,矿工从交易池中取出一部分交易,打包挖矿,获得奖励。奖励币数量是不固定的。
# 实现UTXO
UTXO集定义
~~~
type UTXOSet struct {
Blockchain *Blockchain
}
~~~
UTXO元素是一个区块链Blockchain的引用
## 取得UTXO并存储到新的bucket中
~~~
func (u UTXOSet) Reindex() {
db := u.Blockchain.Db//与区块链使用相同的数据库
bucketName := []byte(utxoBucket)//新的bucket
err := db.Update(func(tx *bolt.Tx) error {
err := tx.DeleteBucket(bucketName)//如果存在,则删除
_, err = tx.CreateBucket(bucketName)//创建新的bucket
})
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())//将交易ID-输出存储到bucket
}
})
}
~~~
这个方法仅仅在区块链新建完成后,唯一一次被调用:当一个新的区块链被创建以后,就会立刻进行重建索引。
~~~
func (cli *CLI) createBlockchain(address string) {
...
bc := CreateBlockchain(address)
defer bc.db.Close()
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
...
}
~~~
## 转账的新方式~~~
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))//从bucket中读取UTXO集
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {//循环UTXO集
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
}
~~~
## 查询余额的新方式
~~~
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))//从bucket中读取UTXO集
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {//循环UTXO集
outs := DeserializeOutputs(v)
for _, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}
return nil
})
return UTXOs
}
~~~
## 同步机制
当挖出一个新块时,应该更新 UTXO 集。
更新意味着移除已花费输出,并从新挖出来的交易中加入未花费输出。
~~~
~~~
func (u UTXOSet) Update(block *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 {//coinbase交易不更新到UTXO集中?那么挖矿奖励金怎么办?
for _, vin := range tx.Vin {
updatedOuts := TXOutputs{}//交易ID为vin.Txid的新的UTXO,将替换原来数据库中交易ID为vin.Txid的UTXO
outsBytes := b.Get(vin.Txid)//从数据库读取的UTXO集中获得被包含到输入的输出交易ID,一个vin中只有一个输出索引
outs := DeserializeOutputs(outsBytes)
for outIdx, out := range outs.Outputs {//迭代数据库该交易的所有索引的输出。输出集合可能有多个索引对应的输出
if outIdx != vin.Vout {
//如果某条索引对于的输出没有被包含在输入中,加入到新的最终输出集合中。
//注意,包含到输入中输出最小单位是输出的某条索引对应的输出(索引从0开始)
updatedOuts.Outputs = append(updatedOuts.Outputs, out)//加入到新的已经花费的输出组中
}
}
if len(updatedOuts.Outputs) == 0 {
//一个交易ID为vin.Txid的交易完成后,交易的输出索引对应的输出将被移除,如果结果是,该交易不再包含任何输出,
//那么这笔交易的输出应该被直接从UTXO数据库中移除(UTXO链没有必要留着空的节点)。
err := b.Delete(vin.Txid)
} else {
//如果交易ID为vin.Txid的交易在完成后,仍然有输出,则更新到数据库,替换该交易原来的UTXO集合
err := b.Put(vin.Txid, updatedOuts.Serialize())
}
}
}
//当然,交易将产生新的输出,直接加入到UTXO中即可
newOutputs := TXOutputs{}
for _, out := range tx.Vout {//迭代Vout,获得输出索引对应的输出
newOutputs.Outputs = append(newOutputs.Outputs, out)
}
err := b.Put(tx.ID, newOutputs.Serialize())//tx.ID为当前交易的ID
}
})
}
~~~
# Merkle树实现
上如上面所提到的,完整的比特币数据库(也就是区块链)需要超过 140 Gb 的磁盘空间。因为比特币的去中心化特性,网络中的每个节点必须是独立,自给自足的,也就是每个节点必须存储一个区块链的完整副本。随着越来越多的人使用比特币,这条规则变得越来越难以遵守:因为不太可能每个人都去运行一个全节点。并且,由于节点是网络中的完全参与者,它们负有相关责任:节点必须验证交易和区块。另外,要想与其他节点交互和下载新块,也有一定的网络流量需求。
在中本聪的[比特币原始论文](https://link.jianshu.com/?t=https://bitcoin.org/bitcoin.pdf)中,对这个问题也有一个解决方案:简易支付验证(Simplified Payment Verification, SPV)。SPV 是比特币轻节点,它不需要下载整个区块链,也**不需要验证区块和交易**。相反,它会在区块链查找交易(为了验证支付),并且需要连接到一个全节点来检索必要的数据。这个机制允许在全网只运行一个全节点的情况下有多个轻节点(轻钱包)。
为了实现 SPV,**需要有一个方式来检查是否一个区块包含了某笔交易,而无须下载整个区块。这就是 Merkle 树所要完成的事情。**
比特币用 Merkle 树来获取交易哈希,哈希被保存在区块头中,并会用于工作量证明系统。
Merkle 树的好处就是一个节点可以在不下载整个块的情况下,验证是否包含某笔交易。并且这些只需要一个当前交易哈希,一个 Merkle 树根哈希和一个 Merkle 路径(向全网请求获得路径),然后本地计算,即可检查是否包含了某笔交易。
# 运行检验
## 转账
![](https://img.kancloud.cn/20/91/20912871fa21fccf1fb65cc58c602945_1580x117.png)
转账结果:
![](https://img.kancloud.cn/d6/f9/d6f9da32dcf17f2c91bcb39031299508_1108x229.png)
之所以转账后from的币数量是17,是因为转账挖矿奖励10币,加上转账余额7币,一共17个币。
## 打印区块链
![](https://img.kancloud.cn/89/a0/89a06f821fd71033c1fc13550055993f_1962x1223.png)
# 优化Findtranaction
在很多场合需要根据交易ID,查找交易,函数Findtrasaction功能即如此。
为此,我们将<tx.ID,block.Hash>保存到数据库,这样,我们通过数据库表utxoBlockBucket的key(tx.ID)可以查询到block的ID,然后通过blocksBucket的Get,可以快速获得交易。(原来是通过迭代blockchain来完成,效率很低)。这样,也为后面轻节点通过网络请求得到交易留下优化空间。
优化之后,节点仍然需要下载整个区块链,才能查询到交易。
## 表
const utxoBlockBucket = "chainstate\_blockid2tx"
## 修改Reindex
![](https://img.kancloud.cn/e3/ed/e3ed0a7ea3cc1435d3eaeb637f7bcdf3_1642x978.png)
将UTXO和UTXOBlock都写入数据库
## 修改同步函数
![](https://img.kancloud.cn/74/2c/742c57b30cdcf7356ad9d0acd51a3a71_908x286.png)
更新UTXO的同时,同步UTXOBlock
## 修改FindtransactionForUTXO
![](https://img.kancloud.cn/37/3e/373ef208664da14d14b1017fc113af81_1337x1473.png)
## 修改其它代码
- 重要更新说明
- linechain发布
- linechain新版设计
- 引言一
- 引言二
- 引言三
- vs-code设置及开发环境设置
- BoltDB数据库应用
- 关于Go语言、VS-code的一些Tips
- 区块链的架构
- 网络通信与区块链
- 单元测试
- 比特币脚本语言
- 关于区块链的一些概念
- 区块链组件
- 区块链第一版:基本原型
- 区块链第二版:增加工作量证明
- 区块链第三版:持久化
- 区块链第四版:交易
- 区块链第五版:实现钱包
- 区块链第六版:实现UTXO集
- 区块链第七版:网络
- 阶段小结
- 区块链第八版:P2P
- P2P网络架构
- 区块链网络层
- P2P区块链最简体验
- libp2p建立P2P网络的关键概念
- 区块链结构层设计与实现
- 用户交互层设计与实现
- 网络层设计与实现
- 建立节点发现机制
- 向区块链网络请求区块信息
- 向区块链网络发布消息
- 运行区块链
- LineChain
- 系统运行流程
- Multihash
- 区块链网络的节点发现机制深入探讨
- DHT
- Bootstrap
- 连接到所有引导节点
- Advertise
- 搜索其它peers
- 连接到搜到的其它peers
- 区块链网络的消息订发布-订阅机制深入探讨
- LineChain:适用于智能合约编程的脚本语言支持
- LineChain:解决分叉问题
- LineChain:多重签名
- libp2p升级到v0.22版本
- 以太坊基础
- 重温以太坊的树结构
- 世界状态树
- (智能合约)账户存储树
- 交易树
- 交易收据树
- 小结
- 以太坊的存储结构
- 以太坊状态数据库
- MPT
- 以太坊POW共识算法
- 智能合约存储
- Polygon Edge
- block结构
- transaction数据结构
- 数据结构小结
- 关于本区块链的一些说明
- UML工具-PlantUML
- libp2p介绍
- JSON-RPC
- docker制作:启动多个应用系统
- Dockerfile
- docker-entrypoint.sh
- supervisord.conf
- docker run
- nginx.conf
- docker基础操作整理
- jupyter计算交互环境
- git技巧一
- git技巧二
- 使用github项目的最佳实践
- windows下package管理工具