区块由上一个区块的散列值,若干条交易,一个调节数等元素构成,节点通过共识维持区块的连续性以此来保证区块链的安全性。其中区块最为重要的特点就是交易的集合,公链或者联盟链将交易打包成区块以后进行持久化存储。
比特币中的区块有体积为1M
,一笔交易的大小在250 Bytes
左右,意味着比特币系统中一个区块可以容纳2500到3000笔左右的交易。区块大小并没有具体的限制,还是根据不同的区块链平台和应用场景来配置,在比特币发展的历史上也曾因区块大小的设置产生过很多分歧。
以太坊中的区块大小并不固定,平均大小约为0.02M(平均出块时间为15秒),其背后的原因,在于它采用了完全不同于比特币的做法。
比特币的转帐交易是统一格式,可以用固定的区块大小来规范。以太坊则不同,V神(以太坊的创立者)将区块链视为世界计算机,在比特币基础上,以太坊实现了智能合约,这就意味着,除了和比特币有同样的转帐功能外,以太坊网络中更多的是要为大量程序提供运算服务。
举个例子,对于转帐交易类事务,以太坊网络的处理是相对一致的;但是对于合约调用而言,需要调用不同的合约方法所需的参数不同,这就导致很难从固定交易大小的角度来约束区块打包的交易数量。
以太坊网络中不同的事务,所需要的计算成本各不相同,并且以太坊明确了每笔操作会有个最低算力消耗值,而智能合约的算力消耗量则在最低消耗值基础上,还需要加上所有代码执行的算力。实际消耗的算力只有在实际使用时才能确认。
如果需要用一个相对固定的参数来规范以太坊区块的话,最直观的就是固定每个区块中所包含的算力了。这个值由矿工在每个区块中的GAS LIMIT
(单位:gas)参数来表示,每个交易提交时也会有算力需求(单位:gas),乘以算力gas的价格(单位:gwei/gas),就是交易成本了(单位:gwei,gwei和ETH类似于比特币里的 聪 和BTC的关系:每 eth = 10^9 gwei)。
2020年2月2日以太坊每个区块的GAS LIMIT大约1千万左右。
提交每笔交易时,需要附加愿意付出的最多成本,矿工在打包时,会遵循以下规则:
- 利益导向——哪笔交易给的酬劳高,会优先打包谁的,直到区块中包含的算力值(gas limit)消耗殆尽;
- 多退少不补——按实际算力收取费用,但如果给的不够,打包时则不会将计算结果提交到链上,费用也会全部收取(每笔事务的最低算力消耗值则为21000gas)。
- 在理解了上述规则后,扩容问题的解决就简单了:为了避免出现比特币类似的区块扩容争议,以太坊协议允许矿工每次可以将上个区块BGL值调整正负0.0976%(=1/1024 ),按平均每15秒出块的频率,以快速满足网络上快速变化的计算需求。
因此,在面对突来的交易激增时,以太坊表现出了较好的灵活性,比如在2017年6月29日,因ICO原因,交易量激增,以太坊在不到2个小时内,就实现了33%的增长。
以太坊网络每个区块中包含算力从最早的3百多万,到目前基本稳定在1千万gas左右。在能提供的算力增长时,如有足够的交易能消耗完,自然矿工会得到更多收益,但也需要矿工付出更多成本——更大的宽带、更快的计算能力,所以这个过程虽然不需要多方争议,但也受限于物理性能,客观上不会一蹴而就。
可以看到与比特币区块中以转账交易为核心的区块打包思路不同,以太坊设计了一套以区块链平台计算能力为核心的区块打包策略,归根到底还是二者的愿景不同,比特币希望成为数字黄金,以太坊则希望成为世界计算机。
看一下以太坊的区块结构。
从上图可以看到,区块由两部分组成,分别是区块头(header)和区块体(body)两部分。
区块头存储了区块的元信息,用来对区块内容进行一些标识,校验,说明。如果将这些元信息进行分类,按照功能可以大体分为三类:首先引用了父区块的哈希值,利用哈希值可以将本区块与父区块相连,形成一条从创世区块开始的区块“链条”;其次是由当前区块交易生成的Merkle根,被用来保证区块头与区块体的对应关系;最后是在共识过程中产生的时间戳随机数,难度值等组成的描述共识信息的数据。
对于公链和联盟链来说区块头的字段又有所不同。
- ParentHash: 父区块的哈希值。
- StateRoot:世界状态的哈希,stateDB的RLP编码后的哈希值。
- TxHash(transaction root hash):交易字典树的根哈希,由本区块所有交易的交易哈希算出。
- ReceptHash:收据树的哈希。
- Time:区块产生出来的Unix时间戳。
- Number:区块号。
- Bloom:布隆过滤器,快速定位日志是否在这个区块中。
- Coinbase:挖出这个块的矿工地址,因为挖出块所奖励的ETH就会发放到这个地址。
- Difficulty:当前工作量证明(Pow)算法的复杂度。
- GasLimit: 每个区块Gas的消耗上线。
- GasUsed:当前区块所有交易使用的Gas之和。
- MixDigest: 挖矿得到的Pow算法证明的摘要,也就是挖矿的工作量证明。
- nonce:挖矿找到的满足条件的值。
- Uncle:叔块是和以太坊的共识算法相关。
一般而言一个类以太坊的联盟链是需要上面介绍的通用字段的,但是也不绝对,还可能与选择的共识算法,隐私保护策略,设计偏好有关。
需要特别注意的是在区块头通用字段中,除了Time字段以外都是依赖链上数据生成的,而标识区块出块时间的Time字段则需要根据外部因素来确定。中本聪在比特币白皮书中提出了时间戳服务器的方案。时间戳服务器通过对区块中所包含的交易计算哈希值后再计算时间戳,最后将时间戳在比特币网络中广播。时间戳证明了区块中所包含交易在特定时间点上是确定存在的,因为数据在该时刻存在才能得到相应的哈希值。每个时间戳又将前一个时间戳纳入其哈希值,使得每一个随后生成的时间戳都对之前的时间戳进行加强,形成一个环环相扣的时间戳链条。链条越长安全性也就越好。
像比特币以太坊这样的公链是一个去中心化分布式的系统,节点的分布是可以在任意的地理空间,同时每个节点也是由其拥有者全权控制的,这就意味着节点可能分布在不同的时区,或者可以恶意篡改伪造节点自己的时间信息。对于由于地理位置不同导致时间不一致的问题,可以采用世界标准UTC时间来解决,如果采用UTC时间后还有时间偏移,比特币节点会对与其连接上的所有节点进行时间调整,调整方式是选择本地UTC时间加上所有已连接节点的中值偏移量。但是,网络时间的调整时间不得超过本地系统时间的70分钟。对于恶意修改时间的情况比特币会对接收到的区块进行时间戳的校验,如果时间戳大于前11个块的中值时间戳,并且小于网络调整时间+ 2小时,则该时间戳被视为有效,否则认为是一个无效的区块。
对于以太坊而言接收到的区块的时间戳大于当前时间15秒,则认为是未来区块(FutureBlock),如果区块的时间戳大于当前时间30秒,则直接丢弃这个区块,未来区块的出现可能是由于网络延迟区块不是按照区块产生顺序有序到达所引起的,以太坊会有定时检查机制来对其进行处理。
在比特币以太坊这样的公链中交易的数据结构中并没有时间戳字段,也就是说对于交易来说并不知道交易生成的确切时间。当交易打包进区块的时候,才会对区块中包含的一批交易计算时间戳,因此区块里交易时间是区块实际产生的时间,也叫出块时间。对于区块链来说更加关注的是交易对区块链状态的改变而非状态何时发生了改变。
区块体(Body)包括这个区块创建过程中的所有经过验证的交易。
以太坊在存储区块的时候,区块头和区块体其实是分开存储的,其实也很容易理解,分开存储可以提供更多的灵活性,比如不用保存全部区块数据的轻节点。
1)区块头存储
以太坊通过如下方式将区块头转换成键值对存储在LevelDB中;
headerPrefix + num + hash -> rlp(header)
Tips: num是以大端序的形式转换成bytes的,其中headerPrefix的值是 []byte("h")
2)区块体存储
区块体的存储方式也是类似;
bodyPrefix + num + hash -> rlp(block)
Tips: num是以大端序的形式转换成bytes的,其中bodyPrefix的值是[]byte("b")
区块链中的第一个区块被称为创世区块,在比特币中第一个区块于2009年创建。它是比特币中所有区块的共同祖先,这意味着如果你从任何区块开始,并随时间上向前追溯,最终将到达创世区块。
节点总是以至少一个区块的区块链开始,因为这个区块是在比特币客户端软件中静态编码的,因此它不能被改变。每个节点总是存储了起始块的哈希值和结构,它创建的固定时间,以及其中的单一交易。因此,每个节点都有区块链的起点,这是一个安全的“根”,从中可以构建受信任的区块链。
可以通过查看Bitcoin Core的代码查看创世区块,以下哈希值标识符属于创世区块:
000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
在比特币的创世区块的铸币交易中包含着这样一段话"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks."翻译过来就是2009年1月3日,财政大臣正处于实施第二轮银行紧急救助的边缘。这句话参考英国报纸 The Times 的标题提供此区块创建的最早日期的证据。它还半开玩笑地提醒人们关注独立货币体系的重要性,比特币发行时正值前所未有的全球货币危机。比特币的创造者中本聪将这个信息写入到了创世区块中。
新加入的节点只知道最开始初始化的创世区块,因此需要向其他节点同步最新的区块。同步的的过程中首先节点之间会互相发送getBlock
消息,其中包含了本节点最新区块的哈希值,当节点发现收到的getblock中的hash在自己的区块链中不是顶端,则说明自己的区块链比较长,于是向较短节点发送inv信息。其中Inv
消息结构如下;
type MsgInv struct {
InvList []*InvVect
}
type InvVect struct {
Type InvType // Type of data
Hash chainhash.Hash // Hash of the data
}
Inv消息只是一个清单,并不包含实际的数据,当落后放收到Inv消息后,开始发送getdata消息请求数据,具体流程如下图所示;
与区块数据同步相比,区块数据的广播是一种更为常见的场景,当一个新的区块产生以后需要广播到网络中的其它节点,为了尽快收到其他节点的信息,节点间并不是直接传递区块信息的。
节点向附近节点发送一个Inv
消息,Inv消息中包含已经被发送者(sender)接收并验证过的“交易记录的哈希”,以及“区块哈希”。接收者(receiver)收到Inv消息后,如果它还尚未从其他节点收到过相同的信息,它会发送一个getdata消息给发送者,要求得到交易记录及区块哈希包含的具体信息。此时,区块和交易记录的信息才会进行整体传递。
区块扩容是指通过增加区块大小的上限,从而增加可以包含交易的数量最后达到缓解网络拥塞提高区块链系统整体性能的目的。对于联盟链或者私链来说,区块的大小通常都会根据上层的业务需求配置,有在链启动前静态配置好不可更改的,有在整个区块链网络运行过程中可以动态调整的,具体需要根据业务场景和区块链平台实现来确认。而一般意义上的区块扩容更多的是针对比特币而言。
针对比特币的区块扩容方案主要分为三类,分别是以算力为核心,以交易量为核心和以随时间递增三种。以算力为核心以BIP100,BIP101方案为代表,特点是由矿工来决定区块调整的方案。以交易量为核心以BIP104,BIP106方案为代表,特点是基于某个阶段交易量和当前区块实际大小动态调整区块容量。随时间递增是以BIP102,BIP103方案为代表,特点是预估比特币的交易量需要然后按年度调整区块容量。这些方案引起了社区的激烈讨论,甚至BIP101方案在2016年1月11日获得了全网大约75%的算力支持,但是最终这些方案都没有得到成功的实施。
BIP101方案,将比特币单个区块的容量提升到8M,并在2036年1月6日前每两年对区块容量上限值翻倍,直到达到8G为止。
虽然这些提案最终都失败了,但是推动比特币区块扩容的社区支持者并没有放弃努力,其中有一些非常重要的时间节点。
1)香港共识 2016年2月,比特币核心开发者,矿池等业内人士在香港召开了一次会议,会议上达成了在部署隔离见证的同时把区块容量提升到2M的共识,然而事后比特币核心团队推翻了此次共识。
2)纽约共识 2017年5月23日,来自全球的21个国际和56家知名区块链公司共同签署了纽约共识,其内容和香港共识一致,但是这次会议没有比特币核心开发团队成员的参与,自然最终的共识也没有得到他们的承认。
3)比特币现金 经历了香港共识和纽约共识的失败,在2017年8月1日由ViaBTC等大矿池的推动下,比特币经过硬分叉产生了一条新的区块链,被称为比特币现金(Bitcoin Cash,BCH)。在比特币现金中支持8M的区块大小,同时获得了大量推动比特币区块扩容团队的支持。在2018年5月15日,比特币现金经过二次硬分叉升级为支持32M区块。同年的11月10日,比特币现金创建了第一个扩容后的区块,体积达到了31.99M。在11月15日比特币现金再一次分叉为Bitcoin ABC和Bitcoin SV,后者进一步把区块容量提升到了128M。到目前为止这两条链仍然处于竞争状态,这对于比特币本身的区块扩容而言具有一定的参考借鉴意义。
无论是在像以太坊这样的公链还是拥有繁忙业务的联盟链,随着时间的推移都会产生出大量的区块数据需要存储,像以太坊最终的区块数据还是通过LevelDB存储在磁盘上的,但是由于LevevDB采用了LSM树,随着数据的写入的增多,数据的随机读取速度会有一定的下降。这种下降目前在以太坊中影响并不大,这个影响包括对以太坊中所有采用LevelDB存储的数据。但是在联盟链中采用了更高效的共识算法,由共识算法产生瓶颈的现在转移到了其他模块,区块存储就是其中之一。
区块链中的区块数据具有一些非常鲜明的特点,区块数据只会不断的追加,旧区块的数据不会发生改变,无需对区块数据进行复杂操作,比如聚合,运算等。其中数据数据只会追加的特点非常契合磁盘顺序写入速度非常快的优势。针对这个特点和LevelDB随数据增长随机读性能下降的劣势,超级账本设计了自己的文件系统,不通过LevelDB直接读写区块数据来获得性能的提升,但是在对世界状态的读写仍然采用了LevelDB或者类似的CouchDB的数据库。