EIP-7706: calldata 的独立 gas 类型
为 calldata 创建独立的 basefee 和 gaslimit
Authors | Vitalik Buterin (@vbuterin) |
---|---|
Created | 2024-05-13 |
Discussion Link | https://ethereum-magicians.org/t/eip-7706-create-a-separate-basefee-and-gaslimit-for-calldata/19998 |
Requires | EIP-1559, EIP-4844 |
摘要
为交易 calldata 添加一个新的 gas 类型。添加一个新的交易类型,该类型提供一个 max_basefee
和 priority_fee
的向量,为执行 gas、blob gas 和 calldata gas 提供值。修改 basefee 调整,以便为这三种 gas 类型使用相同的机制。
动机
反对提高以太坊 gas 限制、降低 calldata 成本或在 PeerDAS 等技术可用之前增加 EIP-4844 blob 数量的一个主要论点是,以太坊区块的理论最大大小已经太大,我们无法承受进一步增加它。然而,这里存在一个效率低下的问题:当前区块的平均大小(不包括 blob)约为 100 kB,而理论最大值为 30,000,000 / 16 = 1,875,000
字节(可以使用零字节创建更大的区块,但实际上,由于 snappy 压缩,零字节占比重的区块将被压缩到小于 187 万字节)。理想情况下,我们应该有一种方法来限制最大值,而不会使 calldata 平均而言 更加稀缺。
这个 EIP 正是这样做的,它采用了与 EIP-4844 中应用于 blob 数据的相同技术:我们为 calldata 引入一个独立的费用市场,具有独立的 basefee 和独立的每区块 gas 限制。区块的理论最大 calldata 大小将大大减少,而基本的经济分析表明,平均而言,calldata 将变得相当便宜。
该 EIP 还引入了一种新的交易类型,其中包括三种类型的 max-basefee 和 priority fee 作为向量,允许相同的代码路径处理所有三种类型的 gas。我们还对 basefee 调整进行了修改,目前执行 gas(在 EIP-1559 中引入)和 blob(在 EIP-4844 中引入)使用不同的机制,现在对所有三种类型的 gas 使用相同的方法。这简化了 basefee 调整规则,并确保新的 EIP-4844 basefee 调整算法的更强的数学特性涵盖了所有三种类型的 gas。
规范
本文档中使用的关键词“MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释 。
参数
FORK_BLKNUM
=TBD
NEW_TX_TYPE
=TBD
CALLDATA_GAS_PER_TOKEN
=4
TOKENS_PER_NONZERO_BYTE
=4
CALLDATA_GAS_LIMIT_RATIO
=4
LIMIT_TARGET_RATIOS = [2, 2, 4]
MIN_BASE_FEE_PER_GAS = 1
# 重命名自 EIP-4844 MIN_BASE_FEE_PER_BLOB_GASBASE_FEE_UPDATE_FRACTION = 8
# 大致匹配 EIP-4844 参数
新的交易类型
从 FORK_BLOCK_NUMBER
开始,引入一种新的 EIP-2718 交易,其 TransactionType
= TX_TYPE(NEW_TX_TYPE)
。
此交易的 EIP-2718 TransactionPayload
是
[chain_id, nonce, gas_limit, to, value, data, access_list, blob_versioned_hashes, max_fees_per_gas, priority_fees_per_gas, y_parity, r, s]
我们要求 max_fees_per_gas
和 priority_fees_per_gas
是长度为 3 的向量,每个向量都包含从 0
到 2**64-1
的整数。
新交易的固有成本继承自 EIP-4844,只是删除了 calldata gas 成本(每个非零字节 16,每个零字节 4)。
区块处理和交易费用
我们添加函数 get_max_fees
和 get_priority_fees
,以计算先前交易类型的这些长度为 3 的向量:
def get_max_fees(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return tx.max_fees_per_gas
elif tx.type == BLOB_TX_TYPE:
return [tx.max_fee_per_gas, tx.max_fee_per_blob_gas, tx.max_fee_per_gas]
elif is_eip_1559(tx.type):
return [tx.max_fee_per_gas, 0, tx.max_fee_per_gas]
else:
return [tx.gasprice, 0, tx.gasprice]
def get_priority_fees(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return tx.priority_fees_per_gas
elif tx.type == BLOB_TX_TYPE:
return [tx.max_priority_fee_per_gas, 0, tx.max_priority_fee_per_gas]
elif is_eip_1559(tx.type):
return [tx.max_priority_fee_per_gas, 0, tx.max_priority_fee_per_gas]
else:
return [tx.gasprice, 0, tx.gasprice]
我们还添加了一些辅助函数:
def all_less_or_equal(v1: [int, int, int], v2: [int, int, int]) -> bool:
return all(x <= y for x, y in zip(v1, v2))
def vector_add(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x+y for x, y in zip(v1, v2)]
def vector_subtract(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x-y for x, y in zip(v1, v2)]
def vector_subtract_clamp_at_zero(v1: [int, int, int], v2: [int, int, int]) -> [uint, uint, uint]:
return [x-y if x >= y else 0 for x, y in zip(v1, v2)]
def vector_mul(v1: [int, int, int], v2: [int, int, int]) -> [int, int, int]:
return [x*y for x, y in zip(v1, v2)]
# 与当前 calldata 定价相同的规则,但重新措辞(类似于 EIP-7623)
def get_calldata_gas(calldata: bytes) -> int:
tokens = calldata.count(0) + (len(calldata) - calldata.count(0)) * TOKENS_PER_NONZERO_BYTE
return tokens * CALLDATA_GAS_PER_TOKEN
def get_gaslimits(tx: Transaction) -> [int, int, int]:
if tx.type == NEW_TX_TYPE:
return [tx.gaslimit, len(tx.blob_versioned_hashes) * GAS_PER_BLOB, get_calldata_gas(tx.data)]
elif tx.type == BLOB_TX_TYPE:
return [tx.gaslimit, len(tx.blob_versioned_hashes) * GAS_PER_BLOB, get_calldata_gas(tx.data)]
elif is_eip_1559(tx.type):
return [tx.gaslimit, 0, get_calldata_gas(tx.data)]
else:
return [tx.gaslimit, 0, get_calldata_gas(tx.data)]
def get_fees_per_gas(tx: Transaction, block_basefees: [int, int, int]) -> [int, int, int]:
max_fees = get_max_fees(tx)
priority_fees = get_priority_fees(tx)
output = []
# 费用充足性检查,类似于 EIP-1559 和 4844
require(all_less_or_equal(block_basefees, max_fees))
# 类似于 EIP-1559 和 4844 的逻辑
return [
min(basefee + priority_fee, max_fee)
for block_basefee, max_fee, priority_fee in zip(block_basefees, max_fees, priority_fees)
]
get_block_gaslimits(block: Block) -> [int, int, int]:
return [block.gaslimit, MAX_BLOB_GAS_PER_BLOCK, block.gaslimit // CALLDATA_GAS_LIMIT_RATIO]
在区块处理开始时:
- 我们初始化一个向量
gas_used_so_far
为[0, 0, 0]
在处理交易开始时:
- 计算
fees_per_gas = get_fees_per_gas(tx, get_block_basefees(block))
和tx_gaslimits = get_gaslimits(tx)
- 检查
all_less_or_equal(vector_add(gas_used_so_far, tx_gaslimits), block.gas_limits)
- 从
tx.origin
帐户中扣除sum(vector_mul(fees_per_gas, tx_gaslimits))
wei
请注意,get_block_basefees(block)
尚未定义,我们将在下面的部分中定义它。 block.gas_limits
字段也在下面的部分中定义。
在处理完交易之后:
- 将
tx_gas_consumed
计算为三项向量,其中第一项是交易执行实际消耗的 gas 量,第二项和第三项与get_gaslimits(tx)
中的值匹配 - 将
sum(vector_mul(fees_per_gas, vector_sub(tx_gaslimits, tx_gas_consumed)))
退还给tx.origin
账户(实际上,目前只有第一项将为非零) - 设置
gas_used_so_far = vector_add(gas_used_so_far, tx_gas_consumed)
在处理完一个区块之后:
- 要求
block.gas_used = gas_used_so_far
block.gas_used
字段将在下面的部分中定义。
区块结构:
我们更新 BlockHeader
字段,以删除 blob_gas_used
、gas_used
、base_fee_per_gas
、gas_limit
和 excess_blob_gas
字段,并添加新字段,所有字段均为 [int, int, int]
类型:gas_limits
、gas_used
、excess_gas
。 生成的 RLP 编码变为:
rlp([
parent_hash,
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers 哈希
coinbase,
state_root,
txs_root,
receipts_root,
logs_bloom,
0, # 难度
number,
timestamp,
extradata,
prev_randao,
0x0000000000000000, # nonce
withdrawals_root,
gas_limits,
gas_used,
excess_gas
])
我们定义:
get_block_gas_targets(parent: Header) -> [int, int, int]:
return [limit // target_ratio for limit, target_ratio in zip(parent.gas_limits, LIMIT_TARGET_RATIOS)]
我们按如下方式计算所需的 excess_gas
值:
def calc_excess_gas(parent: Header) -> [int, int, int]:
return vector_subtract_clamp_at_zero(vector_add(parent_excess, parent_used), get_block_gas_targets(parent))
我们按如下方式计算所需的 gas_limits
:
gas_limits[0]
必须遵循现有的调整公式gas_limits[1]
必须等于MAX_BLOB_GAS_PER_BLOCK
gas_limits[2]
必须等于gas_limits[0] // CALLDATA_GAS_LIMIT_RATIO
现在,我们定义 get_block_basefees
:
def get_block_basefees(parent: Header) -> [int, int, int]:
return [
fake_exponential(
MIN_BASE_FEE_PER_GAS,
excess_gas,
target * BASE_FEE_UPDATE_FRACTION
)
for (excess_gas, target) in zip(parent.excess_gas, get_block_gas_targets(parent))
]
理由
将所有 gas 相关机制转换为向量
这使得用于处理 gas 的相同逻辑可以处理所有三种类型的 gas。 因此,可以说这是对协议 gas 处理逻辑的净简化,尽管 gas 类型的总数从 2 增加到 3
目标比率
执行 gas 和 blob 的目标比率设置为 2; calldata 的目标比率设置为 4。这大大减少了 calldata 实际达到限制的情况的数量,这减轻了 EIP 的经济影响,因为在“低于限制”条件下分析 EIP-1559 风格的费用市场比在“达到限制”条件下简单得多。此外,它降低了需要大量 calldata 的应用程序完全停止工作的风险。
当前的参数将每个区块的目标 calldata 设置为 187,500 字节,约为当前平均值的 2 倍。 使用基本的供需推理,这意味着由于此 EIP,calldata 可能会变得更加便宜。
向后兼容性
先前的交易类型将 calldata basefee 和 priority fee 设置为彼此相等。 calldata gas 成本有意设置为与今天相同,并且 gas 目标与当前使用情况相似,因此将两个费用设置为彼此相等是对最佳行为的合理近似。 实际上,新的交易类型会更好,因此我们希望用户随着时间的推移切换到它。 但是,旧式交易用户遭受的损失不会那么高,因为与 basefee 相比,priority fee 通常很小,并且用户支付的金额与 basefee 成正比。
安全考虑
由于此 EIP,最佳区块构建行为变得更加复杂,尤其是在区块已满一个或两个类型的 gas 的边界条件下。 我们认为这种影响不会太大,因为实际上超过 90% 的区块未满,并且天真的“贪婪算法”可以获得接近最佳的结果。 因此,专有区块构建算法的中心化风险可能远小于 eg. MEV 提取的其他现有风险。
版权
版权和相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Vitalik Buterin (@vbuterin), "EIP-7706: calldata 的独立 gas 类型 [DRAFT]," Ethereum Improvement Proposals, no. 7706, May 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7706.