如何在CL中实施FOCIL

本文介绍了FOCIL(Forced Committee Inclusion Lists)引入到CL(Consensus Layer)中的变更,并探讨了FOCIL与ePBS(enhanced Proposer-Builder Separation)的兼容性。

本文介绍了 FOCIL 将为 CL 带来的改变。此外,本文还考察了 FOCIL 与 ePBS 之间的兼容性,因为 CL 客户端团队主要支持 ePBS 作为 Glamsterdam 的 CL 主打功能。本文旨在阐述 FOCIL 的技术方面,为核心开发人员评估工作范围提供坚实的基础。

FOCIL 引入了包含列表(IL),用于约束下一个 slot 的区块。约束要求区块应包含 IL 交易,以满足 IL 约束。以下各节从 IL 生命周期角度考察了主要需求,描述了更改和规范。

diagram_FOCIL

IL 生产

IL 委员会成员构建一个 IL,并在 P2P 网络上广播。IL 委员会由每个 slot 指定的 16 名信标委员会成员组成。验证者调用 GetInclusionListCommittee 函数以确定它是否分配到了 IL 委员会。

def get_inclusion_list_committee(
    state: BeaconState, slot: Slot
) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]:
    epoch = compute_epoch_at_slot(slot)
    seed = get_seed(state, epoch, DOMAIN_INCLUSION_LIST_COMMITTEE)
    indices = get_active_validator_indices(state, epoch)
    start = (slot % SLOTS_PER_EPOCH) * INCLUSION_LIST_COMMITTEE_SIZE
    end = start + INCLUSION_LIST_COMMITTEE_SIZE
    return Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE](
        [\
            indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)]\
            for i in range(start, end)\
        ]
    )

随着分配的 slot 接近,IL 委员会成员调用 GetInclusionList 引擎 API 来构造 IL。输入参数是 parentHash,返回的 IL 应该建立在其之上,API 返回 IL 交易。

class InclusionList(Container):
    slot: Slot
    validator_index: ValidatorIndex
    inclusion_list_committee_root: Root
    transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]

IL 传播

IL 在 P2P 网络上以 全局 topic inclusion_list 广播。验证者 存储在 IL 视图冻结截止时间之前收到的 IL,即 slot 开始后的 9 秒。当 IL 委员会成员为同一个 slot 产生多个 IL 时,就会发生 IL 抵触。如果 IL 委员会成员发生抵触,则该成员的所有 IL 都应被忽略。

def process_inclusion_list(
    store: InclusionListStore, inclusion_list: InclusionList, is_before_view_freeze_cutoff: bool
) -> None:
    key = (inclusion_list.slot, inclusion_list.inclusion_list_committee_root)

    # 忽略来自抵触者的 `inclusion_list`。
    if inclusion_list.validator_index in store.equivocators[key]:
        return

    for stored_inclusion_list in store.inclusion_lists[key]:
        if stored_inclusion_list.validator_index != inclusion_list.validator_index:
            continue

        if stored_inclusion_list != inclusion_list:
            store.equivocators[key].add(inclusion_list.validator_index)
            store.inclusion_lists[key].remove(stored_inclusion_list)

        # 无论是否是抵触,我们都已经处理了这个 `inclusion_list`。
        return

    # 仅当 `inclusion_list` 在视图冻结截止时间之前到达时才存储。
    if is_before_view_freeze_cutoff:
        store.inclusion_lists[key].add(inclusion_list)

IL 合规性

提议者或构建者应生成一个执行 payload,该 payload 满足 IL 约束,该约束与在 PROPOSER_INCLUSION_LIST_CUTOFF 之前收集到的 IL 相关,即 slot 开始后的 11 秒。为此,提议者或构建者调用 ForkchoiceUpdated 引擎 API,并将 PayloadAttributesinclusionList 字段设置为收集到的 IL。API 通过重建 payload 或仅在 payload 末尾附加 IL 交易来返回符合 IL 的 payload。

(请注意,之前使用过 UpdatePayloadWithInclusionList,但已被 ForkchoiceUpdated 替换,因为 IL 现在是 payload 构建过程的一部分,因此 ForkchoiceUpdated 在语义上更合适。)

IL 执行

证明者通过调用 NewPayload 引擎 API 来验证给定的 payload 是否满足 IL 约束。任何不符合 IL 的区块都将被重新组织,尽管它仍然是一个有效的区块。它不会收到任何提议者奖励证明者将投票给它的父区块,并且提议者将扩展它的父区块

为此,现在分叉选择 Store 跟踪不符合 IL 的区块。

@dataclass
class Store(object):
    # [EIP7805 中的新功能]
    unsatisfied_inclusion_list_blocks: Set[Root] = field(default_factory=Set)

然后,现在将 IL 约束验证添加到 on_block 中。

