到目前为止,我们已经基本实现了区块链中的所有元素。在第一章节就讲到区块链是一个分布式数据库,这也是去中心化的核心所在,但是在前面的章节我们在实现区块链中的各种功能的过程中我们并没有去考虑‘分布式’的问题,只是关注了数据库这部分的实现。在这一章节中我们来讨论区块链的分布式的实现。
本章之后的全部代码已经上传到github上面了,可以点击 这里 查看。
在开始之前我们先总结一下我们已经完成的工作有哪些;
区块链的基本原型
- 区块链的工作量证明POW
- 把区块链存放到bolt数据库里面,实现命令行接口CLI
- 链上交易,首先实现的是coinbase的交易,然后实现了未花费交易输出的查找从而能得到地址的余额,最后实现地址之间的币发送交易。此时没有实现交易池,所以一个区块只能包括一个交易。
- 实现了区块链中的钱包,钱包存储了一对秘钥,用公钥导出了地址,此时有了正真意义上的地址。最后实现了交易的签名。
在上一章中,我们没有实现挖矿奖励,我们只有在创建区块链的时候coinbaseTX给了奖励,但是之后每一次挖矿都没有给出奖励。所以我们要实现每一个区块被挖出后要给矿工一笔挖矿奖励的交易,挖矿奖励实际上就是一笔CoinbaseTX,coinbase交易只有一个输出,我们实现挖矿奖励非常简单,coinbase交易放在区块的Transactions的第一个位置就行了。
修改send方法,实现挖矿奖励:
//send方法
func (cli *CLI) send(from,to string,amount int) {
if !wallet.ValidateAddress(from) {
log.Panic("ERROR: Address is not valid")
}
if !wallet.ValidateAddress(to) {
log.Panic("ERROR: Address is not valid")
}
bc := blockchain.NewBlockchain(from)
defer bc.Db().Close()
tx := blockchain.NewUTXOTransaction(from,to,amount,bc)
//挖矿奖励的交易,把挖矿的奖励发送给矿工,这里的矿工默认为发送交易的地址
cbtx := transaction.NewCoinbaseTX(from,"")
//挖出一个包含该交易的区块,此时区块还包含了-挖矿奖励的交易
bc.MineBlock([]*transaction.Transaction{cbtx,tx})
fmt.Println("发送成功...")
}
下面将实现UTXO集:
在第三章我们实现把区块链存储在数据库的时候,我们创建了一个专门存储区块链的数据库blockchain.db,这样的好处是使我们链上的区块信息写入我们电脑上的磁盘上,实现永久保存。众所周知,区块相当于一个分布式账本,只要安装了相同区块链的终端节点,都可以看到这个区块链账本上的信息,这里的账本信息就是数据库blockchain.db上的信息。如果我们安装的全节点的话,我们就会去下载区块链上的所有区块信息,也就是这条链的blockchain.db,可以理解为我们所说的分布式账本中的账本就是blockchain.db,分布式就是我们所有节点都可以按照规定的规则对这个账本进行操作,所有拥有这个账本的节点都有相同的权限。
好了,我们大致了解了区块链中的blockchain.db之后,我们可能会有疑问,大家一想到数据库就会条件反射的想到是会存放很多数据。没错,当我们这条链越来越长的时候,我们的blockchain.db也就会越来越大,这时候实现区块链的分布式就没那么容易了,因为当这个数据库很大很大的时候,比如现在比特币的这个数据库已经超过100Gb了,一般用户想运行全节点用电脑想下载下来都比较困难。怎么办喃,UTXO集就是来解决这个问题的。
在我们的区块链中我们会创建另外一个blot数据库,叫做chainstate.db。顾名思义链状态数据库,存放区块链状态的数据库。区块链状态? 没错这里的区块链状态其实就是区块链中所有的未花费交易输出,我们就把这个表示为UTXO集。说明一点我们创建的chainstate.db值存放的是未花费交易输出,并不是存放完整的交易信息,所以比起blockchain.db小太多了。
我们为什么要UTXO集喃?首先从代码层面来讲我们我们有这个需求就是找到当前区块链中的所以未花费交易和未花费交易输出。比如下面这三个方法:
//在区块链上找到每一个区块中属于address用户的未花费交易输出,返回未花费输出的交易切片
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []transaction.Transaction {
//通过找到未花费输出交易的集合,我们返回集合中的所有未花费的交易输出
func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []transaction.TXOutput {
//找到可以花费的交易输出,这是基于上面的FindUnspentTransactions 方法
func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte,amount int) (int,map[string][]int) {
无一例外,我们实现以上方法都是需要遍历整个区块链,区块链的长度短还好办,但是像比特币这样的区块链,我们这样一遍又一遍的去遍历它的链,可能你电脑的cpu要发疯,想想都难受。 解决之道就是用chainstate数据库存放了区块链中所有的未花费交易,这样我们需要区块链中的UTXO的时候,就只需要在chainstate.db中找就好了。这就是UTXO集要做的事情:这是一个从所有区块链交易中构建(对区块进行迭代,但是只须做一次)而来的缓存,然后用它来计算余额和验证新的交易。目前为止,比特币区块链中的UTXO 集大概有 3Gb,这就对我们一般不需要运行全节点挖矿的用户比较友好了。
现在我们重新创建一个utxo包,用于实现UTXO集,因为我们这种行为是对区块链的操作,所以我们肯定是要绑定区块链的,下面创建一个UTXOSet结构体