以下为规范译文:
在以太坊2.0当中,有一个被称为“beacon链”的核心系统链,这个beacon链负责存储和管理当前活跃PoS验证者的集合。而最初成为验证者的唯一机制,是在现有的PoW主链发送一笔包含32 ETH
的交易。当你这样做时,一旦PoS链处理了该区块,你就会进入队列,并最终成为一名活跃验证者。当然,你也可以自愿注销验证者身份,或者因为自己的不当行为而被强行注销身份。
beacon链的主要负载来源是证明(attestation)。这些证明会同时确认一个分片区块,以及相对应的beacon链区块。对于相同的分片区块,足够数量的证明就创建了一个“交联”(crosslink)。“交联”用于“确认”分片链的片段进入了主链,并且这也是不同分片之间互相通信的主要方式。
注:项目的python代码库地址为:https://github.com/ethereum/beacon_chain
一、术语
1、验证者(Validator):参与Casper/分片 共识系统的参与者。你可以通过将32 ETH存入这个Casper机制而成为一名验证者。
2、活跃验证者集(Active validator set):指那些正在参与的验证者,Casper机制希望产生并证明区块、“交叉链接”以及其它共识对象;
3、委员会(Committee):活跃验证者集的一个(伪)随机抽样子集。当一个委员会被集体选出时,正如“这个委员会证明了X”一样,这就意味着,该委员会的一些子集,包含了足够的验证者,以致协议承认它代表着该委员会。
4、提出者(Proposer):创建区块的验证者;
5、证明者(Attester):它是委员会的验证成员,其需要在一个区块上进行签名操作;
6、Beacon链(Beacon chain):分片系统的基础,即核心PoS链;
7、分片链(Shard chain):交易所发生的链之一,并用于存储账户数据;
8、交联(Crosslink):一组来自委员会的签名,其可证明一个分片链中的区块,它们可以被包含在beacon链当中。交联是beacon链“了解”分片链更新状态的主要手段;
9、Slot:SLOT_DURATION周期时间,在此期间,一个提出者有能力创建一个区块,而某些证明者能够进行证明工作;
10、朝代变迁(Dynasty transition):验证者集的变化;
11、朝代(Dynasty):自创世以来,在给定链中发生的朝代变迁的数量;
12、周期(Cycle):在这个时期内,所有验证者都有一次投票的机会(除非一个朝代的转变发生在内部)
13、完成区块及合理区块(Finalized, justified),见Casper FFG定稿:https://arxiv.org/abs/1710.09437
14、取款周期(Withdrawal period):验证者退出与验证者余额可取款之间的slot数量;
15、创始时间(Genesis time):beacon链在slot 为0时的创始区块UNIX时间;
二、常量
验证者状态码:
特殊记录类型:
验证者集增量标志:
三、PoW主链注册合约
以太坊2.0的初始部署阶段,是不需要对PoW链进行共识更改的。相反的是,我们会向PoW链添加一个注册合约,以存入ETH。这个合约有一个注册函数,它采用了pubkey
, withdrawal_shard
, withdrawal_address
, randao_commitment
这些参数,正如下面的ValidatorRecord中所定义。
这个注册合约会通过beacon链发出一个带有各种参数的日志。它不会进行验证,而是把注册逻辑发送给beacon链。需要注意的是,注册合约不会去验证占有证明(基于BLS12-381曲线)。
四、Beacon 链数据结构
Beacon链是PoS系统的“主链”,beacon链的主要职责是:
- 存储并维护活跃、列队等待以及退出验证者的集合;
- 处理交联(见上文);
- 处理逐块一致性,以及最终小工具(finality gadget);
4、1 Beacon 链区块
一个BeaconBlock
具有以下字段:
{
# Slot number
'slot': 'int64',
# Proposer RANDAO reveal
'randao_reveal': 'hash32',
# Recent PoW chain reference (block hash)
'pow_chain_reference': 'hash32',
# Skip list of ancestor block hashes (i'th item is 2**i'th ancestor (or zero) for i = 0, ..., 31)
'ancestor_hashes': ['hash32'],
# Active state root
'active_state_root': 'hash32',
# Crystallized state root
'crystallized_state_root': 'hash32',
# Attestations
'attestations': [AttestationRecord],
# Specials (e.g. logouts, penalties)
'specials': [SpecialRecord]
}
一个AttestationRecord
具有以下字段:
{
# Slot number
'slot': 'int64',
# Shard number
'shard': 'int16',
# Block hashes not part of the current chain, oldest to newest
'oblique_parent_hashes': ['hash32'],
# Shard block hash being attested to
'shard_block_hash': 'hash32',
# Attester participation bitfield (1 bit per attester)
'attester_bitfield': 'bytes',
# Slot of last justified block
'justified_slot': 'int64',
# Hash of last justified block
'justified_block_hash': 'hash32',
# BLS aggregate signature
'aggregate_sig': ['int256']
}
一个AttestationSignedData
具有以下字段:
{
# Chain version
'version': 'int64',
# Slot number
'slot': 'int64',
# Shard number
'shard': 'int16',
# 31 parent hashes
'parent_hashes': ['hash32'],
# Shard block hash
'shard_block_hash': 'hash32',
# Slot of last justified block referenced in the attestation
'justified_slot': 'int64'
}
一个SpecialRecord
具有以下字段:
{
# Kind
'kind': 'int8',
# Data
'data': ['bytes']
}
4、2 Beacon链状态
beacon链状态分为了活跃状态(active state )和结晶状态(crystallized state)这两个部分;
下面是活跃状态ActiveState具有的字段:
{
# Attestations not yet processed
'pending_attestations': [AttestationRecord],
# Specials not yet been processed
'pending_specials': [SpecialRecord]
# Most recent 2 * CYCLE_LENGTH block hashes, older to newer
'recent_block_hashes': ['hash32'],
# RANDAO state
'randao_mix': 'hash32'
}
下面是结晶状态CrystallizedState
具有的字段:
{
# Dynasty number
'dynasty': 'int64',
# Dynasty seed (from randomness beacon)
'dynasty_seed': 'hash32',
# Dynasty start
'dynasty_start_slot': 'int64',
# List of validators
'validators': [ValidatorRecord],
# Most recent crosslink for each shard
'crosslinks': [CrosslinkRecord],
# Last crystallized state recalculation
'last_state_recalculation_slot': 'int64',
# Last finalized slot
'last_finalized_slot': 'int64',
# Last justified slot
'last_justified_slot': 'int64',
# Number of consecutive justified slots
'justified_streak': 'int64',
# Committee members and their assigned shard, per slot
'shard_and_committee_for_slots': [[ShardAndCommittee]],
# Total deposits penalized in the given withdrawal period
'deposits_penalized_in_period': ['int32'],
# Hash chain of validator set changes (for light clients to easily track deltas)
'validator_set_delta_hash_chain': 'hash32'
# Parameters relevant to hard forks / versioning.
# Should be updated only by hard forks.
'pre_fork_version': 'int32',
'post_fork_version': 'int32',
'fork_slot_number': 'int64',
}
一个ValidatorRecord具有以下字段:
{
# BLS public key
'pubkey': 'int256',
# Withdrawal shard number
'withdrawal_shard': 'int16',
# Withdrawal address
'withdrawal_address': 'address',
# RANDAO commitment
'randao_commitment': 'hash32',
# Balance
'balance': 'int64',
# Status code
'status': 'int8',
# Slot when validator exited (or 0)
'exit_slot': 'int64'
}
一个CrosslinkRecord
具有以下字段:
{
# Dynasty number
'dynasty': 'int64',
# Slot number
'slot': 'int64',
# Beacon chain block hash
'shard_block_hash': 'hash32'
}
一个ShardAndCommittee 对象具有以下字段:
fields = {
# The shard ID
'shard_id': 'int16',
# Validator indices
'committee': ['int24']
}
4、3 Beacon链的处理
处理beacon链,在很多方面与处理PoW链有着相似之处。例如客户端下载、处理区块,维护当前的“权威链”,并终止于当前“头部”。然而,由于beacon链与现有PoW链之间的关系,并且beacon链是一种PoS链,两者还是有很大不同的。
对于beacon链上节点所处理的区块,必须要满足3个条件:
- 由
ancestor_hashes[0]
指向的父对象已被处理及接受。 - 由
pow_chain_ref
所指向的PoW链区块已被处理及接受; - 节点的本地时间大于或等于由GENESIS_TIME + slot_number * SLOT_DURATION 计算得出的最小时间戳;
如果不满足这三个条件,则客户端应延迟处理区块,直到满足这几个条件为止。
在区块生产方面,由于PoS机制的原因,beacon链显然与PoW链存在着显著差异。客户端应检查权威链应该在什么时候创建区块,并查找它的slot(类似高度)数;当slot到达时,它会根据要求提出或证明一个区块;
4、4。Beacon链分叉选择规则
beacon链使用了Casper FFG分叉选择规则,即“支持包含最高合理检查点(highest-epoch justified checkpoint)的区块链”。为了从相同合理检查点派生而出的两条链之间进行选择,区块链会使用即时消息驱动GHOST(IMD GHOST)方案来选择出权威链 。
具体描述参见:https://ethresear.ch/t/beacon-chain-casper-ffg-rpj-mini-spec/2760
相关模拟实现代码库,请参见:
https://github.com/ethereum/research/blob/master/clock_disparity/ghost_node.py
下图展示了系统工作的一个例子(绿色是完成区块,黄色代表合理区块,灰色代表证明):
五、Beacon链状态转移函数
我们现在定义一下状态转移函数。从高层次讲,状态转换由两个部分组成:
- 结晶状态重计算,这只有当
block.slot_number >= last_state_recalc + CYCLE_LENGTH
时才会发生,而它会影响CrystallizedState
以及ActiveState
; - 每区块处理(per-block processing),它发生在每个区块,并且其只影响
ActiveState
;
结晶状态重计算通常集中于对验证者集的更改,包括调整余额,添加和删除验证者,以及处理交联( crosslink)和设置FFG检查点,而每区块处理通常集中于验证聚合签名并保存与ActiveState
区块内活动有关的临时记录。
5、1 辅助函数(Helper functions)
我们首先定义一些辅助算法。首先是,选择活跃验证者的函数:
def get_active_validator_indices(validators):
return [i for i, v in enumerate(validators) if v.status == ACTIVE]
接下来是洗牌这个列表的函数:
def shuffle(lst, seed):
# entropy is consumed in 3 byte chunks
# rand_max is defined to remove the modulo bias from this entropy source
rand_max = 2**24
assert len(lst) <= rand_max
o = [x for x in lst]
source = seed
i = 0
while i < len(lst):
source = hash(source)
for pos in range(0, 30, 3):
m = int.from_bytes(source[pos:pos+3], 'big')
remaining = len(lst) - i
if remaining == 0:
break
rand_max = rand_max - rand_max % remaining
if m < rand_max:
replacement_pos = (m % remaining) + i
o[i], o[replacement_pos] = o[replacement_pos], o[i]
i += 1
return o
下面是一个将列表拆分成N块的函数:
def split(lst, N):
return [lst[len(lst)*i//N: len(lst)*(i+1)//N] for i in range(N)]
接下来,是我们的组合辅助函数:
def get_new_shuffling(seed, validators, crosslinking_start_shard):
active_validators = get_active_validator_indices(validators)
if len(active_validators) >= CYCLE_LENGTH * MIN_COMMITTEE_SIZE:
committees_per_slot = min(len(active_validators) // CYCLE_LENGTH // (MIN_COMMITTEE_SIZE * 2) + 1, SHARD_COUNT // CYCLE_LENGTH)
slots_per_committee = 1
else:
committees_per_slot = 1
slots_per_committee = 1
while len(active_validators) * slots_per_committee < CYCLE_LENGTH * MIN_COMMITTEE_SIZE \
and slots_per_committee < CYCLE_LENGTH:
slots_per_committee *= 2
o = []
for i, slot_indices in enumerate(split(shuffle(active_validators, seed), CYCLE_LENGTH)):
shard_indices = split(slot_indices, committees_per_slot)
shard_start = crosslinking_start_shard + \
i * committees_per_slot // slots_per_committee
o.append([ShardAndCommittee(
shard = (shard_start + j) % SHARD_COUNT,
committee = indices
) for j, indices in enumerate(shard_indices)])
return o
下面是一个关于正在发生的图解:
我们还定义了两个函数:
def get_shards_and_committees_for_slot(crystallized_state, slot):
earliest_slot_in_array = crystallized_state.last_state_recalculation_slot - CYCLE_LENGTH
assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2
return crystallized_state.shard_and_committee_for_slots[slot - earliest_slot_in_array]
def get_block_hash(active_state, curblock, slot): earliest_slot_in_array = curblock.slot – CYCLE_LENGTH * 2 assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2 return active_state.recent_block_hashes[slot – earliest_slot_in_array]
其中get_block_hash(_, _, s)
应该始终以slot s
返回链中的区块,而get_shards_and_committees_for_slot(_, s)则不应该改变,除非朝代(dynasty)发生改变。
我们还定义了一个函数,为验证者哈希链“添加了一个link”,这在添加或移除一个验证者时使用:
def add_validator_set_change_record(crystallized_state, index, pubkey, flag):
crystallized_state.validator_set_delta_hash_chain = \
hash(crystallized_state.validator_set_delta_hash_chain +
bytes1(flag) + bytes3(index) + bytes32(pubkey))
最后,我们抽象地将用于奖励/惩罚计算的int_sqrt(n)定义为最大整数k,使得k**2<=n:
def int_sqrt(n):
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
5、2 关于启动(On startup)
运行以下代码:
def on_startup(initial_validator_entries):
# Induct validators
validators = []
for pubkey, proof_of_possession, withdrawal_shard, withdrawal_address, \
randao_commitment in initial_validator_entries:
add_validator(validators, pubkey, proof_of_possession,
withdrawal_shard, withdrawal_address, randao_commitment)
# Setup crystallized state
cs = CrystallizedState()
x = get_new_shuffling(bytes([0] * 32), validators, 0)
cs.shard_and_committee_for_slots = x + x
cs.dynasty = 1
cs.crosslinks = [CrosslinkRecord(dynasty=0, slot=0, hash=bytes([0] * 32))
for i in range(SHARD_COUNT)]
# Setup active state
as = ActiveState()
as.recent_block_hashes = [bytes([0] * 32) for _ in range(CYCLE_LENGTH * 2)]
CrystallizedState()
和ActiveState()
构造函数应根据上下文将所有值初始化为零字节、空值或空数组。add_validator
程序定义如下:
def add_validator(validators, pubkey, proof_of_possession, withdrawal_shard,
withdrawal_address, randao_commitment):
# if following assert fails, validator induction failed
# move on to next validator registration log
assert BLSVerify(pub=pubkey,
msg=hash(pubkey),
sig=proof_of_possession)
rec = ValidatorRecord(
pubkey=pubkey,
withdrawal_shard=withdrawal_shard,
withdrawal_address=withdrawal_address,
randao_commitment=randao_commitment,
balance=DEPOSIT_SIZE * GWEI_PER_ETH, # in Gwei
status=PENDING_ACTIVATION,
exit_slot=0
)
index = min_empty_validator(validators)
if index is None:
validators.append(rec)
return len(validators) - 1
else:
validators[index] = rec
return index
5、3 每区块处理(Per-block processing)
这个程序应该在每个区块上执行。
首先,将recent_block_hashes设置为以下输出,其中parent_hash是前一个区块的哈希:
def get_new_recent_block_hashes(old_block_hashes, parent_slot,
current_slot, parent_hash):
d = current_slot - parent_slot
return old_block_hashes[d:] + [parent_hash] * min(d, len(old_block_hashes))
get_block_hash的输出不应该改变,除非它不再 throw for current_slot - 1
,而是 throw for current_slot - CYCLE_LENGTH * 2 - 1
此外,使用以下算法检查区块的ancestor_hashes
数组是否被正确更新:
def update_ancestor_hashes(parent_ancestor_hashes, parent_slot_number, parent_hash):
new_ancestor_hashes = copy.copy(parent_ancestor_hashes)
for i in range(32):
if parent_slot_number % 2**i == 0:
new_ancestor_hashes[i] = parent_hash
return new_ancestor_hashes
区块可以有0个或多个AttestationRecord对象,其中每个AttestationRecord对象具有以下字段:
fields = {
# Slot number
'slot': 'int64',
# Shard ID
'shard_id': 'int16',
# List of block hashes that this signature is signing over that
# are NOT part of the current chain, in order of oldest to newest
'oblique_parent_hashes': ['hash32'],
# Block hash in the shard that we are attesting to
'shard_block_hash': 'hash32',
# Who is participating
'attester_bitfield': 'bytes',
# The actual signature
'aggregate_sig': ['int256']
}
对于这些证明中的每一个:
- 验证
slot <= parent.slot
以及slot >= max(parent.slot - CYCLE_LENGTH + 1, 0)
; - 验证
给定链中的justified_slot
以及justified_block_hash
等于或早于结晶状态的last_justified_slot
- 计算
parent_hashes = [get_block_hash(crystallized_state, active_state, block, slot - CYCLE_LENGTH + i) for i in range(CYCLE_LENGTH - len(oblique_parent_hashes))] + oblique_parent_hashes
- 让
attestation_indices
成为get_indices_for_slot(crystallized_state, active_state, slot)[x]
,选择x
那么attestation_indices.shard_id
就等于 所提供的shard
值,以找到创建证明记录的验证者集; - 验证
len(attester_bitfield) == ceil_div8(len(attestation_indices))
, 其中ceil_div8 = (x + 7) // 8
. 验证位len(attestation_indices)
…. 以及更高的, 如果出现的 (i.e. len(attestation_indices)结果不是8的倍数), 则所有为0 - 让
version = pre_fork_version if slot < fork_slot_number else post_fork_version
. - 使用生成的公钥组以及序列化形式的
AttestationSignedData(version, slot, shard, parent_hashes, shard_block_hash, justified_slot)
验证aggregate_sig
;
5、4 状态重计算
当slot - last_state_recalc >= CYCLE_LENGTH
时进行重复过程;
对于所有 last_state_recalc - CYCLE_LENGTH ... last_state_recalc - 1
范围中的slot s
;
- 确定至少为该区块投票的验证者的总集合;
- 确定这些验证者的总余额,如果该值乘以3等于或超过所有活跃验证者乘以2的总余额,则设置 last_justified_slot = max(last_justified_slot, s) 以及 justified_streak += 1。否则,则设置justified_streak = 0;
- 如果justified_streak >= CYCLE_LENGTH + 1,则设置last_finalized_slot = max(last_finalized_slot, s – CYCLE_LENGTH – 1);
- 删除所有比 last_state_recalc slot更早的证明记录;
还有:
1、设置last_state_recalc += CYCLE_LENGTH; 2、设置indices_for_slots[:CYCLE_LENGTH] = indices_for_slots[CYCLE_LENGTH:];
对于所有(shard_id, shard_block_hash) 元组,计算为该分片区块哈希投票的验证者总存款大小。如果该值乘以3等于或超过委员会中所有验证者的总余额乘以2,并且当前朝代(dynasty)超过crosslink_records[shard_id].dynasty,则设置crosslink_records[shard_id] = CrosslinkRecord(dynasty=current_dynasty, hash=shard_block_hash);
待办事项:
- FFG参与奖励;
- 委员会参与奖励;
六、朝代变迁(Dynasty transition)
如果满足下列所有标准,则在状态重新计算之后发生了朝代变迁:
block.slot - crystallized_state.dynasty_start_slot >= MIN_DYNASTY_LENGTH
last_finalized_slot > dynasty_start_slot
- 对于
shard_and_committee_for_slots
中的每一个shard
分片数,crosslinks[shard].slot > dynasty_start_slot
然后,运行下面的算法来更新验证者集合:
def change_validators(validators):
# The active validator set
active_validators = get_active_validator_indices(validators, dynasty)
# The total balance of active validators
total_balance = sum([v.balance for i, v in enumerate(validators) if i in active_validators])
# The maximum total wei that can deposit+withdraw
max_allowable_change = max(
2 * DEPOSIT_SIZE GWEI_PER_ETH,
total_balance // MAX_VALIDATOR_CHURN_QUOTIENT
)
# Go through the list start to end depositing+withdrawing as many as possible
total_changed = 0
for i in range(len(validators)):
if validators[i].status == PENDING_ACTIVATION:
validators[i].status = ACTIVE
total_changed += DEPOSIT_SIZE
add_validator_set_change_record(crystallized_state, i, validators[i].pubkey, ENTRY)
if validators[i].status == PENDING_EXIT:
validators[i].status = PENDING_WITHDRAW
validators[i].exit_slot = current_slot
total_changed += validators[i].balance
add_validator_set_change_record(crystallized_state, i, validators[i].pubkey, EXIT)
if total_changed >= max_allowable_change:
break
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods period_index = current_slot // WITHDRAWAL_PERIOD total_penalties = ( (crystallized_state.deposits_penalized_in_period[period_index]) + (crystallized_state.deposits_penalized_in_period[period_index – 1] if period_index >= 1 else 0) + (crystallized_state.deposits_penalized_in_period[period_index – 2] if period_index >= 2 else 0) ) # Separate loop to withdraw validators that have been logged out for long enough, and # calculate their penalties if they were slashed for i in range(len(validators)): if validators[i].status in (PENDING_WITHDRAW, PENALIZED) and current_slot >= validators[i].exit_slot + WITHDRAWAL_PERIOD: if validators[i].status == PENALIZED: validators[i].balance -= validators[i].balance * min(total_penalties * 3, total_balance) // total_balance validators[i].status = WITHDRAWN
withdraw_amount = validators[i].balance
...
# STUB: withdraw to shard chain
最后:
- 设置 last_dynasty_start_slot = crystallized_state.last_state_recalculation_slot
- 设置 crystallized_state.dynasty += 1
- 设置 next_start_shard = (shard_and_committee_for_slots[-1][-1].shard + 1) % SHARD_COUNT
- 设置 shard_and_committee_for_slots[CYCLE_LENGTH:] = get_new_shuffling(active_state.randao_mix, validators, next_start_shard)
七、尚未完成的工作
注:这个项目的完成度大约为60%,所缺少的主要部分为:
- 具体如何构造 crystallized_state_root以及active_state_root,包括用于轻客户端的默克尔化证明;
- 关于pow_chain_reference可接受值的具体规则;
- 关于分片链区块、提出者等具体规则;
- 关于强制撤销登记的具体规则;
- 关于(全局时钟、网络延迟、验证者诚实、验证者活跃度等)问题的各种假设;
- 关于监护证明的逻辑,包括削减条件;
- 添加BLS12-381曲线的附录;
- 添加gossip网络以及链外签名聚合逻辑的附录;
- 添加一个词汇表(在单独的词汇表中),以全面而准确地定义所有术语;
- 进行同行评审、安全审核和形式验证;
可能的修订/增补 工作
- 用LMD替换IMD分叉选择规则;
- 将
crystallized_state_root
以及active_state_root
默克尔化成一个单独的根; - 用一个对STARK友好的哈希函数替换掉Blake;
- 去掉朝代(dynasties)的概念;
- 将slot直接换成8秒;
- 允许延迟聚合签名的包含;
- 引入一种RANDAO削减条件;
- 对于占有证明,使用一种单独的哈希函数;
- 修改
ShardAndCommittee
数据结构; - 为历史beacon链区块添加一个双批(double-batched)的默克尔累加器;
- 允许存款大于32ETH,并设置存款上限;
- 对于存款低于32ETH(或其它阈值)的情况,设置一个罚金;
- 为寄存器添加一个
SpecialRecord
; - 重写文档可读性;
- 清楚地记录各种边缘情况,例如委员会的大小;
附录
附录A -哈希函数
我们的目标是在推出beacon链时,拥有一个对STARK友好的哈希函数hash(x)
。这个哈希函数的标准化过程,是由STARKware主导的,他们将制定详细的报告说明。我们会使用BLAKE2b-512
作为占位符。明确地说,我们设置了hash(x) := BLAKE2b-512(x)[0:32]
,其中BLAKE2b-512
算法是在RFC 7693中定义的。