def validate_inclusion_lists(
    store: Store, beacon_block_root: Root, execution_engine: ExecutionEngine
) -> bool:
    inclusion_list_store = get_inclusion_list_store()

    block = store.blocks[beacon_block_root]
    state = store.block_states[beacon_block_root]

    inclusion_list_transactions = get_inclusion_list_transactions(
        inclusion_list_store, state, Slot(block.slot - 1)
    )

    return execution_engine.is_inclusion_list_satisfied(
        block.body.execution_payload, inclusion_list_transactions
    )

def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
    ...

    # [EIP7805 中的新功能]
    # 检查区块是否满足包含列表约束
    # 如果不满足,则将此区块添加到存储中,作为包含列表约束未满足的区块
    is_inclusion_list_satisfied = validate_inclusion_lists(store, block_root, EXECUTION_ENGINE)
    if not is_inclusion_list_satisfied:
        store.unsatisfied_inclusion_list_blocks.add(block_root)

    # 如果区块及时、不与现有区块冲突且满足包含列表约束,则添加提议者分数提升。
    is_first_block = store.proposer_boost_root == Root()
    # [EIP7805 中修改]
    if is_timely and is_first_block and is_inclusion_list_satisfied:
        store.proposer_boost_root = hash_tree_root(block)

    ...

如果区块不符合 IL,证明者将不会投票给它,并且提议者也不会扩展它,这将重新组织该区块。

def get_attester_head(store: Store, head_root: Root) -> Root:
    head_block = store.blocks[head_root]
    head_hash = head_block.body.signed_execution_payload_bid.message.block_hash

    if head_hash in store.unsatisfied_inclusion_list_payloads:
        return head_block.parent_root

    return head_root
def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
    ...

    # [EIP7805 中的新功能]
    # 检查头部区块是否在未满足的包含列表区块中
    inclusion_list_not_satisfied = head_root in store.unsatisfied_inclusion_list_blocks

    if reorg_prerequisites and (head_late or inclusion_list_not_satisfied):
        return parent_root
    else:
        return head_root

FOCIL 和 ePBS

FOCIL 和 ePBS 之间的协同作用之一来自于信标区块和执行 payload 的分离。使用 ePBS,如果执行 payload 不符合 IL,则只会重新组织执行 payload,而不会影响信标链的稳定性。

主要变化是引入了 IL 位列表和 IL 包含性检查(在 IL 合规性阶段),以及在 IL 执行阶段使 IL 验证逻辑适应信标区块和执行 payload 的分离。

IL 生产

无需更改。

IL 传播

无需更改。

IL 合规性

当提议者在 ePBS 中接受竞标时,他们需要知道该竞标是否满足 IL 约束。为此,构建者现在在其竞标中包含 IL 位列表,表明他们已考虑哪些 IL。提议者将每个竞标的 IL 位列表与其主观 IL 视图进行比较,并从那些 IL 位列表是超集的竞标中选择一个。

验证者还将信标区块中包含的 IL 位列表与其本地 IL 视图进行比较。这里需要注意的一点是,他们只检查 IL 位列表是否是其 IL 视图的超集。即使他们已经收到了执行 payload,他们也不会在 IL 包含性检查期间验证执行 payload 是否满足 IL 约束。IL 约束由下一个 slot 的证明者强制执行。

证明者或 PTC 成员都可以执行此检查,但会有不同的权衡。当证明者在证明期间考虑这一点时,我们会利用整个信标委员会,但未能通过 IL 包含性检查会导致信标区块重新组织,从而影响信标链的稳定性。另一方面,PTC 方法可以保留信标区块,尽管它只涉及一小部分验证者。是否使用证明者或 PTC 仍有待讨论。

首先,将 IL 位列表添加到执行 payload 竞标中。

class ExecutionPayloadBid(Container):
    ...
    inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]

然后,证明者或 PTC 成员使用以下函数检查 IL 包含性。

def get_inclusion_list_bits(
    store: InclusionListStore, state: BeaconState, slot: Slot
) -> Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]:
    inclusion_list_committee = get_inclusion_list_committee(state, slot)
    inclusion_list_committee_root = hash_tree_root(inclusion_list_committee)
    key = (slot, inclusion_list_committee_root)

    validator_indices = [\
        inclusion_list.validator_index for inclusion_list in store.inclusion_lists[key]\
    ]

    return Bitvector[INCLUSION_LIST_COMMITTEE_SIZE](
        validator_index in validator_indices for validator_index in inclusion_list_committee
    )

def is_inclusion_list_bits_inclusive(
    store: InclusionListStore,
    state: BeaconState,
    slot: Slot,
    inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE],
) -> bool:
    inclusion_list_store = get_inclusion_list_store()

    local_inclusion_list_bits = get_inclusion_list_bits(inclusion_list_store, state, Slot(slot - 1))

    return all(
        inclusion_bit or not local_inclusion_bit
        for inclusion_bit, local_inclusion_bit in zip(
            inclusion_list_bits, local_inclusion_list_bits
        )
    )

