以太坊 1.0 链的费用市场改变

该文详细介绍了以太坊改进提案EIP-1559,该提案引入了一种新的交易费用机制。此机制包括一个根据网络拥堵程度动态调整并销毁的基础费用(Base Fee),以及一个支付给矿工的优先费用(Priority Fee),同时支持区块大小的动态扩展/收缩以应对瞬时拥堵。该提案旨在提高费用预测性、解决当前拍卖机制的低效问题,并通过销毁部分ETH来对抗通胀。

markdown

简单总结

一种交易定价机制,其中包括固定每区块销毁的网络费用,并根据瞬时拥堵动态扩展/收缩区块大小。

摘要

我们引入了一种新的 EIP-2718 交易类型,其格式为 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])

协议中有一个基础 gas 费用,它根据一个公式每个区块上下浮动,该公式是父区块中已使用的 gas 量和父区块的 gas 目标(区块 gas 上限除以弹性乘数)的函数。 该算法导致当区块高于 gas 目标时,基础 gas 费用增加;当区块低于 gas 目标时,基础 gas 费用减少。 基础 gas 费用会被销毁。 交易会指定它们愿意支付给矿工的最高 gas 费用,以激励矿工将其交易打包(即:优先费)。 交易还会指定它们愿意支付的总最高 gas 费用(即:最大费用),这包括优先费和区块的网络 gas 费用(即:基础费)。 发送方将始终支付其交易被打包所在区块的基础 gas 费用,并支付交易中设定的优先 gas 费用,只要这两种费用的总和不超过交易的最大 gas 费用。

动机

以太坊历史上使用简单的拍卖机制对交易费用进行定价,用户发送带有出价("gas价格")的交易,矿工选择出价最高的交易,被打包的交易支付他们指定的出价。这导致了几个主要的效率低下来源:

  • 交易费用水平波动性与交易社会成本之间的不匹配:在具有足够使用量导致区块已满的成熟公共区块链上,打包交易的出价往往极度波动。认为网络接受一个交易到区块中的成本在 gas 费用为 10 nanoeth 时比 gas 费用为 1 nanoeth 时高出 10 倍是荒谬的;在这两种情况下,都只是 800 万 gas 和 802 万 gas 之间的差异。
  • 给用户造成不必要的延迟:由于严格的每区块 gas 限制以及交易量的自然波动,交易通常需要等待几个区块才能被打包,但这在社会上是无益的;没有人能从缺乏“弹性”机制(允许一个区块更大而下一个区块更小以满足区块间需求差异)中显著受益。
  • 一级价格拍卖的低效率:当前的方法是交易发送方发布带有最高费用出价的交易,矿工选择出价最高的交易,每个人都支付他们出价的金额。这在机制设计文献中是众所周知的低效率,因此需要复杂的费用估算算法。但即使这些算法也常常效果不佳,导致频繁的费用超付。
  • 没有区块奖励的区块链的不稳定性:从长远来看,目前没有发行(包括比特币和 Zcash)的区块链打算完全通过交易费用来奖励矿工。然而,这存在一些已知问题,很可能导致大量的不稳定,激励挖矿“姐妹区块”来窃取交易费用,开启更强大的自私挖矿攻击向量等等。目前对此还没有很好的缓解措施。

本 EIP 中的提议是,从一个基础费用金额开始,该金额由协议根据网络拥堵程度进行调整。当网络超过每区块 gas 使用目标时,基础费用会略微增加;当容量低于目标时,基础费用会略微减少。由于这些基础费用变化受到限制,区块之间基础费用的最大差异是可预测的。这使得钱包能够以高度可靠的方式为用户自动设置 gas 费用。预计大多数用户将不必手动调整 gas 费用,即使在网络活动高峰期也是如此。对于大多数用户来说,钱包将估算基础费用,并自动设置一个小的优先费(例如 1 nanoeth),以补偿矿工承担的孤块风险。用户还可以手动设置交易最大费用来限制其总成本。

这个费用系统的一个重要方面是矿工只能保留优先费。基础费用总是被销毁(即被协议销毁)。这确保了只有 ETH 才能用于支付以太坊上的交易,从而巩固了 ETH 在以太坊平台内的经济价值,并降低了与矿工可提取价值 (MEV) 相关的风险。此外,这种销毁抵消了以太坊的通货膨胀,同时仍将区块奖励和优先费给予矿工。最后,确保区块矿工不接收基础费用非常重要,因为它消除了矿工操纵费用以从用户那里提取更多费用的动机。

规范

区块的有效性在下面的参考实现中定义。 GASPRICE (0x3a) 操作码必须返回下面参考实现中定义的 effective_gas_price

FORK_BLOCK_NUMBER 开始,引入了一个新的 EIP-2718 交易,其 TransactionType 为 2。

