【前言】由于以太坊分片方案处于更新状态,因此这只能代表当前一阶段的共识。
这是描述Casper + Sharding链第2版规范的工作进度文档。与早期版本严重依赖现有的以太坊PoW链作为中心链相比,本版本规范将基于RANDAO 信标,证明和Casper FFG机制的权益证明链作为基础,与PoW主链相对简单,包括块引用和单向存款。
有一条中心PoS链存储和管理当前活跃的PoS验证者们。起初成为一名验证者唯一可用的机制是在现有的PoW主链上发送一条包含32ETH的交易。当你这样做了,PoS链一处理区块,你将会处在一个队列中,并最终以一名活跃的验证者正式加入,直到你要么自愿退出,要么强制退出以作为对反常行为的一种惩罚。
PoS链上的主要负载源是跨链路(cross-links)。跨链路是一种特殊的交易,它表示“这是分片X上的某个最近区块的哈希值。这是随机选择的占M个验证者(例如,M=1024)中至少2/3的签名,这些签名用来证明跨链路的有效性。”每个分片(例如,总共可能有4000个分片)本身是一条PoS链,并且所有分片链是存储交易和账户的地方。跨链路用于“确认”分片链的片段到主链中,并且也是不同分片链能够互相交流的主要方式。
注意:在https://github.com/ethereum/beacon_chain的python代码不能反映最新的更改。如果有出入,本文档很可能会反映最近的变化。
验证者 —— Casper/分片共识系统的参与者。通过存32ETH到Casper机制中就能成为一名验证者。
活跃验证者集合 —— 那些当前正在参与的验证者以及Casper机制寻求产生以及证明区块,跨链路和其他共识对象的验证者。
公证人 —— 跨链路的签名者
委员会 —— 从活跃验证者集合中(伪)随机抽样的子集。当一个委员会被集体提及时,如“该委员会证明X”,这种假定为“该委员会的某个子集包含足够的验证者,该协议承认其代表委员会”。
提案者 —— 创建一个区块的验证者
证明者 —— 在区块上签名的委员会中的一名验证者
信标链 —— 中心PoS链是分片系统的基础
分片链 —— 进行交易和存储账户数据的链之一
主链 —— 这里用来指现有的PoW链,但是注意到这只是临时的。长远来看,信标链将是主链。
跨链路 —— 来自委员会的一组签名,证明一个块在分片链中,(签名)可以包含在信标链中。跨链路是信标链“了解”分片链更新状态的主要手段。
纪元 —— 100个区块为一个周期
最终化,合理 —— 参见Casper FFG终稿https://arxiv.org/abs/1710.09437
分片数 —— 指分片数量的一个常数。目前设为1024。
NOTARIES_PER_CROSSLINK —— 对于一次跨链路,公证人集合大小的一个常数。目前设为1024。
本PoS/分片提案可以独立于主链实施。主链只需要做两处变更(且第二个在技术上讲不是严格必要的)。
在主链上添加一个合约。该合约允许你存32ETH。deposit函数接收4个参数,分别是(i)pubkey(bytes),(ii)withdrawal_shard_id(int),(iii)withdrawal_addr(address)和(iv)randao_commitment(bytes32)。
主链客户端将实现一个方法,prioritize(block_hash,value)。如果该区块可用并得到验证,该方法将(区块)分数设定为给定值,并递归调整所有后代的分数。这允许PoS信标链的终结小工具也能隐式地最终化主链区块。
信标链是PoS系统中的“主链”。信标链的主要职责是:
这是进入每个信标链的区块的字段:
fields = {
# Hash of the parent block
'parent_hash': 'hash32',
# Number of skips (for the full PoS mechanism)
'skip_count': 'int64',
# Randao commitment reveal
'randao_reveal': 'hash32',
# Bitfield of who from the attestation committee participated
'attestation_bitfield': 'bytes',
# Their aggregate sig
'attestation_aggregate_sig': ['int256'],
# Shard aggregate votes
'shard_aggregate_votes': [AggregateVote],
# Reference to main chain block
'main_chain_ref': 'hash32',
# Hash of the state
'state_hash': 'bytes',
# Signature from proposer
'sig': ['int256']
}
信标链状态被划分为两部分,结晶状态和活动状态。活动状态改变每个区块,而结晶状态仅每纪元(100个块)改变。这是活跃的状态:
fields = {
# Block height
'height': 'int64',
# Global RANDAO beacon state
'randao': 'hash32',
# Which validators have made FFG votes this epoch (as bitfield)
'ffg_voter_bitfield': 'bytes',
# Block attesters in the last epoch
'recent_attesters': ['int24'],
# Storing data about crosslinks-in-progress in this epoch
'partial_crosslinks': [PartialCrosslinkRecord],
# Total number of skips (used to determine minimum timestamp)
'total_skip_count': 'int64',
# Block proposers in the last epoch
'recent_proposers': [RecentProposerRecord]
}
每个PartialCrossLinkRecord是一个对象,其中包含有关本纪元期间放在一起的crosslinks信息:
fields = {
# What shard is the crosslink being made for
'shard_id': 'int16',
# Hash of the block
'shard_block_hash': 'hash32',
# Which of the eligible voters are voting for it (as bitfield)
'voter_bitfield': 'bytes'
}
每个RecentProposerRecord是一个对象,其中包含有关最近区块提案者的信息:
fields = {
# Proposer index
'index': 'int24',
# New RANDAO commitment
'randao_commitment': 'hash32',
# Balance delta
'balance_delta': 'int24'
}
下面是结晶状态:
fields = {
# List of active validators
'active_validators': [ValidatorRecord],
# List of joined but not yet inducted validators
'queued_validators': [ValidatorRecord],
# List of removed validators pending withdrawal
'exited_validators': [ValidatorRecord],
# The permutation of validators used to determine who
# cross-links what shard in this epoch
'current_shuffling': ['int24'],
# The current epoch
'current_epoch': 'int64',
# The last justified epoch
'last_justified_epoch': 'int64',
# The last finalized epoch
'last_finalized_epoch': 'int64',
# The current dynasty
'dynasty': 'int64',
# The next shard that cross-linking assignment will start from
'next_shard': 'int16',
# The current FFG checkpoint
'current_checkpoint': 'hash32',
# Records about the most recent crosslink for each shard
'crosslink_records': [CrosslinkRecord],
# Total balance of deposits
'total_deposits': 'int256',
# Used to select the committees for each shard
'crosslink_seed': 'hash32',
# Last epoch the crosslink seed was reset
'crosslink_seed_last_reset': 'int64'
}
每个ValidatorRecord都是包含有关验证者信息的对象:
fields = {
# The validator's public key
'pubkey': 'int256',
# What shard the validator's balance will be sent to
# after withdrawal
'withdrawal_shard': 'int16',
# And what address
'withdrawal_address': 'address',
# The validator's current RANDAO beacon commitment
'randao_commitment': 'hash32',
# Current balance
'balance': 'int64',
# Dynasty where the validator can
# (be inducted | be removed | withdraw their balance)
'switch_dynasty': 'int64'
}
CrosslinkRecord包含有关要提交到链中的最后完全形成的跨链路信息:
fields = {
# What epoch the crosslink was submitted in
'epoch': 'int64',
# The block hash
'hash': 'hash32'
}
状态根等价于连接blake(serialize(crystallized_state))和blake(serialize(active_state))【blake是一个密码哈希函数】。注意到:这意味着大多数时间,当结晶状态不改变时,不需要重新进行哈希运算。活跃状态一般相当较小(例如,4百万的验证者理论上至多~1MB,实际上是~100kb ),但是结晶状态大得多,达到数十MB。
在很多方面,处理信标链基本上与处理一条PoW的链很相似。客户端下载和处理区块,并维护当前“权威链”的一个视图,在当前“块头”终止。然而,由于信标链与现有的PoW链的关系,并且因为它是一个PoS链,因此还是有一些区别。
对于信标链上的区块想要被一个node处理,得满足三个条件:
区块生产稍微不同,这缘于权益证明机制。客户端每次改变头的视图(如何定义头参见下面的分叉选择部分),对于所有的skip_count值,从0到M,对某个相当大的M而言(参见下面的算法),他们应该计算区块的提案者。令i是客户端选中的skip_count。当时间戳达到minimum_timestamp(head) + PER_HEIGHT_DELAY + i * PER_SKIP_DELAY,并且如果到那时头还没改变,那么客户端应该发布一个新块。
当客户端改变其头的视图,他们也应该计算证明者的列表(签署头的验证者集合,细节参见下面),并立即发布对那个块的证明,用他们的密钥对父亲区块的序列化形式进行签名。
当信标链成为一个独立的PoS系统时,那么分叉选择规则是简单的区块得分最高规则,其中分数与Casper FFG中的一样:
score = last_justified_epoch + height * ε
尽管信标链是作为PoW主链的标签实现的,但是分叉选择规则是从属的分叉选择规则:信标链的头是得分最高的信标链区块,其中main_chain_ref位于分数最高的主链【这里的主链相对于信标链的分叉链讲的】。
由于要求一个信标链区块的main_chain_ref等于其双亲的main_chain_ref或其后代的附加有效性条件。这意味着在信标链上的每个区块的main_chain_ref位于得分最高的主链内【因为main_chain_ref是在信标链的主链里,附加条件又要求等于其双亲的main_chain_ref,这就是加强】。这确保信标链遵守实际权威的PoW主链。
这可以使用下面的算法“在线更新”。首先,不仅追踪主链和信标链的头很重要,而且追踪每个高度的区块也是很重要的。这可以通过下面的算法维护,每次改变头的时候运行:
def update_head(chain, old_head, new_head):
a, b = old_head, new_head
while a.height > b.height:
a = get_parent(a)
while b.height > a.height:
b = get_parent(b)
while a != b:
a, b = get_parent(a), get_parent(b)
b = new_head
new_chain = []
while b.height > a.height:
new_chain = [b] + new_chain
b = get_parent(b)
chain = chain[:a.height + 1] + new_chain
当接收一个新的信标链区块时,只在其分数是对头的分数的一种增进以及新区块的main_chain_ref在PoW主链时才改变头:
def should_I_change_head(main_chain, old_beacon_head, new_beacon_block):
return get_score(new_beacon_block) > get_score(old_beacon_head) and \
new_beacon_block.main_chain_ref in main_chain
主链重组后,也要重组信标链:
def reorg_beacon_chain(main_chain, beacon_chain):
old_head = beacon_chain[-1] //将倒数第一个块赋值给old_head
while beacon_chain[-1].main_chain_ref not in main_chain:
beacon_chain.pop()
queue = [beacon_chain[-1]]
new_head = beacon_chain[-1]
while len(queue) > 0:
b = queue.pop(0)
for c in get_children(b):
if c.main_chain_ref in main_chain:
if get_score(c) > get_score(new_head):
new_head = c
queue.append(c)
update_head(beacon_chain, old_head, new_head)
我们现在定义状态转移函数。从比较高的层面讲,状态转移由2部分组成:
纪元转移,只发生在active_state.height % SHARD_COUNT == 0,并且影响结晶状态和活跃状态。
处理每个块,这发生在每个块(如果是在纪元转换区块期间,它发生在纪元转换之后)并且只影响活跃状态。
纪元转移一般侧重验证人集合的改变上,包括调整余额,添加和移除验证者,以及处理跨链路和设置FFG检查点,并且处理每个块一般侧重保存有关在活跃状态中区块内活动的临时记录。
我们通过定义一些辅助函数开始。首先,基于一些种子随机地混洗验证者集合的算法:
def get_shuffling(seed, validator_count):
assert validator_count <= 16777216
rand_max = 16777216 - 16777216 % validator_count
o = list(range(validator_count)); source = seed
i = 0
while i < validator_count:
source = blake(source)
for pos in range(0, 30, 3):
m = int.from_bytes(source[pos:pos+3], 'big')
remaining = validator_count - i
if remaining == 0:
break
if validator_count < rand_max:
replacement_pos = (m % remaining) + i
o[i], o[replacement_pos] = o[replacement_pos], o[i]
i += 1
return o
该算法将用来选择分片委员会以及为区块选择提案者和证明者。这是该方法应用于为给定块选择证明者集合以及该块的N-skip孩子的提案者。
def get_attesters_and_proposer(crystallized_state, active_state, skip_count):
attestation_count = min(len(crystallized_state.active_validators), ATTESTER_COUNT)
indices = get_shuffling(active_state.randao, len(crystallized_state.active_validators))
return indices[:attestation_count], indices[attestation_count + skip_count]
对于分片跨链路,此过程稍微复杂一些。首先,我们选择每个纪元期间活跃的分片集合。我们希望给每个跨链路定位一些固定数量的公证人,但这意味着由于存在固定数量的分片和可变的验证者,我们将无法遍历每个纪元的每个分片。因此,我们首先需要选择给定纪元期间内跨链路的分片:
def get_crosslink_shards(crystallized_state):
start_from = crystallized_state.next_shard
count = len(crystallized_state.active_validators) // NOTARIES_PER_CROSSLINK
if start_from + count <= SHARD_COUNT:
return list(range(start_from, start_from + count))
else:
return list(range(start_from, SHARD_COUNT)) + list(range(start_from + count - SHARD_COUNT))
接着我们计算对于这些分片的任意一个,计算公证人集合:
def get_crosslink_notaries(crystallized_state, shard_id):
crosslink_shards = get_crosslink_shards(crystallized_state)
if shard_id not in crosslink_shards:
return None
all = len(crystallized_state.current_shuffling)
start = all * crosslink_shards.index(shard_id) // len(crosslink_shards)
end = all * (crosslink_shards.index(shard_id)+1) // len(crosslink_shards)
return crystallized_state.current_shuffling[start: end]
在朝代更替的开始就重新计算current_shuffling。
每个块的处理可以分解成3部分:
对一个区块的期望证明者数量是min(len(crystallized_state.active_validators), ATTESTER_COUNT)。令这个值为attester_count。检查attestation_bitfield作为长度为(attester_count + 7) // 8的字节数组。将其从左向右解释为一个比特列表(例如,比特0是attestation_bitfield[0] >> 7),比特9是attestation_bitfield[1] >> 6等)。检查attester_count-1之后的任意比特全是0。检查证明者的总数(例如,1比特的总数)至少是attester_count / (2 + block.skip_count)。
自增当前纪元
将current_checkpoint设置为前一个块的哈希
重置余额▶️,FFG投票人比特字段和部分跨链路列表
基于RecentProposerRecord更新活跃验证者的RANDAO承诺
1个验证者32ETH,我们看下面三种情况:
31250个验证者(1百万ETH),最小情况
312500个验证者(1千万ETH),平均情况
40000000个验证者(假设128M ETH作为供应上限),最坏情况:每个人都抵押ETH
平均状态的大小是:
‘height’: 8 bytes
‘randao’: 32 bytes,
‘ffg_voter_bitfield’: 1/8 bytes per validator,
‘recent_attesters’: 3 bytes/attesters, ~130 attesters/block, 100 blocks/epoch:
39000 bytes
‘partial_crosslinks’: PartialCrosslink * (validator count / 1024)
‘total_skip_count’: 8 bytes
‘recent_proposers’: (3 + 32 + 3) bytes per RecentProposerRecord *
100 proposers = 3800 bytes
部分跨链路增加:
'shard_id': 2 bytes,
'shard_block_hash': 32 bytes,
'voter_bitfield': 1/8 bytes/validator * 1024 validators = 128 bytes
因此每个跨链路是162字节,并且固定数据是42848字节。每个验证者,在FFG比特字节中,我们得到1/8字节。并且在部分跨链路中每个验证者162/1024~=0.158字节,合计每个验证者~0.283。因此,我们得到下面的值作为总的活跃状态大小:
1M ETH:42848 + 31250 * 0.283 = 51692 bytes
10M ETH: 42848 + 312500 * 0.283 = 131286 bytes
128M ETH: 42848 + 31250 * 0.283 = 1174848 bytes
结晶状态的大小是:
'active_validators': [ValidatorRecord],
'queued_validators': [ValidatorRecord],
'exited_validators': [ValidatorRecord],
'current_shuffling': 3 bytes per validator,
'current_epoch': 8 bytes,
'last_justified_epoch': 8 bytes,
'last_finalized_epoch': 8 bytes,
'dynasty': 8 bytes,
'next_shard': 2 bytes,
'current_checkpoint': 32 bytes,
'crosslink_records': [CrosslinkRecord],
'total_deposits': 32 bytes,
'crosslink_seed': 32 bytes,
'crosslink_seed_last_reset': 8 bytes
那是141字节的固定成本,不包括跨链路和验证者。
一个ValidatorRecord包括:
'pubkey': 32 bytes,
'withdrawal_shard': 2 bytes,
'withdrawal_address': 20 bytes,
'randao_commitment': 32 bytes,
'balance': 8 bytes,
'switch_dynasty': 8 bytes
总计102字节。为了简化,我们将假设处于排队的退出的验证人集合是空的并且侧重活跃集合。
一个CrosslinkRecord只是:
‘epoch’: 8 bytes,
‘hash’: 32 bytes
因此,crosslink_records列表每个分片只是40字节。总得分片数取决于验证人的最大数量:128M/32/1024~=4000分片,因此,crosslink_records将占用160000字节。
因此,160141字节的固定成本和每个验证者102字节的可变成本,我们得到下面的值作为总的结晶状态大小:
• 1M ETH: 160141 + 102 * 31250 = 3347641 bytes
• 10M ETH: 160141 + 102 * 312500 = 32035141 bytes
• 128M ETH: 160141 + 102 * 4000000 = 408160141 bytes
因此,即便是在最极端的情况,信标链状态也可以适应相对较旧的计算机的内存。
在我的机器(Thinkpad X270)上测试时,Black哈希400MB只花费~0.1秒,这是一个非常可接受的最坏情况结果,预计每100个块只会发生一次。【因为纪元选的是100个块】,尽管我们可以进一步通过明智地使用默克尔树来增加效率。
一个区块包括:
'parent_hash': 32 bytes,
'skip_count': 8 bytes,
'randao_reveal': 32 bytes,
'attestation_bitfield': 1/8 byte per participant, so 16 bytes,
'attestation_aggregate_sig': 64 bytes,
'shard_aggregate_votes': [AggregateVote],
'main_chain_ref': 32 bytes,
'state_hash': 64 bytes,
'sig': 64 bytes
【译者注】32+8+32+16+64+32+64+64=312B
一个AggregateVote是:
'shard_id': 2 bytes,
'shard_block_hash': 32 bytes,
'notary_bitfield': 1/8 bytes/validator * 1024 validators,
'aggregate_sig': 64 bytes
不带聚合投票的一个区块总计312字节。每个聚合投票是226字节。我们预期每纪元有验证者数量/1024这么多的聚合投票数(以及每块1/100的投票数),因此我们得到下面的值作为平均区块大小:
• 1M ETH: 312 + 31250 * 226 / 1024 / 100 = 380 bytes
• 10M ETH: 312 + 312500 * 226 / 1024 / 100 = 1002 bytes
• 128M ETH: 312 + 4000000 * 226 / 1024 / 100 = 9140 bytes
区块处理需要:
再哈希活跃状态
再哈希结晶状态(只在纪元转移)
验证1次BLS签名,外加