IL 执行

随着信标区块和执行 payload 的分离,IL 执行现在同时发生——首先是在执行 payload 到达时,然后是在处理信标区块时。

在分离之后,分叉选择 Store 现在跟踪不符合 IL 的执行 payload。

@dataclass
class Store(object):
    ...
    # [EIP7805 中的新功能]
    unsatisfied_inclusion_list_payloads: Set[Hash32] = field(default_factory=Set)

当执行 payload 传递时,验证逻辑保持不变。CL 依赖于 EL 进行验证。

def is_inclusion_list_satisfied_payload(
    store: Store,
    block_root: Root,
    payload: ExecutionPayload,
    execution_engine: ExecutionEngine,
) -> bool:
    inclusion_list_store = get_inclusion_list_store()

    block = store.blocks[block_root]
    state = store.block_states[block_root]

    inclusion_list_transactions = get_inclusion_list_transactions(
        inclusion_list_store, state, Slot(block.slot - 1)
    )

    return execution_engine.is_inclusion_list_satisfied(payload, inclusion_list_transactions)

def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None:
    ...

    # [EIP7805 中的新功能]
    # 检查 payload 是否满足包含列表约束
    # 如果不满足,则将此 payload 添加到存储中,作为包含列表未满足的 payload
    is_inclusion_list_satisfied = is_inclusion_list_satisfied_payload(
        store, envelope.beacon_block_root, envelope.payload, EXECUTION_ENGINE
    )
    if not is_inclusion_list_satisfied:
        store.unsatisfied_inclusion_list_payloads.add(envelope.payload.block_hash)

    ...

当信标区块到达时,只有当它的 IL 位列表具有包含性,并且它扩展了符合 IL 的执行 payload(如果父区块已满),证明者才会投票支持它。换句话说,如果 payload 不符合 IL,则信标区块应重新组织它,以便获得证明。其他情况遵循 ePBS 规范。

def is_inclusion_list_satisfied_block(store: Store, block_root: Root) -> bool:
    inclusion_list_store = get_inclusion_list_store()

    block = store.blocks[block_root]
    state = store.block_states[block_root]
    payload_bid = block.body.signed_execution_payload_bid.message

    inclusion_list_bits_inclusive = is_inclusion_list_bits_inclusive(
        inclusion_list_store, state, block.slot, payload_bid.inclusion_list_bits
    )
    inclusion_list_transactions_satisfied = (
        not is_parent_node_full(store, block)
        or payload_header.parent_block_hash not in store.unsatisfied_inclusion_list_payloads
    )

    return inclusion_list_bits_inclusive and inclusion_list_transactions_satisfied

def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
    ...

    # 如果区块及时且不与现有区块冲突,则添加提议者分数提升
    is_first_block = store.proposer_boost_root == Root()
    # [EIP-7805 中的新功能]
    is_inclusion_list_satisfied = is_inclusion_list_satisfied_block(store, block_root)
    # [EIP7805 中修改]
    if is_timely and is_first_block and is_inclusion_list_satisfied:
        store.proposer_boost_root = hash_tree_root(block)

    ...

与之前相同,如果信标区块不符合 IL,证明者将不会投票支持它,并且提议者也不会扩展它,这将重新组织该区块。

def get_attester_head(store: Store, head_root: Root) -> Root:
    head_block = store.blocks[head_root]
    head_hash = head_block.body.signed_execution_payload_bid.message.block_hash

    if head_hash in store.unsatisfied_inclusion_list_payloads:
        return head_block.parent_root

    return head_root
def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root:
    ...

    # [EIP7805 中的新功能]
    # 检查头部区块是否满足包含列表约束
    inclusion_list_satisfied = is_inclusion_list_satisfied_block(store, head_root)

    if reorg_prerequisites and (head_late or not inclusion_list_satisfied):
        return parent_root
    else:
        return head_root

结束语

FOCIL 具有稳定的规范,并且 6 个 CL 客户端团队中的 4 个已经拥有互操作原型。从研究和规范的角度也考察了将 FOCIL 重新定位到 ePBS 上。

在 Glamsterdam 中,FOCIL 收到了所有构建原型的核心开发人员的积极反馈。作者鼓励更多的开发人员(无论是从事客户端、开发运营还是测试)仔细审查 FOCIL 在 Glamsterdam 中的可行性。

上一篇:如何在 EL 中实现 FOCIL

链接

  • 原文链接: hackmd.io/@jihoonsong/rJ...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
jihoonsong
jihoonsong
江湖只有他的大名,没有他的介绍。