新交易的固有成本继承自 EIP-2930,具体为 $$21000 + 16 \times \text{non-zero calldata bytes} + 4 \times \text{zero calldata bytes} + 1900 \times \text{access list storage key count} + 2400 \times \text{access list address count}$$

此交易的 EIP-2718 TransactionPayloadrlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])

此交易的 signature_y_parity, signature_r, signature_s 元素表示对以下内容的 secp256k1 签名: $$\text{keccak256}(0x02 \ || \ \text{rlp}([\text{chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list}]))$$

此交易的 EIP-2718 ReceiptPayloadrlp([status, cumulative_transaction_gas_used, logs_bloom, logs])

注意:$//$ 是整数除法,向下取整。

from typing import Union, Dict, Sequence, List, Tuple, Literal
from dataclasses import dataclass, field
from abc import ABC, abstractmethod

@dataclass
class TransactionLegacy:
    signer_nonce: int = 0
    gas_price: int = 0
    gas_limit: int = 0
    destination: int = 0
    amount: int = 0
    payload: bytes = bytes()
    v: int = 0
    r: int = 0
    s: int = 0

@dataclass
class Transaction2930Payload:
    chain_id: int = 0
    signer_nonce: int = 0
    gas_price: int = 0
    gas_limit: int = 0
    destination: int = 0
    amount: int = 0
    payload: bytes = bytes()
    access_list: List[Tuple[int, List[int]]] = field(default_factory=list)
    signature_y_parity: bool = False
    signature_r: int = 0
    signature_s: int = 0

@dataclass
class Transaction2930Envelope:
    type: Literal[1] = 1
    payload: Transaction2930Payload = Transaction2930Payload()

@dataclass
class Transaction1559Payload:
    chain_id: int = 0
    signer_nonce: int = 0
    max_priority_fee_per_gas: int = 0
    max_fee_per_gas: int = 0
    gas_limit: int = 0
    destination: int = 0
    amount: int = 0
    payload: bytes = bytes()
    access_list: List[Tuple[int, List[int]]] = field(default_factory=list)
    signature_y_parity: bool = False
    signature_r: int = 0
    signature_s: int = 0

@dataclass
class Transaction1559Envelope:
    type: Literal[2] = 2
    payload: Transaction1559Payload = Transaction1559Payload()

Transaction2718 = Union[Transaction1559Envelope, Transaction2930Envelope]

Transaction = Union[TransactionLegacy, Transaction2718]

@dataclass
class NormalizedTransaction:
    signer_address: int = 0
    signer_nonce: int = 0
    max_priority_fee_per_gas: int = 0
    max_fee_per_gas: int = 0
    gas_limit: int = 0
    destination: int = 0
    amount: int = 0
    payload: bytes = bytes()
    access_list: List[Tuple[int, List[int]]] = field(default_factory=list)

@dataclass
class Block:
    parent_hash: int = 0
    uncle_hashes: Sequence[int] = field(default_factory=list)
    author: int = 0
    state_root: int = 0
    transaction_root: int = 0
    transaction_receipt_root: int = 0
    logs_bloom: int = 0
    difficulty: int = 0
    number: int = 0
    gas_limit: int = 0 # 注意 gas_limit 是 gas_target * ELASTICITY_MULTIPLIER
    gas_used: int = 0
    timestamp: int = 0
    extra_data: bytes = bytes()
    proof_of_work: int = 0
    nonce: int = 0
    base_fee_per_gas: int = 0

@dataclass
class Account:
    address: int = 0
    nonce: int = 0
    balance: int = 0
    storage_root: int = 0
    code_hash: int = 0

INITIAL_BASE_FEE = 1000000000
INITIAL_FORK_BLOCK_NUMBER = 10 # 待定
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
ELASTICITY_MULTIPLIER = 2

