Skip to content

Commit 7495224

Browse files
authored
Merge pull request #3 from EgoSay/feat/block-building
Feat/block building
2 parents 2747cd6 + 51ab504 commit 7495224

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
- [交易剖析](./transaction.md)
1010
- [JSON-RPC](./JSON-RPC.md)
1111
- [RLP](./RLP.md)
12+
- [区块构建](./block-building.md)

src/block-building.md

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# 区块构建
2+
3+
## 介绍
4+
5+
区块构建是以太坊区块链功能中的关键任务,涉及多个流程,这些流程决定了验证者如何获取区块并进行提议。
6+
7+
以太坊网络由运行互联共识客户端(CL)和执行客户端(EL)的节点组成,这两者对参与网络和在每个时间槽中生成区块都是不可或缺的。
8+
9+
执行客户端(EL)具有许多重要功能,你可以通过学习 [el-architecture](https://epf.wiki/#/wiki/EL/el-architecture) 来深入了解, 本文的重点是介绍其为共识客户端(CL)构建区块的角色。
10+
11+
当一个验证者在某个 Slot 时间槽中被选中提议出块时,它会寻找由共识客户端(CL)生成的区块。值得注意的是,验证者并不限于广播自己 EL 生成的区块,也可以广播由外部构建者生成的区块;详情请参阅 [PBS](https://ethereum.org/en/roadmap/pbs/)
12+
13+
本文特别探讨了 执行客户端(EL)如何生成区块,以及哪些要素影响着区块的成功生成和交易执行。
14+
15+
## Payload 构建流程
16+
17+
> 译者注:
18+
>
19+
> 在以太坊中,Payload 通常是指由执行层(EL)生成的区块数据,包含了区块中的所有重要信息,如交易数据、状态根(state root)、交易根(transaction root)、收据根(receipts root)等,用于确保区块的有效性和完整性,会被传递到共识层(CL)进行验证和传播
20+
21+
当共识层通过 `engine API's fork choice updated` 端点指示执行层客户端时,区块就被创建了,然后通过 payload building routine 启动区块构建过程。
22+
23+
> 译者注:
24+
>
25+
> 在以太坊中,共识层(CL)通过分叉选择规则(fork choice rule) 来决定在存在多个区块链(或分叉)时,哪条链被认为是有效链的逻辑,尤其是在网络分裂或同时产生多个区块的情况下。
26+
>
27+
> `engine API's fork choice updated` 端点 是指以太坊协议中的一个特定 API 端点,允许执行层(EL)做出决定,选择哪条分叉链应该被视为有效链。
28+
>
29+
> 当一个验证者被选中提议区块时,通过 engine API 调用 fork choice updated 端点,通知执行层当前的最佳链。这一操作有助于决定接下来应该构建哪一个区块,确保执行层在正确的链分支上工作,并根据这个分支构建新的区块
30+
31+
**注:** 费用接收者可能与预期的接收者不同,在正常情况下,区块中规定了一个建议的费用接收者地址,但在某些情况下,实际构建区块时,所选定的费用接收者地址可能与建议的地址不同。例如,如果某个外部构建者(external builder)参与了区块的构建过程,他们可能会选择将费用发送给其他地址,而不是建议的接收者地址
32+
33+
![](./images/el-architecture/architecture-overview.png)
34+
35+
节点通过 P2P 点对点网络传播交易。这些交易被认为是有效的,但尚未被包含在区块中。
36+
交易的有效性主要指以下条件:交易的 nonce 是账户的下一个有效 nonce,并且账户拥有足够的余额来覆盖交易费用。
37+
38+
有时,节点会被分配到生成区块的任务。共识层通过随机选择过程来确定每个 epoch 中哪个验证者来负责构建区块。
39+
如果你的验证者被选中构建区块,你的共识层客户端将使用执行引擎的 `fork choice updated` 方法进行构建,并提供区块构建所需的上下文。
40+
41+
---
42+
43+
我们可以简化并模拟区块构建的过程,使用 Go 语言来完成一个简单的特定实现:
44+
45+
```go
46+
func build(env environment, pool txpool.Pool, state state.StateDB) (types.Block, state.StateDB) {
47+
var (
48+
gasUsed = 0
49+
txs []types.Transactions
50+
)
51+
52+
for ; gasUsed < 30_000_000 || !pool.Empty(); {
53+
transaction := pool.Pop()
54+
res, gas, err := vm.Run(env, transaction, state)
55+
if err != nil {
56+
// transaction invalid
57+
continue
58+
}
59+
gasUsed += gas
60+
transactions = append(transactions, transaction)
61+
}
62+
return core.Finalize(env, transactions, state)
63+
}
64+
```
65+
66+
1. 接收 3 个传参变量
67+
- `env environment` 包含环境所有必要信息,包括时间戳、区块编号、前置区块、基础费用以及需要在区块中发生的所有提取操作, 这些信息本质上来源于共识层。
68+
- 交易池 `txpool.Pool` 变量,即交易的集合,为简单起见,我们假设这些交易按其价值升序排列,价值越高的交易更容易被确认打包
69+
- `state.StateDB` 状态数据库表示执行这些交易的状态存储
70+
71+
最后生成并返回以下内容:
72+
73+
- 一个完整的区块
74+
- 一个包含所有交易的状态数据库(state DB),它记录了所有交易的执行结果和更新后的状态
75+
- 如果在处理过程中出现了错误,还可能会返回一个错误信息
76+
77+
2. 在 build 函数中,我们跟踪 gas 消耗 `gasUsed`,因为我们可以使用的 gas 是有限的。我们还存储所有将被包含在区块中的交易。
78+
79+
3. 我们继续添加交易,直到交易池为空,或者消耗的 gas 超过 gas 限制。为了简单起见,在这个例子中,gas 限制设定为 3000 万(大约是主网当前的 gas 限制)。
80+
81+
4. 为了获取一笔交易,我们必须查询交易池,假设交易池会维护一个有序的交易列表,确保我们始终获取到下一个最有价值的交易。
82+
83+
5. 交易在 EVM 中执行,在执行交易时,系统将交易、环境和当前状态作为输入,并在这些输入条件下执行交易。交易的执行会根据当前环境(如区块链状态)进行,并在执行过程中更新状态数据库,包括所有已成功执行的交易
84+
85+
6. 如果交易执行失败,并且在执行过程中发生错误,我们将继续处理下一笔交易,而不立即中断。这表明该交易无效,并且由于区块中仍有未使用的 gas,我们不希望立即生成错误。因为在区块中尚未发生错误,因此我们可以继续进行处理。然而,很可能该交易是无效的,因为它在执行过程中发生了错误,或者交易池中的数据略有过时。在这种情况下,我们允许继续并尝试从交易池中获取下一笔交易,继续将其加入到当前区块中。
86+
87+
7. 一旦我们验证运行交易没有错误,我们就会将该交易添加到交易列表中,并将运行返回的气体添加到所使用的气体中。例如,如果第一笔交易是简单的转账,需要花费 21,000 Gas,那么我们使用的 Gas 将从 0 到 21,000,我们将继续执行此过程步骤 3-7,直到满足步骤 3 的条件
88+
89+
8. 我们通过获取一组交易和相关区块信息, 以最终确定生成一个完整的区块, 这样做的目的是为了最后进行一定的计算。由于 header 包含交易根、收据根和提款根,因此必须通过默克尔化列表来计算这些值并将其添加到块的 header 中
90+
91+
## 代码走读
92+
### Geth
93+
以下示例使用 Geth 的代码库来解释执行客户端如何构建区块。
94+
95+
1. 首先,当一个验证者被选中作为区块构建者时,它通过执行层(EL)的 Engine API 调用 `engine_forkchoiceUpdatedV2` 函数。此时,执行层启动区块构建过程
96+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/eth/catalyst/api.go#L398](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/eth/catalyst/api.go#L398)
97+
98+
2. 区块构建的大部分核心逻辑和交易执行都在 Geth 的 `miner` 模块中。`buildPayload` 函数最初会创建一个空区块,这样节点就不会错过时间槽,并且有东西可以提议。函数的实现还会启动一个 go 协程,其任务是填充该空区块,然后将填充的交易并发更新到区块中
99+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/payload_building.go#L180](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/payload_building.go#L180)
100+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/payload_building.go#L204](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/payload_building.go#L204)
101+
102+
3.`buildPayload` 函数中,go 协程正在等待多个通信操作的“case”。在第一个 case 中,它调用 `getSealingBlock` 函数,并显式指定区块不应为空(参数为 `noTxs:False`
103+
104+
4.`getSealingBlock` 的定义中,请求被发送到 `getWorkCh` 通道。这个通道正在被监听,用于从中检索数据并生成工作任务
105+
- [https://github.com/ethereum/goethereum/blob/master/miner/worker.go#L1222](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1222)
106+
107+
5. `getWorkCh` 通道正在同一文件中的 `mainLoop` 函数内被监听。从 `getWorkCh` 通道接收到的数据会发送到 `w.generateWork` 函数
108+
- [https://github.com/ethereum/go-ethereum/blob/master/miner/worker.go#L537](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L537)
109+
110+
6. `generateWork` 函数是将交易填充到区块中的地方
111+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1094](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1094)
112+
113+
7. `w.fillTransactions` 函数从内存池(mempool)中检索所有待处理交易并填充到区块中。这包括所有类型的交易,包括 `blobs`
114+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1024](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1024)
115+
116+
8. 交易按其费用排序后填充,并传递给 `commitTransactions` 函数
117+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1072](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L1072)
118+
119+
9. `commitTransactions` 函数检查每笔交易是否有足够的 gas 可以使用,然后提交该交易。此外,每个区块允许的 `blobs` 数量由 `EIP-4844` 规定
120+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L888](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L888)
121+
- [https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md)
122+
123+
10. 如果查看 `commitTransaction` 函数,会发现它会回调 `w.applyTransaction` 函数
124+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L760C18-L760C36](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L760C18-L760C36)
125+
126+
11. `applyTransaction` 函数进一步调用核心包中的 `core.ApplyTransaction`,该函数会根据本地执行层状态执行所有交易
127+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L794](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/miner/worker.go#L794)
128+
129+
12. `ApplyTransaction` 函数会在本地执行层状态中运行交易并进行所有状态更改。它会创建 EVM 上下文和环境以在 EVM 中执行交易。合约调用也在这里完成。如果一切顺利,状态将成功地发生转换
130+
- [https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/core/state_processor.go#L161](https://github.com/ethereum/go-ethereum/blob/0a2f33946b95989e8ce36e72a88138adceab6a23/core/state_processor.go#L161)
131+
132+
13. 当然交易也可能失败,如果交易失败,状态则不会转换。失败的原因可能包括链上原因,例如 gas 耗尽、合约调用失败等。
133+
134+
14. 从这一点开始,所有交易会逐一执行。交易随后被打包到区块中。
135+
136+
15. 然后,共识层通过 Engine API 请求执行层,获取填充了交易的有效 `payload`。执行层将此 `payload` 返回给共识层,后者将此 `payload` 放入信标区块并传播它
137+
138+
139+
## 引用资源
140+
141+
- [Block Building](https://epf.wiki/#/wiki/EL/block-production)
142+
- [GETH codebase](https://github.com/ethereum/go-ethereum)
143+
- [Engine API: A Visual Guide](https://hackmd.io/@danielrachi/engine_api)

0 commit comments

Comments
 (0)