Flashblock 级别访问列表 (FAL)

  • base__
  • 发布于 2025-06-02 22:18
  • 阅读 9

本文档介绍了 Flashblock 级别访问列表 (FAL),它是对 EIP-7928 区块级别访问列表 (BAL) 的一种调整,适用于生成 flashblock 的 OP Stack 链。

Flashblock级别访问列表 (FAL)

摘要

本文档介绍了 Flashblock 级别访问列表 (FAL),它是 EIP-7928 区块级别访问列表 (BAL) 的一个改编版本,专为生成 flashblock 的 OP Stack 链设计。FAL 记录了在 flashblock 执行期间访问的所有账户和存储位置,以及它们在执行后的值。与 BAL 类似,FAL 支持并行磁盘读取、并行交易验证和无执行的状态更新,但它是专门为 OP Stack 链中使用的 flashblock 架构而设计的。

动机

FAL 将 BAL 规范适配于生成 flashblock 的 OP Stack 链 - 以子区块间隔(例如,对于 2 秒的规范区块时间,每 200 毫秒)生成的增量式 “迷你区块”。虽然 BAL 是为以太坊 L1 规范区块设计的,但 FAL 必须处理:

  • 通过 flashblock 增量进行增量区块构建
  • OP Stack 特定的交易类型(L1 属性交易,L1→L2 存款)
  • 不同的费用分配机制(费用金库而不是 COINBASE)
  • 缺少信标链功能(提款、信标根、提款/整合请求)
  • OP Stack 系统合约 (L1Block, 费用金库等)

与 BAL 的主要区别

结构差异

  1. Flashblock 增量结构:FAL 操作包含增量更改的 flashblock 增量,而不是完整的规范区块
  2. 元数据存储:FAL 哈希存储在 flashblock 元数据字段中,而不是区块头字段中
  3. 没有信标链功能:FAL 省略了 EIP-4895(提款)、EIP-4788(信标根)、EIP-7002(提款请求)和 EIP-7251(整合)

费用分配

  • BAL:记录接收交易费用的 COINBASE 地址的余额变化
  • FAL:记录 OP Stack 费用金库的余额变化:
    • 排序器费用金库(优先级费用)
    • 基础费用金库(基础费用)
    • L1 费用金库(L1 数据费用)

交易类型

  • BAL:仅限常规交易和系统合约调用
  • FAL:包括 L1 属性交易(索引 0 处的已存入交易)和 L1→L2 用户存款(视为常规交易)

区块访问索引分配

  • 0:预执行系统合约 + L1 属性交易
  • 1 到 n:常规 L2 交易(包括 L1→L2 用户存款)
  • n + 1:后执行系统合约(如果存在)

规范

Flashblock 结构修改

我们向 flashblock 元数据引入一个新字段 flashblock_access_list,其中包含完整的 flashblock 访问列表结构,包括交易索引边界和哈希值。

##[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct FlashblocksPayloadV1 {
    pub payload_id: PayloadId,
    pub index: u64,
    pub base: Option<ExecutionPayloadBaseV1>,
    pub diff: ExecutionPayloadFlashblockDeltaV1,
    pub metadata: Value, // Contains "flashblock_access_list"
}

// The flashblock_access_list field in metadata contains:
##[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FlashblockAccessList {
    pub min_tx_index: u64,      // Inclusive starting transaction index in the overall block
    pub max_tx_index: u64,      // Exclusive ending transaction index in the overall block
    pub account_changes: Vec<AccountChanges>,  // List of all account changes
    pub fal_hash: B256,         // Keccak-256 hash of RLP-encoded account_changes
}

交易索引边界:

  • min_tx_index (包含):整体区块中的起始交易索引
  • max_tx_index (排除):整体区块中的结束交易索引

这些边界允许每个 flashblock 在整体区块的交易序列中保持其位置。

