干货 | 以太坊 2.0 Phase 0 V0.8.0 技术规范详解(下)
干货 | 以太坊 2.0 Phase 0 V0.8.0 技术规范详解(上)
(接上)信标链区块
-
BeaconBlock
-
这个可以被认为是一个信标链区块的 主要容器/区块头。 -
注意 hash_tree_root(BeaconBlock) == hash_tree_root(BeaconBlockHeader)
所以每一个签名都是等价的。 -
字段 -
slot
—— 该区块创建时候的 slot。必须比parent_root
所定义的上一区块的时隙数更大 -
parent_root
—— 父区块的区块根,以此形成一条区块链 -
state_root
—— 使用当前区块运行完状态转换函数后的状态的哈希根 -
body
—— 上述所有的信标链操作对象以及一些补充性字段都包含在这一字段中 -
signature
—— 区块提议者对该区块的签名 -
BeaconBlockBody
-
randao_reveal
—— (区块提议者)对当前时段(epoch)的签名,以及,当该字段与其他验证者揭示的值(reveal)混合在一起时,可以继续生成随机性种子 -
Eth1Data
—— 一个对 Eth1 链上近期数据的投票。它维护着下列字段: -
deposit_root
—— 保证金合约中存储资金的 SSZ 列表hash_tree_root
-
deposit_count
—— 迄今为止已经发生的保证金笔数 -
block_hash
—— 包含了deposit_root
的 Eth1 区块哈希值。这一block_hash
可能在未来用于为 Eth1 链提供 Finality(类似于原来的用 FFG 合约来敲定 Eth1 链的计划)。 -
graffiti
—— 这是一个验证者可以随意填写的空间。该字段没有协议层的用途。 -
proposer_slashings
,attester_slashings
,attestations
,deposits
,voluntary_exits
,transfers
-
即可以被包含到 BeaconBlockBody
中的操作类型的列表(有最大长度)
信标链状态
BeaconState
即是从创世区块开始、用一系列区块运行状态转换函数后所得的结果。它包含了验证者注册器、关于随机数的信息、关于 Finality 的信息、相关的缓存,以及 eth1 链的数据。-
Versioning -
genesis_time
—— 跟踪创世事件发生的事件。这一数据让客户端可以根据经过的时间计算现在该是哪个 slot 了 -
slot
—— 时间被划分为固定长度的 slot(“时隙”),所有行动和状态转换都在具体的时隙中发生。这一字段追踪相应状态的时隙 -
fork
—— 一种处理信标链上分叉(升级)的机制。该字段的主要用意在于处理签名的版本控制,并处理分叉前后不同的签名对象。 -
历史(History) -
latest_block_header
—— 存储所看到最新区块头。在一个区块所在的 slot 中,区块头会不带状态根保存起来;等到下一个时隙开始的时候,状态根才会加入到区块头中。这是为了消除嵌入区块头中的状态根所导致的循环依赖性 -
block_roots
—— 逐 slot 存储的近期区块根。一个时隙的区块根在下一个时隙开始时才会加入,以避免因为状态根嵌入区块而导致的循环依赖性。至于那些被跳过的时隙(即在给定时间内没有产生区块),就存储前一个有效的区块根 -
state_roots
—— 逐 slot 存储的近期状态根。一个时隙的状态根在下一个时隙开始时才会加入 -
historical_roots
—— 一个双批次的最新区块和状态根默克尔累加器来产生对 XXX 的历史性默克尔证明 -
Eth1 -
eth1_data
—— 验证者达成共识并存储在状态中的Eth1Data
-
eth1_data_votes
—— 存储在状态中的Eth1Data
的实时列表。任何Eth1Data
只要在一个投票期中拿到了> 50%
的提议者投票,数据就会存进状态中,而新的准备金也可以得到处理 -
eth1_deposit_index
—— 新的要被处理的准备金的索引。只要eth1_data.deposit_count > eth1_deposit_index
,准备金就必须被添加到下一个区块中 -
注册表(Registry) -
validators
—— 一个Validator
记录的列表,用来跟踪完整的当前注册表。每个验证者都保存着与之相关的数据,比如公钥、有效余额,还有 status(pending、活跃、已退出,等等) -
balances
—— 一个与validator_registry
一一对应的列表。频繁且颗粒式变化的余额被抽取出来,以减少每个时段所需执行的重哈希数量的。 -
混洗(Shuffling) -
start_shard
—— 跟踪当前时段的 “启动分片”,作为基础去计算一个时段中为各委员会安排哪个分片 ——(shard_shard + committee_offset) % SHARD_COUNT
-
randao_mixes
—— 每一时段中的 Randao 混合值(mix)。每一时段开始的时候,上一时段的 randao_mix 会复制下来用作新一时段的计算基础。每个区块中,block.randao_reveal
的hash
会跟实时的混合值(running mix)进行异或运算(XOR)。 -
active_index_roots
—— 逐时段储存的活跃验证者索引的哈希根。主要是为了更好地服务轻客户端 -
compact_committee_roots
—— 逐时段存储的当前时段的CompactCommittee
列表的哈希根。这也是委员会的简洁代表,用来更好地服务轻客户端。预计会在 Phase 1 把常设委员会加入这个根中。 -
罚没(Slashing) -
slashings
—— 逐时段存储的该时段内被罚没的总额。任意时间点,一个列表的总和都给出了 “近期被罚没的余额”,并被用于计算相关验证者需要被罚没的余额比例 -
见证(Attestation) -
来自区块的 Attestations
被转化为PendingAttestations
并存储在状态中,用于跨越时段边界时候的批量记账(bulk accounting)。我们储存了两个独立的列表 -
previous_epoch_attesations
—— 上一时段的各时隙的PendingAttestations
的列表。注意:这些乃是在上一时段各时隙中发出的见证,并不必然就是那些在上一时段中被打包到区块中的见证 -
current_epoch_attesations
—— 当前时段的各时隙中的PendingAttestations
列表。在当前时段处理结束后被转移到previous_epoch_attestations
-
交联(Crosslink) -
我们存储过往和当前时段的交联,因为交联必须形成一条链,所以 Attestation
要在创建过程中原样引用交联。要在当前时段验证来自以往时段的见证中的交联引用,我们使用previous_crosslinks
。 -
current_crosslinks
—— 当前时段的交联列表(每个分片一个交联) -
previous_crosslinks
—— 以往时段的交联列表(保持原样不变) -
Finality -
justification_bits
—— 4 个 bit 的、用来跟踪最新 4 个时段中的确定性辩护(justification),以帮助确定性计算 -
previous_justified_checkpoint
—— 以往时段最新被合理化(justified)的Checkpoint
,保持它在以往时段中的原样。用来验证以往时段的见证 -
current_justified_checkpoint
—— 当前时段最新被合理化的Checkpoint
。用来验证当前时段的见证,并用于分叉选择 -
finalized_checkpoint
—— 最新被敲定的Checkpoint
,该点以前的区块将永远保持在主链上,不会被重组
状态转换
-
state_transition
-
这是状态转换的顶层函数。它以一个旧状态和一个信标链区块为输入,然后输出一个新状态 -
process_slots
-
状态转换函数的第一个组件,处理所有发生在时隙中的操作(无论属于哪个区块)。如果在运行状态转换函数的区块与其父块之间存在被跳过的 slot,那么区块所在时隙前的多个时隙都会在 process_slots
期间得到处理 -
在见证区块或者产生区块的时候,参与共识的客户端有时候调用这个函数来转换状态,以跳过空的时隙 -
每个时隙要缓存近期数据的时候都要调用 process_slot
-
只有跨越时段边界的时候才需要调用 process_epoch
。注意,因为state.slot
尚未增加,调用会在slot % SLOTS_PER_EPOCH == 0
开始的那个时段的状态转换中发生 -
process_slot
-
每个时隙都要执行一些对上一时隙的簿记工作。注意,因为 state
还未更新,所以仍表示着上一时隙完成后的状态。同样地,state.slot
仍等于上一时隙(process_slot
在process_slots
内完成之后state.slot += 1
)。 -
前一时隙的完成后状态根会在下一时隙中插入到状态中,以避免循环依赖性 -
缓存状态根 -
上一时隙的状态根会缓存在状态中。注意 hash_tree_root(state) == root_of_previous_state
,因为state
还是上一时隙完成后的状态,还没有改变过来。 -
缓存最新的区块头状态根 -
如果上一时隙产生了一个区块(既并非被跳过的空时隙),那么我们将 previous_state_root
插入到缓存的区块头中。如果遇到空时隙,这个区块头会一直保存在状态中,直到产生下一个区块。 -
缓存区块根 -
我们会在每个时隙中将最新的区块缓存入 latest_block_roots
。如果该 时隙被跳过了,那就会缓存来自最新的没有被跳过的时隙的区块。
时段处理
slot % EPOCH_LENGTH == 0
)开始的时候开始。注意,运行 process_epoch
的时候 state.slot
仍旧等于上一时隙(slot % EPOCH_LENGTH == EPOCH_LENGTH - 1
),只在前者完成后,后者才增加。process_epoch
主要是个容器函数,会调用其它的时段处理次级函数。-
process_justification_and_finalization
-
Justification 的更新首先要估计有多少验证者(用余额来加权)投票给上一个时段作为辩护的目标。只要超过 2/3 的活跃余额给相同时段做了见证,而该时段是上一时段,那就将 justification 的状态更新为上一时段。然后考虑当前时段,如果超过 2/3 的活跃余额都给当前时段做了见证,则当前时段也被合理化。 -
只有最近的 2 个、3 个、4 个时段可以被敲定。实现方式是检查最近的 justification 用作来源的那个时段,以及被近期合理化时段用作来源的最新被合理化的时段(在过去 4 个时段以内),是否已被敲定。我们只考虑在草案的 5.5 中讨论的 k=1
和k=2
确定性规则。
-
process_crosslinks
-
对每一个分片,只要至少 2/3 的委员会成员见证了相同的交联,就将该分片(在信标链视角中)的最新交联更新为该胜出的交联。信标链会检查上一时段和当前时段,以防止上一时段加入了更多交联。 -
交联必须通过 crosslink.parent_root
形成一条链;只有那些符合链标准的交联才会在get_winning_crosslink_and_attesting_indices
中被考虑。
-
process_rewards_and_penalties
-
所有最后一个时段对验证者余额的奖励和罚没都用这个函数来收集。奖励和惩罚从 get_crosslink_deltas
和get_attestation_deltas
处收集,后两者会以列表的元组(tuples of lists)的形式返回每一个验证者所得的奖励和惩罚。奖励和惩罚是分开的,以避免出现 signed integer。 -
get_base_reward
-
所有奖励都会乘以一个 1/[TotalBalance^(1/2)] 。想理解为什么,请看设计哲学 -
get_attestation_deltas
-
根据每个验证者在上一时段的见证行为的函数来决定验证者的余额如何更改 -
对每一个活跃的验证者来设: -
FFG 的 Source 、Target 和 Head 奖励 —— 如果一个验证者的 source、target 或者 head 见证与上一时段当时的 source、target 以及 head 相匹配,那就为他发放奖励,否则就发放惩罚。 -
见证被接受的速度进行奖励 —— 找出每个验证者的最快被打包的 attestation,并给相应的区块提议者一小笔奖励、给相应被打包的验证者一笔与其见证被打包的速度成比例的奖励 -
怠惰惩罚 —— 如果链不能敲定,那就惩罚所有人,尤其是那些不参与的验证者。注意,怠惰惩罚不会降临到那些完全参与的验证者头上 -
get_crosslink_deltas
-
每一个为(在他们的委员会中)胜出的交联做见证的验证者都可以收到一个与参与程度成比例的奖励,反之就会受到惩罚
-
process_registry_updates
-
余额足以激活验证者身份,但还没有被添加队列中的,将被添加到激活队列中 -
余额太低的验证者将被 “弹出”,放到 “取款队列” 中 -
激活队列中的验证者会在一段时间内(within the churn limit)激活
-
process_slashings
-
验证者被罚没的力度会与在一定时间内被罚没的验证者数量成比例。这一点与验证者被罚没的顺序是相互独立的。 process_slashings
会遍历近期的罚没事件,并成比例地罚没验证者。
-
process_final_updates
-
该方程处理需要随时段运作的各种功能,即: -
在新一轮投票期开始的时候,重置 eth1 数据投票 -
重新计算有效余额(使用 husteresis) -
确定下一时段开始的新分片 -
给众所周知索引要确定下来的最近一个时段设定索引根 ( next_epoch + ACTIVATION_EXIT_DELAY
) -
为下一个时段设定委员会根 -
将下一个时段的被罚没余额设定为 0 -
将当前时段的 randao 混合值结果设定为下一时段的基本 randao 混合值 -
(潜在地)将状态追加到历史根 -
将 current_epoch_attestations
移动到previous
,然后将current
设为空值
区块处理
process_block
是调用区块处理流程的次级函数的主函数。如果在块处理中抛出了任何断言或者异常,该块就会被视为无效的,并被丢弃。process_block_header
决定了一个区块是否有效。验证方式有:-
该区块是从合适的时隙中产生的 -
父区块的哈希值是正确的 -
区块提议者没有被罚没 -
相应提议者对区块的签名是有效的
process_block_header
也会在 state
中存储区块头,以便后续用于状态转变函数。注意, state_root
会被设为空值,以避免循环依赖。状态根会在下一个时隙开始的时候通过 process_slot
添加进去。process_randao
验证该区块的 RANDAO 揭示值是当前时段区块提议者的有效签名,并且,若然,则将它混合进当前时段的混合值中。process_eth1_data
把该区块的 eth1-data 添加到状态的 eth1-data-vote 中。如果该投票期中超过半数的票都投给了相同的 eth1-data,就更新 state.eth1_data
。process_operations
保证罚没、见证、保证金存入、保证金退出、保证金转移的数量都如后续部分的函数定义的那样发生。这是通过处理 ProposerSlashing
, AttesterSlashing
、Attestation
、Deposit
、VoluntaryExit
和 Transfer
对象来实现的。(文内提供了许多超链接,请点击阅读原文到 EthFans 网站上获取)
原文链接:
https://notes.ethereum.org/jDcuUp3-T8CeFTv0YpAsHw?view#Phase-0-accompanying-resource-v080
作者: Danny Ryan
翻译: 阿剑
你可能还喜欢: