{"content":{"title":"以太坊 Gas 机制详解(EIP-1559)","body":"以太坊中的 `Gas` 是执行智能合约和进行交易所需的一种衡量单位。它代表了网络中执行操作所需的计算资源。`Gas` 用于防止网络滥用和保证交易和智能合约的执行效率。每个操作和交易都有 `Gas` 消耗，用户通过设置 `Gas` 价格来为其支付。`Gas` 的机制帮助保持以太坊网络的健康运行，通过对资源消耗的计费来调节网络负载。\r\n\r\n如果把以太坊网络比作一名工人，那么 `Gas` 就是工人付出的劳动力。在工人完成工作后，需要支付劳动报酬。劳动报酬则等于每单位劳动力价格乘以付出的总的劳动力。每单位劳动力价格被称作 `GasPrice`, 其值由以太坊网络动态决定的。因此总的劳动报酬就是 `Gas * GasPrice`\r\n\r\n`GasLimit` 可以理解为愿意为多少劳动力买单。假如某项工作需要付出 100 的劳动力，但你只愿意支付 80 劳动力的费用。因此这项工作就无法完成。但当你愿意支付 120 劳动力的费用时，则会在工作完成后会退还这 20 劳动力的费用。类比到以太坊网络就是愿意为这笔交易最多支付多少 `Gas`\r\n\r\n现有一笔如下所示的[交易](https://etherscan.io/tx/0x2a47d57a8bfd4b8fbfbbad922509b8bed45c470c73068de3623388c6d3aaf117):\r\n\r\n![transaction.png](https://img.learnblockchain.cn/attachments/2023/11/zar0RHTX6556db8006237.png)\r\n\r\n可以看到 `Gas` 部分由以下部分组成:\r\n\r\n- `Gas Limit & Usage by Txn`: `GasLimit` 和 实际花费的 `Gas` 以及其在 `GasLimit` 中的占比\r\n- `Gas Fees`\r\n  - `Base`: 基础 `GasPrice`\r\n  - `Max`: 最大 `GasPrice`\r\n  - `Max Priority`: 支付给以太坊节点矿工的 `GasPrice`\r\n- `Burnt & Txn Savings Fees`\r\n  - `Burnt`: 燃烧的手续费\r\n  - `Txn Savings`: 交易节省的费用\r\n\r\n`Txn Type: 2(EIP-1559)`: 根据 `EIP-2718` 明确交易类型为 2, 指明这是一笔 `EIP-1559` 的交易\r\n\r\n## BaseFee\r\n\r\n`BaseFee` 是 `EIP-1559` 提案中引入的一个机制，目的是改善以太坊的费用市场并提高用户体验。`BaseFee` 是每个区块的基础费用，它的目的是通过**自适应地调整费用**来反映网络的拥堵程度。\r\n\r\n源码中 `BaseFee` 的计算如下:\r\n\r\n```go showLineNumbers\r\nfunc CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {\r\n\t// If the current block is the first EIP-1559 block, return the InitialBaseFee.\r\n\tif !config.IsLondon(parent.Number) {\r\n\t\treturn new(big.Int).SetUint64(params.InitialBaseFee)\r\n\t}\r\n\r\n\tparentGasTarget := parent.GasLimit / config.ElasticityMultiplier()\r\n\t// If the parent gasUsed is the same as the target, the baseFee remains unchanged.\r\n\tif parent.GasUsed == parentGasTarget {\r\n\t\treturn new(big.Int).Set(parent.BaseFee)\r\n\t}\r\n\r\n\tvar (\r\n\t\tnum   = new(big.Int)\r\n\t\tdenom = new(big.Int)\r\n\t)\r\n\r\n\tif parent.GasUsed > parentGasTarget {\r\n\t\t// If the parent block used more gas than its target, the baseFee should increase.\r\n\t\t// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)\r\n\t\tnum.SetUint64(parent.GasUsed - parentGasTarget)\r\n\t\tnum.Mul(num, parent.BaseFee)\r\n\t\tnum.Div(num, denom.SetUint64(parentGasTarget))\r\n\t\tnum.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator()))\r\n\t\tbaseFeeDelta := math.BigMax(num, common.Big1)\r\n\r\n\t\treturn num.Add(parent.BaseFee, baseFeeDelta)\r\n\t} else {\r\n\t\t// Otherwise if the parent block used less gas than its target, the baseFee should decrease.\r\n\t\t// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)\r\n\t\tnum.SetUint64(parentGasTarget - parent.GasUsed)\r\n\t\tnum.Mul(num, parent.BaseFee)\r\n\t\tnum.Div(num, denom.SetUint64(parentGasTarget))\r\n\t\tnum.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator()))\r\n\t\tbaseFee := num.Sub(parent.BaseFee, num)\r\n\r\n\t\treturn math.BigMax(baseFee, common.Big0)\r\n\t}\r\n}\r\n```\r\n\r\n计算 `BaseFee` 过程如下:\r\n\r\n- 如果当前区块是第一个 `EIP-1559` 区块，则返回 `InitialBaseFee`, 其值为 `1 Gwei`\r\n- 计算 `parentGasTarget` 等于父区块 `GasLimit` 的一半\r\n\r\n  - 父区块的 `GasUsed` 等于 `parentGasTarget`: `BaseFee` 不变\r\n  - 父区块的 `GasUsed` 大于 `parentGasTarget`: `BaseFee` 增加, 计算遵循公式\r\n\r\n```go\r\n\r\nparentBaseFee + max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)\r\n```\r\n\r\n- 父区块的 `GasUsed` 小于 `parentGasTarget`: `BaseFee` 减少, 计算遵循公式\r\n\r\n```go\r\n\r\nmax(0, parentBaseFee - parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)\r\n```\r\n\r\n其中:\r\n\r\n- `GasUsed`: 实际使用的 `Gas`\r\n- `parentBaseFee` 等于 父区块的 `BaseFee`\r\n- `gasUsedDelta` 等于 `parent.GasUsed - parentGasTarget`，即父区块的 `Gas` 实际使用量与目标总量之间的差额\r\n- `parentGasTarget` 父区块 `GasLimit` 的一半, 一般为 15000000\r\n- `BaseFeeChangeDenominator` 常量，值为 8\r\n\r\n在 `etherscan` [区块列表页](https://etherscan.io/blocks) 有如下两个区块\r\n\r\n\r\n![blocks.png](https://img.learnblockchain.cn/attachments/2023/11/gPlpJbld6556db93d6291.png)\r\n\r\n在 18247918 区块中，可以知道\r\n\r\n- `BaseFee` 等于 6.79 Gwei\r\n- `gasUsedDelta / parentGasTarget` 等于 -24%, 说明需要降低 `BaseFee`\r\n\r\n按照公式计算 18247919 区块的 `BaseFee` 等于\r\n\r\n$$\r\n\\mathrm{BaseFee = max(0, 6.79 - 6.79 * 0.24 / 8) = 6.5863}\r\n$$\r\n\r\n与图示一致\r\n\r\n## MaxPriorityFee\r\n\r\n`MaxPriorityFee` 是优先费用。是对每单位 `Gas` 的额外加价, 这部分的费用将支付给矿工，值越大则交易更快的被打包。通过[GasTracker](https://etherscan.io/gastracker)可查看当前最新的 `Gas` 信息\r\n\r\n最终的 `GasPrice` 等于 `BaseFee` 和 `MaxPriorityFee` 之和\r\n\r\n对于交易:\r\n\r\n![transaction.png](https://img.learnblockchain.cn/attachments/2023/11/zar0RHTX6556db8006237.png)\r\n\r\n$$\r\n\\begin{aligned}\r\n\\tt{Transaction\\ Fee} & = \\tt{GasUsed * GasPrice} \\\\\r\n&= \\tt{GasUsed * (BaseFee + MaxPriorityFee)} \\\\\r\n&= 115855 * (7.407585749 + 0.05) \\\\\r\n& = 863998.597\\tt{Gwei}\r\n\\end{aligned}\r\n$$\r\n\r\n交易费用与图中 `Transaction Fee` 字段值一致\r\n\r\n## MaxFee\r\n\r\n`MaxFee` 意为最大的 `GasPrice`\r\n\r\n由于发送的交易不一定会在下一个区块内打包，而 `BaseFee` 又是在动态的改变，如果交易设置的 `MaxPriorityFee` 过低，则有可能交易不会被打包。只能等待后续区块的打包。但如果后续区块的 `BaseFee` 比之前的高，则会导致交易被丢弃。而设置较高的 `MaxFee`, 则可以保证交易在未来几个区块内不会因为 `BaseFee` 设置过低而被丢弃。\r\n\r\n还是以劳动力举例。现在每单位的劳动力的价格(`BaseFee`)是变动的, 由市场决定。在你发布一个工作后，而且还对每单位的劳动力付出额外报酬(`MaxPriorityFee`)的情况下，由于你愿意支付的报酬低于市场价，此时没人愿意为你工作，则会对你发布的工作进行下架处理。因此你给出了最大的每单位的劳动力价格(`MaxFee`)，只要当前的 `BaseFee` 加上 `MaxPriorityFee` 是小于 `MaxFee`，就可以继续招工。每单位劳动力价格仍按 `BaseFee + MaxPriorityFee`计算\r\n\r\n通常情况下 `MaxFee` 计算遵循公式:\r\n\r\n$$\r\n\\tt{Max Fee = (2 * BaseFee) + MaxPriorityFee}\r\n$$\r\n\r\n可以保证**连续** 6 个区块满 `Gas` 的情况下仍在内存池中等待打包\r\n\r\n## Burnt\r\n\r\n燃烧的手续费, 即将这部分费用转入黑洞地址。转入数量由 `BaseFee` 决定\r\n\r\n$$\r\n\\mathtt{Burnt = BaseFee * GasUsed}\r\n$$\r\n\r\n以上图中交易为例计算\r\n\r\n$$\r\n\\begin{aligned}\r\n\\tt{Burnt} & = \\tt{BaseFee * GasUsed} \\\\\r\n& = 7.407585749 * 115855 \\\\\r\n&= 858205.846950395 \\ \\tt{Gwei}\r\n\\end{aligned}\r\n$$\r\n\r\n与图中 `Burnt` 字段值一致\r\n\r\n## Txn Savings\r\n\r\n交易节省的费用，等于最大可接受交易费用减去实际消耗的交易费用\r\n\r\n$$\r\n\\tt{Tx Savings Fees = MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed}\r\n$$\r\n\r\n以上图中交易为例计算\r\n\r\n$$\r\n\\begin{aligned}\r\n\\tt{Tx Savings Fees} & = \\tt{MaxFee * GasUsed - (BaseFee + MaxPriorityFee) * GasUsed} \\\\\r\n& = 7.657591636 * 115855 - 863998.597 \\\\\r\n& = 23171.68198878  \\tt{Gwei} \\\\\r\n\\end{aligned}\r\n$$\r\n\r\n与图中 `Txn Savings` 字段值一致\r\n\r\n## JSON-RPC\r\n\r\n在发起 EIP1559 交易时, 常需要在交易中手动填入 `Gas` 相关的参数，这些参数可以通过向节点预发送 `http` 请求获得，包括了以下 `JSON-RPC` 方法\r\n\r\n- `eth_estimateGas`\r\n- `eth_maxPriorityFeePerGas`\r\n- `eth_getBlockByNumber`\r\n\r\n### eth_estimateGas\r\n\r\n将交易发送到该接口，可以获得预估 `Gas`, 常用来设置交易的 `GasLimit`\r\n\r\n```ts showLineNumbers\r\n// Request Payload\r\n{\r\n  \"jsonrpc\": \"2.0\",\r\n  \"method\": \"eth_estimateGas\",\r\n  \"params\": [\r\n    {\r\n      \"from\": \"0xD28C383dd3a1C0154129F67067175884e933cf4e\",\r\n      \"to\": \"0x7071D6EF9FaF45aA48c22bae7d4a295aD68DC038\",\r\n      \"value\": \"0x186a0\"\r\n    }\r\n  ],\r\n  \"id\": 1\r\n}\r\n// Response\r\n{\r\n  \"id\":1,\r\n  \"jsonrpc\": \"2.0\",\r\n  \"result\": \"0x5208\" // 21000\r\n}\r\n```\r\n\r\n### eth_maxPriorityFeePerGas\r\n\r\n该接口用来获取当前最新的 `MaxPriorityFee`\r\n\r\n```ts\r\n// Request Payload\r\n{\r\n  \"jsonrpc\": \"2.0\",\r\n  \"method\": \"eth_maxPriorityFeePerGas\",\r\n  \"params\": [],\r\n  \"id\": 1\r\n}\r\n// Response\r\n{\r\n  \"jsonrpc\": \"2.0\",\r\n  \"result\": \"0x9b8495\", // MaxPriorityFee\r\n  \"id\": 1\r\n}\r\n```\r\n\r\n### eth_getBlockByNumber\r\n\r\n该接口用于获取区块信息，其中包含了 `BaseFee` 等信息\r\n\r\n```ts\r\n// Request Payload\r\n{\r\n  \"jsonrpc\": \"2.0\",\r\n  \"method\": \"eth_getBlockByNumber\",\r\n  \"params\": [\r\n    \"latest\",\r\n    false\r\n  ],\r\n  \"id\": 1\r\n}\r\n// Response\r\n{\r\n  \"jsonrpc\": \"2.0\",\r\n  \"result\": {\r\n    \"baseFeePerGas\": \"0x1bc47470a\", // baseFee\r\n    \"difficulty\": \"0x0\",\r\n    \"extraData\": \"0x546974616e2028746974616e6275696c6465722e78797a29\",\r\n    \"gasLimit\": \"0x1c9c380\",\r\n    \"gasUsed\": \"0xced6fd\",\r\n    \"hash\": \"0xbb9b314d0b8208e655a0afc17384f56f44659a63e3ba4e244609105da497a7d9\",\r\n    ...\r\n  },\r\n  \"id\": 1\r\n}\r\n```\r\n\r\n获取最新的区块信息后, 字段 `baseFeePerGas` 的值就是 `BaseFee`。结合上面获取的 `MaxPriorityFee`, 可以用来设置 `MaxFee`\r\n\r\n```\r\nMax Fee = (2 * BaseFee) + MaxPriorityFee\r\n```"},"author":{"user":"https://learnblockchain.cn/people/9647","address":null},"history":null,"timestamp":1700191204,"version":1}