示例: 如果一个区块有 15 个交易,分布在 3 个 flashblock 中:

  • Flashblock 0:min_tx_index=0, max_tx_index=5(包含交易 0-4)
  • Flashblock 1:min_tx_index=5, max_tx_index=10(包含交易 5-9)
  • Flashblock 2:min_tx_index=10, max_tx_index=15(包含交易 10-14)

当不存在状态变更时,account_changes 是一个空列表,fal_hash 是一个空 RLP 列表的哈希值:0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347,即 keccak256(rlp.encode([]))

RLP 数据结构

FAL 使用与 BAL 相同的 RLP 编码,遵循以下模式:address -> field -> block_access_index -> change

## Type aliases for RLP encoding (identical to BAL)
Address = bytes  # 20-byte Ethereum address
StorageKey = bytes  # 32-byte storage slot key
StorageValue = bytes  # 32-byte storage value
CodeData = bytes  # Variable-length contract bytecode
BlockAccessIndex = uint16  # Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution)
Balance = uint256  # Post-transaction balance in wei
Nonce = uint64  # Account nonce

## Constants (adapted for OP Stack)
MAX_TXS = 30_000
MAX_SLOTS = 300_000
MAX_ACCOUNTS = 300_000
MAX_CODE_SIZE = 24_576
MAX_CODE_CHANGES = 1

## Core change structures (identical to BAL)
StorageChange = [BlockAccessIndex, StorageValue]
BalanceChange = [BlockAccessIndex, Balance]
NonceChange = [BlockAccessIndex, Nonce]
CodeChange = [BlockAccessIndex, CodeData]
SlotChanges = [StorageKey, List[StorageChange]]

## AccountChanges: [address, storage_changes, storage_reads, balance_changes, nonce_changes, code_changes]
AccountChanges = [
    Address,
    List[SlotChanges],          # storage_changes
    List[StorageKey],           # storage_reads
    List[BalanceChange],        # balance_changes
    List[NonceChange],          # nonce_changes
    List[CodeChange]            # code_changes
]

## FlashblockAccessList: List of AccountChanges
FlashblockAccessList = List[AccountChanges]

范围和包含

FlashblockAccessList 是 flashblock 执行期间访问的所有地址的集合。

必须包括:

  • 具有状态更改(存储、余额、nonce 或代码)的地址
  • 在没有状态更改的情况下访问的地址,包括:
    • BALANCEEXTCODESIZEEXTCODECOPYEXTCODEHASH 操作码的目标
    • CALLCALLCODEDELEGATECALLSTATICCALL 的目标(即使它们回退)
    • CREATE/CREATE2 的目标地址(即使创建失败)
    • 交易发送者和接收者地址(即使是零值转账)
    • 接收费用时的费用金库地址(排序器费用金库、基础费用金库、L1 费用金库)
    • SELFDESTRUCT 的受益人地址
    • 在预/后执行期间访问的系统合约地址
    • L1→L2 存款接收者地址
    • 当调用或访问时,预编译合约
    • L1Block 合约,当被 L1 属性交易访问时

没有状态更改的地址必须仍然存在,但带有空的更改列表。

来自 EIP-2930 访问列表的条目不得自动包含。仅记录在执行期间实际触及或更改的地址和存储槽。

排序和确定性

以下排序规则必须适用:

  • 地址: 字典顺序(按字节)
  • 存储键: 每个帐户内的字典顺序
  • 区块访问索引: 每个更改列表内的升序

BlockAccessIndex 分配

BlockAccessIndex必须按如下方式分配:

  • 0 用于预执行系统合约调用和 L1 属性交易(仅在 min_tx_index = 0 的第一个 flashblock 中)
  • min_tx_index … max_tx_index - 1 用于此 flashblock 中的交易(包括 L1→L2 用户存款和常规 L2 交易)
  • n + 1 用于后执行系统合约调用(如果存在,仅在最后一个 flashblock 中)

