diff --git a/CN/02_state_management_statedb.md b/CN/02_state_management_statedb.md index eacd740..fd35250 100644 --- a/CN/02_state_management_statedb.md +++ b/CN/02_state_management_statedb.md @@ -3,12 +3,12 @@ ## 概述 在本章中,我们来简析一下 go-ethereum 状态管理模块 StateDB。 +core/state ## 理解 StateDB 的结构 我们知道以太坊是是基于以账户为核心的状态机 (State Machine)的模型。在账户的值发生变化的时候,我们说该账户从一个状态转换到了另一个状态。我们知道,在实际中,每个地址都对应了一个账户。随着以太坊用户和合约数量的增加,如何管理这些账户是客户端开发人员需要解决的首要问题。在 go-ethereum 中,StateDB 模块就是为管理账户状态设计的。它是直接提供了与 `StateObject` (账户和合约的抽象) 相关的 CURD 的接口给其他的模块,比如: -- (这个模块在 Merge 之后被废弃) Mining 模块: 执行新 Block 中的交易时调用 `StateDB` 来更新对应账户的值,并且形成新 world state。 - Block 同步模块,执行新 Block 中的交易时调用 `StateDB` 来更新对应账户的值,并且形成新 world state,同时用这个计算出来的 world state与 Block Header 中提供的 state root 进行比较,来验证区块的合法性。 - 在 EVM 模块中,调用与合约存储有关的相关的两个 opcode, `sStore` 和 `sSload` 时会调用 `StateDB`中的函数来查询和更新 Contract 中的持久化存储. - ... @@ -20,103 +20,138 @@ ```go type StateDB struct { - db Database - prefetcher *triePrefetcher - trie Trie - hasher crypto.KeccakState - - // originalRoot is the pre-state root, before any changes were made. - // It will be updated when the Commit is called. - originalRoot common.Hash - - snaps *snapshot.Tree - snap snapshot.Snapshot - snapAccounts map[common.Hash][]byte - snapStorage map[common.Hash]map[common.Hash][]byte - - // This map holds 'live' objects, which will get modified while processing a state transition. - stateObjects map[common.Address]*stateObject - stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie - stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution - stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block - - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. - dbErr error - - // The refund counter, also used by state transitioning. - refund uint64 - - thash common.Hash - txIndex int - logs map[common.Hash][]*types.Log - logSize uint - - preimages map[common.Hash][]byte - - // Per-transaction access list - accessList *accessList - - // Transient storage - transientStorage transientStorage - - // Journal of state modifications. This is the backbone of - // Snapshot and RevertToSnapshot. - journal *journal - validRevisions []revision - nextRevisionId int - - // Measurements gathered during execution for debugging purposes - AccountReads time.Duration - AccountHashes time.Duration - AccountUpdates time.Duration - AccountCommits time.Duration - StorageReads time.Duration - StorageHashes time.Duration - StorageUpdates time.Duration - StorageCommits time.Duration - SnapshotAccountReads time.Duration - SnapshotStorageReads time.Duration - SnapshotCommits time.Duration - TrieDBCommits time.Duration - - AccountUpdated int - StorageUpdated int - AccountDeleted int - StorageDeleted int + db Database + + // trie预取器,用于优化数据加载性能 + prefetcher *triePrefetcher + + // 状态树的根节点,用于存储所有账户状态 + trie Trie + + // 状态读取器接口,提供对状态的只读访问 + reader Reader + + // 原始状态根哈希,记录状态变更前的根哈希 + // 当调用Commit时会更新此值 + originalRoot common.Hash + + // 活跃对象映射,存储当前正在处理的状态对象 + // key是账户地址,value是账户状态对象 + // 这些对象在状态转换过程中会被修改 + stateObjects map[common.Address]*stateObject + + // 已删除对象映射,存储被标记为删除的状态对象 + // 同一个地址可能同时存在于stateObjects中(账户重生的情况) + // 这里存储的是转换前的原始值 + // 此映射在交易边界被填充 + stateObjectsDestruct map[common.Address]*stateObject + + // 账户变更追踪映射,记录状态转换过程中的账户变更 + // 同一账户的未提交变更可以合并为一个等效的数据库操作 + // 此映射在交易边界被填充 + mutations map[common.Address]*mutation + + // 数据库错误 + // 状态对象被共识核心和VM使用,它们无法处理数据库级别的错误 + // 任何数据库读取错误都会被记录在这里,最终由StateDB.Commit返回 + // 这个错误也会被所有缓存的状态对象共享 + dbErr error + + // 退款计数器,用于状态转换过程中的gas退款 + refund uint64 + + // 交易上下文和交易范围内产生的所有日志 + thash common.Hash // 交易哈希 + txIndex int // 交易索引 + logs map[common.Hash][]*types.Log // 日志映射 + logSize uint // 日志大小 + + // 区块范围内VM看到的原像映射 + preimages map[common.Hash][]byte + + // 每个交易的访问列表,记录被访问的账户和存储槽 + accessList *accessList + accessEvents *AccessEvents + + // 临时存储,用于存储交易执行期间的临时数据 + transientStorage transientStorage + + // 状态修改日志,用于实现快照和回滚功能 + journal *journal + + // 状态见证,用于交叉验证 + witness *stateless.Witness + + // 执行过程中收集的性能指标,用于调试目的 + AccountReads time.Duration // 账户读取耗时 + AccountHashes time.Duration // 账户哈希计算耗时 + AccountUpdates time.Duration // 账户更新耗时 + AccountCommits time.Duration // 账户提交耗时 + StorageReads time.Duration // 存储读取耗时 + StorageUpdates time.Duration // 存储更新耗时 + StorageCommits time.Duration // 存储提交耗时 + SnapshotCommits time.Duration // 快照提交耗时 + TrieDBCommits time.Duration // Trie数据库提交耗时 + + // 状态转换过程中的统计数据 + AccountLoaded int // 从数据库加载的账户数量 + AccountUpdated int // 更新的账户数量 + AccountDeleted int // 删除的账户数量 + StorageLoaded int // 从数据库加载的存储槽数量 + StorageUpdated atomic.Int64 // 更新的存储槽数量(原子操作) + StorageDeleted atomic.Int64 // 删除的存储槽数量(原子操作) } ``` ### db -`StateDB` 结构中的第一个变量 `db` 是一个由 `Database` 类型定义的。这里的 `Database` 是一个抽象层的接口类型,它的定义如下所示。我们可以看到在`Database`接口中定义了一些操作更细粒度的数据管理模块的函数。例如 `DiskDB()` 函数会返回一个更底层的 key-value disk database 的实例,`TrieDB()` 函数会返回一个指向更底层的 Trie Databse 的实例。这两个模块都是非常重要的管理链上数据的模块。由于这两个模块本身就涉及到了大量的细节,因此我们在此就不对两个模块进行细节分析。在后续的章节中,我们会单独的对这两个模块的实现进行解读。 +`StateDB` 结构中的第一个变量 `db` 是一个由 `Database` 类型定义的。这里的 `Database` 是一个抽象层的接口类型,它的定义如下所示。我们可以看到在`Database`接口中定义了一些操作更细粒度的数据管理模块的函数。`TrieDB()` 函数会返回一个指向更底层的 Trie Databse 的实例。这两个模块都是非常重要的管理链上数据的模块。 ```go +// Database 接口封装了对状态树和合约代码的访问 +// 它是以太坊状态数据库的核心抽象接口 type Database interface { - // OpenTrie opens the main account trie. - OpenTrie(root common.Hash) (Trie, error) - - // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) - - // CopyTrie returns an independent copy of the given trie. - CopyTrie(Trie) Trie - - // ContractCode retrieves a particular contract's code. - ContractCode(addrHash, codeHash common.Hash) ([]byte, error) - - // ContractCodeSize retrieves a particular contracts code's size. - ContractCodeSize(addrHash, codeHash common.Hash) (int, error) - - // DiskDB returns the underlying key-value disk database. - DiskDB() ethdb.KeyValueStore - - // TrieDB retrieves the low level trie database used for data storage. - TrieDB() *trie.Database + // Reader 返回与指定状态根关联的状态读取器 + // 参数: + // - root: 状态树的根哈希 + // 返回: + // - Reader: 状态读取器接口 + // 用途: 提供对特定状态的只读访问能力 + Reader(root common.Hash) (Reader, error) + + // OpenTrie 打开主账户树 + // 参数: root: 账户树的根哈希 + // 返回: Trie: 账户树接口 + // 用途: 访问和操作账户状态树 + OpenTrie(root common.Hash) (Trie, error) + + // OpenStorageTrie 打开账户的存储树 + // 参数: + // - stateRoot: 状态树根哈希 + // - address: 账户地址 + // - root: 存储树根哈希 + // - trie: 父树实例 + // 返回: + // - Trie: 存储树接口 + // - error: 可能的错误 + // 用途: 访问和操作智能合约的存储数据 + OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) + + // PointCache 返回用于verkle树键计算的点缓存 + // 返回: 点缓存实例 + // 用途: 优化verkle树的键计算性能 + PointCache() *utils.PointCache + + // TrieDB 返回底层的trie数据库 + // 返回: trie数据库实例 + // 用途: 管理trie节点的底层存储 + TrieDB() *triedb.Database + + // Snapshot 返回底层状态快照 + // 返回:状态快照树 + // 用途: 提供状态快照功能,用于优化状态访问 + Snapshot() *snapshot.Tree } ``` ### Trie @@ -124,81 +159,257 @@ type Database interface { 这里的 `trie` 变量同样的是由一个 `Trie` 类型的接口定义的。通过这个 `Trie` 类型的接口,上层其他模块就可以通过 `StateDB.tire` 来具体的对 `trie` 的数据进行操作。 ```go +// Trie 接口定义了以太坊的 Merkle Patricia Trie 的操作 type Trie interface { - // GetKey returns the sha3 preimage of a hashed key that was previously used - // to store a value. - // - // TODO(fjl): remove this when StateTrie is removed - GetKey([]byte) []byte - - // TryGet returns the value for key stored in the trie. The value bytes must - // not be modified by the caller. If a node was not found in the database, a - // trie.MissingNodeError is returned. - TryGet(key []byte) ([]byte, error) - - // TryGetAccount abstracts an account read from the trie. It retrieves the - // account blob from the trie with provided account address and decodes it - // with associated decoding algorithm. If the specified account is not in - // the trie, nil will be returned. If the trie is corrupted(e.g. some nodes - // are missing or the account blob is incorrect for decoding), an error will - // be returned. - TryGetAccount(address common.Address) (*types.StateAccount, error) - - // TryUpdate associates key with value in the trie. If value has length zero, any - // existing value is deleted from the trie. The value bytes must not be modified - // by the caller while they are stored in the trie. If a node was not found in the - // database, a trie.MissingNodeError is returned. - TryUpdate(key, value []byte) error - - // TryUpdateAccount abstracts an account write to the trie. It encodes the - // provided account object with associated algorithm and then updates it - // in the trie with provided address. - TryUpdateAccount(address common.Address, account *types.StateAccount) error - - // TryDelete removes any existing value for key from the trie. If a node was not - // found in the database, a trie.MissingNodeError is returned. - TryDelete(key []byte) error - - // TryDeleteAccount abstracts an account deletion from the trie. - TryDeleteAccount(address common.Address) error - - // Hash returns the root hash of the trie. It does not write to the database and - // can be used even if the trie doesn't have one. - Hash() common.Hash - - // Commit collects all dirty nodes in the trie and replace them with the - // corresponding node hash. All collected nodes(including dirty leaves if - // collectLeaf is true) will be encapsulated into a nodeset for return. - // The returned nodeset can be nil if the trie is clean(nothing to commit). - // Once the trie is committed, it's not usable anymore. A new trie must - // be created with new root and updated trie database for following usage - Commit(collectLeaf bool) (common.Hash, *trie.NodeSet) - - // NodeIterator returns an iterator that returns nodes of the trie. Iteration - // starts at the key after the given start key. - NodeIterator(startKey []byte) trie.NodeIterator - - // Prove constructs a Merkle proof for key. The result contains all encoded nodes - // on the path to the value at key. The value itself is also included in the last - // node and can be retrieved by verifying the proof. - // - // If the trie does not contain a value for key, the returned proof contains all - // nodes of the longest existing prefix of the key (at least the root), ending - // with the node that proves the absence of the key. - Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error + // GetKey 返回之前用于存储值的哈希键的原像 + // 注意:这个方法计划在 StateTrie 移除后废弃 + GetKey([]byte) []byte + + // GetAccount 从 trie 中读取账户信息 + // 参数:账户地址 + // 返回: + // - 账户状态对象(如果账户不存在则返回 nil) + // - 错误信息(如果 trie 损坏或节点丢失) + GetAccount(address common.Address) (*types.StateAccount, error) + + // GetStorage 从 trie 中获取存储值 + // 参数: + // - addr: 合约地址 + // - key: 存储键 + // 返回: + // - 存储值(调用者不得修改返回的字节数组) + // - 错误信息(如果节点未找到则返回 MissingNodeError) + GetStorage(addr common.Address, key []byte) ([]byte, error) + + // UpdateAccount 将账户信息写入 trie + // 参数: + // - address: 账户地址 + // - account: 账户状态对象 + // - codeLen: 合约代码长度 + // 返回:可能的错误信息 + UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error + + // UpdateStorage 在 trie 中更新存储键值对 + // 参数: + // - addr: 合约地址 + // - key: 存储键 + // - value: 存储值(如果长度为零则删除现有值) + // 返回:可能的错误信息 + UpdateStorage(addr common.Address, key, value []byte) error + + // DeleteAccount 从 trie 中删除账户 + // 参数:要删除的账户地址 + // 返回:可能的错误信息 + DeleteAccount(address common.Address) error + + // DeleteStorage 从 trie 中删除存储键值对 + // 参数: + // - addr: 合约地址 + // - key: 要删除的存储键 + // 返回:可能的错误信息 + DeleteStorage(addr common.Address, key []byte) error + + // UpdateContractCode 更新合约代码 + // 参数: + // - address: 合约地址 + // - codeHash: 代码哈希 + // - code: 合约代码 + // 返回:可能的错误信息 + UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error + + // Hash 返回 trie 的根哈希 + // 注意:不会写入数据库,即使 trie 没有数据库也可以使用 + Hash() common.Hash + + // Commit 提交所有脏节点并用对应的节点哈希替换它们 + // 参数: + // - collectLeaf: 是否收集脏叶子节点 + // 返回: + // - 新的根哈希 + // - 收集到的节点集合(如果 trie 是干净的则为 nil) + // 注意:提交后的 trie 不能再使用,需要创建新的 trie + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) + + // Witness 返回包含所有已访问 trie 节点的集合 + // 返回:访问过的节点集合(如果为空则返回 nil) + Witness() map[string]struct{} + + // NodeIterator 返回遍历 trie 节点的迭代器 + // 参数:起始键(迭代从该键之后开始) + // 返回:节点迭代器和可能的错误 + NodeIterator(startKey []byte) (trie.NodeIterator, error) + + // Prove 为指定键构造默克尔证明 + // 参数: + // - key: 要证明的键 + // - proofDb: 用于写入证明的数据库 + // 返回:可能的错误信息 + // 注意:如果键不存在,返回的证明包含最长存在前缀的所有节点 + Prove(key []byte, proofDb ethdb.KeyValueWriter) error + + // IsVerkle 返回该 trie 是否基于 Verkle 树 + IsVerkle() bool } ``` ## StateDB 的持久化 -当新的 Block 被添加到 Blockchain 时,State 的数据并不一会立即被写入到 Disk Database 中。在`writeBlockWithState`函数中,函数会判断 `gc` 条件,只有满足一定的条件,才会在此刻调用 `TrieDB` 中的 `Cap` 或者 `Commit` 函数将数据写入 Disk Database 中。 +``` +StateDB --> CachingDB --> TrieDB --> LevelDB +``` -具体 World State 的更新顺序是: +### 1. StateDB 层面的持久化 +StateDB 的持久化主要通过 Commit 操作完成,这个过程会处理所有状态对象(stateObject)的变更。主要涉及: +```go +// stateObject 表示一个正在被修改的以太坊账户 +type stateObject struct { + db *StateDB + address common.Address + data types.StateAccount // 当前区块范围内的所有变更 + + // 存储相关的缓存 + originStorage Storage // 在当前区块内被访问的存储条目 + dirtyStorage Storage // 在当前交易中被修改的存储条目 + pendingStorage Storage // 在当前区块中被修改的存储条目 + uncommittedStorage Storage // 尚未提交的存储变更 +} +``` +### 2. 存储变更的处理流程 +存储变更经过多个阶段的处理: +```go +// 1. 交易执行时的存储更新 +func (s *stateObject) SetState(key, value common.Hash) common.Hash { + prev, origin := s.getState(key) + if prev == value { + return prev + } + s.db.journal.storageChange(s.address, key, prev, origin) + s.setState(key, value, origin) + return prev +} + +// 2. 交易结束时的存储整理 +func (s *stateObject) finalise() { + for key, value := range s.dirtyStorage { + s.pendingStorage[key] = value + } + if len(s.dirtyStorage) > 0 { + s.dirtyStorage = make(Storage) + } +} +// 3. 区块提交时的存储更新 +func (s *stateObject) updateTrie() (Trie, error) { + for key, value := range s.uncommittedStorage { + if err := tr.UpdateStorage(s.address, key[:], value[:]); err != nil { + return nil, err + } + } +} ``` -StateDB --> Memory_Trie_Database --> LevelDB +### 4. 数据库层面的持久化 +最终的持久化由 CachingDB 完成: +```go + func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { + // 1. 提交账户元数据变更 + op := &accountUpdate{ + address: s.address, + data: types.SlimAccountRLP(s.data), + } + + // 2. 提交合约代码(如果有修改) + if s.dirtyCode { + op.code = &contractCode{...} + } + + // 3. 提交存储变更 + s.commitStorage(op) + + // 4. 提交 trie 变更 + root, nodes := s.trie.Commit(false) + return op, nodes, nil + } ``` -StateDB 调用 `Commit` 的时候并没有同时触发 `TrieDB` 的 Commit 。 +## 持久化流程 +1. 交易执行过程 +- 状态变更记录在 stateObject 的 dirtyStorage +- 通过 journal 记录回滚信息 +2. 交易结束时: +- 调用 ```finalise() ```将 ```dirtyStorage ```中的变更移至 ```pendingStorage``` +- 清空 ```dirtyStorage``` 为下一个交易做准备 +3. 区块提交时: +```go +func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { + // 1. 提交账户元数据变更 + op := &accountUpdate{ + address: s.address, + data: types.SlimAccountRLP(s.data), + } + + // 2. 提交合约代码(如果有修改) + if s.dirtyCode { + op.code = &contractCode{...} + } + + // 3. 提交存储变更 + s.commitStorage(op) + + // 4. 提交 trie 变更 + root, nodes := s.trie.Commit(false) + return op, nodes, nil +} +``` +4. 最终持久化: +- Trie 节点被序列化并存储到底层数据库 +- 合约代码被存储到专门的代码存储区 +- 新的状态根被计算并返回 + +### 主要同步流程: +1. 新交易产生: +- 本地产生新交易或从其他节点接收到新交易 +- 通过 `BroadcastTransactions` 传播到其他节点 +2. 交易传播策略: +- 小交易:直接广播给部分节点 +- 大交易和Blob交易:只广播哈希,按需获取 +- 使用平方根算法优化广播范围 +3. 新节点同步: +- 节点连接时通过 `syncTransactions` 同步现有交易 +- 发送交易哈希列表给新节点 +- 新节点按需请求具体交易内容 +4. 交易验证和处理: +- 接收到交易后进行验证 +- 验证通过后加入本地交易池 +- 继续向其他节点传播 +5. 状态维护: +- 交易池管理待处理交易 +- 定期清理过期或已确认的交易 +- 维护交易的各种状态(待处理、已确认等) +这个机制确保了: +- 交易能快速传播到网络中的其他节点 +- 避免重复传输相同交易 +- 优化网络带宽使用 +- 保持交易状态的一致性 + + +## 关键机制 +1. 多级缓存: +- originStorage: 原始状态缓存 +- dirtyStorage: 交易内变更缓存 +- pendingStorage: 区块内变更缓存 +2. 预取机制: +```go +func (s *stateObject) getPrefetchedTrie() Trie { + if s.db.prefetcher != nil { + return s.db.prefetcher.trie(s.addrHash, s.data.Root) + } + return nil +} +``` +3. 原子性保证 +- 使用 journal 记录所有变更 +- 支持回滚到任意快照点 -在Block被插入到 Blockchain 的这个Workflow中,stateDB的commit首先在`writeBlockWithState`函数中被调用了。之后`writeBlockWithState`函数会判断 `GC` 的状态来决定在本次调用中,是否需要向 `Disk Database` 写入数据。当新的Block被添加到Blockchain时,State 的数据并不一会立即被写入到 Disk Database 中。在`writeBlockWithState`函数中,函数会判断 `gc` 条件,只有满足一定的条件,才会在此刻调用 TrieDB 中的 `Cap` 或者 `Commit` 函数将数据写入Disk Database中。 +4. 数据一致性: +- 通过多级存储确保状态一致性 +- 使用 Merkle Patricia Trie 验证数据完整性 \ No newline at end of file diff --git a/CN/04_transaction.md b/CN/04_transaction.md index b9fe324..2537af8 100644 --- a/CN/04_transaction.md +++ b/CN/04_transaction.md @@ -33,30 +33,84 @@ type Transaction struct { 目前,`TxData`类型是一个接口,它的定义如下面的代码所示。 ```go +// TxData 定义了以太坊交易的核心接口,包含了所有交易类型共有的方法 type TxData interface { - txType() byte // returns the type ID - copy() TxData // creates a deep copy and initializes all fields - - chainID() *big.Int - accessList() AccessList - data() []byte - gas() uint64 - gasPrice() *big.Int - gasTipCap() *big.Int - gasFeeCap() *big.Int - value() *big.Int - nonce() uint64 - to() *common.Address - - rawSignatureValues() (v, r, s *big.Int) - setSignatureValues(chainID, v, r, s *big.Int) + // txType 返回交易类型的标识符 + // 0x00: Legacy + // 0x01: AccessList + // 0x02: DynamicFee (EIP-1559) + // 0x03: Blob (EIP-4844) + txType() byte + + // copy 创建交易数据的深拷贝 + // 用于确保交易数据的不可变性 + copy() TxData + + // chainID 返回交易的链 ID + // 用于防止交易重放攻击(EIP-155) + chainID() *big.Int + + // accessList 返回交易的访问列表 + // EIP-2930 引入,用于优化 gas 消耗 + accessList() AccessList + + // data 返回交易的输入数据 + // 包含合约调用的方法签名和参数 + data() []byte + + // gas 返回交易的 gas 限制 + // 表示愿意为交易执行支付的最大 gas 量 + gas() uint64 + + // gasPrice 返回交易的 gas 价格 + // 对于传统交易,这是固定值 + gasPrice() *big.Int + + // gasTipCap 返回最大优先费用(小费) + // EIP-1559 引入,用于激励矿工优先打包 + gasTipCap() *big.Int + + // gasFeeCap 返回最大总费用 + // EIP-1559 引入,gas_fee_cap = base_fee + priority_fee + gasFeeCap() *big.Int + + // value 返回交易转账的 ETH 数量 + value() *big.Int + + // nonce 返回发送方的交易序号 + // 用于防止重放攻击和确保交易顺序 + nonce() uint64 + + // to 返回接收方地址 + // 如果是合约创建交易,返回 nil + to() *common.Address + + // rawSignatureValues 返回交易签名的原始值 + // v: 签名恢复标识符 + // r,s: 签名的两个组成部分 + rawSignatureValues() (v, r, s *big.Int) + + // setSignatureValues 设置交易的签名值 + // 用于在交易签名后更新签名数据 + setSignatureValues(chainID, v, r, s *big.Int) + + // effectiveGasPrice 计算交易实际的 gas 价格 + // 对于 EIP-1559 交易,这取决于区块的 base fee + // 返回值是独立的副本,调用者可以安全修改 + effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int + + // encode 将交易数据编码到缓冲区 + // 用于序列化交易数据 + encode(*bytes.Buffer) error + + // decode 从字节数据解码交易 + // 用于反序列化交易数据 + decode([]byte) error } ``` 这里注意,在目前版本的geth中(1.10.*),根据[EIP-2718][EIP2718]的设计,原来的TxData现在被声明成了一个interface,而不是定义了具体的结构。这样的设计好处在于,后续版本的更新中可以对Transaction类型进行更加灵活的修改。目前,在Ethereum中定义了三种类型的Transaction来实现TxData这个接口。按照时间上的定义顺序来说,这三种类型的Transaction分别是,LegacyT,AccessListTx,TxDynamicFeeTx。LegacyTx顾名思义,是原始的Ethereum的Transaction设计,目前市面上大部分早年关于Ethereum Transaction结构的文档实际上都是在描述LegacyTx的结构。而AccessListTX是基于EIP-2930(Berlin分叉)的Transaction。DynamicFeeTx是[EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)(伦敦分叉)生效之后的默认的Transaction。 -(PS:目前Ethereum的黄皮书只更新到了Berlin分叉的内容,还没有添加London分叉的更新, 2022.3.10) - ### LegacyTx LegacyTx 是最原始的以太坊交易的定义。 @@ -123,7 +177,7 @@ Transaction的执行主要在发生在两个Workflow中: ### Transaction修改Contract的持久化存储的 -在Ethereum中,当Miner开始构造新的区块的时候,首先会启动*miner/worker.go*的 `mainLoop()`函数。具体的函数如下所示。 +在Ethereum中,当Miner开始构造新的区块的时候,首先会启动*miner/worker.go*的 `generateWork()`函数。具体的函数如下所示。 ```go func (w *worker) mainLoop() { @@ -148,7 +202,7 @@ func (w *worker) mainLoop() { 在Mining新区块前,Worker首先需要决定,哪些Transaction会被打包到新的Block中。这里选取Transaction其实经历了两个步骤。首先,`txs`变量保存了从Transaction Pool中拿去到的合法的,以及准备好被打包的交易。这里举一个例子,来说明什么是**准备好被打包的交易**,比如Alice先后发了新三个交易到网络中,对应的Nonce分别是100和101,102。假如Miner只收到了100和102号交易。那么对于此刻的Transaction Pool来说Nonce 100的交易就是**准备好被打包的交易**,交易Nonce 是102需要等待Nonce 101的交易被确认之后才能提交。 -在Worker会从Transaction Pool中拿出若干的transaction, 赋值给*txs*之后, 然后调用`NewTransactionsByPriceAndNonce`函数按照Gas Price和Nonce对*txs*进行排序,并将结果赋值给*txset*。此外在Worker的实例中,还存在`fillTransactions`函数,为了未来定制化的给Transaction的执行顺序进行排序。 +在Worker会从Transaction Pool中拿出若干的transaction, 赋值给*txs*之后, 然后调用`newTransactionsByPriceAndNonce`函数按照Gas Price和Nonce对*txs*进行排序,并将结果赋值给*txset*。此外在Worker的实例中,还存在`fillTransactions`函数,为了未来定制化的给Transaction的执行顺序进行排序。 在拿到*txset*之后,mainLoop函数会调用`commitTransactions`函数,正式进入Mining新区块的流程。`commitTransactions`函数如下所示。 diff --git a/CN/05_block_blockchain.md b/CN/05_block_blockchain.md index b6ee4f5..ebea5e7 100644 --- a/CN/05_block_blockchain.md +++ b/CN/05_block_blockchain.md @@ -6,36 +6,110 @@ ```go type Block struct { - header *Header - uncles []*Header - transactions Transactions - hash atomic.Value - size atomic.Value - td *big.Int - ReceivedAt time.Time - ReceivedFrom interface{} + // 核心数据 + header *Header // 区块头 + uncles []*Header // 叔块列表 + transactions Transactions // 交易列表 + withdrawals Withdrawals // 提款操作列表 + + // 状态证明 + witness *ExecutionWitness // 执行见证数据(非区块体的一部分) + + // 缓存字段 + hash atomic.Pointer[common.Hash] // 区块哈希缓存 + size atomic.Uint64 // 区块大小缓存 + + // 网络相关字段 + ReceivedAt time.Time // 接收时间 + ReceivedFrom interface{} // 接收来源 } ``` ```go +// Header 表示以太坊区块链中的区块头 type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner" gencodec:"required"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *big.Int `json:"difficulty" gencodec:"required"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - // BaseFee was added by EIP-1559 and is ignored in legacy headers. - BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + // ParentHash 是父区块的哈希值 + // 用于维护区块链的链式结构 + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + + // UncleHash 是叔块列表的哈希值 + // 在 PoW 中用于奖励接近但未被选中的区块 + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + + // Coinbase 是接收挖矿奖励的地址 + // 在 PoS 中是接收交易费用的地址 + Coinbase common.Address `json:"miner"` + + // Root 是状态树的根哈希 + // 代表该区块后整个以太坊状态的快照 + Root common.Hash `json:"stateRoot" gencodec:"required"` + + // TxHash 是交易树的根哈希 + // 包含该区块中所有交易的哈希 + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + + // ReceiptHash 是收据树的根哈希 + // 包含该区块中所有交易收据的哈希 + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + + // Bloom 是布隆过滤器 + // 用于快速查询日志事件 + Bloom Bloom `json:"logsBloom" gencodec:"required"` + + // Difficulty 是区块的难度值 + // 在 PoW 中用于调整挖矿难度 + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + + // Number 是区块号 + // 表示该区块在区块链中的高度 + Number *big.Int `json:"number" gencodec:"required"` + + // GasLimit 是区块的燃料上限 + // 限制区块中所有交易可以使用的最大燃料量 + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + + // GasUsed 是区块中实际使用的燃料总量 + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + + // Time 是区块的时间戳 + // 表示区块创建的近似时间 + Time uint64 `json:"timestamp" gencodec:"required"` + + // Extra 是额外数据字段 + // 可以包含任意数据,通常用于特殊用途 + Extra []byte `json:"extraData" gencodec:"required"` + + // MixDigest 是混合哈希 + // 在 PoW 中用于验证工作量证明 + MixDigest common.Hash `json:"mixHash"` + + // Nonce 是用于 PoW 的随机数 + // 矿工通过改变这个值来寻找有效的区块哈希 + Nonce BlockNonce `json:"nonce"` + + // EIP-1559 引入的字段 + // BaseFee 是基础费用,用于动态调整交易费用 + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + + // EIP-4895 引入的字段 + // WithdrawalsHash 是提款操作列表的哈希 + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + + // EIP-4844 引入的字段 + // BlobGasUsed 是 blob 交易使用的燃料量 + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` + + // EIP-4844 引入的字段 + // ExcessBlobGas 是超出目标的 blob 燃料量 + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + + // EIP-4788 引入的字段 + // ParentBeaconRoot 是父区块对应的信标链区块根 + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + + // EIP-7685 引入的字段 + // RequestsHash 是请求列表的哈希 + RequestsHash *common.Hash `json:"requestsRoot" rlp:"optional"` } ``` @@ -43,59 +117,288 @@ type Header struct { ### 基础数据结构 +```golang +// BlockChain 代表基于创世区块数据库的规范链。 +// 它管理链导入、回滚和链重组等操作。 +type BlockChain struct { + // 配置相关 + chainConfig *params.ChainConfig // 链和网络配置参数 + cacheConfig *CacheConfig // 缓存修剪配置 + + // 数据库和状态存储 + db ethdb.Database // 用于存储最终内容的底层持久化数据库 + snaps *snapshot.Tree // 用于快速访问 trie 叶子节点的快照树 + triegc *prque.Prque[int64, common.Hash] // 用于垃圾回收的区块号到 trie 的优先队列映射 + gcproc time.Duration // trie 转储的规范区块处理累计时间 + lastWrite uint64 // 最后一次状态刷新时的区块号 + flushInterval atomic.Int64 // 状态刷新的时间间隔(处理时间) + + // 数据库处理器 + triedb *triedb.Database // 用于维护 trie 节点的数据库处理器 + statedb *state.CachingDB // 在导入之间重用的状态数据库(包含状态缓存) + txIndexer *txIndexer // 交易索引器,如果未启用则为 nil + + // 链管理 + hc *HeaderChain // 区块头链管理器 + + // 事件系统 + rmLogsFeed event.Feed // 移除日志的事件通道 + chainFeed event.Feed // 链更新事件通道 + chainHeadFeed event.Feed // 链头更新事件通道 + logsFeed event.Feed // 日志事件通道 + blockProcFeed event.Feed // 区块处理事件通道 + scope event.SubscriptionScope // 事件订阅范围 + + // 创世区块 + genesisBlock *types.Block // 创世区块 + + // 同步锁 + // 此互斥锁同步链写入操作 + // 读取操作不需要获取锁,可以直接读取数据库 + chainmu *syncx.ClosableMutex + + // 当前状态指针 + currentBlock atomic.Pointer[types.Header] // 当前链头 + currentSnapBlock atomic.Pointer[types.Header] // 当前快照同步的链头 + currentFinalBlock atomic.Pointer[types.Header] // 最新的(共识)最终确定区块 + currentSafeBlock atomic.Pointer[types.Header] // 最新的(共识)安全区块 + + // 缓存系统 + bodyCache *lru.Cache[common.Hash, *types.Body] // 区块体缓存 + bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] // RLP 编码的区块体缓存 + receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // 收据缓存 + blockCache *lru.Cache[common.Hash, *types.Block] // 区块缓存 + + // 交易查找缓存 + txLookupLock sync.RWMutex // 交易查找锁 + txLookupCache *lru.Cache[common.Hash, txLookup] // 交易查找缓存 + + // 同步和控制 + wg sync.WaitGroup // 等待组,用于优雅关闭 + quit chan struct{} // 关闭信号,在 Stop 时关闭 + stopping atomic.Bool // 链运行状态标志:false 表示运行中,true 表示已停止 + procInterrupt atomic.Bool // 区块处理中断信号器 + + // 处理组件 + engine consensus.Engine // 共识引擎 + validator Validator // 区块和状态验证器接口 + prefetcher Prefetcher // 预取器 + processor Processor // 区块交易处理器接口 + vmConfig vm.Config // 虚拟机配置 + logger *tracing.Hooks // 跟踪钩子 +} +``` +### 主要功能 +```golang +// 1. 区块插入 +func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { + // 验证区块 + // 执行状态转换 + // 更新数据库 +} + +// 2. 状态管理 +func (bc *BlockChain) State() (*state.StateDB, error) { + // 返回当前状态 +} + +// 3. 区块检索 +func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block +func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block + +// 4. 链重组 +func (bc *BlockChain) reorg(oldBlock, newBlock *types.Header) error { + // 处理分叉 + // 更新规范链 +} +``` +### 3. 关键流程 +1. 区块处理流程: + +```golang +func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, error) { + // 1. 基本验证 + for i := 1; i < len(chain); i++ { + if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || + chain[i].ParentHash() != chain[i-1].Hash() { + return i, fmt.Errorf("非连续区块") + } + } + + // 2. 状态处理 + for i, block := range chain { + // 执行交易 + // 更新状态 + // 写入数据库 + } + + // 3. 更新链头 + bc.writeHeadBlock(chain[len(chain)-1]) +} +``` +2. 状态管理: +```golang +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error { + // 1. 写入区块数据 + batch := bc.db.NewBatch() + rawdb.WriteBlock(batch, block) + rawdb.WriteReceipts(batch, block.Hash(), receipts) + + // 2. 提交状态 + root, err := state.Commit() + if err != nil { + return err + } + + // 3. 垃圾回收 + bc.triegc.Push(root, -int64(block.NumberU64())) +} +``` +### 事件系统 +```golang +// 事件通知 +type ChainEvent struct { + Block *types.Block + Hash common.Hash + Logs []*types.Log +} + +// 订阅机制 +func (bc *BlockChain) SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription +``` +### blockchain 是如何将 block 串联起来的 +1. 区块间的链接机制 +```golang +// Header 结构中的关键链接字段 +type Header struct { + ParentHash common.Hash // 指向父区块的哈希 + Number *big.Int // 区块高度 + // ... 其他字段 +} +``` +2. BlockChain 结构的管理 ```golang type BlockChain struct { - chainConfig *params.ChainConfig // Chain & network configuration - cacheConfig *CacheConfig // Cache configuration for pruning - - db ethdb.Database // Low level persistent database to store final content in - snaps *snapshot.Tree // Snapshot tree for fast trie leaf access - triegc *prque.Prque // Priority queue mapping block numbers to tries to gc - gcproc time.Duration // Accumulates canonical block processing for trie dumping - - // txLookupLimit is the maximum number of blocks from head whose tx indices - // are reserved: - // * 0: means no limit and regenerate any missing indexes - // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes - // * nil: disable tx reindexer/deleter, but still index new blocks - txLookupLimit uint64 - - hc *HeaderChain - rmLogsFeed event.Feed - chainFeed event.Feed - chainSideFeed event.Feed - chainHeadFeed event.Feed - logsFeed event.Feed - blockProcFeed event.Feed - scope event.SubscriptionScope - genesisBlock *types.Block - - // This mutex synchronizes chain write operations. - // Readers don't need to take it, they can just read the database. - chainmu *syncx.ClosableMutex - - currentBlock atomic.Value // Current head of the block chain - currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) - - stateCache state.Database // State database to reuse between imports (contains state cache) - bodyCache *lru.Cache // Cache for the most recent block bodies - bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format - receiptsCache *lru.Cache // Cache for the most recent receipts per block - blockCache *lru.Cache // Cache for the most recent entire blocks - txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. - futureBlocks *lru.Cache // future blocks are blocks added for later processing - - wg sync.WaitGroup // - quit chan struct{} // shutdown signal, closed in Stop. - running int32 // 0 if chain is running, 1 when stopped - procInterrupt int32 // interrupt signaler for block processing - - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - forker *ForkChoice - vmConfig vm.Config -} - -``` \ No newline at end of file + // 当前链状态 + currentBlock atomic.Pointer[types.Header] // 当前链头 + currentSnapBlock atomic.Pointer[types.Header] // 快照同步的链头 + currentFinalBlock atomic.Pointer[types.Header] // 最终确认的区块 + currentSafeBlock atomic.Pointer[types.Header] // 安全区块 + + // 数据存储 + db ethdb.Database // 底层数据库 + triedb *triedb.Database // trie 数据库 + + // 缓存系统 + bodyCache *lru.Cache[common.Hash, *types.Body] + bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] + blockCache *lru.Cache[common.Hash, *types.Block] +} +``` + +3. 区块插入流程 +```golang +func (bc *BlockChain) insertChain(chain types.Blocks) (int, error) { + // 1. 验证区块连续性 + for i := 1; i < len(chain); i++ { + if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || + chain[i].ParentHash() != chain[i-1].Hash() { + return 0, fmt.Errorf("非连续区块") + } + } + + // 2. 处理每个区块 + for i, block := range chain { + // 验证区块 + if err := bc.engine.VerifyHeader(bc, block.Header()); err != nil { + return i, err + } + + // 执行状态转换 + state, err := bc.StateAt(block.ParentHash()) + if err != nil { + return i, err + } + + // 更新状态 + receipts, err := bc.processor.Process(block, state) + if err != nil { + return i, err + } + + // 写入数据库 + bc.writeBlockWithState(block, receipts, state) + } +} +``` +4. 分叉处理 +```golang +func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { + // 1. 找到共同祖先 + var ( + newChain types.Blocks + oldChain types.Blocks + commonBlock *types.Block + ) + + // 2. 回滚旧链 + for oldBlock.NumberU64() > commonBlock.NumberU64() { + oldChain = append(oldChain, oldBlock) + oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) + } + + // 3. 应用新链 + for newBlock.NumberU64() > commonBlock.NumberU64() { + newChain = append(newChain, newBlock) + newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) + } + + // 4. 写入新链 + for _, block := range newChain { + // 应用区块 + } +} +``` +5. 区块检索机制 +```golang +func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { + // 1. 检查缓存 + if block := bc.blockCache.Get(hash); block != nil { + return block + } + + // 2. 从数据库读取 + block := rawdb.ReadBlock(bc.db, hash) + if block != nil { + bc.blockCache.Add(hash, block) + } + return block +} + +func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { + hash := rawdb.ReadCanonicalHash(bc.db, number) + if hash == (common.Hash{}) { + return nil + } + return bc.GetBlockByHash(hash) +} +``` +6. 状态维护 +```golang +// 更新链头 +func (bc *BlockChain) writeHeadBlock(block *types.Block) { + // 1. 更新当前区块 + bc.currentBlock.Store(block.Header()) + + // 2. 写入规范链哈希 + rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64()) + + // 3. 更新链头 + rawdb.WriteHeadBlockHash(bc.db, block.Hash()) +} +``` +- 区块间的 ParentHash 链接 +- 规范链的维护 +- 分叉处理机制 +- 高效的缓存系统 +- 可靠的数据存储 \ No newline at end of file diff --git a/CN/06_sync.md b/CN/06_sync.md index c83c265..a4e4782 100644 --- a/CN/06_sync.md +++ b/CN/06_sync.md @@ -14,34 +14,253 @@ ```go type Ethereum struct { - config *ethconfig.Config - txPool *txpool.TxPool - blockchain *core.BlockChain - handler *handler // 我们关注的核心对象 - ethDialCandidates enode.Iterator - snapDialCandidates enode.Iterator - merger *consensus.Merger - chainDb ethdb.Database // Block chain database - eventMux *event.TypeMux - engine consensus.Engine - accountManager *accounts.Manager - bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests - bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports - closeBloomHandler chan struct{} - APIBackend *EthAPIBackend - miner *miner.Miner - gasPrice *big.Int - etherbase common.Address - networkID uint64 - netRPCService *ethapi.NetAPI - p2pServer *p2p.Server - lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) - shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully + // 1. 核心协议对象 + config *ethconfig.Config // 以太坊配置参数 + txPool *txpool.TxPool // 交易池,存储待处理的交易 + blockchain *core.BlockChain // 区块链核心组件,管理区块和状态 + + // 2. 网络处理 + handler *handler // 处理以太坊协议的网络消息 + discmix *enode.FairMix // 节点发现混合器,用于P2P网络 + + // 3. 数据库接口 + chainDb ethdb.Database // 区块链数据库,存储所有区块数据 + + // 4. 事件和共识 + eventMux *event.TypeMux // 事件多路复用器,处理各种事件 + engine consensus.Engine // 共识引擎(PoW/PoS) + accountManager *accounts.Manager // 账户管理器 + + // 5. Bloom过滤器相关 + bloomRequests chan chan *bloombits.Retrieval // 布隆过滤器数据检索请求通道 + bloomIndexer *core.ChainIndexer // 区块导入时的布隆索引器 + closeBloomHandler chan struct{} // 关闭布隆处理器的信号通道 + + // 6. API和挖矿 + APIBackend *EthAPIBackend // 后端API接口 + miner *miner.Miner // 挖矿器 + gasPrice *big.Int // 燃料价格 + + // 7. 网络相关 + networkID uint64 // 网络ID + netRPCService *ethapi.NetAPI // RPC服务 + p2pServer *p2p.Server // P2P服务器 + + // 8. 同步和保护 + lock sync.RWMutex // 读写锁,保护可变字段 + shutdownTracker *shutdowncheck.ShutdownTracker // 跟踪节点是否非正常关闭 } ``` 这里值得提醒一下,在 Geth 代码中,不少地方都使用 `backend` 这个变量名,来指代 `Ethereum`。但是,也存在一些代码中使用 `backend` 来指代 `ethapi.Backend` 接口。在这里,我们可以做一下区分,`Ethereum` 负责维护节点后端的生命周期的函数,例如 Miner 的开启与关闭。而`ethapi.Backend` 接口主要是提供对外的业务接口,例如查询区块和交易的状态。读者可以根据上下文来判断 `backend` 具体指代的对象。我们在 geth 是如何启动的一章中提到,`Ethereum` 是在 Geth 启动的实例化的。在实例化 `Ethereum` 的过程中,就会创建一个 `APIBackend *EthAPIBackend` 的成员变量,它就是`ethapi.Backend` 接口类型的。 +1. 交易广播机制 (handler.go) +```go +func (h *handler) BroadcastTransactions(txs types.Transactions) { + var ( + txset = make(map[*ethPeer][]common.Hash) // 直接传输的交易 + annos = make(map[*ethPeer][]common.Hash) // 仅通知的交易 + ) + + // 计算广播目标节点数量(使用平方根来优化网络负载) + direct := big.NewInt(int64(math.Sqrt(float64(h.peers.len())))) + + for _, tx := range txs { + // 根据交易类型决定传播策略 + switch { + case tx.Type() == types.BlobTxType: + // Blob交易只发送通知 + blobTxs++ + case tx.Size() > txMaxBroadcastSize: + // 大交易只发送通知 + largeTxs++ + default: + // 普通交易可能直接广播 + maybeDirect = true + } + + // 选择未收到该交易的节点进行传播 + for _, peer := range h.peers.peersWithoutTransaction(tx.Hash()) { + if broadcast { + txset[peer] = append(txset[peer], tx.Hash()) + } else { + annos[peer] = append(annos[peer], tx.Hash()) + } + } + } +} +``` +2. 交易同步机制 (sync.go) +```go +func (h *handler) syncTransactions(p *eth.Peer) { + // 获取所有待处理的交易 + var hashes []common.Hash + for _, batch := range h.txpool.Pending(txpool.PendingFilter{OnlyPlainTxs: true}) { + for _, tx := range batch { + hashes = append(hashes, tx.Hash) + } + } + + // 发送交易哈希给新peer + if len(hashes) > 0 { + p.AsyncSendPooledTransactionHashes(hashes) + } +} +``` +3. 交易处理流程 (handler_eth.go) +```go +func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + switch packet := packet.(type) { + case *eth.NewPooledTransactionHashesPacket: + // 处理新交易哈希通知 + return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes) + + case *eth.TransactionsPacket: + // 处理接收到的交易 + return h.txFetcher.Enqueue(peer.ID(), *packet, false) + + case *eth.PooledTransactionsResponse: + // 处理交易池响应 + return h.txFetcher.Enqueue(peer.ID(), *packet, true) + } +} +``` +1. 交易广播机制 (handler.go) +```go +``` ## How Geth syncs Blocks:同步区块状态 +### 1. 同步模式选择 (backend.go) +```go +func (s *Ethereum) SyncMode() ethconfig.SyncMode { + // 1. 检查是否在快照同步模式 + if s.handler.snapSync.Load() { + return ethconfig.SnapSync + } + + // 2. 检查是否需要重新启用快照同步 + head := s.blockchain.CurrentBlock() + if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil { + if head.Number.Uint64() < *pivot { + return ethconfig.SnapSync + } + } + + // 3. 检查状态完整性 + if !s.blockchain.HasState(head.Root) { + log.Info("Reenabled snap sync as chain is stateless") + return ethconfig.SnapSync + } + + return ethconfig.FullSync +} +``` +### 2. 节点同步处理 (handler.go) +```go +func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + // 1. 执行以太坊握手 + var ( + genesis = h.chain.Genesis() + head = h.chain.CurrentHeader() + hash = head.Hash() + number = head.Number.Uint64() + td = h.chain.GetTd(hash, number) + ) + + // 2. 验证分叉ID + forkID := forkid.NewID(h.chain.Config(), genesis, number, head.Time) + if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { + return err + } + + // 3. 注册到下载器 + if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { + return err + } + + // 4. 如果支持快照同步,注册快照同步 + if snap != nil { + if err := h.downloader.SnapSyncer.Register(snap); err != nil { + return err + } + } +} +``` +### 3. 区块同步流程 +3.1. 初始化同步: +```go +func newHandler(config *handlerConfig) (*handler, error) { + h := &handler{ + // ... + downloader: downloader.New(config.Database, h.eventMux, h.chain, h.removePeer) + } + + // 根据配置决定同步模式 + if config.Sync == ethconfig.FullSync { + // 检查是否需要切换到快照同步 + if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 { + h.snapSync.Store(true) + } + } +} +``` +3.2. 同步过程管理: +```go +func (h *handler) Start(maxPeers int) { + // 启动同步处理器 + h.txFetcher.Start() + + // 启动协议处理器追踪 + go h.protoTracker() +} +``` +3.3. 状态验证: +```go +func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + // 验证必需区块 + for number, hash := range h.requiredBlocks { + // 请求区块头 + req, err := peer.RequestHeadersByNumber(number, 1, 0, false, resCh) + + // 验证响应 + if headers[0].Number.Uint64() != number || headers[0].Hash() != hash { + return errors.New("required block mismatch") + } + } +} +``` +### 4. 同步完成处理 +```go +func (h *handler) enableSyncedFeatures() { + // 标记节点已同步 + h.synced.Store(true) + + // 如果是快照同步完成,禁用后续快照同步 + if h.snapSync.Load() { + log.Info("Snap sync complete, auto disabling") + h.snapSync.Store(false) + } +} +``` +主要同步流程: +1. 同步模式确定 +- 根据节点状态选择同步模式 +- 支持快照同步和全同步 +- 可动态切换同步模式 +2. 节点连接处理 +- 执行以太坊握手 +- 验证分叉ID +- 注册peer到下载器 +3. 区块下载和验证 +- 请求区块头和区块体 +- 验证区块有效性 +- 处理分叉情况 +4. 状态同步 +- 同步状态树 +- 处理快照数据 +- 验证状态完整性 +5. 同步完成处理 +- 更新节点状态 +- 启用同步后功能 +- 处理模式切换 \ No newline at end of file diff --git a/CN/07_txpool.md b/CN/07_txpool.md index dca1843..b8fdf5e 100644 --- a/CN/07_txpool.md +++ b/CN/07_txpool.md @@ -1,57 +1,195 @@ -# Transaction Pool +# Transaction Pool (交易池) ## 概述 -交易可以分为 Local Transaction 和 Remote Transaction 两种。通过节点提供的 RPC 传入的交易,被划分为 Local Transaction,通过 P2P 网络传给节点的交易被划分为 Remote Transaction。 +交易池是以太坊节点用于存储和管理待处理交易的内存数据结构。交易可以分为两种类型: + +1. Local Transaction (本地交易) + - 通过节点的 RPC 接口提交的交易 + - 享有特权地位,不会被轻易驱逐 + - 会被持久化保存到本地磁盘 + +2. Remote Transaction (远程交易) + - 通过 P2P 网络接收的交易 + - 在资源受限时可能被驱逐 + - 不会被持久化 + +注:本文主要讨论 legacypool,适用于 Legacy、AccessList 和 Dynamic 类型的交易。 ## 交易池的基本结构 -Transaction Pool 主要有两个内存组件,`Pending` 和 `Queue` 组成。具体的定义如下所示。 +Transaction Pool 主要由两个核心组件构成: + +1. Pending Pool + - 存储当前可执行的交易 + - 交易的 nonce 值连续且正确 + - 账户余额足够支付交易费用 + +2. Queue Pool + - 存储暂时无法执行的交易 + - 可能是因为 nonce 过高 + - 或账户余额不足等原因 + +### 核心数据结构 + +```go +type LegacyPool struct { + // 基础配置 + config Config // 交易池配置参数(如容量限制、价格限制等) + chainconfig *params.ChainConfig // 区块链配置参数(如网络ID、分叉规则等) + chain BlockChain // 区块链接口,用于访问链状态 + gasTip atomic.Pointer[uint256.Int] // 最低gas小费要求,原子操作保证线程安全 + txFeed event.Feed // 交易事件发送器,用于通知新交易 + signer types.Signer // 交易签名器,用于验证交易签名 + mu sync.RWMutex // 读写锁,保护并发访问 + + // 当前状态 + currentHead atomic.Pointer[types.Header] // 当前区块头,原子操作保证线程安全 + currentState *state.StateDB // 当前状态数据库 + pendingNonces *noncer // 待处理的nonce追踪器,用于nonce计数 + + // 本地交易管理 + locals *accountSet // 本地账户集合,这些账户的交易免于驱逐规则 + journal *journal // 本地交易日志,用于持久化存储本地交易 + + // 交易存储和管理 + reserve txpool.AddressReserver // 地址预留器,确保跨子池的地址互斥性 + pending map[common.Address]*list // 当前可执行的交易映射(按地址索引) + queue map[common.Address]*list // 未来待执行的交易映射(按地址索引) + beats map[common.Address]time.Time // 每个账户的最后活动时间 + all *lookup // 所有交易的查找表,支持快速查找 + priced *pricedList // 按价格排序的交易列表 + + // 通道和事件处理 + reqResetCh chan *txpoolResetRequest // 请求重置交易池的通道 + reqPromoteCh chan *accountSet // 请求提升交易的通道 + queueTxEventCh chan *types.Transaction // 交易队列事件通道 + reorgDoneCh chan chan struct{} // 重组完成通知通道 + reorgShutdownCh chan struct{} // 请求关闭重组循环的通道 + wg sync.WaitGroup // 等待组,用于追踪goroutine + initDoneCh chan struct{} // 初始化完成通道(用于测试) + + // 状态追踪 + changesSinceReorg int // 在重组之间执行的删除操作计数器 +} + +``` + +## 交易验证与排序 +验证交易: +- nonce检查 +- gas价格检查 +- 余额检查 +- gas限制检查 + +```go +func validateTx(tx *types.Transaction, local bool) error { + // 1. 大小验证 + if tx.Size() > txMaxSize { // txMaxSize = 128KB + return ErrOversizedData + } + + // 2. 签名验证 + from, err := types.Sender(pool.signer, tx) + if err != nil { + return ErrInvalidSender + } + + // 3. Nonce检查 + nonce := pool.currentState.GetNonce(from) + if tx.Nonce() < nonce { + // nonce太低,交易已过期 + return ErrNonceTooLow + } + + // 4. Gas价格验证 + if !local { // 本地交易豁免 + gasTipCap, _ := tx.EffectiveGasTip(baseFee) + if gasTipCap.Cmp(pool.gasTip.Load()) < 0 { + return ErrUnderpriced + } + } + + // 5. 余额验证 + balance := pool.currentState.GetBalance(from) + if balance.Cmp(tx.Cost()) < 0 { + return ErrInsufficientFunds + } + + // 6. Gas限制验证 + if tx.Gas() > pool.currentHead.Load().GasLimit { + return ErrGasLimit + } + + return nil +} +``` + +### 交易排序机制 + +交易池使用两级排序机制: + +1. 账户内排序 + - 使用 `TransactionsByNonce` 按 nonce 值排序 + - 确保交易按序执行 + +2. 账户间排序 + - 使用 `pricedList` 按 gas 价格排序 + - 高价格交易优先处理 ```go -type TxPool struct { - config Config - chainconfig *params.ChainConfig - chain blockChain - gasPrice *big.Int - txFeed event.Feed - scope event.SubscriptionScope - signer types.Signer - mu sync.RWMutex - - istanbul bool // Fork indicator whether we are in the istanbul stage. - eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. - eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions. - shanghai bool // Fork indicator whether we are in the Shanghai stage. - - currentState *state.StateDB // Current state in the blockchain head - pendingNonces *noncer // Pending state tracking virtual nonces - currentMaxGas uint64 // Current gas limit for transaction caps - - locals *accountSet // Set of local transaction to exempt from eviction rules - journal *journal // Journal of local transaction to back up to disk - - pending map[common.Address]*list // All currently processable transactions - queue map[common.Address]*list // Queued but non-processable transactions - beats map[common.Address]time.Time // Last heartbeat from each known account - all *lookup // All transactions to allow lookups - priced *pricedList // All transactions sorted by price - - chainHeadCh chan core.ChainHeadEvent - chainHeadSub event.Subscription - reqResetCh chan *txpoolResetRequest - reqPromoteCh chan *accountSet - queueTxEventCh chan *types.Transaction - reorgDoneCh chan chan struct{} - reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop - wg sync.WaitGroup // tracks loop, scheduleReorgLoop - initDoneCh chan struct{} // is closed once the pool is initialized (for tests) - - changesSinceReorg int // A counter for how many drops we've performed in-between reorg. +type pricedList struct { + all *lookup // 指向所有交易的指针 + items *prque.Prque[*types.Transaction] // 按价格排序的优先队列 + stales int64 // 作废交易的数量 +} + +// Put 添加一个交易到价格排序列表 +func (l *pricedList) Put(tx *types.Transaction) { + // 计算交易的有效gas价格 + gasFeeCap := tx.GasFeeCap() + l.items.Push(tx, -gasFeeCap.Int64()) // 负值使得高价格排在前面 +} + +// list 实现按nonce排序的交易列表 +type list struct { + txs *types.TransactionsByNonce // 按nonce排序的交易 + nonces map[uint64]*types.Transaction // nonce到交易的映射 +} + +// Add 添加一个交易到nonce排序列表 +func (l *list) Add(tx *types.Transaction) { + nonce := tx.Nonce() + if l.nonces[nonce] == nil { + l.txs.Insert(tx) + l.nonces[nonce] = tx + } } ``` +### 交易选择 +从pending池中选择交易打包时: + 1. 首先按账户分组 + 2. 每个账户内部按nonce排序 + 3. 不同账户间按gas价格排序 + +### 交易替换 + +```go +// 当收到相同nonce的新交易时: +if newTx.GasPrice().Cmp(oldTx.GasPrice()) > pool.config.PriceBump { + // 如果新交易的gas价格比旧交易高出足够多(默认10%) + // 则替换旧交易 +} +``` + +### 交易池清理 +当池满时移除交易: + 1. 优先移除gas价格低的交易 + 2. 保护本地交易不被移除 + 3. 确保每个账户的连续性(nonce不能断开) + ## 交易池的限制 交易池设置了一些的参数来限制单个交易的 Size ,以及整个 Transaction Pool 中所保存的交易的总数量。当交易池的中维护的交易超过某个阈值的时候,交易池会丢弃/驱逐(Discard/Evict)一部分的交易。这里注意,被清除的交易通常都是 Remote Transaction,而 Local Transaction 通常都会被保留下来。 @@ -62,7 +200,168 @@ Transaction Pool 引入了一个名为 `txSlotSize` 的 Metrics 作为计量一 按照默认的设置,交易池的最多保存 `4096+1024` 个交易,其中 Pending 区保存 4096 个 `txSlot` 规模的交易,Queue 区最多保存 1024 个 `txSlot` 规模的交易。 + ## 交易池的更新 +### 1.交易添加流程 +```go +// add 验证并添加交易到池中 +func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { + // 获取交易发送者 + from, err := types.Sender(pool.signer, tx) + if err != nil { + return false, err + } + + // 加锁保护并发访问 + pool.mu.Lock() + defer pool.mu.Unlock() + + // 1. 基础验证 + if err := pool.validateTx(tx, local); err != nil { + return false, err + } + + // 2. 检查是否替换现有交易 + if old := pool.all.Get(tx.Hash()); old != nil { + // 如果新交易gas价格不够高,拒绝替换 + if !pool.shouldReplace(old, tx) { + return false, ErrReplaceUnderpriced + } + // 标记为替换操作 + replaced = true + } + + // 3. 将交易添加到合适的队列 + if pool.pending[from] == nil { + pool.pending[from] = newTxList(true) + } + if pool.queue[from] == nil { + pool.queue[from] = newTxList(false) + } + + // 4. 根据nonce决定放入pending还是queue + if tx.Nonce() > pool.currentState.GetNonce(from) { + pool.queue[from].Add(tx) + } else { + pool.pending[from].Add(tx) + } + + // 5. 更新价格排序 + pool.priced.Put(tx) + + return replaced, nil +} +``` +### 2. 状态更新流程 +```go +// reset 处理链状态更新 +func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { + // 1. 初始化新状态 + var reinject types.Transactions + if oldHead != nil && oldHead.Hash() != newHead.ParentHash { + // 发生链重组,需要重新注入交易 + oldNum := oldHead.Number.Uint64() + newNum := newHead.Number.Uint64() + + // 收集需要重新注入的交易 + for i := oldNum + 1; i <= newNum; i++ { + block := pool.chain.GetBlock(newHead.Hash(), i) + for _, tx := range block.Transactions() { + reinject = append(reinject, tx) + } + } + } + + // 2. 更新当前状态 + pool.currentState, _ = pool.chain.StateAt(newHead.Root) + pool.pendingNonces = newNoncer(pool.currentState) + + // 3. 重新验证所有pending交易 + pool.demoteUnexecutables() + + // 4. 重新注入交易 + if len(reinject) > 0 { + pool.addTxsLocked(reinject, false) + } +} +``` +### 3. 交易提升流程 +```go +// promoteTx 尝试将交易从queue提升到pending +func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { + // 1. 检查nonce + if pool.currentState.GetNonce(addr) != tx.Nonce() { + return false + } + + // 2. 检查余额 + if pool.currentState.GetBalance(addr).Cmp(tx.Cost()) < 0 { + return false + } + + // 3. 从queue移除 + pool.queue[addr].Remove(hash) + + // 4. 添加到pending + pool.pending[addr].Add(tx) + + // 5. 更新状态 + pool.beats[addr] = time.Now() + + return true +} +``` + +## 容量限制与清理机制 + +### 容量限制 + +1. 单个交易限制 + - 最小占用: 1个 txSlot (32KB) + - 最大占用: 4个 txSlot (128KB) + +2. 交易池容量 + - Pending池: 4096个 txSlot + - Queue池: 1024个 txSlot + +### 清理策略 + +当池满时,按以下策略清理交易: + +1. 优先清理远程交易,保护本地交易 +2. 按gas价格排序,优先清理低价交易 +3. 保持每个账户的nonce连续性 +4. 清理长期未被打包的过期交易 + +## 状态更新与维护 + +### 定期维护任务 + +交易池运行以下定期维护任务: +```go +func loop() { + // 1. 状态报告 (每分钟) + case <-report.C: + logPoolStats() + + // 2. 交易清理 (每小时) + case <-evict.C: + removeExpiredTxs() + + // 3. 本地交易持久化 (每小时) + case <-journal.C: + persistLocalTxs() +} +``` + +### 链状态同步 + +当区块链状态发生变化时: +1. 检测链重组,重新注入相关交易 +2. 更新状态数据库引用 +3. 重新验证所有pending交易 +4. 尝试将queue中的交易提升到pending +这些机制共同确保了交易池的正确性和效率。 \ No newline at end of file diff --git a/CN/worker.md b/CN/worker.md new file mode 100644 index 0000000..133e97d --- /dev/null +++ b/CN/worker.md @@ -0,0 +1,183 @@ +# miner/worker.go +### 1. 关键结构 +```go +// environment 是当前区块生成环境的状态 +type environment struct { + signer types.Signer // 交易签名器 + state *state.StateDB // 状态数据库 + tcount int // 当前周期的交易计数 + gasPool *core.GasPool // 可用 gas 池 + coinbase common.Address // 矿工地址 + header *types.Header // 区块头 + txs []*types.Transaction // 交易列表 + receipts []*types.Receipt // 收据列表 + sidecars []*types.BlobTxSidecar // blob 交易的额外数据 +} + +// generateParams 封装了区块生成的参数 +type generateParams struct { + timestamp uint64 // 时间戳 + forceTime bool // 是否强制使用给定时间戳 + parentHash common.Hash // 父区块哈希 + coinbase common.Address // 矿工地址 + random common.Hash // 随机数(来自信标链) + withdrawals types.Withdrawals // 提款列表 + beaconRoot *common.Hash // 信标根 +} +``` +### 2. 主要流程 +#### 1.区块生成: +```go +func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult { + // 1. 准备区块环境 + work, err := miner.prepareWork(params, witness) + if err != nil { + return &newPayloadResult{err: err} + } + + // 2. 填充交易(如果不是空区块) + if !params.noTxs { + interrupt := new(atomic.Int32) + timer := time.AfterFunc(miner.config.Recommit, func() { + interrupt.Store(commitInterruptTimeout) + }) + defer timer.Stop() + + err := miner.fillTransactions(interrupt, work) + if errors.Is(err, errBlockInterruptedByTimeout) { + log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit)) + } + } + + // 3. 完成区块构建 + block, err := miner.engine.FinalizeAndAssemble(...) + return &newPayloadResult{...} +} +``` +#### 2.准备工作环境: +```go +func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*environment, error) { + // 1. 获取父区块 + parent := miner.chain.CurrentBlock() + + // 2. 构建区块头 + header := &types.Header{ + ParentHash: parent.Hash(), + Number: new(big.Int).Add(parent.Number, common.Big1), + GasLimit: core.CalcGasLimit(parent.GasLimit, miner.config.GasCeil), + Time: timestamp, + Coinbase: genParams.coinbase, + } + + // 3. 设置共识相关字段 + if err := miner.engine.Prepare(miner.chain, header); err != nil { + return nil, err + } + + // 4. 创建执行环境 + env, err := miner.makeEnv(parent, header, genParams.coinbase, witness) + return env, nil +} +``` +#### 4.交易处理: +```go +func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment) error { + // 1. 获取最低 gas 价格配置 + miner.confMu.RLock() + tip := miner.config.GasPrice + miner.confMu.RUnlock() + + // 2. 设置交易过滤条件 + filter := txpool.PendingFilter{ + MinTip: uint256.MustFromBig(tip), // 设置最低小费要求 + } + // 如果是 EIP-1559 交易,设置基础费用 + if env.header.BaseFee != nil { + filter.BaseFee = uint256.MustFromBig(env.header.BaseFee) + } + // 如果是 EIP-4844 blob 交易,设置 blob 费用 + if env.header.ExcessBlobGas != nil { + filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) + } + + // 3. 分别获取普通交易和 blob 交易 + // 首先获取普通交易 + filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false + pendingPlainTxs := miner.txpool.Pending(filter) + + // 然后获取 blob 交易 + filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true + pendingBlobTxs := miner.txpool.Pending(filter) + + // 4. 将交易分为本地交易和远程交易 + localPlainTxs, remotePlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs + localBlobTxs, remoteBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs + + // 遍历本地账户,将其交易从远程列表移到本地列表 + for _, account := range miner.txpool.Locals() { + // 处理普通交易 + if txs := remotePlainTxs[account]; len(txs) > 0 { + delete(remotePlainTxs, account) + localPlainTxs[account] = txs + } + // 处理 blob 交易 + if txs := remoteBlobTxs[account]; len(txs) > 0 { + delete(remoteBlobTxs, account) + localBlobTxs[account] = txs + } + } + + // 5. 优先处理本地交易 + if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 { + // 按价格和 nonce 对交易排序 + plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee) + blobTxs := newTransactionsByPriceAndNonce(env.signer, localBlobTxs, env.header.BaseFee) + + // 提交本地交易 + if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil { + return err + } + } + + // 6. 处理远程交易 + if len(remotePlainTxs) > 0 || len(remoteBlobTxs) > 0 { + // 按价格和 nonce 对交易排序 + plainTxs := newTransactionsByPriceAndNonce(env.signer, remotePlainTxs, env.header.BaseFee) + blobTxs := newTransactionsByPriceAndNonce(env.signer, remoteBlobTxs, env.header.BaseFee) + + // 提交远程交易 + if err := miner.commitTransactions(env, plainTxs, blobTxs, interrupt); err != nil { + return err + } + } + return nil +} +``` +- 交易分类: + - 区分普通交易和 blob 交易 + - 区分本地交易和远程交易 + - 本地交易优先处理 +- 排序机制: + - 按照价格和 nonce 排序 + - 确保交易按正确顺序执行 +#### 4.交易处理: +```go +func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) error { + // 1. 处理 blob 交易 + if tx.Type() == types.BlobTxType { + return miner.commitBlobTransaction(env, tx) + } + + // 2. 应用交易 + receipt, err := miner.applyTransaction(env, tx) + if err != nil { + return err + } + + // 3. 更新环境 + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + env.tcount++ + return nil +} +``` \ No newline at end of file