UTXO
比特币系统的全节点要维护一个叫UTXO(unspent transaction output)的数据结构。区块链上有很多交易,交易的输入是付款者,输出的币的新持有者。有些交易的输出可能已经在别的交易中被花掉,有些还没有被花掉。所有没有被花掉的币的集合就叫做UTXO。
一个交易可能有多个输出。假如A给B 5个比特币,B花掉了。A也给了C3个比特币,C没有花掉。这时5个比特币就不算UTXO,而3个比特币算。UTXO集合当中的每个元素要给出产生输出的交易的哈希值,以及它在这个交易里是第几个输出。这两个信息就可以定位到UTXO中币的来源。但是注意,UTXO维护的仅仅是各个交易的输出项,而不是账户余额。我们无法直接查到每个账户的余额,只能通过UTXO间接计算得到。
UTXO集合有什么作用?——为了检测double spending。即检测新发布的交易是否合法。第三节提到,比特币每个交易的输入部分都包含币的来源。有了UTXO,我们只需要验证币的来源在不在UTXO集合里。
除了比特币这种基于交易的模式,与之对应的还有基于账户的模式(account-based ledger),比如以太坊系统。在这种模式中,系统是要显示的记录每个账户上有多少币。
比特币基于交易的模式,隐私保护性较好。缺点是比特币当中的转账交易要说明币的来源,而基于账户的模式就不用。
交易手续费
注意:每个交易可以有多个输入,也可以有多个输出,所有输入金额之和要等于输出金额之和。即total inputs=total outputs。因此一个交易可能来自多个地址,可能有多个签名。
也有些交易total inputs略微大于total outputs。假如输入1比特币,输出0.99比特币,另外0.01比特币作为交易费给获得记账权发布区块的节点。
想一想,发布区块的节点为什么一定要把你的交易打包在区块呢?他们还要验证你的交易的合法性,如果交易较多占用的带宽比较大,网络传输速度也会更慢。所以只有区块奖励是不够的。
因此比特币系统设计了第二个激励机制:交易费(transaction fee)。也就是你把我的交易打包在区块里,我给你一些小费。交易费一般很小,区块奖励一般是它的几百倍;也有一些简单的交易没有交易费。但随着时间推移,区块奖励逐渐减小,手续费会成为主要激励。
挖矿双层循环:extra nonce
nonce只有2的32次方个可能的取值。现在比特币十分火爆,大量算力加入。为了保持十分钟的平均出块时间,挖矿难度被调整得很高。实际情况是,很可能把输入空间(2的32次方个值)遍历一遍也找不到满足条件的nonce。那怎么办呢?那就要在block header中找到另外的可以调整的域。
回忆一下块头里的各个域:比特币协议的版本号、前一个区块的块头的哈希值、Merkle tree的根哈希值、区块产生的时间戳、目标阈值、随机数nonce.
思来想去,只有Merkle tree的根哈希值适合调整,以求整个块头的哈希值满足puzzle.
每一个区块中,都有一个铸币交易,它专门为打包者提供区块奖励。它有一个coinbase域,可以写入任何的内容。这个域对我们有什么用呢?

上图是block header里的根哈希值对应的Merkle tree,左下角的交易是铸币交易,包含coinbase域。更改coinbase的内容,让Merkle tree的根哈希值变化。可以把coinbase域的前八个字节当做extra nonce来用,加上块头中的nonce(4个字节),这样子搜索空间就增大到了2的96次方。
所以真正挖矿的时候只有两层循环,外层循环调整coinbase域的extra nonce。算出block header里的根哈希值之后,内层循环再调整block header里的nonce。
恶意攻击
比特币求解的puzzle,除了比拼算力之外,没有其他实际意义。比特币的稀缺性是人为造成的。
虽然挖矿求解puzzle本身没有实际意义,但是它对于维护比特币系统的安全性是至关重要的。挖矿提供一种凭借算力投票的有效手段,只要大部分算力是掌握在诚实的节点手里,系统的安全性就能够得到保证。
假设好的矿工占90%的算力,坏的矿工占10%的算力。那么有10%的概率记账权会落在有恶意的矿工手里。
第一个问题
- 他能不能偷币?能不能把别人账上的钱转给自己?
不能,因为他没有办法伪造别人的签名,交易不合法,诚实的节点不会接受这个区块。
第二个问题
- 他能不能把已经花了的币再花一遍(即double spending)?
假如他把M→A
的交易写在了一个区块里面,现在他获得了记账权,他又发布另一个交易,把钱再花一遍,即M→M'
,并把这个交易打包的区块连接在分叉处。这明显是double spending。