重要提示: 交易的 block_access_index 使用整体区块交易索引,而不是 flashblock 本地索引。这允许组合来自多个 flashblock 的 FAL,同时保持正确的交易顺序。

按更改类型记录语义

存储
  • 写入包括:

    • 任何值更改(after-value ≠ before-value)
    • 清零一个槽(before-value 存在,after-value 为零)
  • 读取包括:

    • 通过 SLOAD 访问但未写入的槽
    • 写入但值未更改的槽(即,after-value 等于 before-value 的 SSTORE,也称为“无操作写入”)

注意:实现必须检查 pre-transaction 值才能正确区分实际写入和无操作写入。

余额 (balance_changes)

记录以下对象的交易后余额 (uint256):

  • 交易发送者(gas + value + L1 数据费用)
  • 交易接收者(仅当 value > 0 时)
  • 每次交易后接收费用的费用金库(排序器费用金库、基础费用金库、L1 费用金库)
  • SELFDESTRUCT/SENDALL 受益人
  • L1→L2 存款接收者

零值转账: 不得记录在 balance_changes 中,但相应的地址必须仍然包含在空的 AccountChanges 中。

代码

跟踪已部署或修改合约的 交易后运行时字节码,以及 EIP-7702 中定义的成功委托的 委托指示符

Nonce

记录以下对象的交易后 nonce

  • EOA 发送者
  • 执行了成功的 CREATECREATE2 的合约
  • 已部署的合约
  • EIP-7702 授权人

OP Stack 特定的特殊情况

费用金库

排序器费用金库 (0x4200000000000000000000000000000000000011):

  • 记录收集优先级费用时的余额变化
  • 在每次支付优先级费用的交易后更新余额

基础费用金库 (0x4200000000000000000000000000000000000019):

  • 记录收集基础费用时的余额变化
  • 在每次交易后更新余额

L1 费用金库 (0x420000000000000000000000000000000000001a):

  • 记录收集 L1 数据费用时的余额变化
  • 在每次支付 L1 费用的交易后更新余额
L1 属性交易

L1 属性交易(索引 0 处的已存入交易):

  • 使用 block_access_index = 0
  • 更新 L1Block 合约 (0x4200000000000000000000000000000000000015)
  • 记录 L1Block 合约槽的存储更改:
    • basefee (槽 1)
    • blobBaseFee (槽 5)
    • hash (槽 6)
    • number (槽 0)
    • timestamp (槽 2)
    • sequenceNumber (槽 4)
    • batcherHash (槽 3)
L1→L2 用户存款

来自 L1 的用户存款 (例如,通过 OptimismPortal):

  • 被视为 block_access_index = 1..n 的常规交易
  • 记录余额变化的发送者(L1 地址)和接收者(L2 地址)
  • 包括支付给费用金库的 gas 费用
EIP-2935 (区块哈希存储)

记录环形缓冲区中单个更新的存储槽的系统合约存储差异。

OP Stack 注意: 区块哈希存储可能使用相同的 EIP-2935 机制或修改后的版本。记录实际发生的任何存储更改。

特殊情况(通用,继承自 BAL)

  • 预编译合约: 在访问时必须包含预编译合约。如果预编译合约接收到 value,则会记录余额变化。否则,将包含带有空变更列表的内容。
  • SENDALL: 对于正 value 的 selfdestruct,发送者和受益人会记录余额变化。
  • SELFDESTRUCT (在交易中): 在交易中销毁的帐户必须包含在 AccountChanges 中,而没有 nonce 或代码的更改。但是,如果该帐户在交易前有正余额,则必须记录余额更改为零。在 self-destructed 合约中修改或读取的存储键必须作为 storage_read 包含。
  • 已访问但未更改: 包含带有空变更的地址(例如,EXTCODEHASHEXTCODESIZEBALANCESTATICCALL 等的目标)。
  • 零值转账: 包含地址;从 balance_changes 中省略。
  • Gas 退款: 在每次交易后记录发送者的最终余额。
  • 费用金库支付: 在每次产生费用的交易后记录每个费用金库的最终余额。
  • 异常中止: 在每次交易后记录发送者的最终nonce 和余额,以及费用金库的最终余额。丢弃来自回退调用的状态更改,但必须包含所有访问的地址。如果没有剩余的更改,则包含带有空列表的地址;如果读取了存储,则相应的键必须出现在 storage_reads 中。
  • 预执行系统合约调用: 所有状态更改必须使用 block_access_index = 0
  • 后执行系统合约调用: 所有状态更改必须使用 block_access_index = len(transactions) + 1
  • EIP-7702 委托: 在任何成功的委托设置、重置或更新后,授权人地址必须包含 nonce 和代码更改,如果由于无效的 nonce 导致授权失败,必须还包含一个空的更改集。委托目标不得在委托创建期间包含,并且在authority 执行下作为调用目标加载时必须包含。