class World(ABC):
    def validate_block(self, block: Block) -> None:
        parent_gas_target = self.parent(block).gas_limit // ELASTICITY_MULTIPLIER
        parent_gas_limit = self.parent(block).gas_limit

        # 在分叉区块上,不考虑 ELASTICITY_MULTIPLIER 以避免不必要地将 gas 目标减半。
        if INITIAL_FORK_BLOCK_NUMBER == block.number:
            parent_gas_target = self.parent(block).gas_limit
            parent_gas_limit = self.parent(block).gas_limit * ELASTICITY_MULTIPLIER 

        parent_base_fee_per_gas = self.parent(block).base_fee_per_gas
        parent_gas_used = self.parent(block).gas_used
        transactions = self.transactions(block)

        # 检查区块是否使用了过多的 gas
        assert block.gas_used <= block.gas_limit, 'invalid block: too much gas used'

        # 检查区块是否过度改变了 gas 限制
        assert block.gas_limit < parent_gas_limit + parent_gas_limit // 1024, 'invalid block: gas limit increased too much'
        assert block.gas_limit > parent_gas_limit - parent_gas_limit // 1024, 'invalid block: gas limit decreased too much'

        # 检查 gas 限制是否至少达到最小 gas 限制
        assert block.gas_limit >= 5000

        # 检查基础费用是否正确
        if INITIAL_FORK_BLOCK_NUMBER == block.number:
            expected_base_fee_per_gas = INITIAL_BASE_FEE
        elif parent_gas_used == parent_gas_target:
            expected_base_fee_per_gas = parent_base_fee_per_gas
        elif parent_gas_used > parent_gas_target:
            gas_used_delta = parent_gas_used - parent_gas_target
            base_fee_per_gas_delta = max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)
            expected_base_fee_per_gas = parent_base_fee_per_gas + base_fee_per_gas_delta
        else:
            gas_used_delta = parent_gas_target - parent_gas_used
            base_fee_per_gas_delta = parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR
            expected_base_fee_per_gas = parent_base_fee_per_gas - base_fee_per_gas_delta
        assert expected_base_fee_per_gas == block.base_fee_per_gas, 'invalid block: base fee not correct'

        # 执行交易并进行 gas 核算
        cumulative_transaction_gas_used = 0
        for unnormalized_transaction in transactions:
            # 注意:这将验证交易签名和链 ID,这必须在我们下面进行规范化之前进行,因为规范化交易不包含签名或链 ID
            signer_address = self.validate_and_recover_signer_address(unnormalized_transaction)
            transaction = self.normalize_transaction(unnormalized_transaction, signer_address)

            signer = self.account(signer_address)

            signer.balance -= transaction.amount
            assert signer.balance >= 0, 'invalid transaction: signer does not have enough ETH to cover attached value'
            # 签名者必须能够支付交易
            assert signer.balance >= transaction.gas_limit * transaction.max_fee_per_gas

            # 确保用户至少愿意支付基础费用
            assert transaction.max_fee_per_gas >= block.base_fee_per_gas

            # 防止出现过大的数字
            assert transaction.max_fee_per_gas < 2**256
            # 防止出现过大的数字
            assert transaction.max_priority_fee_per_gas < 2**256
            # 总和必须是两者中较大的一个
            assert transaction.max_fee_per_gas >= transaction.max_priority_fee_per_gas

            # 优先费是有限制的,因为基础费用会首先被填满
            priority_fee_per_gas = min(transaction.max_priority_fee_per_gas, transaction.max_fee_per_gas - block.base_fee_per_gas)
            # 签名者支付优先费和基础费
            effective_gas_price = priority_fee_per_gas + block.base_fee_per_gas
            signer.balance -= transaction.gas_limit * effective_gas_price
            assert signer.balance >= 0, 'invalid transaction: signer does not have enough ETH to cover gas'
            gas_used = self.execute_transaction(transaction, effective_gas_price)
            gas_refund = transaction.gas_limit - gas_used
            cumulative_transaction_gas_used += gas_used
            # 签名者获得未使用的 gas 退款
            signer.balance += gas_refund * effective_gas_price
            # 矿工只收取优先费;注意基础费不给任何人(它被销毁了)
            self.account(block.author).balance += gas_used * priority_fee_per_gas

        # 检查区块交易是否消耗了过多的 gas
        assert cumulative_transaction_gas_used == block.gas_used, 'invalid block: gas_used does not equal total gas used in all transactions'

        # TODO:验证账户余额是否与区块的账户余额匹配(通过状态根比较)
        # TODO:验证区块的其余部分

    def normalize_transaction(self, transaction: Transaction, signer_address: int) -> NormalizedTransaction:
        # 传统交易
        if isinstance(transaction, TransactionLegacy):
            return NormalizedTransaction(
                signer_address = signer_address,
                signer_nonce = transaction.signer_nonce,
                gas_limit = transaction.gas_limit,
                max_priority_fee_per_gas = transaction.gas_price,
                max_fee_per_gas = transaction.gas_price,
                destination = transaction.destination,
                amount = transaction.amount,
                payload = transaction.payload,
                access_list = [],
            )
        # 2930 交易
        elif isinstance(transaction, Transaction2930Envelope):
            return NormalizedTransaction(
                signer_address = signer_address,
                signer_nonce = transaction.payload.signer_nonce,
                gas_limit = transaction.payload.gas_limit,
                max_priority_fee_per_gas = transaction.payload.gas_price,
                max_fee_per_gas = transaction.payload.gas_price,
                destination = transaction.payload.destination,
                amount = transaction.payload.amount,
                payload = transaction.payload.payload,
                access_list = transaction.payload.access_list,
            )
        # 1559 交易
        elif isinstance(transaction, Transaction1559Envelope):
            return NormalizedTransaction(
                signer_address = signer_address,
                signer_nonce = transaction.payload.signer_nonce,
                gas_limit = transaction.payload.gas_limit,
                max_priority_fee_per_gas = transaction.payload.max_priority_fee_per_gas,
                max_fee_per_gas = transaction.payload.max_fee_per_gas,
                destination = transaction.payload.destination,
                amount = transaction.payload.amount,
                payload = transaction.payload.payload,
                access_list = transaction.payload.access_list,
            )
        else:
            raise Exception('invalid transaction: unexpected number of items')

    @abstractmethod
    def parent(self, block: Block) -> Block: pass

    @abstractmethod
    def block_hash(self, block: Block) -> int: pass

    @abstractmethod
    def transactions(self, block: Block) -> Sequence[Transaction]: pass

    # effective_gas_price 是 GASPRICE (0x3a) 操作码返回的值
    @abstractmethod
    def execute_transaction(self, transaction: NormalizedTransaction, effective_gas_price: int) -> int: pass

    @abstractmethod
    def validate_and_recover_signer_address(self, transaction: Transaction) -> int: pass

    @abstractmethod
    def account(self, address: int) -> Account: pass