这样生成的两条区块链,都是合法的。主要看其他节点沿着哪一个链往下扩展,最后一个胜出一个作废。交易回滚。 这种攻击的目的是什么?如果M→A的交易,产生了某种不可逆的外部效果,然后M→M’再把M→A的交易回滚了,那么M就可以从中不当获利。
如何防范这种攻击呢?如果M→A的交易所在的区块后面还连了其他的区块,那么这种攻击的难度就会大大增加。要是想回滚M→A的交易,还是要插在它之前的一个区块,然后想办法成为最长合法链。这个难度是很大的。因为诚实的节点,不会沿着它生成的恶意攻击区块往下扩展(最长合法链协议)。因此防范这种攻击的方法就是多等几个区块,或者叫多等几个confirmation。M→A交易刚刚写入区块里时,我们把它叫作one confirmation。这时后面加的区块,依次叫two confirmations、three confirmations…比特币协议当中,直到six confirmations,才认定M→A的交易是不可篡改的。平均要等一个小时。
区块链是不可篡改的账本,那是不是意味着凡是写入区块链中的内容就永远改不了呢?经上述分析可以看出,这种不可篡改只是一种概率上的保证。刚刚写入区块链的内容,还是比较容易被改动的。经过一段等待时间之后,或者后面几个区块被确认之后,被篡改的概率就大幅度下降(指数级别的下降)。
第三个问题
- 某个有恶意的节点获得记账权,能不能故意不把某些合法的交易写入区块链里?
这是可以的。比特币协议并没有规定获得记账权的节点一定要把哪些交易发布到区块里。但出现这种情况问题也不大,因为这些合法的交易一定会被写入下一个区块里,总有诚实的节点愿意发布这些交易。
区块链在正常工作下,也会出现合法的交易没有被包含进去的情况。比特币协议中规定,每个区块的大小是有限制的,最多不能超过1MB。所以如果交易的数目太多了,那么有些交易可能就只能等到下一个区块再发布。
会不会出现这种情况?M→M’的交易所在的区块所在的链条虽然短,但是先偷偷的生成比上面更多的区块,然后等上面的链条公布后再公布,就能够胜过上面的几个区块了?这种方法叫作selfish mining。
正常情况下挖到一个区块马上就发布,原因是你不发布别人可能就发布了,那样就拿不到区块奖励了。而selfish mining是先藏着不发布,这是分叉攻击的一种手段。但这样成功的概率并不大,因为有恶意的节点本来算力占比就不高,还要生成更多的区块,就非常困难。
以上是selfish mining的其中一个目的,它还有另一个目的。假如A挖了两个区块都没有发布,而在B挖到一个区块公布后立马公布,这样B挖的区块就作废了。这样的好处就是减少竞争,因为A在挖第二个区块时,别人还在挖第一个区块(前提还是A算力足够强,能够接连挖两个区块)。
挖矿难度调整
比特币用的哈希算法是SHA-256,这个产生的哈希值是256位。所以整个输出空间是2的256次方。调整这个比例,即目标空间占输出空间的比例。而目标空间的大小,直接与目标阈值target挂钩。目标阈值越小,目标空间越小,挖矿的难度越大。
为什么调整?
为什么要调整挖矿难度呢?如果不调整难度,随着系统里的总算力越来越强,出块时间越来越短。 这样会有什么问题呢?比如说不到一秒就出一个区块,但区块在网络上传播的时间可能需要几十秒(底层的比特币网络可能需要几十秒才能让其他节点都收到某个区块)。别的节点没有收到这个区块之前还是继续沿着已有的区块链往下扩展。如果有两个节点同时都发布一个区块,这个时候就会出现分叉。
出块时间如果越来越短的话,这种分叉会成为常态,而且不仅会出现二分叉,可能会出现很多的分叉。比如10个区块同时被挖出来,系统可能会出现10分叉。
分叉如果过多,对于系统达成共识是没有好处的,而且危害了系统的安全性。比特币协议是假设大部分算力掌握在诚实的矿工手里。系统当中的总算力越强,安全性就越好。如果分叉多的话,前面某个区块里的某个交易,很可能就遭受分叉攻击,恶意节点会试图回滚。由于分叉多,总算力分散,恶意节点凭借算力攻击成功的概率更大。这个时候恶意节点就不需要51%的算力了,可能20%的算力就够了,因此出块时间不是越短越好。
那10分钟的出块时间是不是最优的呢?不一定。改成其他值也可以,有间隔只是说应该有个常数范围。以太坊系统出块时间就降低到了15秒,所以以太坊的出块速度是比特币的40倍。
如何调整?
讲完了为什么要调整挖矿难度,现在讲一下怎么调整挖矿难度。比特币协议中规定,每2016个区块后就要调整目标阈值,这大概是每两个星期调整一次。
具体的调整公式:target =target×(actual time/expected time)。actual time指产生2016个区块实际花费的时间,expected time指产生2016个区块应用的时间,即2016×10mins。如果实际花费时间超过了两周,即平均出块时间超过了10min。那么这时候挖矿难度要调的低一点,应该让出块更容易。因此该公式算出来的target会变大,则难度会下降。
实际上,上调和下调都有四倍的限制。假如实际时间超过了8个星期,那么我们计算公式时也只能按4倍算。总之,目标阈值最多只能增大或减小4倍。
那怎么才能让所有的矿工同时调整目标阈值呢?计算target的方法写在比特币系统的代码里,每挖到2016个区块会自动进行调整。如果有有恶意的节点故意不调,会怎么样?
如果遇到有恶意的矿工,该调的时候不调,这时检查区块的合法性就通不过。因为每个节点要独立验证发布的区块的合法性。检查的内容就包括:nBits,即目标阈值设的对不对。如果投机取巧设计一个过大的目标阈值,使得你自己挖矿容易了,但这个区块是不会被接受的。
【nBits是target一个编码的版本,在block header里没有直接存储target的域,因为target的域是256位,直接存target的话要32个字节。nBits在header里只有四个字节,可以认为是它的一个压缩编码】

上图显示的是比特币系统中总算力的变化情况。在比特币没有流行前,有很长一段时间,算力没有太明显的增长,前面这些年的hash rate几乎是0。其实这些年算力也是增长的,只是后面这些年算力增长的太快了,所以前面部分看上去像是一条直线。去年是涨得非常猛的一年,这也体现在了hash rate 的增长上,算力呈现出指数级的增长。即使在这段黄金时期,算力也不是单调递增的,中间也是有很多波动。

上图是挖矿难度的变化情况,跟算力的增长基本上是同步的,这也符合难度调整的设计目标。通过调整挖矿难度,使得出块时间保持稳定。注意这个图显示的是挖矿难度,不是目标阈值。
- Post link: https://www.godhearing.cn/qu-kuai-lian-gong-kai-ke-bi-ji-4-bi-te-bi-de-shi-xian/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.