验证

状态转换函数必须验证提供的 FAL 是否与实际的状态访问匹配:

def validate_flashblock(flashblock):
    # 1. Extract FAL from metadata
    import rlp
    fal = flashblock.metadata['flashblock_access_list']
    min_tx_index = fal['min_tx_index']
    max_tx_index = fal['max_tx_index']
    provided_account_changes = fal['account_changes']
    provided_fal_hash = fal['fal_hash']

    # 2. Verify provided hash matches account_changes
    computed_hash = keccak256(rlp.encode(provided_account_changes))
    assert computed_hash == provided_fal_hash

    # 3. Execute flashblock and collect actual accesses
    actual_account_changes = execute_and_collect_accesses(flashblock, min_tx_index, max_tx_index)

    # 4. Verify actual execution matches provided account_changes
    actual_fal_hash = keccak256(rlp.encode(actual_account_changes))
    assert actual_fal_hash == provided_fal_hash

def execute_and_collect_accesses(flashblock, min_tx_index, max_tx_index):
    """Execute flashblock and collect all state accesses into FAL format

    Args:
        flashblock: The flashblock to execute
        min_tx_index: Starting transaction index (inclusive) in the overall block
        max_tx_index: Ending transaction index (exclusive) in the overall block
    """
    accesses = {}

    # Pre-execution: L1 attributes transaction (block_access_index = 0)
    # Only include for first flashblock (min_tx_index == 0)
    if min_tx_index == 0:
        track_l1_attributes_tx(flashblock, accesses, block_access_index=0)
        track_system_contracts_pre(flashblock, accesses, block_access_index=0)

    # Execute transactions (block_access_index = min_tx_index..max_tx_index)
    # This includes both L1→L2 deposits and regular L2 transactions
    for i, tx in enumerate(flashblock.diff.transactions):
        tx_index = min_tx_index + i
        execute_transaction(tx)
        track_state_changes(tx, accesses, block_access_index=tx_index)
        track_fee_vault_changes(tx, accesses, block_access_index=tx_index)

    # Post-execution system contracts
    # Only include for last flashblock (would need to be indicated separately)
    # For now, omitted as flashblocks are incremental

    # Convert to FAL format and sort
    return build_fal(accesses)

def track_state_changes(tx, accesses, block_access_index):
    """Track all state changes from a transaction"""
    for addr in get_touched_addresses(tx):
        if addr not in accesses:
            accesses[addr] = {
                'storage_writes': {},  # slot -> [(index, value)]
                'storage_reads': set(),
                'balance_changes': [],
                'nonce_changes': [],
                'code_changes': []
            }

        # Track storage changes
        for slot, value in get_storage_writes(addr).items():
            if slot not in accesses[addr]['storage_writes']:
                accesses[addr]['storage_writes'][slot] = []
            accesses[addr]['storage_writes'][slot].append((block_access_index, value))

        # Track reads (slots accessed but not written)
        for slot in get_storage_reads(addr):
            if slot not in accesses[addr]['storage_writes']:
                accesses[addr]['storage_reads'].add(slot)

        # Track balance, nonce, code changes
        if balance_changed(addr):
            accesses[addr]['balance_changes'].append((block_access_index, get_balance(addr)))
        if nonce_changed(addr):
            accesses[addr]['nonce_changes'].append((block_access_index, get_nonce(addr)))
        if code_changed(addr):
            accesses[addr]['code_changes'].append((block_access_index, get_code(addr)))