向后兼容性

传统的以太坊交易仍然可以工作并被打包到区块中,但它们不会直接受益于新的定价系统。这是因为从传统交易升级到新交易会导致传统交易的 gas_price 完全被 base_fee_per_gaspriority_fee_per_gas 消耗。

区块哈希改变

传递给 keccak256 以计算区块哈希的数据结构正在改变,所有验证区块是否有效或使用区块哈希验证区块内容的应用都需要进行调整以支持新的数据结构(一个额外的项)。如果你只取区块头字节并对其进行哈希,你仍然应该正确获得哈希,但如果你从其组成元素构建区块头,你需要在末尾添加新的元素。

GASPRICE

在此更改之前,GASPRICE 代表签名者每 gas 支付的 ETH 以及矿工每 gas 收到的 ETH。此更改后,GASPRICE 现在仅代表签名者每 gas 支付的 ETH 数量,矿工因交易收到的金额不再可以直接在 EVM 中访问。

安全考虑

增加最大区块大小/复杂性

这个 EIP 将增加最大区块大小,如果矿工无法足够快地处理区块,可能会导致问题,因为它会迫使他们挖空块。随着时间的推移,平均区块大小应该与没有此 EIP 的情况大致相同,所以这只是短期大小突增的问题。可能有一个或多个客户端在短期大小突增时处理不当并出错(例如内存不足或类似情况),客户端实现应确保其客户端能够适当地处理达到最大大小的单个区块。

交易排序

由于大多数人不再在优先费上竞争,而是使用基准费用来获得打包,交易排序现在取决于各个客户端的内部实现细节,例如它们如何在内存中存储交易。建议将具有相同优先费用的交易按收到交易的时间排序,以保护网络免受垃圾邮件攻击,在这种攻击中,攻击者将大量交易投入待处理池,以确保至少一个交易落在有利位置。从自私挖矿的角度来看,矿工仍然应该优先选择 gas 溢价较高的交易,而不是 gas 溢价较低的交易。

矿工挖空块

矿工有可能一直挖空块,直到基础费用非常低,然后开始挖半满的区块并恢复根据优先费对交易进行排序。虽然这种攻击是可能的,但只要挖矿是去中心化的,它就不是一个特别稳定的均衡。在攻击持续的整个过程中(即使在基础费用达到 0 之后),任何偏离这种策略的叛逃者都将比参与攻击的矿工更有利可图。由于任何矿工都可以匿名脱离卡特尔,并且无法证明特定矿工叛逃,因此执行此攻击唯一可行的方法是控制 50% 或更多的算力。如果攻击者拥有恰好 50% 的算力,他们将无法从优先费中赚取任何以太币,而叛逃者将赚取双倍的以太币优先费。要让攻击者获利,他们需要拥有超过 50% 的算力,这意味着他们可以执行双花攻击或简单地忽略任何其他矿工,这是一种更有利可图的策略。

如果矿工试图执行此攻击,我们可以简单地增加弹性乘数(目前为 2 倍),这要求他们在理论上能够对抗叛逃者获利之前,拥有更多的可用算力。

ETH 销毁排除了固定供应量

通过销毁基础费用,我们无法再保证固定的以太币供应量。这可能导致经济不稳定,因为 ETH 的长期供应量将不再随时间保持不变。虽然这是一个合理的问题,但很难量化这将产生多大的影响。如果销毁的基础费用多于挖矿奖励产生的费用,则 ETH 将是通货紧缩的;如果挖矿奖励产生的费用多于销毁的费用,则 ETH 将是通货膨胀的。由于我们无法控制用户对区块空间的需求,我们目前无法确定 ETH 最终是通货膨胀还是通货紧缩,因此这一变化导致核心开发者对以太币的长期数量失去了一些控制。

版权

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

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

0 条评论

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