def track_fee_vault_changes(tx, accesses, block_access_index):
    """Track OP Stack fee vault balance changes after each transaction"""
    # Sequencer Fee Vault (priority fees)
    SEQUENCER_FEE_VAULT = 0x4200000000000000000000000000000000000011
    # Base Fee Vault (base fees)
    BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019
    # L1 Fee Vault (L1 data fees)
    L1_FEE_VAULT = 0x420000000000000000000000000000000000001a

    for vault in [SEQUENCER_FEE_VAULT, BASE_FEE_VAULT, L1_FEE_VAULT]:
        if vault not in accesses:
            accesses[vault] = {
                'storage_writes': {},
                'storage_reads': set(),
                'balance_changes': [],
                'nonce_changes': [],
                'code_changes': []
            }

        # Record vault balance after transaction if it changed
        if balance_changed(vault):
            accesses[vault]['balance_changes'].append((block_access_index, get_balance(vault)))

def track_l1_attributes_tx(flashblock, accesses, block_access_index):
    """Track L1 attributes transaction (deposited tx at index 0)"""
    L1_BLOCK_CONTRACT = 0x4200000000000000000000000000000000000015

    if L1_BLOCK_CONTRACT not in accesses:
        accesses[L1_BLOCK_CONTRACT] = {
            'storage_writes': {},
            'storage_reads': set(),
            'balance_changes': [],
            'nonce_changes': [],
            'code_changes': []
        }

    # Track storage updates to L1Block contract
    # Slots: number(0), basefee(1), timestamp(2), batcherHash(3),
    #        sequenceNumber(4), blobBaseFee(5), hash(6)
    for slot in [0, 1, 2, 3, 4, 5, 6]:
        if slot_changed(L1_BLOCK_CONTRACT, slot):
            if slot not in accesses[L1_BLOCK_CONTRACT]['storage_writes']:
                accesses[L1_BLOCK_CONTRACT]['storage_writes'][slot] = []
            value = get_storage(L1_BLOCK_CONTRACT, slot)
            accesses[L1_BLOCK_CONTRACT]['storage_writes'][slot].append((block_access_index, value))

def build_fal(accesses):
    """Convert collected accesses to FAL format"""
    fal = []
    for addr in sorted(accesses.keys()):  # Sort addresses lexicographically
        data = accesses[addr]

        # Format storage changes: [slot, [[index, value], ...]]
        storage_changes = [[slot, sorted(changes)]
                          for slot, changes in sorted(data['storage_writes'].items())]

        # Account entry: [address, storage_changes, reads, balance_changes, nonce_changes, code_changes]
        fal.append([
            addr,
            storage_changes,
            sorted(list(data['storage_reads'])),
            sorted(data['balance_changes']),
            sorted(data['nonce_changes']),
            sorted(data['code_changes'])
        ])

    return fal

FAL 必须完整且准确。缺少或虚假的条目会使 flashblock 无效。

客户端可以通过将执行收集的访问与 FAL 进行比较来验证。

如果任何交易超出声明状态,客户端可以立即失效。

具体示例

OP Stack 上的示例 flashblock: 预执行 (block_access_index = 0):

  • L1 属性交易更新 L1Block 合约 (0x4200000000000000000000000000000000000015)
  • EIP-2935 区块哈希存储位于 0x0000F90827F1C53a10cb7A02335B175320002935

交易:

  1. Alice (0xaaaa...) 向 Bob (0xbbbb...) 发送 1 ETH
  2. Charlie (0xcccc...) 调用工厂 (0xffff...) 在 0xdddd... 部署合约
  3. L1→L2 存款:Dave (0xdave...) 从 L1 接收 10 ETH

后执行 (block_access_index = 4):

  • 此示例中没有

结果 FAL(RLP 结构):

[
    # 地址按字典顺序排序
    [ # 0x0000F90827F1C53a10cb7A02335B175320002935 的 AccountChanges(区块哈希合约)
        0x0000F90827F1C53a10cb7A02335B175320002935,
        [ # storage_changes
            [b'\\x00...\\x0f\\xa0', [[0, b'...']]]  # 槽, [[block_access_index, parent_hash]]
        ],
        [],  # storage_reads
        [],  # balance_changes
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0x4200000000000000000000000000000000000011 的 AccountChanges(排序器费用金库)
        0x4200000000000000000000000000000000000011,
        [],  # storage_changes
        [],  # storage_reads
        [[1, 0x...fee1], [2, 0x...fee2], [3, 0x...fee3]],  # balance_changes: 每次 tx 之后
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0x4200000000000000000000000000000000000015 的 AccountChanges (L1Block 合约)
        0x4200000000000000000000000000000000000015,
        [ # 来自 L1 属性 tx 的 storage_changes
            [b'\\x00...\\x00', [[0, b'...']]],  # number
            [b'\\x00...\\x01', [[0, b'...']]],  # basefee
            [b'\\x00...\\x02', [[0, b'...']]],  # timestamp
            [b'\\x00...\\x03', [[0, b'...']]],  # batcherHash
            [b'\\x00...\\x04', [[0, b'...']]],  # sequenceNumber
            [b'\\x00...\\x05', [[0, b'...']]],  # blobBaseFee
            [b'\\x00...\\x06', [[0, b'...']]]   # hash
        ],
        [],  # storage_reads
        [],  # balance_changes
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0x4200000000000000000000000000000000000019 的 AccountChanges(基础费用金库)
        0x4200000000000000000000000000000000000019,
        [],  # storage_changes
        [],  # storage_reads
        [[1, 0x...base1], [2, 0x...base2], [3, 0x...base3]],  # balance_changes: 每次 tx 之后
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0x420000000000000000000000000000000000001a 的 AccountChanges(L1 费用金库)
        0x420000000000000000000000000000000000001a,
        [],  # storage_changes
        [],  # storage_reads
        [[1, 0x...l1fee1], [2, 0x...l1fee2], [3, 0x...l1fee3]],  # balance_changes: 每次 tx 之后
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0xaaaa... 的 AccountChanges(Alice - 发送者 tx 1)
        0xaaaa...,
        [],  # storage_changes
        [],  # storage_reads
        [[1, 0x...29a241a]],  # balance_changes: [[block_access_index, post_balance]]
        [[1, 10]],  # nonce_changes: [[block_access_index, new_nonce]]
        []  # code_changes
    ],
    [ # 0xbbbb... 的 AccountChanges(Bob - 接收者 tx 1)
        0xbbbb...,
        [],  # storage_changes
        [],  # storage_reads
        [[1, 0x...b9aca00]],  # balance_changes: +1 ETH
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0xcccc... 的 AccountChanges(Charlie - 发送者 tx 2)
        0xcccc...,
        [],  # storage_changes
        [],  # storage_reads
        [[2, 0x...bc16d67]],  # balance_changes: gas 之后
        [[2, 5]],  # nonce_changes
        []  # code_changes
    ],
    [ # 0xdave... 的 AccountChanges(Dave - L1→L2 存款接收者 tx 3)
        0xdave...,
        [],  # storage_changes
        [],  # storage_reads
        [[3, 0x...8ac7230]],  # balance_changes: 来自 L1 的 +10 ETH
        [],  # nonce_changes
        []   # code_changes
    ],
    [ # 0xdddd... 的 AccountChanges(已部署合约)
        0xdddd...,
        [],  # storage_changes
        [],  # storage_reads
        [],  # balance_changes
        [[2, 1]],  # nonce_changes: 新合约 nonce
        [[2, b'\\x60\\x80\\x60\\x40...']]  # code_changes: 已部署字节码
    ],
    [ # 0xffff... 的 AccountChanges(工厂合约)
        0xffff...,
        [ # storage_changes
            [b'\\x00...\\x01', [[2, b'\\x00...\\xdd\\xdd...']]]  # 槽 1, 已部署地址
        ],
        [],  # storage_reads
        [],  # balance_changes
        [[2, 5]],  # nonce_changes: CREATE 之后
        []  # code_changes
    ]
]

RLP 编码和压缩:~500-600 字节(由于 L1Block 合约更新而略大于 BAL)。

理论基础

FAL 设计选择

  1. Flashblock 兼容性:FAL 专为增量 flashblock 增量设计,同时保持与 BAL 核心结构的兼容性
  2. OP Stack 特殊性:FAL 处理 OP Stack 特定的功能:
    • 多个费用金库而不是单个 COINBASE
    • 用于区块上下文的 L1 属性交易
    • L1→L2 存款被视为常规交易
    • L1Block 合约更新
  3. 省略的功能:信标链功能(EIP-4895、4788、7002、7251)被省略,因为它们在 OP Stack 中不存在
  4. 大小开销:预期的 flashblock 访问列表大小与 BAL 相似(平均压缩后约 40-50 KiB),但有额外的开销:
    • L1Block 合约更新(每个 flashblock 约 200 字节)
    • 每个交易三个费用金库余额更新,而一个 COINBASE 更新
  5. 并行执行:与 BAL 类似,FAL 启用:
    • 跨交易的并行磁盘读取
    • 并行交易验证
    • 无需执行的状态重建

区块大小考虑因素

Flashblock 大小影响(估计):

  • 平均值:约 45 KiB(压缩)- 由于 OP Stack 开销而略高于 BAL
  • L1Block 更新:每个 flashblock 约 200 字节
  • 费用金库更新:每次交易约 150 字节(3 个金库与 1 个 coinbase 相比)
  • 存储/nonce/代码差异:与 BAL 相似

替代设计考虑因素

替代方案 1:L1 属性交易的单独索引
  • 提议:为 L1 属性 tx 使用单独的 block_access_index (例如,-1 或 0xFFFF)
  • 优点:系统交易和用户交易之间有明显的区别
  • 缺点:更复杂的索引,破坏了与 BAL 结构的兼容性
  • 决定:未采用;与预执行 (index 0) 分组更简单
替代方案验证者必须:
  1. 从 flashblock 元数据中提取 FAL
  2. 验证 fal_hash 是否与 keccak256(rlp.encode(account_changes)) 匹配
  3. 使用 min_tx_indexmax_tx_index 执行 flashblock,以分配正确的区块访问索引
  4. 验证收集到的访问是否与提供的 account_changes 匹配
  5. 如果 FAL 不完整或不准确,则拒绝 flashblock

标准区块重构

从 flashblock 组装标准区块时:

  • 每个 flashblock 都有自己的 FAL
  • 如果实施 BAL,标准区块可以聚合所有 flashblock FAL
  • FAL 能够独立验证每个 flashblock

安全考虑

验证开销

验证访问列表会增加验证开销,但对于防止接受无效的 flashblock 至关重要。开销与 BAL 验证相当。

Flashblock 大小

增加的 flashblock 大小会影响传播。平均开销(约 45 KiB)对于 200 毫秒的 flashblock 间隔是合理的,并且可以通过并行化实现显著的性能提升。

手续费金库追踪

追踪三个手续费金库而不是一个 COINBASE 会稍微增加 FAL 的大小,但可以完全透明地了解 OP Stack 手续费分配。

参考文献

版权

通过 CC0 放弃版权和相关权利。

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

0 条评论

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