<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>LXDAO</title>
        <link>https://paragraph.com/@lxdao</link>
        <description>LXDAO 是一个专注研发的 DAO 组织，致力于构建支持有价值的公共物品和开源项目的无限循环。

LXDAO is an R&amp;D-focused DAO dedicated to building an Infinite Cycle that supports valuable</description>
        <lastBuildDate>Thu, 04 Jun 2026 04:50:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>LXDAO</title>
            <url>https://storage.googleapis.com/papyrus_images/d51b7e392dcea9a894c12ff1342117ce41bf0a400a8611d0ab03a28baa50a885.jpg</url>
            <link>https://paragraph.com/@lxdao</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Geth 源码系列：Blockchain 的设计及实现]]></title>
            <link>https://paragraph.com/@lxdao/geth-blockchain</link>
            <guid>O8hpcmJBw99zdlXkks9z</guid>
            <pubDate>Mon, 11 Aug 2025 22:05:21 GMT</pubDate>
            <description><![CDATA[本文作者：Ray 这篇文章是 Geth 源码系列的第六篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第六篇文章中，我们将深入研究以太坊的 Blockchain 结构，Blockchain 代表的是以太坊共识结果，Blockchain 模块会负责验证新的区块、处理链重组以及区块的存储和检索，本文将深入研究 Blockchain 的设计及实现。Blockchain 简介在以太坊中，用户提交交易之后的所有流程，包括交易传播、出块节点选择、共识层选择链的过程，都是为了最终产出区块做准备。出块节点通过打包交易来产出区块，新区块会去链接前一个区块，最终形成一个链状结构，也就是区块链，Blockchain 模块的职责就是管理这个过程，与共识层配合，共同完成区块链状态和区块的维护。Blockchain 架构及生命周期Blockchain 负责对共识结果的持久化，连接了共识层和持久化数据库，负责将区块链网络中以及产生的共识结果持久化到本地。在正常情况下，区块链网络共识过程正常，节点也能正常接收对应的共识结果，执行...]]></description>
            <content:encoded><![CDATA[<p>本文作者：Ray</p><p><strong>这篇文章是 Geth 源码系列的第六篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第六篇文章中，我们将深入研究以太坊的 Blockchain 结构，Blockchain 代表的是以太坊共识结果，Blockchain 模块会负责验证新的区块、处理链重组以及区块的存储和检索，本文将深入研究 Blockchain 的设计及实现。</strong></p><h2 id="h-blockchain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Blockchain 简介</strong></h2><p>在以太坊中，用户提交交易之后的所有流程，包括交易传播、出块节点选择、共识层选择链的过程，都是为了最终产出区块做准备。出块节点通过打包交易来产出区块，新区块会去链接前一个区块，最终形成一个链状结构，也就是区块链，Blockchain 模块的职责就是管理这个过程，与共识层配合，共同完成区块链状态和区块的维护。</p><h2 id="h-blockchain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Blockchain 架构及生命周期</strong></h2><p>Blockchain 负责对共识结果的持久化，连接了共识层和持久化数据库，负责将区块链网络中以及产生的共识结果持久化到本地。在正常情况下，区块链网络共识过程正常，节点也能正常接收对应的共识结果，执行层只需要负责接收共识层传递的共识结果，然后持久化到本地。但在以太坊这样的分布式的网络中，可能会有很多异常的情况出现，那么就有可能需要对已经产生的区块重组。Blockchain 模块的架构图如下所示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6a690663c0e483e53c7995305dbce211ce38beb65bf605ca362d284159dbaecd.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>总体来说，Blockchain 模块不会参与共识的过程，只会负责管理执行层的共识结果，Blockchain 实例会随着节点的启动而创建，在后续区块插入或者重组的流程中都会使用这一个实例。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>核心数据结构</strong></h2><p>Blockchain 涉及到的数据结构比较少，主要的业务逻辑都在围绕 Blockchain 这个结构体展开。</p><h3 id="h-blockchain" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Blockchain</strong></h3><p>Blockchain 在 core/blockchain.go 中定义，结构体中包含了在维护区块的过程中所依赖的模块。</p><pre data-type="codeBlock" text="type BlockChain struct {  chainConfig *params.ChainConfig // 链配置和网络配置，包含区块链的共识规则、分叉点和网络参数  cacheConfig *CacheConfig        // 缓存配置，用于控制修剪（pruning）策略和内存使用  db            ethdb.Database                   // 底层持久化数据库  snaps         *snapshot.Tree                   // 快照树，用于快速访问 trie 叶子节点  triegc        *prque.Prque[int64, common.Hash] // 优先队列，映射区块号到需要垃圾回收的 tries  gcproc        time.Duration                    // 累积规范区块处理时间，用于 trie 转储  lastWrite     uint64                           // 最后一次状态刷新的区块  flushInterval atomic.Int64                     // 状态刷新的时间间隔（处理时间）  triedb        *triedb.Database                 // Trie 数据库处理器，用于维护 trie 节点  statedb       *state.CachingDB                 // 状态数据库，用于缓存状态数据的中间状态  txIndexer     *txIndexer                       // 交易索引器，可能为 nil（如果未启用）  hc               *HeaderChain  // 区块头链，管理区块头的导入和验证  rmLogsFeed       event.Feed  // 移除日志的事件源  chainFeed        event.Feed  // 链更新的事件源  chainHeadFeed    event.Feed  // 链头更新的事件源  logsFeed         event.Feed  // 日志的事件源  blockProcFeed    event.Feed  // 区块处理的事件源  blockProcCounter int32       // 区块处理计数器  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   // 链运行状态标志  procInterrupt atomic.Bool   // 区块处理中断信号  engine     consensus.Engine // 共识引擎，负责区块验证和挖矿  validator  Validator // 区块和状态验证器接口  prefetcher Prefetcher // 状态预取器，优化状态访问性能  processor  Processor // 区块交易处理器接口  vmConfig   vm.Config // EVM 设置  logger     *tracing.Hooks // 日志钩子}
"><code>type <span class="hljs-title class_">BlockChain</span> struct {  chainConfig *params.<span class="hljs-title class_">ChainConfig</span> /<span class="hljs-regexp">/ 链配置和网络配置，包含区块链的共识规则、分叉点和网络参数  cacheConfig *CacheConfig        /</span><span class="hljs-regexp">/ 缓存配置，用于控制修剪（pruning）策略和内存使用  db            ethdb.Database                   /</span><span class="hljs-regexp">/ 底层持久化数据库  snaps         *snapshot.Tree                   /</span><span class="hljs-regexp">/ 快照树，用于快速访问 trie 叶子节点  triegc        *prque.Prque[int64, common.Hash] /</span><span class="hljs-regexp">/ 优先队列，映射区块号到需要垃圾回收的 tries  gcproc        time.Duration                    /</span><span class="hljs-regexp">/ 累积规范区块处理时间，用于 trie 转储  lastWrite     uint64                           /</span><span class="hljs-regexp">/ 最后一次状态刷新的区块  flushInterval atomic.Int64                     /</span><span class="hljs-regexp">/ 状态刷新的时间间隔（处理时间）  triedb        *triedb.Database                 /</span><span class="hljs-regexp">/ Trie 数据库处理器，用于维护 trie 节点  statedb       *state.CachingDB                 /</span><span class="hljs-regexp">/ 状态数据库，用于缓存状态数据的中间状态  txIndexer     *txIndexer                       /</span><span class="hljs-regexp">/ 交易索引器，可能为 nil（如果未启用）  hc               *HeaderChain  /</span><span class="hljs-regexp">/ 区块头链，管理区块头的导入和验证  rmLogsFeed       event.Feed  /</span><span class="hljs-regexp">/ 移除日志的事件源  chainFeed        event.Feed  /</span><span class="hljs-regexp">/ 链更新的事件源  chainHeadFeed    event.Feed  /</span><span class="hljs-regexp">/ 链头更新的事件源  logsFeed         event.Feed  /</span><span class="hljs-regexp">/ 日志的事件源  blockProcFeed    event.Feed  /</span><span class="hljs-regexp">/ 区块处理的事件源  blockProcCounter int32       /</span><span class="hljs-regexp">/ 区块处理计数器  scope            event.SubscriptionScope /</span><span class="hljs-regexp">/ 事件订阅范围  genesisBlock     *types.Block /</span><span class="hljs-regexp">/ 创世区块  chainmu *syncx.ClosableMutex  /</span><span class="hljs-regexp">/ 链写操作的互斥锁  currentBlock      atomic.Pointer[types.Header] /</span><span class="hljs-regexp">/ 当前链头区块  currentSnapBlock  atomic.Pointer[types.Header] /</span><span class="hljs-regexp">/ 当前快照同步的链头  currentFinalBlock atomic.Pointer[types.Header] /</span><span class="hljs-regexp">/ 最新的（共识）最终确定区块  currentSafeBlock  atomic.Pointer[types.Header] /</span><span class="hljs-regexp">/ 最新的（共识）安全区块  bodyCache     *lru.Cache[common.Hash, *types.Body] /</span><span class="hljs-regexp">/ 区块体缓存  bodyRLPCache  *lru.Cache[common.Hash, rlp.RawValue] /</span><span class="hljs-regexp">/ 区块体 RLP 编码缓存  receiptsCache *lru.Cache[common.Hash, []*types.Receipt] /</span><span class="hljs-regexp">/ 收据缓存  blockCache    *lru.Cache[common.Hash, *types.Block] /</span><span class="hljs-regexp">/ 区块缓存  txLookupLock  sync.RWMutex  txLookupCache *lru.Cache[common.Hash, txLookup] /</span><span class="hljs-regexp">/ 交易查询缓存  wg            sync.WaitGroup /</span><span class="hljs-regexp">/ 并发控制组，用于优雅关闭  quit          chan struct{} /</span><span class="hljs-regexp">/ 关闭信号，在 Stop 中关闭  stopping      atomic.Bool   /</span><span class="hljs-regexp">/ 链运行状态标志  procInterrupt atomic.Bool   /</span><span class="hljs-regexp">/ 区块处理中断信号  engine     consensus.Engine /</span><span class="hljs-regexp">/ 共识引擎，负责区块验证和挖矿  validator  Validator /</span><span class="hljs-regexp">/ 区块和状态验证器接口  prefetcher Prefetcher /</span><span class="hljs-regexp">/ 状态预取器，优化状态访问性能  processor  Processor /</span><span class="hljs-regexp">/ 区块交易处理器接口  vmConfig   vm.Config /</span><span class="hljs-regexp">/ EVM 设置  logger     *tracing.Hooks /</span><span class="hljs-regexp">/ 日志钩子}
</span></code></pre><p>在 Blockchain 结构中，有三个关键的 interface，这三个 interface 会执行具体处理区块的逻辑：</p><ul><li><p>Validator：负责验证区块和状态的合法性</p></li></ul><p>验证区块头信息以及叔块的有效性</p><p>验证状态转换的正确性</p><ul><li><p>Prefercher：预先加载可能需要的状态数据，提升区块处理效率</p></li></ul><p>在区块处理前预取状态数据、减少后续实际执行时所需要加载数据的时间</p><ul><li><p>Processor：执行区块中的业务逻辑</p></li></ul><p>按照区块中交易打包顺序执行交易，并计算 gas 消耗和 gas 退款，生成对应的收据和日志</p><p>处理共识层的请求，比如退款等等</p><pre data-type="codeBlock" text="// 用于验证区块type Validator interface {  ValidateBody(block *types.Block) error  ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error}// 提前缓存状态数据type Prefetcher interface {  Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool)}// 处理区块，包括执行区块的中的交易，并且将交易引起的状态变更持久化到状态数据库中type Processor interface {  Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)}
"><code>// 用于验证区块<span class="hljs-keyword">type</span> Validator interface {  ValidateBody<span class="hljs-punctuation">(</span><span class="hljs-keyword">block</span> *types.Block<span class="hljs-punctuation">)</span> error  ValidateState<span class="hljs-punctuation">(</span><span class="hljs-keyword">block</span> *types.Block, state *state.StateDB, res *ProcessResult, stateless bool<span class="hljs-punctuation">)</span> error}// 提前缓存状态数据<span class="hljs-keyword">type</span> Prefetcher interface {  Prefetch<span class="hljs-punctuation">(</span><span class="hljs-keyword">block</span> *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool<span class="hljs-punctuation">)</span>}// 处理区块，包括执行区块的中的交易，并且将交易引起的状态变更持久化到状态数据库中<span class="hljs-keyword">type</span> Processor interface {  Process<span class="hljs-punctuation">(</span><span class="hljs-keyword">block</span> *types.Block, statedb *state.StateDB, cfg vm.Config<span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span>*ProcessResult, error<span class="hljs-punctuation">)</span>}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>关键流程</strong></h2><p>NGO 当执行层处在不同状态时，获取共识结果（区块）的方式不一样。对于 Blockchain 来说，有以下的关键流程需要处理：</p><ul><li><p>如果当前节点是一个新加入网络的节点，需要从其他的节点处同步的区块数据（Full Sync），然后由 Blockchain 模块将区块在本地执行，插入新区块和保存状态数据；</p></li><li><p>如果当前节点是一个已经同步完成并正常运行的节点，执行层会与共识层进行交互，获取共识层的共识结果，然后由 Blockchain 模块将区块插入，并更新状态数据；</p></li><li><p>在插入区块的过程中，也会判断当前同步的区块数据是否正常，是否需要触发区块的重组。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>与共识层的交互</strong></h3><p>以太坊执行层在运行的过程中，需要共识层来确定执行层需要插入哪些区块，因此，执行层需要需要从共识层获取下一个需要插入的区块。同时，共识层会负责将一些共识的结果传递到执行层，比如头块信息、已经被 Finalized 的区块，这些信息通过 Engine API 传递到执行层之后，其中的交易会被执行，产生的区块和状态变化就会被持久化存储。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>区块同步</strong></h3><p>当一个节点因为新加入网络或者由于宕机后重新接入网络，那么就需要重新同步区块，通过区块的方式有多种，如果采用了 Full Sync 的方式，所有同步过来的区块需要对其中的的每一条交易进行执行，然后将产生的状态变更持久化到状态数据库中。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>新区块插入</strong></h3><p>无论是通过共识层获取到新的共识结果，还是从其他节点处同步过来了新的区块，都需要通过新区块插入的方式来完成区块验证、交易执行和持久化存储几个过程。这个过程会调用 Blockchain 中的特定方法来完成，在这个过程中，会依赖 EVM 来执行交易并产出状态变更，然后依赖状态数据库将执行结果持久化存储到磁盘中。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>链重组</strong></h3><p>在以太坊这样一个节点遍布全球的分布式网络，会出现各种异常情况，这些情况都有可能导致区块的产生出现异常情况。在异常情况下，执行层很有可能会发生区块重组，需要回滚本地的区块和状态信息，重新同步当前的主流链。这些异常情况可能是网络分区导致竞争区块出现、节点客户端漏洞、节点客户端版本等等问题。</p><h2 id="h-blockchain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Blockchain 源码分析</strong></h2><p>下面通过代码来详细看一下 Blockchain 如何实现上面的各个流程。在 eth/backend.go 的 New 方法是启动节点的入口，在这个方法中也会创建 Blockchain 模块的实例：</p><pre data-type="codeBlock" text="  // 初始化 Blockchain 实例  eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &amp;overrides, eth.engine, vmConfig, &amp;config.TransactionHistory)  if err != nil {    return nil, err  }
"><code>  // 初始化 Blockchain 实例  eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, <span class="hljs-built_in">config</span>.Genesis, &#x26;overrides, eth.engine, vmConfig, &#x26;<span class="hljs-built_in">config</span>.TransactionHistory)  <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err  }
</code></pre><p>在 core/blockchain.go 中的 NewBlockchain 方法中会执行具体的初始化工作，主要的工作有三个：</p><ul><li><p>从磁盘中加载状态数据，并验证创世区块及状态数据的可用性，并初始化好状态数据库</p></li><li><p>为后续加载区块等数据设置缓存</p></li><li><p>为 validator、prefetcher、processor 等 interface 初始化具体实现，后续具体的区块处理逻辑依赖这三个 interface 的实现</p></li></ul><pre data-type="codeBlock" text="func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, txLookupLimit *uint64) (*BlockChain, error) {    // 如果缓存配置为空，使用默认配置    if cacheConfig == nil {        cacheConfig = defaultCacheConfig    }        // 检查是否在创世块启用Verkle树    enableVerkle, err := EnableVerkleAtGenesis(db, genesis)    if err != nil {        return nil, err    }        // 使用配置创建 Trie 数据库    triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(enableVerkle))    // 如果数据库未初始化，写入提供的创世块    // 返回链配置（来自提供的创世块或本地存储的配置）    chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides)    if err != nil {        return nil, err    }        //.....    // 创建区块链核心结构    bc := &amp;BlockChain{        chainConfig:   chainConfig,  // 链和网络配置        cacheConfig:   cacheConfig,   // 缓存配置        db:            db,           // 底层数据库        triedb:        triedb,       // Trie数据库        triegc:        prque.Newint64, common.Hash, // Trie垃圾回收队列        quit:          make(chan struct{}), // 关闭信号        chainmu:       syncx.NewClosableMutex(), // 链操作互斥锁        bodyCache:     lru.NewCachecommon.Hash, *types.Body, // 区块体缓存        bodyRLPCache:  lru.NewCachecommon.Hash, rlp.RawValue, // RLP编码区块体缓存         receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), // 收据缓存        blockCache:    lru.NewCachecommon.Hash, *types.Block, // 完整区块缓存        txLookupCache: lru.NewCachecommon.Hash, txLookup, // 交易查询缓存        engine:        engine,       // 共识引擎        vmConfig:      vmConfig,     // 虚拟机配置        logger:        vmConfig.Tracer, // 日志追踪器    }        // 初始化头部链    bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)    if err != nil {        return nil, err    }        // 设置 Trie 刷新间隔    bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))        // 创建状态数据库    bc.statedb = state.NewDatabase(bc.triedb, nil)        // 初始化区块验证器和状态处理器，这里是处理区块所用到的主要组件    bc.validator = NewBlockValidator(chainConfig, bc)    bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)    bc.processor = NewStateProcessor(chainConfig, bc.hc)    // 获取创世块头并创建创世块    genesisHeader := bc.GetHeaderByNumber(0)    bc.genesisBlock = types.NewBlockWithHeader(genesisHeader)    if bc.genesisBlock == nil {        return nil, ErrNoGenesis    }    // 初始化当前区块指针    bc.currentBlock.Store(nil)    bc.currentSnapBlock.Store(nil)    bc.currentFinalBlock.Store(nil)    bc.currentSafeBlock.Store(nil)    // 更新链信息指标    chainInfoGauge.Update(metrics.GaugeInfoValue{&quot;chain_id&quot;: bc.chainConfig.ChainID.String()})    // 如果使用的是 Freezer 存储，重新初始化缺失的链索引和标志    if bc.empty() {        rawdb.InitDatabaseFromFreezer(bc.db)    }        // 从磁盘加载区块链状态    if err := bc.loadLastState(); err != nil {        return nil, err    }        // 确保区块状态可用，否则等待状态同步    head := bc.CurrentBlock()    if !bc.HasState(head.Root) {        if head.Number.Uint64() == 0 {            // 创世状态缺失，等待状态同步            log.Info(&quot;Genesis state is missing, wait state sync&quot;)        } else {            var diskRoot common.Hash            if bc.cacheConfig.SnapshotLimit &gt; 0 {                diskRoot = rawdb.ReadSnapshotRoot(bc.db)            }            // 处理头部状态缺失情况            if diskRoot != (common.Hash{}) {                log.Warn(&quot;Head state missing, repairing&quot;, &quot;number&quot;, head.Number, &quot;hash&quot;, head.Hash(), &quot;snaproot&quot;, diskRoot)                snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)                if err != nil {                    return nil, err                }                if snapDisk != 0 {                    rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)                }            } else {                log.Warn(&quot;Head state missing, repairing&quot;, &quot;number&quot;, head.Number, &quot;hash&quot;, head.Hash())                if _, err := bc.setHeadB eyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {                    return nil, err                }            }        }    }        //....        // 验证当前头块    bc.engine.VerifyHeader(bc, bc.CurrentHeader())    //......    // 加载或重建快照    if bc.cacheConfig.SnapshotLimit &gt; 0 {        var recover bool        head := bc.CurrentBlock()        if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil &amp;&amp; *layer &gt;= head.Number.Uint64() {            log.Warn(&quot;Enabling snapshot recovery&quot;, &quot;chainhead&quot;, head.Number, &quot;diskbase&quot;, *layer)            recover = true        }                snapconfig := snapshot.Config{            CacheSize:  bc.cacheConfig.SnapshotLimit,            Recovery:   recover,            NoBuild:    bc.cacheConfig.SnapshotNoBuild,            AsyncBuild: !bc.cacheConfig.SnapshotWait,        }        bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)        bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)    }    //....        return bc, nil}
"><code>func <span class="hljs-title class_">NewBlockChain</span>(db ethdb.<span class="hljs-title class_">Database</span>, cacheConfig *<span class="hljs-title class_">CacheConfig</span>, genesis *<span class="hljs-title class_">Genesis</span>, overrides *<span class="hljs-title class_">ChainOverrides</span>, engine consensus.<span class="hljs-title class_">Engine</span>, vmConfig vm.<span class="hljs-title class_">Config</span>, txLookupLimit *uint64) (*<span class="hljs-title class_">BlockChain</span>, error) {    <span class="hljs-regexp">//</span> 如果缓存配置为空，使用默认配置    <span class="hljs-keyword">if</span> cacheConfig == <span class="hljs-literal">nil</span> {        cacheConfig = defaultCacheConfig    }        /<span class="hljs-regexp">/ 检查是否在创世块启用Verkle树    enableVerkle, err := EnableVerkleAtGenesis(db, genesis)    if err != nil {        return nil, err    }        /</span><span class="hljs-regexp">/ 使用配置创建 Trie 数据库    triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(enableVerkle))    /</span><span class="hljs-regexp">/ 如果数据库未初始化，写入提供的创世块    /</span><span class="hljs-regexp">/ 返回链配置（来自提供的创世块或本地存储的配置）    chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides)    if err != nil {        return nil, err    }        /</span><span class="hljs-regexp">/.....    /</span><span class="hljs-regexp">/ 创建区块链核心结构    bc := &#x26;BlockChain{        chainConfig:   chainConfig,  /</span><span class="hljs-regexp">/ 链和网络配置        cacheConfig:   cacheConfig,   /</span><span class="hljs-regexp">/ 缓存配置        db:            db,           /</span><span class="hljs-regexp">/ 底层数据库        triedb:        triedb,       /</span><span class="hljs-regexp">/ Trie数据库        triegc:        prque.Newint64, common.Hash, /</span><span class="hljs-regexp">/ Trie垃圾回收队列        quit:          make(chan struct{}), /</span><span class="hljs-regexp">/ 关闭信号        chainmu:       syncx.NewClosableMutex(), /</span><span class="hljs-regexp">/ 链操作互斥锁        bodyCache:     lru.NewCachecommon.Hash, *types.Body, /</span><span class="hljs-regexp">/ 区块体缓存        bodyRLPCache:  lru.NewCachecommon.Hash, rlp.RawValue, /</span><span class="hljs-regexp">/ RLP编码区块体缓存         receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), /</span><span class="hljs-regexp">/ 收据缓存        blockCache:    lru.NewCachecommon.Hash, *types.Block, /</span><span class="hljs-regexp">/ 完整区块缓存        txLookupCache: lru.NewCachecommon.Hash, txLookup, /</span><span class="hljs-regexp">/ 交易查询缓存        engine:        engine,       /</span><span class="hljs-regexp">/ 共识引擎        vmConfig:      vmConfig,     /</span><span class="hljs-regexp">/ 虚拟机配置        logger:        vmConfig.Tracer, /</span><span class="hljs-regexp">/ 日志追踪器    }        /</span><span class="hljs-regexp">/ 初始化头部链    bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)    if err != nil {        return nil, err    }        /</span><span class="hljs-regexp">/ 设置 Trie 刷新间隔    bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))        /</span><span class="hljs-regexp">/ 创建状态数据库    bc.statedb = state.NewDatabase(bc.triedb, nil)        /</span><span class="hljs-regexp">/ 初始化区块验证器和状态处理器，这里是处理区块所用到的主要组件    bc.validator = NewBlockValidator(chainConfig, bc)    bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)    bc.processor = NewStateProcessor(chainConfig, bc.hc)    /</span><span class="hljs-regexp">/ 获取创世块头并创建创世块    genesisHeader := bc.GetHeaderByNumber(0)    bc.genesisBlock = types.NewBlockWithHeader(genesisHeader)    if bc.genesisBlock == nil {        return nil, ErrNoGenesis    }    /</span><span class="hljs-regexp">/ 初始化当前区块指针    bc.currentBlock.Store(nil)    bc.currentSnapBlock.Store(nil)    bc.currentFinalBlock.Store(nil)    bc.currentSafeBlock.Store(nil)    /</span><span class="hljs-regexp">/ 更新链信息指标    chainInfoGauge.Update(metrics.GaugeInfoValue{"chain_id": bc.chainConfig.ChainID.String()})    /</span><span class="hljs-regexp">/ 如果使用的是 Freezer 存储，重新初始化缺失的链索引和标志    if bc.empty() {        rawdb.InitDatabaseFromFreezer(bc.db)    }        /</span><span class="hljs-regexp">/ 从磁盘加载区块链状态    if err := bc.loadLastState(); err != nil {        return nil, err    }        /</span><span class="hljs-regexp">/ 确保区块状态可用，否则等待状态同步    head := bc.CurrentBlock()    if !bc.HasState(head.Root) {        if head.Number.Uint64() == 0 {            /</span><span class="hljs-regexp">/ 创世状态缺失，等待状态同步            log.Info("Genesis state is missing, wait state sync")        } else {            var diskRoot common.Hash            if bc.cacheConfig.SnapshotLimit > 0 {                diskRoot = rawdb.ReadSnapshotRoot(bc.db)            }            /</span><span class="hljs-regexp">/ 处理头部状态缺失情况            if diskRoot != (common.Hash{}) {                log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot)                snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)                if err != nil {                    return nil, err                }                if snapDisk != 0 {                    rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)                }            } else {                log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())                if _, err := bc.setHeadB eyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {                    return nil, err                }            }        }    }        /</span><span class="hljs-regexp">/....        /</span><span class="hljs-regexp">/ 验证当前头块    bc.engine.VerifyHeader(bc, bc.CurrentHeader())    /</span><span class="hljs-regexp">/......    /</span><span class="hljs-regexp">/ 加载或重建快照    if bc.cacheConfig.SnapshotLimit > 0 {        var recover bool        head := bc.CurrentBlock()        if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil &#x26;&#x26; *layer >= head.Number.Uint64() {            log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer)            recover = true        }                snapconfig := snapshot.Config{            CacheSize:  bc.cacheConfig.SnapshotLimit,            Recovery:   recover,            NoBuild:    bc.cacheConfig.SnapshotNoBuild,            AsyncBuild: !bc.cacheConfig.SnapshotWait,        }        bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)        bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)    }    /</span><span class="hljs-regexp">/....        return bc, nil}
</span></code></pre><p>到这里，Blockchain 模块就初始化完成，可以用于处理具体的业务逻辑。先考虑节点处在同步状态下的行为，当节点处在同步状态时，那么以太坊就还无法作为出块节点或者接收新的共识结果，需要等待节点节点同步完成。如果节点使用的是 Full Sync，那么所有的区块中的交易都需要被执行，然后通过交易执行的结果来还原出状态数据库。</p><p>在 eth/downloader/downloader.go 的 processFullSyncContent 方法中，最后会调用 Blockchain 中的 InsertChain 来处理从其他节点同步过来的区块。</p><pre data-type="codeBlock" text="func (d *Downloader) processFullSyncContent() error {  for {    results := d.queue.Results(true)    if len(results) == 0 {      return nil    }    if d.chainInsertHook != nil {      d.chainInsertHook(results)    }    // 在这里处理同步过来的区块    if err := d.importBlockResults(results); err != nil {      return err    }  }}func (d *Downloader) importBlockResults(results []*fetchResult) error {  // ....  // 在这里调用 Blockchain 的 InsertChain 方法来处理区块  if index, err := d.blockchain.InsertChain(blocks); err != nil {    // ...  }  return nil}
"><code>func (d <span class="hljs-operator">*</span>Downloader) processFullSyncContent() <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{  <span class="hljs-keyword">for</span> {    results :<span class="hljs-operator">=</span> d.queue.Results(<span class="hljs-literal">true</span>)    <span class="hljs-keyword">if</span> len(results) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {      <span class="hljs-keyword">return</span> nil    }    <span class="hljs-keyword">if</span> d.chainInsertHook <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {      d.chainInsertHook(results)    }    <span class="hljs-comment">// 在这里处理同步过来的区块    if err := d.importBlockResults(results); err != nil {      return err    }  }}func (d *Downloader) importBlockResults(results []*fetchResult) error {  // ....  // 在这里调用 Blockchain 的 InsertChain 方法来处理区块  if index, err := d.blockchain.InsertChain(blocks); err != nil {    // ...  }  return nil}</span>
</code></pre><p>共识层会通过 eth/catalyst/api.go 中的 forkchoiceUpdated 方法来判断区块是否同步完成，如果同步完成，那么就会将整个执行层的状态设置为同步完成，然后就可以开始通过共识层来接收最新的共识结果了。同时，这个方法还会去判断当前以太坊网络中是否已经对一些区块达成了 Finalized 状态，如果已经达成，那么也会使用 Blockchain 中的方法来设置区块为 Finalized，后续将无法回滚和重组：</p><pre data-type="codeBlock" text="func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {    //...    api.eth.SetSynced()    //...    if update.FinalizedBlockHash != (common.Hash{}) {        // 获取要设置为 Finalized 的区块        finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)        if finalBlock == nil {          log.Warn(&quot;Final block not available in database&quot;, &quot;hash&quot;, update.FinalizedBlockHash)          return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New(&quot;final block not available in database&quot;))        } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {          log.Warn(&quot;Final block not in canonical chain&quot;, &quot;number&quot;, finalBlock.NumberU64(), &quot;hash&quot;, update.FinalizedBlockHash)          return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New(&quot;final block not in canonical chain&quot;))        }        // 将这个区块设置为 Finalized        api.eth.BlockChain().SetFinalized(finalBlock.Header())     }    //...}
"><code>func (api <span class="hljs-operator">*</span>ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes <span class="hljs-operator">*</span>engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness <span class="hljs-keyword">bool</span>) (engine.ForkChoiceResponse, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{    <span class="hljs-comment">//...    api.eth.SetSynced()    //...    if update.FinalizedBlockHash != (common.Hash{}) {        // 获取要设置为 Finalized 的区块        finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)        if finalBlock == nil {          log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)          return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database"))        } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {          log.Warn("Final block not in canonical chain", "number", finalBlock.NumberU64(), "hash", update.FinalizedBlockHash)          return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))        }        // 将这个区块设置为 Finalized        api.eth.BlockChain().SetFinalized(finalBlock.Header())     }    //...}</span>
</code></pre><p>区块在同步完成之后，执行层就可以接收共识层最新的共识结果，并验证共识结果然后持久化存储，共识层会通过 eth/catalyst/api.go 中的 newPayload 来导入最新的共识结果，调用 Blockchain 的 InsertBlockWithoutSetHead 方法来导入新的区块：</p><pre data-type="codeBlock" text="func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) {  //...  proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)  if err != nil {    //...  }  //...}
"><code>func (api <span class="hljs-operator">*</span>ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot <span class="hljs-operator">*</span>common.Hash, requests [][]<span class="hljs-keyword">byte</span>, witness <span class="hljs-keyword">bool</span>) (engine.PayloadStatusV1, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{  <span class="hljs-comment">//...  proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)  if err != nil {    //...  }  //...}</span>
</code></pre><p>无论是区块同步，还是从共识层接收最新的共识结果，最后都是通过 core/blockchain.go 中的 insertChain 方法来实际插入区块，在这个过程中会使用到  validator、prefetcher、processor 这三个 interface 的具体实现来处理区块：</p><pre data-type="codeBlock" text="// insertChain 是区块链插入新链的核心函数func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {    // 1. 检查链是否已停止    if bc.insertStopped() {        return nil, 0, nil    }    // ...    for ; block != nil &amp;&amp; err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {        // ...        // 2. 启用状态预取，会调用 prefetcher 的实现来加载数据        if bc.chainConfig.IsByzantium(block.Number()) {            if bc.vmConfig.StatelessSelfValidation || (makeWitness &amp;&amp; len(chain) == 1) {                witness, err = stateless.NewWitness(block.Header(), bc)                if err != nil {                    return nil, it.index, err                }            }            statedb.StartPrefetcher(&quot;chain&quot;, witness)        }        activeState = statedb        // ...        // 3. 处理区块，在 processBlock 方法种会调用 Validator 的 ValidateState 方法来验证状态是否正确        res, err := bc.processBlock(block, statedb, start, setHead)        followupInterrupt.Store(true)        if err != nil {            return nil, it.index, err        }        // ...    }}
"><code>// insertChain 是区块链插入新链的核心函数func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, <span class="hljs-built_in">error</span>) {    // <span class="hljs-number">1.</span> 检查链是否已停止    <span class="hljs-keyword">if</span> bc.insertStopped() {        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">nil</span>    }    // ...    <span class="hljs-keyword">for</span> ; block != <span class="hljs-literal">nil</span> &#x26;&#x26; err == <span class="hljs-literal">nil</span> || errors.Is(err, ErrKnownBlock); block, err = it.<span class="hljs-built_in">next</span>() {        // ...        // <span class="hljs-number">2.</span> 启用状态预取，会调用 prefetcher 的实现来加载数据        <span class="hljs-keyword">if</span> bc.chainConfig.IsByzantium(block.Number()) {            <span class="hljs-keyword">if</span> bc.vmConfig.StatelessSelfValidation || (makeWitness &#x26;&#x26; <span class="hljs-built_in">len</span>(chain) == <span class="hljs-number">1</span>) {                witness, err = stateless.NewWitness(block.Header(), bc)                <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {                    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, it.index, err                }            }            statedb.StartPrefetcher(<span class="hljs-string">"chain"</span>, witness)        }        activeState = statedb        // ...        // <span class="hljs-number">3.</span> 处理区块，在 processBlock 方法种会调用 Validator 的 ValidateState 方法来验证状态是否正确        res, err := bc.processBlock(block, statedb, start, setHead)        followupInterrupt.Store(<span class="hljs-literal">true</span>)        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, it.index, err        }        // ...    }}
</code></pre><p>其中 Prefetch 预加载数据的逻辑其实很简单，就是直接将所有的交易执行一遍，预热这个过程中会使用到的状态数据：</p><pre data-type="codeBlock" text="func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {  // 构造一个交易执行的上下文信息  var (    header       = block.Header()    gaspool      = new(GasPool).AddGas(block.GasLimit())    blockContext = NewEVMBlockContext(header, p.chain, nil)    evm          = vm.NewEVM(blockContext, statedb, p.config, cfg)    signer       = types.MakeSigner(p.config, header.Number, header.Time)  )    byzantium := p.config.IsByzantium(block.Number())  for i, tx := range block.Transactions() {    // 如果预加载被中断，那么就直接返回    if interrupt != nil &amp;&amp; interrupt.Load() {      return    }    // 构造交易执行的消息    msg, err := TransactionToMessage(tx, signer, header.BaseFee)    if err != nil {      return     }    // 设置交易的上下文信息    statedb.SetTxContext(tx.Hash(), i)    // 通过执行交易来预热数据，但直接丢弃直接结果    if _, err := ApplyMessage(evm, msg, gaspool); err != nil {      return     }        if !byzantium {      statedb.IntermediateRoot(true)    }  }  if byzantium {    statedb.IntermediateRoot(true)  }}
"><code>func (<span class="hljs-selector-tag">p</span> *statePrefetcher) Prefetch(block *types<span class="hljs-selector-class">.Block</span>, statedb *state<span class="hljs-selector-class">.StateDB</span>, cfg vm<span class="hljs-selector-class">.Config</span>, interrupt *atomic<span class="hljs-selector-class">.Bool</span>) {  // 构造一个交易执行的上下文信息  <span class="hljs-selector-tag">var</span> (    <span class="hljs-selector-tag">header</span>       = block<span class="hljs-selector-class">.Header</span>()    gaspool      = new(GasPool)<span class="hljs-selector-class">.AddGas</span>(block<span class="hljs-selector-class">.GasLimit</span>())    blockContext = NewEVMBlockContext(<span class="hljs-selector-tag">header</span>, <span class="hljs-selector-tag">p</span><span class="hljs-selector-class">.chain</span>, nil)    evm          = vm<span class="hljs-selector-class">.NewEVM</span>(blockContext, statedb, <span class="hljs-selector-tag">p</span><span class="hljs-selector-class">.config</span>, cfg)    signer       = types<span class="hljs-selector-class">.MakeSigner</span>(<span class="hljs-selector-tag">p</span><span class="hljs-selector-class">.config</span>, <span class="hljs-selector-tag">header</span><span class="hljs-selector-class">.Number</span>, <span class="hljs-selector-tag">header</span><span class="hljs-selector-class">.Time</span>)  )    byzantium := p.config.<span class="hljs-built_in">IsByzantium</span>(block.<span class="hljs-built_in">Number</span>())  for i, tx := range block.<span class="hljs-built_in">Transactions</span>() {    // 如果预加载被中断，那么就直接返回    if interrupt != nil &#x26;&#x26; interrupt<span class="hljs-selector-class">.Load</span>() {      return    }    // 构造交易执行的消息    msg, err := <span class="hljs-built_in">TransactionToMessage</span>(tx, signer, header.BaseFee)    if err != nil {      return     }    // 设置交易的上下文信息    statedb<span class="hljs-selector-class">.SetTxContext</span>(tx<span class="hljs-selector-class">.Hash</span>(), <span class="hljs-selector-tag">i</span>)    // 通过执行交易来预热数据，但直接丢弃直接结果    if _, err := <span class="hljs-built_in">ApplyMessage</span>(evm, msg, gaspool); err != nil {      return     }        if !byzantium {      statedb<span class="hljs-selector-class">.IntermediateRoot</span>(true)    }  }  if byzantium {    statedb<span class="hljs-selector-class">.IntermediateRoot</span>(true)  }}
</code></pre><p>在 processBlock 中，则包含处理交易的实际逻辑，包括具体执行区块中的交易，这部分我们在之前的文章中已经详细说明，这里不再赘述，然后是验证区块执行完成之后的状态数据，如果符合预期，就再根据传入的参数来判断是否要将当前的区块信息设置为头块。</p><pre data-type="codeBlock" text="func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) {  //...  // 处理具体的交易逻辑  res, err := bc.processor.Process(block, statedb, bc.vmConfig)  if err != nil {    bc.reportBlock(block, res, err)    return nil, err  }  // 验证区块中交易执行完成之后的状态信息  if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {    bc.reportBlock(block, res, err)    return nil, err  }    //...  var (    wstart = time.Now()    status WriteStatus  )  // 是否要设置头区块  if !setHead {    err = bc.writeBlockWithState(block, res.Receipts, statedb)  } else {    status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)  }  //...}
"><code>func (bc <span class="hljs-operator">*</span>BlockChain) processBlock(<span class="hljs-built_in">block</span> <span class="hljs-operator">*</span>types.Block, statedb <span class="hljs-operator">*</span>state.StateDB, start time.Time, setHead <span class="hljs-keyword">bool</span>) (<span class="hljs-keyword">_</span> <span class="hljs-operator">*</span>blockProcessingResult, blockEndErr <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{  <span class="hljs-comment">//...  // 处理具体的交易逻辑  res, err := bc.processor.Process(block, statedb, bc.vmConfig)  if err != nil {    bc.reportBlock(block, res, err)    return nil, err  }  // 验证区块中交易执行完成之后的状态信息  if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {    bc.reportBlock(block, res, err)    return nil, err  }    //...  var (    wstart = time.Now()    status WriteStatus  )  // 是否要设置头区块  if !setHead {    err = bc.writeBlockWithState(block, res.Receipts, statedb)  } else {    status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)  }  //...}</span>
</code></pre><p>在执行层中，当出现同步的区块不一致时，就会触发区块的重组，在这个过程中，需要对本地已经同步的区块和状态数据进行回滚，并重新同步新的区块。区块重组会分为三个阶段：</p><ul><li><p>寻找共同祖先，对于产生分叉的两条链，先找到共同的祖先</p></li><li><p>找到共同的祖先之后，就会执行区块重组和状态回滚</p></li><li><p>最后会更新链状态并且清理无用的数据</p></li></ul><pre data-type="codeBlock" text="func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error {    var (        newChain    []*types.Header  // 新链区块头列表        oldChain    []*types.Header  // 旧链区块头列表        commonBlock *types.Header    // 共同祖先区块    )  // 将两条链调整到相同高度，便于寻找共同祖先  if oldHead.Number.Uint64() &gt; newHead.Number.Uint64() {    // 旧链更长，收集所有将被删除的交易和日志    for ; oldHead != nil &amp;&amp; oldHead.Number.Uint64() != newHead.Number.Uint64(); oldHead = bc.GetHeader(oldHead.ParentHash, oldHead.Number.Uint64()-1) {      oldChain = append(oldChain, oldHead)    }  } else {    // 新链更长，暂存所有新块待后续插入    for ; newHead != nil &amp;&amp; newHead.Number.Uint64() != oldHead.Number.Uint64(); newHead = bc.GetHeader(newHead.ParentHash, newHead.Number.Uint64()-1) {      newChain = append(newChain, newHead)    }  }  if oldHead == nil {    return errInvalidOldChain  }  if newHead == nil {    return errInvalidNewChain  }  // 寻找两条链共同的祖先  for {    if oldHead.Hash() == newHead.Hash() {      commonBlock = oldHead      break    }    oldChain = append(oldChain, oldHead)    newChain = append(newChain, newHead)    oldHead = bc.GetHeader(oldHead.ParentHash, oldHead.Number.Uint64()-1)    if oldHead == nil {      return errInvalidOldChain    }    newHead = bc.GetHeader(newHead.ParentHash, newHead.Number.Uint64()-1)    if newHead == nil {      return errInvalidNewChain    }  }  // 记录重组日志  if len(oldChain) &gt; 0 &amp;&amp; len(newChain) &gt; 0 {    logFn := log.Info    msg := &quot;Chain reorg detected&quot;    if len(oldChain) &gt; 63 {      msg = &quot;Large chain reorg detected&quot;      logFn = log.Warn  // 记录重组的区块高度信息    }    logFn(msg, &quot;number&quot;, commonBlock.Number, &quot;hash&quot;, commonBlock.Hash(),      &quot;drop&quot;, len(oldChain), &quot;dropfrom&quot;, oldChain[0].Hash(), &quot;add&quot;, len(newChain), &quot;addfrom&quot;, newChain[0].Hash())    blockReorgAddMeter.Mark(int64(len(newChain)))    blockReorgDropMeter.Mark(int64(len(oldChain)))    blockReorgMeter.Mark(1)  } else if len(newChain) &gt; 0 {    log.Info(&quot;Extend chain&quot;, &quot;add&quot;, len(newChain), &quot;number&quot;, newChain[0].Number, &quot;hash&quot;, newChain[0].Hash())    blockReorgAddMeter.Mark(int64(len(newChain)))  } else {    log.Error(&quot;Impossible reorg, please file an issue&quot;, &quot;oldnum&quot;, oldHead.Number, &quot;oldhash&quot;, oldHead.Hash(), &quot;oldblocks&quot;, len(oldChain), &quot;newnum&quot;, newHead.Number, &quot;newhash&quot;, newHead.Hash(), &quot;newblocks&quot;, len(newChain))  }    // 通过 txLookupLock 确保交易索引更新与链重组原子化，避免中间状态被外部读取  bc.txLookupLock.Lock()  // 准备存储受影响的交易和日志  var (      deletedTxs []common.Hash  // 将被删除的交易      rebirthTxs []common.Hash  // 将被恢复的交易      deletedLogs []*types.Log // 将被删除的日志      rebirthLogs []*types.Log // 将被恢复的日志  )  // 这里是旧的业务逻辑，为了保持兼容性不能删除  {    for i := len(oldChain) - 1; i &gt;= 0; i-- {      block := bc.GetBlock(oldChain[i].Hash(), oldChain[i].Number.Uint64())      if block == nil {        return errInvalidOldChain       }      if logs := bc.collectLogs(block, true); len(logs) &gt; 0 {        deletedLogs = append(deletedLogs, logs...)      }      if len(deletedLogs) &gt; 512 {        bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})        deletedLogs = nil      }    }    if len(deletedLogs) &gt; 0 {      bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})    }  }  // 反向处理旧链 日志删除按从新到旧（反向遍历），新增按 从旧到新（正向遍历），这样比较符合区块执行顺序  for i := 0; i &lt; len(oldChain); i++ {    // 收集即将被删除的交易    block := bc.GetBlock(oldChain[i].Hash(), oldChain[i].Number.Uint64())    if block == nil {      return errInvalidOldChain     }    for _, tx := range block.Transactions() {      deletedTxs = append(deletedTxs, tx.Hash())    }    if logs := bc.collectLogs(block, true); len(logs) &gt; 0 {      slices.Reverse(logs)    }  }  // 按照区块产生的顺序来处理信链  for i := len(newChain) - 1; i &gt;= 1; i-- {    // 收集即将被新增加的交易    block := bc.GetBlock(newChain[i].Hash(), newChain[i].Number.Uint64())    if block == nil {      return errInvalidNewChain // Corrupt database, mostly here to avoid weird panics    }    for _, tx := range block.Transactions() {      rebirthTxs = append(rebirthTxs, tx.Hash())    }    // 收集要产生的日子    if logs := bc.collectLogs(block, false); len(logs) &gt; 0 {      rebirthLogs = append(rebirthLogs, logs...)    }    if len(rebirthLogs) &gt; 512 {      bc.logsFeed.Send(rebirthLogs)      rebirthLogs = nil    }    // 从共同祖先后第一个新区块开始，按顺序写入新链区块，并更新链头    bc.writeHeadBlock(block)  }  if len(rebirthLogs) &gt; 0 {    bc.logsFeed.Send(rebirthLogs)  }  // 删除无用的交易索引  batch := bc.db.NewBatch()  for _, tx := range types.HashDifference(deletedTxs, rebirthTxs) {    rawdb.DeleteTxLookupEntry(batch, tx)  }  // 删除因重组而不再属于链的区块哈希标记，确保数据库只保留当前有效链的索引  number := commonBlock.Number  if len(newChain) &gt; 1 {    number = newChain[1].Number  }  for i := number.Uint64() + 1; ; i++ {    hash := rawdb.ReadCanonicalHash(bc.db, i)    if hash == (common.Hash{}) {      break    }    rawdb.DeleteCanonicalHash(batch, i)  }  if err := batch.Write(); err != nil {    log.Crit(&quot;Failed to delete useless indexes&quot;, &quot;err&quot;, err)  }  // 重置缓存  bc.txLookupCache.Purge()  // 释放锁  bc.txLookupLock.Unlock()  return nil}
"><code>func (bc *BlockChain) reorg(oldHead *types<span class="hljs-selector-class">.Header</span>, newHead *types<span class="hljs-selector-class">.Header</span>) error {    <span class="hljs-selector-tag">var</span> (        newChain    <span class="hljs-selector-attr">[]</span>*types<span class="hljs-selector-class">.Header</span>  // 新链区块头列表        oldChain    <span class="hljs-selector-attr">[]</span>*types<span class="hljs-selector-class">.Header</span>  // 旧链区块头列表        commonBlock *types<span class="hljs-selector-class">.Header</span>    // 共同祖先区块    )  // 将两条链调整到相同高度，便于寻找共同祖先  if oldHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>() > newHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>() {    // 旧链更长，收集所有将被删除的交易和日志    for ; oldHead != nil &#x26;&#x26; oldHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>() != newHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>(); oldHead = bc<span class="hljs-selector-class">.GetHeader</span>(oldHead<span class="hljs-selector-class">.ParentHash</span>, oldHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>()-<span class="hljs-number">1</span>) {      oldChain = append(oldChain, oldHead)    }  } else {    // 新链更长，暂存所有新块待后续插入    for ; newHead != nil &#x26;&#x26; newHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>() != oldHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>(); newHead = bc<span class="hljs-selector-class">.GetHeader</span>(newHead<span class="hljs-selector-class">.ParentHash</span>, newHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>()-<span class="hljs-number">1</span>) {      newChain = append(newChain, newHead)    }  }  if oldHead == nil {    return errInvalidOldChain  }  if newHead == nil {    return errInvalidNewChain  }  // 寻找两条链共同的祖先  for {    if oldHead<span class="hljs-selector-class">.Hash</span>() == newHead<span class="hljs-selector-class">.Hash</span>() {      commonBlock = oldHead      break    }    oldChain = append(oldChain, oldHead)    newChain = append(newChain, newHead)    oldHead = bc<span class="hljs-selector-class">.GetHeader</span>(oldHead<span class="hljs-selector-class">.ParentHash</span>, oldHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>()-<span class="hljs-number">1</span>)    if oldHead == nil {      return errInvalidOldChain    }    newHead = bc<span class="hljs-selector-class">.GetHeader</span>(newHead<span class="hljs-selector-class">.ParentHash</span>, newHead<span class="hljs-selector-class">.Number</span><span class="hljs-selector-class">.Uint64</span>()-<span class="hljs-number">1</span>)    if newHead == nil {      return errInvalidNewChain    }  }  // 记录重组日志  if len(oldChain) > <span class="hljs-number">0</span> &#x26;&#x26; len(newChain) > <span class="hljs-number">0</span> {    logFn := log.Info    msg := <span class="hljs-string">"Chain reorg detected"</span>    if <span class="hljs-built_in">len</span>(oldChain) > <span class="hljs-number">63</span> {      msg = "Large chain reorg detected"      logFn = log<span class="hljs-selector-class">.Warn</span>  // 记录重组的区块高度信息    }    logFn(msg, "number", commonBlock<span class="hljs-selector-class">.Number</span>, "hash", commonBlock<span class="hljs-selector-class">.Hash</span>(),      "drop", len(oldChain), "dropfrom", oldChain<span class="hljs-selector-attr">[0]</span><span class="hljs-selector-class">.Hash</span>(), "add", len(newChain), "addfrom", newChain<span class="hljs-selector-attr">[0]</span><span class="hljs-selector-class">.Hash</span>())    blockReorgAddMeter<span class="hljs-selector-class">.Mark</span>(int64(len(newChain)))    blockReorgDropMeter<span class="hljs-selector-class">.Mark</span>(int64(len(oldChain)))    blockReorgMeter<span class="hljs-selector-class">.Mark</span>(<span class="hljs-number">1</span>)  } else if len(newChain) > <span class="hljs-number">0</span> {    log<span class="hljs-selector-class">.Info</span>("Extend chain", "add", len(newChain), "number", newChain<span class="hljs-selector-attr">[0]</span><span class="hljs-selector-class">.Number</span>, "hash", newChain<span class="hljs-selector-attr">[0]</span><span class="hljs-selector-class">.Hash</span>())    blockReorgAddMeter<span class="hljs-selector-class">.Mark</span>(int64(len(newChain)))  } else {    log<span class="hljs-selector-class">.Error</span>("Impossible reorg, please file an issue", "oldnum", oldHead<span class="hljs-selector-class">.Number</span>, "oldhash", oldHead<span class="hljs-selector-class">.Hash</span>(), "oldblocks", len(oldChain), "newnum", newHead<span class="hljs-selector-class">.Number</span>, "newhash", newHead<span class="hljs-selector-class">.Hash</span>(), "newblocks", len(newChain))  }    // 通过 txLookupLock 确保交易索引更新与链重组原子化，避免中间状态被外部读取  bc<span class="hljs-selector-class">.txLookupLock</span><span class="hljs-selector-class">.Lock</span>()  // 准备存储受影响的交易和日志  <span class="hljs-selector-tag">var</span> (      deletedTxs <span class="hljs-selector-attr">[]</span>common<span class="hljs-selector-class">.Hash</span>  // 将被删除的交易      rebirthTxs <span class="hljs-selector-attr">[]</span>common<span class="hljs-selector-class">.Hash</span>  // 将被恢复的交易      deletedLogs <span class="hljs-selector-attr">[]</span>*types<span class="hljs-selector-class">.Log</span> // 将被删除的日志      rebirthLogs <span class="hljs-selector-attr">[]</span>*types<span class="hljs-selector-class">.Log</span> // 将被恢复的日志  )  // 这里是旧的业务逻辑，为了保持兼容性不能删除  {    for <span class="hljs-selector-tag">i</span> := <span class="hljs-built_in">len</span>(oldChain) - <span class="hljs-number">1</span>; <span class="hljs-selector-tag">i</span> >= <span class="hljs-number">0</span>; <span class="hljs-selector-tag">i</span>-- {      block := bc.<span class="hljs-built_in">GetBlock</span>(oldChain[i].<span class="hljs-built_in">Hash</span>(), oldChain[i].Number.<span class="hljs-built_in">Uint64</span>())      if block == nil {        return errInvalidOldChain       }      if logs := bc.<span class="hljs-built_in">collectLogs</span>(block, true); len(logs) > <span class="hljs-number">0</span> {        deletedLogs = append(deletedLogs, logs...)      }      if len(deletedLogs) > <span class="hljs-number">512</span> {        bc<span class="hljs-selector-class">.rmLogsFeed</span><span class="hljs-selector-class">.Send</span>(RemovedLogsEvent{deletedLogs})        deletedLogs = nil      }    }    if len(deletedLogs) > <span class="hljs-number">0</span> {      bc<span class="hljs-selector-class">.rmLogsFeed</span><span class="hljs-selector-class">.Send</span>(RemovedLogsEvent{deletedLogs})    }  }  // 反向处理旧链 日志删除按从新到旧（反向遍历），新增按 从旧到新（正向遍历），这样比较符合区块执行顺序  for <span class="hljs-selector-tag">i</span> := <span class="hljs-number">0</span>; <span class="hljs-selector-tag">i</span> &#x3C; len(oldChain); <span class="hljs-selector-tag">i</span>++ {    // 收集即将被删除的交易    block := bc.<span class="hljs-built_in">GetBlock</span>(oldChain[i].<span class="hljs-built_in">Hash</span>(), oldChain[i].Number.<span class="hljs-built_in">Uint64</span>())    if block == nil {      return errInvalidOldChain     }    for _, tx := range block.<span class="hljs-built_in">Transactions</span>() {      deletedTxs = append(deletedTxs, tx<span class="hljs-selector-class">.Hash</span>())    }    if logs := bc.<span class="hljs-built_in">collectLogs</span>(block, true); len(logs) > <span class="hljs-number">0</span> {      slices<span class="hljs-selector-class">.Reverse</span>(logs)    }  }  // 按照区块产生的顺序来处理信链  for <span class="hljs-selector-tag">i</span> := <span class="hljs-built_in">len</span>(newChain) - <span class="hljs-number">1</span>; <span class="hljs-selector-tag">i</span> >= <span class="hljs-number">1</span>; <span class="hljs-selector-tag">i</span>-- {    // 收集即将被新增加的交易    block := bc.<span class="hljs-built_in">GetBlock</span>(newChain[i].<span class="hljs-built_in">Hash</span>(), newChain[i].Number.<span class="hljs-built_in">Uint64</span>())    if block == nil {      return errInvalidNewChain // Corrupt database, mostly here <span class="hljs-selector-tag">to</span> avoid weird panics    }    for _, tx := range block.<span class="hljs-built_in">Transactions</span>() {      rebirthTxs = append(rebirthTxs, tx<span class="hljs-selector-class">.Hash</span>())    }    // 收集要产生的日子    if logs := bc.<span class="hljs-built_in">collectLogs</span>(block, false); len(logs) > <span class="hljs-number">0</span> {      rebirthLogs = append(rebirthLogs, logs...)    }    if len(rebirthLogs) > <span class="hljs-number">512</span> {      bc<span class="hljs-selector-class">.logsFeed</span><span class="hljs-selector-class">.Send</span>(rebirthLogs)      rebirthLogs = nil    }    // 从共同祖先后第一个新区块开始，按顺序写入新链区块，并更新链头    bc<span class="hljs-selector-class">.writeHeadBlock</span>(block)  }  if len(rebirthLogs) > <span class="hljs-number">0</span> {    bc<span class="hljs-selector-class">.logsFeed</span><span class="hljs-selector-class">.Send</span>(rebirthLogs)  }  // 删除无用的交易索引  batch := bc.db.<span class="hljs-built_in">NewBatch</span>()  for _, tx := range types.<span class="hljs-built_in">HashDifference</span>(deletedTxs, rebirthTxs) {    rawdb<span class="hljs-selector-class">.DeleteTxLookupEntry</span>(batch, tx)  }  // 删除因重组而不再属于链的区块哈希标记，确保数据库只保留当前有效链的索引  number := commonBlock.Number  if <span class="hljs-built_in">len</span>(newChain) > <span class="hljs-number">1</span> {    number = newChain<span class="hljs-selector-attr">[1]</span><span class="hljs-selector-class">.Number</span>  }  for <span class="hljs-selector-tag">i</span> := number.<span class="hljs-built_in">Uint64</span>() + <span class="hljs-number">1</span>; ; <span class="hljs-selector-tag">i</span>++ {    hash := rawdb.<span class="hljs-built_in">ReadCanonicalHash</span>(bc.db, i)    if hash == (common.Hash{}) {      break    }    rawdb<span class="hljs-selector-class">.DeleteCanonicalHash</span>(batch, <span class="hljs-selector-tag">i</span>)  }  if err := batch.<span class="hljs-built_in">Write</span>(); err != nil {    log<span class="hljs-selector-class">.Crit</span>("Failed <span class="hljs-selector-tag">to</span> delete useless indexes", "err", err)  }  // 重置缓存  bc<span class="hljs-selector-class">.txLookupCache</span><span class="hljs-selector-class">.Purge</span>()  // 释放锁  bc<span class="hljs-selector-class">.txLookupLock</span><span class="hljs-selector-class">.Unlock</span>()  return nil}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>总结</strong></h2><p>对于以太坊，最终的目的都是为了将用户提交的交易转化为共识的结果，而区块就是共识的结果。在正常情况下，用户提交交易之后，会经历进入交易池、广播、然后被出块节点打包进区块，最终区块广播给网络中所有的节点，最后再完成区块的最终性确认。但是在以太坊这样一个开放的分布式网络中，可能会出现各种意外情况，比如区块重组等情况。这些流程都将由 Blockchain 模块配合共识层来完成执行，保证以太坊有序处理所有交易，不断接收新的共识结果。</p><p><strong>Ref</strong></p><p>[1] </p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b">https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b</a></p><p>[2] </p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://hackmd.io/@danielrachi/engine_api">https://hackmd.io/@danielrachi/engine_api</a></p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/2bd6cff4309fd90a75ab1d1005bd76937d3f89af207bfccde5d45227eca95fec.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Geth 源码系列：EVM 设计及实现]]></title>
            <link>https://paragraph.com/@lxdao/geth-evm</link>
            <guid>fTTGKbTKtEFq4kukPuni</guid>
            <pubDate>Thu, 07 Aug 2025 04:53:03 GMT</pubDate>
            <description><![CDATA[“这篇文章是 Geth 源码系列的第五篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第五篇文章中，我们将深入研究 EVM，EVM 是以太坊执这个交易驱动的状态机的核心部分，所有的交易需要经过 EVM 的执行才能成为状态的一部分。会详细介绍 EVM 的版本迭代，也会详细说明 EVM 的设计及实现，以及对交易的处理细节。 本文作者：RayEVM 简介如果把以太坊看作是一个交易驱动的状态机，那么 EVM 就是其中的状态转换函数，EVM 会以当前的世界状态和交易作为输入，输出下一个世界状态。 在了解 EVM 之前，需要先理解一下以太坊的账户模型， 与比特币不一样，以太坊中是以账户为基础的存储单元，每个账户都有一个地址，地址之间的账户会引起世界状态的变化。在以太坊中有两种账户类型：EOA（Externally-owned account）：通常称为外部账户，这类账户由私钥控制合约账户（Contract account）：需要部署合约代码，由代码来控制账户但是在 EIP-7702 实施之后，EOA 也能附加...]]></description>
            <content:encoded><![CDATA[<p>“这篇文章是 Geth 源码系列的第五篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第五篇文章中，我们将深入研究 EVM，EVM 是以太坊执这个交易驱动的状态机的核心部分，所有的交易需要经过 EVM 的执行才能成为状态的一部分。会详细介绍 EVM 的版本迭代，也会详细说明 EVM 的设计及实现，以及对交易的处理细节。</p><p><strong>本文作者：Ray</strong></p><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EVM 简介</h2><p>如果把以太坊看作是一个交易驱动的状态机，那么 EVM 就是其中的状态转换函数，EVM 会以当前的世界状态和交易作为输入，输出下一个世界状态。</p><p>在了解 EVM 之前，需要先理解一下以太坊的账户模型， 与比特币不一样，以太坊中是以账户为基础的存储单元，每个账户都有一个地址，地址之间的账户会引起世界状态的变化。在以太坊中有两种账户类型：</p><ul><li><p>EOA（Externally-owned account）：通常称为外部账户，这类账户由私钥控制</p></li><li><p>合约账户（Contract account）：需要部署合约代码，由代码来控制账户</p></li></ul><p>但是在 EIP-7702 实施之后，EOA 也能附加代码，EOA 和合约账户的界限正在模糊。这两种账户都能持有发送和接收 ETH（以太币），并且都可以与其他的地址进行交互。每个账户都有四个属性：</p><ul><li><p><strong>nonce</strong> ：一个计数器，用来显示 EOA 发送的交易数量或合约帐户创建的合约数量，这个字段主要用来防止重放攻击</p></li><li><p><strong>balance</strong> ：这个地址拥有的 ETH 数量，以 Wei 为单位，每个 ETH 有 1e¹⁸ Wei</p></li><li><p><strong>codeHash</strong> ： 这个字段表示该地址指向的代码，可以被 EVM 执行，默认情况下， EOA 的这个字段为空，但是可以通过 EIP-7702 为 EOA 附加合约代码，合约账户的这个字段不能被修改</p></li><li><p><strong>storageRoot</strong> ： 存储 hash，指向合约的状态存储，默认情况下为空，只有合约中代码执行之后引起了状态的变化，这个字段才会有数据</p></li></ul><p>以太坊的世界状态就是将这些账户的数据通过 MPT 的方式组织起来，EVM 在执行交易之后，会更新这些账户中的数据，然后更新后的数据会引起 MPT 的更新，从而引起每个区块中状态根的变化。也只有 EVM 能够更新世界状态，所以可以把 EVM 看作是以太坊这个状态机的状态转换函数。</p><p>以太坊中可以由 Solidity、Vyper、Yul 等语言来编写合约，合约实际在部署到链上的时候，实际上会被编译成字节码，EVM 也只能处理字节码，而不能直接执行编程语言。字节码在 EVM 中会被解析为具体的操作码，这也是 EVM 最终执行的指令。合约部署到链上之后，合约暴露出来的方法可以被 EOA 账户或者其他其他合约调用，调用合约的过程中就是解析字节码到操作码的过程。</p><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EVM 的主要迭代</h2><p>EVM 从主网上线之后，结构和运行方式基本没有变更，每次硬分叉升级基本都是 EVM 的指令集（opcodes）进行更新，一般是新增指令或者对现有指令的更新。以下是各个硬分叉升级中 EVM 的更新情况：</p><h3 id="h-frontier-london" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Frontier 到 London</h3><p>Frontier（2015 年 7 月）：EVM 的最初版本，包括基础的算术、逻辑、存储、控制等操作，包含 STOP，ADD，MUL，SUB，DIV，KECCAK256，BALANCE，SLOAD，SSTORE，JUMP 等基础指令</p><ul><li><p>Homestead（2016 年 3 月）：新增 DELEGATECALL 指令</p></li><li><p>Tangerine Whistle（2016 年 10 月）：</p><ul><li><p>没有新增指令，提高了 BALANCE，EXTCODESIZE，SLOAD，EXTCO DECOPY，CALL，CALLCODE，DELEGATECALL 的 gas 成本</p></li></ul></li><li><p>Spurious Dragon（2016 年 11 月）：</p><ul><li><p>调整了 EXP 指令的 gas 计算方式</p></li></ul></li><li><p>Byzantium（2017 年 10 月）：</p><ul><li><p>新增 REVERT 指令 — 回滚状态并返回数据</p></li><li><p>新增 STATICCALL 指令 — 静态调用，不允许状态修改</p></li><li><p>新增 RETURNDATASIZE 指令 — 获取返回数据大小</p></li></ul></li><li><p>新增 RETURNDATACOPY 指令 — 复制返回数据</p></li><li><p>Constantinople（2019 年 2 月）：</p><ul><li><p>新增位移操作指令： SHL (左移), SHR (右移), SAR (算术右移)</p></li><li><p>新增 EXTCODEHASH 指令 — 获取合约代码哈希</p></li><li><p>新增 CREATE2 指令 — 确定性合约创建</p></li></ul></li><li><p>Istanbul（2019 年 12 月）：</p><ul><li><p>新增 CHAINID 指令 — 获取链 ID</p></li><li><p>优化了 SSTORE 的 gas 计量</p></li></ul></li><li><p>Berlin（2021 年 04 月）：</p><ul><li><p>引入 AccessList 交易</p></li></ul></li><li><p>London （2021 年 08 月）：</p><ul><li><p>交易手续费机制改变，引入 BASEFEE 指令</p></li></ul></li></ul><h3 id="h-paris-prague" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Paris 到 Prague</h3><ul><li><p>Paris（The Merge 2022 年 9 月）：</p><ul><li><p>将 DIFFICULTY 指令重新定义为 PREVRANDAO，废除挖矿难度机制，用来存储 RANDAO 随机数</p></li></ul></li><li><p>Shanghai（2023 年 4 月）： 新增 PUSH0 指令 — 推送零值到栈</p></li><li><p>Cancun（2024 年 3 月）：</p><ul><li><p>新增 TLOAD 和 TSTORE 指令 — 支持瞬态存储操作</p></li><li><p>新增 BLOBHASH 指令 — 获取 blob 哈希</p></li><li><p>新增 BLOBBASEFEE 指令 — 获取 blob 基础费用</p></li><li><p>新增 MCOPY 指令 — 内存复制操作 修改 SELFDESTRUCT 行为，只在同一交易中生效</p></li></ul></li><li><p>Prague（2025 年 5 月）：</p><ul><li><p>引入 SetCode 交易</p></li></ul></li></ul><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EVM 架构</h2><p>EVM 需要确保每个交易在不同的硬件既不同的操作系统上执行的结果是一致的，由于不同的硬件和不同的操作系统系统对应指令解析以及内存管理等等都不相同，EVM 相当于在计算机和合约之间搭建了一个中间层，EVM 负责执行所有的字节码，然后将数据调用系统的能力保存到此案。 EVM 屏蔽了不同硬件和操作系统的实现，Java 语言中的 JVM 也是同样的原理。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ecd9fb3e0ae04d4556dfdb0dc7b56aa6d528a4d876086af56e6c3aa19fdf69b0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Zoom image will be displayed</p><p>EVM 是以太坊的核心组件，在 core/vm 中实现，EVM 是一个基于栈的虚拟机，负责执行智能合约代码，提供的核心功能包括：</p><ul><li><p>提供交易（包括合约创建交易）的执行环境，管理交易执行的状态和上下文，并输出最后的状态数据更新情况</p></li><li><p>实现所有 EVM 操作码（opcodes）</p></li><li><p>实现 gas 计算和消耗机制</p></li></ul><p>EVM 只负责执行交易和输出新的状态数据，但是不会负责状态变更的保存。EVM 会有多种用途，一种是出块节点用于产出新的区块，新产生的区块会广播到网络中被其他的节点所验证；二是验证区块也需要执行区块中的交易；三是调用 eth_call 等 API 来模拟执行一笔交易的时候，也会使用 EVM 来执行。</p><p>EVM 的生命周期很简单，当需要执行交易时， 就会创建一个 EVM 实例，当交易执行结束之后， EVM 实例被回收。</p><h3 id="h-evm" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">EVM 解释器</h3><p>EVM 指令解释器，在 core/vm/interpreter.go 中定义，会根据当前指令集来执行交易，当前的指令集在 table 中定义，table 会根据当前网络的硬分叉版本来决定。</p><pre data-type="codeBlock" text="type EVMInterpreter struct {  // EVM 实例  evm   *EVM  // EVM 指令集，根据当前的硬分叉版本来选择指令集  table *JumpTable    // 共享的 hash 计算实例  hasher    crypto.KeccakState   // 哈希计算结果的缓冲区  hasherBuf common.Hash      // 只读标识，当设置为 True 时，任何的状态修改都会抛出异常  readOnly   bool     // 存储最近一次 CALL 操作的返回数据，供后续操作使用  returnData []byte} JumpTable 是一个长度为 256 的数组，在 core/vm/jump_table.go 中定义，其中数组元素的类型是 operation，也在 core/vm/jump_tanle.go 中定义， 每个 operation 代表一个具体的指令，新增也是创建一个 operation 实例，目最多支持 256 个指令，目前还有很大的扩展空间，目前已经定义的操作码指令在 core/vm/opcodes.go。
"><code>type <span class="hljs-title class_">EVMInterpreter</span> struct {  <span class="hljs-regexp">//</span> <span class="hljs-variable constant_">EVM</span> 实例  evm   *<span class="hljs-variable constant_">EVM</span>  /<span class="hljs-regexp">/ EVM 指令集，根据当前的硬分叉版本来选择指令集  table *JumpTable    /</span><span class="hljs-regexp">/ 共享的 hash 计算实例  hasher    crypto.KeccakState   /</span><span class="hljs-regexp">/ 哈希计算结果的缓冲区  hasherBuf common.Hash      /</span><span class="hljs-regexp">/ 只读标识，当设置为 True 时，任何的状态修改都会抛出异常  readOnly   bool     /</span><span class="hljs-regexp">/ 存储最近一次 CALL 操作的返回数据，供后续操作使用  returnData []byte} JumpTable 是一个长度为 256 的数组，在 core/vm</span><span class="hljs-regexp">/jump_table.go 中定义，其中数组元素的类型是 operation，也在 core/vm</span><span class="hljs-regexp">/jump_tanle.go 中定义， 每个 operation 代表一个具体的指令，新增也是创建一个 operation 实例，目最多支持 256 个指令，目前还有很大的扩展空间，目前已经定义的操作码指令在 core/vm</span><span class="hljs-regexp">/opcodes.go。
</span></code></pre><pre data-type="codeBlock" text="type JumpTable [256]*operationtype operation struct {  // 指令执行函数  execute     executionFunc  // 固定 gas 消耗  constantGas uint64  // 动态 gas 消耗  dynamicGas  gasFunc  // 最小堆栈要求  minStack int  // 最大堆栈要求  maxStack int  // 内存大小计算  memorySize memorySizeFunc  // 是否为未定义指令  undefined bool} 
"><code>type <span class="hljs-title class_">JumpTable</span> [<span class="hljs-number">256</span>]*operationtype operation struct {  <span class="hljs-regexp">//</span> 指令执行函数  execute     executionFunc  /<span class="hljs-regexp">/ 固定 gas 消耗  constantGas uint64  /</span><span class="hljs-regexp">/ 动态 gas 消耗  dynamicGas  gasFunc  /</span><span class="hljs-regexp">/ 最小堆栈要求  minStack int  /</span><span class="hljs-regexp">/ 最大堆栈要求  maxStack int  /</span><span class="hljs-regexp">/ 内存大小计算  memorySize memorySizeFunc  /</span><span class="hljs-regexp">/ 是否为未定义指令  undefined bool} 
</span></code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">合约抽象</h3><p>合约结构体，在 core/vm/contract.go 中定义，代表对一次合约调用上下文的封装，会在解释器中被执行：</p><p><code>type Contract struct {  // 调用者地址  caller  common.Address  // 合约地址  address common.Address  // 跳转目标缓存  jumpdests map[common.Hash]bitvec  // 本地分析缓存  analysis  bitvec                  // 合约字节码  Code     []byte  // 合约字节码 hash  CodeHash common.Hash  // 输入数据  Input    []byte  // 是否为合约部署  IsDeployment bool  // 是否为合约调用  IsSystemCall bool  // 可用 Gas  Gas   uint64  // 转帐的金额  value *uint256.Int}</code></p><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>EVM 核心数据结构</strong></h2><pre data-type="codeBlock" text="type EVM struct {  // 提供区块链和交易级别的上下文信息  Context BlockContext  TxContext    // 对底层状态信息的访问接口  StateDB StateDB  // 跟踪调用栈深度  depth int  // 当前链配置信息  chainConfig *params.ChainConfig  // 当前环境的规则，用于判断特定 EIP 是否在当前环境中激活  chainRules params.Rules  // 虚拟机配置选项  Config Config  // EVM 字节码解释器  interpreter *EVMInterpreter  // 用于中止 EVM 执行的原子布尔标志  abort atomic.Bool  // 临时存储当前执行可用的 Gas  callGasTemp uint64  // 预编译合约映射表  precompiles map[common.Address]PrecompiledContract  // JUMPDEST 分析结果缓存  jumpDests map[common.Hash]bitvec}
"><code>type <span class="hljs-variable constant_">EVM</span> struct {  <span class="hljs-regexp">//</span> 提供区块链和交易级别的上下文信息  <span class="hljs-title class_">Context</span> <span class="hljs-title class_">BlockContext</span>  <span class="hljs-title class_">TxContext</span>    /<span class="hljs-regexp">/ 对底层状态信息的访问接口  StateDB StateDB  /</span><span class="hljs-regexp">/ 跟踪调用栈深度  depth int  /</span><span class="hljs-regexp">/ 当前链配置信息  chainConfig *params.ChainConfig  /</span><span class="hljs-regexp">/ 当前环境的规则，用于判断特定 EIP 是否在当前环境中激活  chainRules params.Rules  /</span><span class="hljs-regexp">/ 虚拟机配置选项  Config Config  /</span><span class="hljs-regexp">/ EVM 字节码解释器  interpreter *EVMInterpreter  /</span><span class="hljs-regexp">/ 用于中止 EVM 执行的原子布尔标志  abort atomic.Bool  /</span><span class="hljs-regexp">/ 临时存储当前执行可用的 Gas  callGasTemp uint64  /</span><span class="hljs-regexp">/ 预编译合约映射表  precompiles map[common.Address]PrecompiledContract  /</span><span class="hljs-regexp">/ JUMPDEST 分析结果缓存  jumpDests map[common.Hash]bitvec}
</span></code></pre><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>EVM 实现详解</strong></h2><p>在了解完 EVM 的架构和关键组件之后，我们来看一下 EVM 完整的实现，EVM 的核心逻辑是将交易转换成状态变化，然后将状态变化输出给存储模块处理。这个过程，需要依赖多个流程才能实现。首先需要为交易执行提供对应的上下文信息，包括状态数据、合约信息、区块数据等等信息，然后在交易执行的过程中，需要将合约字节码解析成具体的操作码并执行，在执行操作码的过程中，还需要计算交易的 gas 消耗，最后将交易执行完成对状态数据的更新情况返回。</p><h3 id="h-evm" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">EVM 运行时环境</h3><p>首先，EVM 在执行交易之前，需要实例化一个 EVM，EVM 不是线程安全的，所以也不能并行处理交易，每次执行交易前都需要实例化一个 EVM，这样可以确保每个交易的执行都是隔离的，以防会出现在交易执行结果不一致的情况。</p><p>EVM 的运行时环境可以分为两部分：</p><ul><li><p>交易执行所依赖的信息</p><ul><li><p>区块上下文信息：当前的区块的高度、区块 Hash、区块的 Gas Limit、BaseFee、RANDDAO 随机数等等</p></li><li><p>交易上下文信息：交易发起的地址、GasPrice 等等</p></li><li><p>合约代码：以字节码的形式存储在状态数据库中，使用合约地址进行引用</p></li><li><p>EVM 的配置信息</p></li></ul></li><li><p>交易的执行环境</p><ul><li><p>Stack：操作码执行的栈空间，每个合约在 EVM 执行期间，都会初始化一个空的栈，栈是一个 32 字节的数组列表，合约的操作码在执行的过程中，栈用来存储操作码的输入和输出。当交易调用结束的时候，它就会被销毁。目前栈的最大深度是 1024</p></li><li><p>Memory：操作码执行的内存空间，为每个 EVM 合约执行而初始化的一个内存空间，在调用结束之后，内存空间就会被销毁</p></li><li><p>Storage：状态数据的存储，每个合约都有自己的存储，不能直接读取或者修改其他合约的存储内容，只能通过合约调用来读取。依赖存储模块提供的 StateDB 模块来实现，EVM 只负责更新相关数据，不负责持久化存储</p></li><li><p>Calldata：交易传入的参数，calldata 区域是作为交易的一部分，如果是普通的合约调用，其中就是调用合约所需要的参数，如果是部署一个新的合约，calldata 中就是合约字节码信息以及合约构造函数的参数</p></li><li><p>Return Data：合约调用所返回的数据，可以通过 RETURN 或者 REVERT 操作码来返回</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">区块上下文信息</h3><p>BlockContext 结构体在 core/vm/evm.go 中定义，在初始化 EVM 实例时，在执行区块中的交易之前，会通过 core/evm.go 中的 NewEVMBlockContext 函数来负责初始化区块下文信息。</p><pre data-type="codeBlock" text="type BlockContext struct {    // 转账相关函数    CanTransfer CanTransferFunc  // 检查账户是否有足够余额进行转账    Transfer    TransferFunc     // 执行账户间的以太币转移    GetHash     GetHashFunc      // 获取指定区块号的哈希值        // 区块信息（对应EVM指令）    Coinbase    common.Address   // 矿工地址    GasLimit    uint64          // 区块Gas限制    BlockNumber *big.Int        // 区块号    Time        uint64          // 区块时间戳    Difficulty  *big.Int        // 区块难度（当前已经无用）    BaseFee     *big.Int        // EIP-1559 基础费用    BlobBaseFee *big.Int        // EIP-4844 Blob 基础费用    Random      *common.Hash    // 随机数} 
"><code>type <span class="hljs-title class_">BlockContext</span> struct {    <span class="hljs-regexp">//</span> 转账相关函数    <span class="hljs-title class_">CanTransfer</span> <span class="hljs-title class_">CanTransferFunc</span>  /<span class="hljs-regexp">/ 检查账户是否有足够余额进行转账    Transfer    TransferFunc     /</span><span class="hljs-regexp">/ 执行账户间的以太币转移    GetHash     GetHashFunc      /</span><span class="hljs-regexp">/ 获取指定区块号的哈希值        /</span><span class="hljs-regexp">/ 区块信息（对应EVM指令）    Coinbase    common.Address   /</span><span class="hljs-regexp">/ 矿工地址    GasLimit    uint64          /</span><span class="hljs-regexp">/ 区块Gas限制    BlockNumber *big.Int        /</span><span class="hljs-regexp">/ 区块号    Time        uint64          /</span><span class="hljs-regexp">/ 区块时间戳    Difficulty  *big.Int        /</span><span class="hljs-regexp">/ 区块难度（当前已经无用）    BaseFee     *big.Int        /</span><span class="hljs-regexp">/ EIP-1559 基础费用    BlobBaseFee *big.Int        /</span><span class="hljs-regexp">/ EIP-4844 Blob 基础费用    Random      *common.Hash    /</span><span class="hljs-regexp">/ 随机数} 
</span></code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">交易上下文信息</h3><p>TxContext 结构体在 core/vm/evm.go 中定义，包含特定于单个交易的信息，每个交易都有独立的上下文， 通过 core/evm.go 中的 NewEVMTxContext 函数来负责创建交易的上下文信息：</p><pre data-type="codeBlock" text="type TxContext struct {    Origin       common.Address      //  交易发起者地址    GasPrice     *big.Int           //  有效Gas价格    BlobHashes   []common.Hash      // EIP-4844 Blob哈希列表    BlobFeeCap   *big.Int          // Blob 费用上限    AccessEvents *state.AccessEvents // EIP-4762 状态访问事件记录} 
"><code><span class="hljs-keyword">type</span> TxContext <span class="hljs-keyword">struct</span> {    Origin       common.Address      <span class="hljs-comment">//  交易发起者地址    GasPrice     *big.Int           //  有效Gas价格    BlobHashes   []common.Hash      // EIP-4844 Blob哈希列表    BlobFeeCap   *big.Int          // Blob 费用上限    AccessEvents *state.AccessEvents // EIP-4762 状态访问事件记录} </span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">合约代码</h3><p>在执行合约时，会根据需要调用的合约地址，从 StateDB 的接口中加载对应的合约代码，然后使用 EVM interpreter 执行：</p><pre data-type="codeBlock" text="// 根据合约地址加载合约代码  contract := NewContract(caller, caller, value, gas, evm.jumpDests)  contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))  ret, err = evm.interpreter.Run(contract, input, false)
"><code>// 根据合约地址加载合约代码  contract := <span class="hljs-built_in">NewContract</span>(caller, caller, value, gas, evm.jumpDests)  contract.<span class="hljs-built_in">SetCallCode</span>(evm.<span class="hljs-built_in">resolveCodeHash</span>(addr), evm.<span class="hljs-built_in">resolveCode</span>(addr))  ret, err = evm.interpreter.<span class="hljs-built_in">Run</span>(contract, input, false)
</code></pre><p>在 EVM 的结构体中 有一个 jumpDests 字段，在执行 JUMP 和 JUMPI 操作码时，EVM 需要验证跳转目标是否为有效的 JUMPDEST 指令。通过 jumpDests 可以快速查找已分析的结果。jumpDests 会懒加载，只有在第一次需要验证跳转目标时才进行分析：</p><pre data-type="codeBlock" text="func (c *Contract) isCode(udest uint64) bool {    // ...     if c.CodeHash != (common.Hash{}) {        analysis, exist := c.jumpdests[c.CodeHash]        if !exist {            // 首次分析该合约代码            analysis = codeBitmap(c.Code)            c.jumpdests[c.CodeHash] = analysis        }        c.analysis = analysis        return analysis.codeSegment(udest)    }    // ... } 
"><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Contract)</span></span> isCode(udest <span class="hljs-type">uint64</span>) <span class="hljs-type">bool</span> {    <span class="hljs-comment">// ...     if c.CodeHash != (common.Hash{}) {        analysis, exist := c.jumpdests[c.CodeHash]        if !exist {            // 首次分析该合约代码            analysis = codeBitmap(c.Code)            c.jumpdests[c.CodeHash] = analysis        }        c.analysis = analysis        return analysis.codeSegment(udest)    }    // ... } </span>
</code></pre><h3 id="h-stack" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Stack</h3><p>Stack 是 EVM 的核心数据结构，在 core/vm/stack.go 中定义，栈使用 uint256.Int 类型的切片来存储数据，这是因为 EVM 使用 256 位整数作为基本数据类型，用于存储操作码执行时的操作数和中间结果。EVM 是基于栈的虚拟机，所有的计算都通过栈操作完成。</p><pre data-type="codeBlock" text="type Stack struct {  data []uint256.Int} Stack 
"><code><span class="hljs-keyword">type</span> Stack <span class="hljs-keyword">struct</span> {  data []<span class="hljs-keyword">uint256</span>.Int} Stack 
</code></pre><p>采用了对象池来优化性能：</p><pre data-type="codeBlock" text="var stackPool = sync.Pool{    New: func() interface{} {        return &amp;Stack{data: make([]uint256.Int, 0, 16)}    },}func newstack() *Stack {    return stackPool.Get().(*Stack)}func returnStack(s *Stack) {    s.data = s.data[:0]    stackPool.Put(s)} 
"><code><span class="hljs-keyword">var</span> stackPool <span class="hljs-operator">=</span> sync.Pool{    New: func() <span class="hljs-class"><span class="hljs-keyword">interface</span></span>{} {        <span class="hljs-keyword">return</span> <span class="hljs-operator">&#x26;</span>Stack{data: make([]<span class="hljs-keyword">uint256</span>.Int, <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)}    },}func newstack() <span class="hljs-operator">*</span>Stack {    <span class="hljs-keyword">return</span> stackPool.Get().(<span class="hljs-operator">*</span>Stack)}func returnStack(s <span class="hljs-operator">*</span>Stack) {    s.data <span class="hljs-operator">=</span> s.data[:<span class="hljs-number">0</span>]    stackPool.Put(s)} 
</code></pre><p>Stack 在 EVM interprter 执行合约代码时创建，具体在 Run 方法中，和内存、合约信息一起作为本次合约组成一个 ScopeContext 实例，作为本次执行的上下文：</p><pre data-type="codeBlock" text="func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {    // ...     var (        op          OpCode        // 当前的操作码        mem         = NewMemory() // 内存        stack       = newstack()  // 在这里新建栈        callContext = &amp;ScopeContext{ // 和内存、合约信息一起作为本次合约执行的上下文            Memory:   mem,            Stack:    stack,            Contract: contract,        }        // ...     )        defer func() {        returnStack(stack)        mem.Free()    }()    // ... } 
"><code>func (in <span class="hljs-operator">*</span>EVMInterpreter) Run(<span class="hljs-class"><span class="hljs-keyword">contract</span> *<span class="hljs-title">Contract</span>, <span class="hljs-title">input</span> []<span class="hljs-title"><span class="hljs-keyword">byte</span></span>, <span class="hljs-title">readOnly</span> <span class="hljs-title"><span class="hljs-keyword">bool</span></span>) (<span class="hljs-params">ret []<span class="hljs-keyword">byte</span>, err <span class="hljs-keyword">error</span></span>) </span>{    <span class="hljs-comment">// ...     var (        op          OpCode        // 当前的操作码        mem         = NewMemory() // 内存        stack       = newstack()  // 在这里新建栈        callContext = &#x26;ScopeContext{ // 和内存、合约信息一起作为本次合约执行的上下文            Memory:   mem,            Stack:    stack,            Contract: contract,        }        // ...     )        defer func() {        returnStack(stack)        mem.Free()    }()    // ... } </span>
</code></pre><p>Stack 提供了以下基本操作：</p><ul><li><p>Push 操作 ：将数据压入栈顶</p></li><li><p>Pop 操作 ：从栈顶弹出数据 Peek 操作 ：查看栈顶数据但不弹出</p></li><li><p>Swap 操作 ：交换栈中不同位置的元素</p></li><li><p>Dup 操作 ：复制栈中的元素</p></li></ul><h3 id="h-memory" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Memory</h3><p>EVM 中的内存是通过 core/vm/memory.go 文件中的 Memory 结构体实现，内存使用字节切片 store 来存储数据， lastGasCost 字段用于跟踪上次内存扩展的 gas 成本，用于计算增量 gas 费用。</p><pre data-type="codeBlock" text="type Memory struct {  store       []byte  lastGasCost uint64 
"><code><span class="hljs-keyword">type</span> Memory <span class="hljs-keyword">struct</span> {  store       []<span class="hljs-type">byte</span>  lastGasCost <span class="hljs-type">uint64</span> 
</code></pre><p>Memory 同样采用了对象池来优化性能，与 Stack 的创建时机相同：</p><pre data-type="codeBlock" text="var memoryPool = sync.Pool{    New: func() any {        return &amp;Memory{}    },}func NewMemory() *Memory {    return memoryPool.Get().(*Memory)}func (m *Memory) Free() {    // To reduce peak allocation, return only smaller memory instances to the pool.    const maxBufferSize = 16 &lt;&lt; 10    if cap(m.store) &lt;= maxBufferSize {        m.store = m.store[:0]        m.lastGasCost = 0        memoryPool.Put(m)    }} 
"><code><span class="hljs-keyword">var</span> memoryPool <span class="hljs-operator">=</span> sync.Pool{    New: func() any {        <span class="hljs-keyword">return</span> <span class="hljs-operator">&#x26;</span>Memory{}    },}func NewMemory() <span class="hljs-operator">*</span>Memory {    <span class="hljs-keyword">return</span> memoryPool.Get().(<span class="hljs-operator">*</span>Memory)}func (m <span class="hljs-operator">*</span>Memory) Free() {    <span class="hljs-comment">// To reduce peak allocation, return only smaller memory instances to the pool.    const maxBufferSize = 16 &#x3C;&#x3C; 10    if cap(m.store) &#x3C;= maxBufferSize {        m.store = m.store[:0]        m.lastGasCost = 0        memoryPool.Put(m)    }} </span>
</code></pre><p>Memory 是 EVM 的临时存储区域，用于：</p><ul><li><p>存储函数调用的参数和返回值</p></li><li><p>保存中间计算结果</p></li><li><p>处理动态数据（如字符串、数组）</p></li><li><p>作为操作码之间的数据传递媒介</p></li></ul><h3 id="h-gas" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Gas 计算机制</h3><p>EVM 会使用 Gas 来衡量交易所消耗的计算资源，每个操作都会消耗 Gas，这些 Gas 会从交易的 Gas limit 中扣取，这里需要注意，不同操作所消耗的 gas 不一样。而且既有固定 Gas 成本的操作，也有动态 Gas 成本的操作。</p><p>EVM 中的 gas 计算采用分层设计，主要包括以下几个组成部分：</p><ul><li><p>静态 gas (constantGas) ：固定消耗，在操作码定义时确定</p></li><li><p>动态 gas (dynamicGas) ：根据运行时状态计算，如内存扩展、存储访问等</p></li><li><p>内存扩展 gas ：内存使用量增加时的二次方成本</p></li><li><p>访问列表 gas ：EIP-2929 引入的冷/热访问机制，通过预先声明访问的地址和存储槽来降低 gas 成本</p></li><li><p>如果是转帐交易或者预编译合约调用，会独立进行 gas 计算</p></li></ul><p>gas 的计算流程在 core/vm/interpreter.go 的 Run 方法主执行循环中，Gas 计算按以下顺序进行：</p><p>首先扣除操作码执行的静态 gas：</p><pre data-type="codeBlock" text="// 获取操作码和对应的操作定义op = contract.GetOp(pc)operation := in.table[op]cost = operation.constantGas // 静态Gas// 检查Gas是否足够并扣除if contract.Gas &lt; cost {    return nil, ErrOutOfGas} else {    contract.Gas -= cost} 
"><code>// 获取操作码和对应的操作定义op = contract.GetOp(pc)operation := <span class="hljs-keyword">in</span>.<span class="hljs-built_in">table</span>[op]cost = operation.constantGas // 静态Gas// 检查Gas是否足够并扣除<span class="hljs-keyword">if</span> contract.Gas &#x3C; cost {    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, ErrOutOfGas} <span class="hljs-keyword">else</span> {    contract.Gas -= cost} 
</code></pre><p>然后计算执行操作码过程中的动态 gas，如果一个操作码有动态 gas 的部分，会在这里根据执行的情况来计算：</p><pre data-type="codeBlock" text="if operation.dynamicGas != nil {    // 计算内存大小    if operation.memorySize != nil {        memSize, overflow := operation.memorySize(stack)        if overflow {            return nil, ErrGasUintOverflow        }        if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {            return nil, ErrGasUintOverflow        }    }        // 计算动态Gas    var dynamicCost uint64    dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)    cost += dynamicCost        // 检查并扣除动态Gas    if contract.Gas &lt; dynamicCost {        return nil, ErrOutOfGas    } else {        contract.Gas -= dynamicCost    }} 
"><code><span class="hljs-keyword">if</span> operation.dynamicGas != <span class="hljs-literal">nil</span> {    // 计算内存大小    <span class="hljs-keyword">if</span> operation.memorySize != <span class="hljs-literal">nil</span> {        memSize, overflow := operation.memorySize(stack)        <span class="hljs-keyword">if</span> overflow {            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, ErrGasUintOverflow        }        <span class="hljs-keyword">if</span> memorySize, overflow = <span class="hljs-built_in">math</span>.SafeMul(toWordSize(memSize), <span class="hljs-number">32</span>); overflow {            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, ErrGasUintOverflow        }    }        // 计算动态Gas    var dynamicCost uint64    dynamicCost, err = operation.dynamicGas(<span class="hljs-keyword">in</span>.evm, contract, stack, mem, memorySize)    cost += dynamicCost        // 检查并扣除动态Gas    <span class="hljs-keyword">if</span> contract.Gas &#x3C; dynamicCost {        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, ErrOutOfGas    } <span class="hljs-keyword">else</span> {        contract.Gas -= dynamicCost    }} 
</code></pre><p>其中动态内存扩展的成本会按照指数成长，在 core/vm/gas_table.go 中实现：</p><pre data-type="codeBlock" text="func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {    if newMemSize == 0 {        return 0, nil    }        // 防止溢出检查    if newMemSize &gt; 0x1FFFFFFFE0 {        return 0, ErrGasUintOverflow    }        newMemSizeWords := toWordSize(newMemSize)    newMemSize = newMemSizeWords * 32    if newMemSize &gt; uint64(mem.Len()) {        square := newMemSizeWords * newMemSizeWords        linCoef := newMemSizeWords * params.MemoryGas  // 线性部分：3 * words        quadCoef := square / params.QuadCoeffDiv       // 二次方部分：words² / 512        newTotalFee := linCoef + quadCoef        fee := newTotalFee - mem.lastGasCost  // 只收取增量部分        mem.lastGasCost = newTotalFee        return fee, nil    }    return 0, nil} 
"><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">memoryGasCost</span><span class="hljs-params">(mem *Memory, newMemSize <span class="hljs-type">uint64</span>)</span></span> (<span class="hljs-type">uint64</span>, <span class="hljs-type">error</span>) {    <span class="hljs-keyword">if</span> newMemSize == <span class="hljs-number">0</span> {        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>, <span class="hljs-literal">nil</span>    }        <span class="hljs-comment">// 防止溢出检查    if newMemSize > 0x1FFFFFFFE0 {        return 0, ErrGasUintOverflow    }        newMemSizeWords := toWordSize(newMemSize)    newMemSize = newMemSizeWords * 32    if newMemSize > uint64(mem.Len()) {        square := newMemSizeWords * newMemSizeWords        linCoef := newMemSizeWords * params.MemoryGas  // 线性部分：3 * words        quadCoef := square / params.QuadCoeffDiv       // 二次方部分：words² / 512        newTotalFee := linCoef + quadCoef        fee := newTotalFee - mem.lastGasCost  // 只收取增量部分        mem.lastGasCost = newTotalFee        return fee, nil    }    return 0, nil} </span>
</code></pre><p>在计算 gas 的过程中，如果交易中的 gas 不够，就会报 ErrOutOfGas。</p><p>EVM 的 Gas 计算是一个精密的经济模型，通过不同的定价策略来，在保证网络安全的同时，提供了可预测的执行成本：</p><ul><li><p>防止 DoS 攻击 ：为计算资源定价</p></li><li><p>激励优化 ：昂贵操作促使开发者优化</p></li><li><p>网络平衡 ：通过动态定价调节网络负载</p></li><li><p>安全保障 ：防止无限循环和资源滥用</p></li></ul><h3 id="h-statedb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">StateDB 接口</h3><p>在 /core/vm/interface.go 中定义了 EVM 在状态读写中所需要的接口，通过 StateDB 接口，EVM 实现了对状态存储的解耦，EVM 可以通过 StateDB 来获取执行交易所需要的状态数据，然后输出交易执行完成之后状态的变化情况，后续状态变化的持久化操作完全由存储模块来完成。</p><pre data-type="codeBlock" text="type StateDB interface {  // 账户管理  CreateAccount(common.Address)  CreateContract(common.Address)  // 余额操作  SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int  AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int  GetBalance(common.Address) *uint256.Int  // Nonce 管理  GetNonce(common.Address) uint64  SetNonce(common.Address, uint64, tracing.NonceChangeReason)  // 合约代码管理   GetCodeHash(common.Address) common.Hash  GetCode(common.Address) []byte  // 为地址设置代码  SetCode(common.Address, []byte) []byte  GetCodeSize(common.Address) int  //......}
"><code>type <span class="hljs-title class_">State</span>DB interface {  <span class="hljs-regexp">//</span> 账户管理  <span class="hljs-title class_">CreateAccount</span>(common.<span class="hljs-title class_">Address</span>)  <span class="hljs-title class_">CreateContract</span>(common.<span class="hljs-title class_">Address</span>)  /<span class="hljs-regexp">/ 余额操作  SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int  AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int  GetBalance(common.Address) *uint256.Int  /</span><span class="hljs-regexp">/ Nonce 管理  GetNonce(common.Address) uint64  SetNonce(common.Address, uint64, tracing.NonceChangeReason)  /</span><span class="hljs-regexp">/ 合约代码管理   GetCodeHash(common.Address) common.Hash  GetCode(common.Address) []byte  /</span><span class="hljs-regexp">/ 为地址设置代码  SetCode(common.Address, []byte) []byte  GetCodeSize(common.Address) int  /</span><span class="hljs-regexp">/......}
</span></code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">错误处理机制</h3><p>以下是 EVM 中的错误定义，在 EVM 执行交易的过程中，如果出现不符合 EVM 规范，就会抛出以下错误。其中 out of gas 是日常最常见的错误，如果在调用合约的交易中，如果 gas limit 设置的不够，就会出现这个错误。</p><pre data-type="codeBlock" text="var (  ErrOutOfGas                 = errors.New(&quot;out of gas&quot;)  ErrCodeStoreOutOfGas        = errors.New(&quot;contract creation code storage out of gas&quot;)  ErrDepth                    = errors.New(&quot;max call depth exceeded&quot;)  ErrInsufficientBalance      = errors.New(&quot;insufficient balance for transfer&quot;)  ErrContractAddressCollision = errors.New(&quot;contract address collision&quot;)  ErrExecutionReverted        = errors.New(&quot;execution reverted&quot;)  ErrMaxCodeSizeExceeded      = errors.New(&quot;max code size exceeded&quot;)  ErrMaxInitCodeSizeExceeded  = errors.New(&quot;max initcode size exceeded&quot;)  ErrInvalidJump              = errors.New(&quot;invalid jump destination&quot;)  ErrWriteProtection          = errors.New(&quot;write protection&quot;)  ErrReturnDataOutOfBounds    = errors.New(&quot;return data out of bounds&quot;)  ErrGasUintOverflow          = errors.New(&quot;gas uint64 overflow&quot;)  ErrInvalidCode              = errors.New(&quot;invalid code: must not begin with 0xef&quot;)  ErrNonceUintOverflow        = errors.New(&quot;nonce uint64 overflow&quot;)  // 用于控制内部循环终止，不会对外部调用开放  errStopToken = errors.New(&quot;stop token&quot;)) 
"><code><span class="hljs-keyword">var</span> (  ErrOutOfGas                 <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"out of gas"</span>)  ErrCodeStoreOutOfGas        <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"contract creation code storage out of gas"</span>)  ErrDepth                    <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"max call depth exceeded"</span>)  ErrInsufficientBalance      <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"insufficient balance for transfer"</span>)  ErrContractAddressCollision <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"contract address collision"</span>)  ErrExecutionReverted        <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"execution reverted"</span>)  ErrMaxCodeSizeExceeded      <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"max code size exceeded"</span>)  ErrMaxInitCodeSizeExceeded  <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"max initcode size exceeded"</span>)  ErrInvalidJump              <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"invalid jump destination"</span>)  ErrWriteProtection          <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"write protection"</span>)  ErrReturnDataOutOfBounds    <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"return data out of bounds"</span>)  ErrGasUintOverflow          <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"gas uint64 overflow"</span>)  ErrInvalidCode              <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"invalid code: must not begin with 0xef"</span>)  ErrNonceUintOverflow        <span class="hljs-operator">=</span> errors.New(<span class="hljs-string">"nonce uint64 overflow"</span>)  <span class="hljs-comment">// 用于控制内部循环终止，不会对外部调用开放  errStopToken = errors.New("stop token")) </span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">预编译合约</h3><p>预编译合约（Precompiled Contracts）是 EVM 中一种特殊的合约类型，它们以原生 Go 代码实现，而不是以字节码形式存在，预编译合约通常是为密码学相关操作提供标准实现，会比 EVM 字节码执行的速度更快，并且在执行过程中，gas 的消耗是可以预测的。预编译合约都定义在 core/vm/contracts.go 中，预编译合约都需要实现 PrecompiledContract 接口：</p><pre data-type="codeBlock" text="type PrecompiledContract interface {  // 计算预编译合约需要消耗的 gas  RequiredGas(input []byte) uint64  // 运行预编译合约   Run(input []byte) ([]byte, error)} 
"><code><span class="hljs-built_in">type</span> PrecompiledContract interface {  // 计算预编译合约需要消耗的 gas  RequiredGas(<span class="hljs-built_in">input</span> []<span class="hljs-built_in">byte</span>) uint64  // 运行预编译合约   Run(<span class="hljs-built_in">input</span> []<span class="hljs-built_in">byte</span>) ([]<span class="hljs-built_in">byte</span>, <span class="hljs-built_in">error</span>)} 
</code></pre><p>以太坊地址是 20 字节（160 位）的十六进制数，从 0x0000000000000000000000000000000000000000 到 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF。</p><p>低地址空间（Low Address Space）通常指接近零的小数值地址，这些地址会专门分配给预编译合约，在 Prague 升级之前，地址从 0x01 到 0x11 都已经分配给了预编译合约，具体在 core/vm/contracts.go 中可见，后续可能会接着这些地址继续增加行的预编译合约。</p><p>高地址空间（High Address Space）通常指数值较大的地址，通常是经过计算生成的复杂地址，用于普通合约地址、EOA 地址、系统合约地址等等。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">系统调用</h3><p>在以太坊协议中，系统调用指的是以太坊协议中由系统账户（0xfffffffffffffffffffffffffffffffffffffffe）发起的交易，而不是由普通用户发起，并且系统调用通常用于实现特定的协议功能，通常会在处理区块的特定阶段执行。</p><p>常见的几个系统调用合约：</p><ul><li><p>信标链根处理（EIP-4788）：ProcessBeaconBlockRoot 函数实现了EIP-4788，它将信标链区块根写入到一个特殊的预编译合约中。这使得以太坊执行层可以访问共识层（信标链）的信息</p></li><li><p>父区块哈希处理 （EIP-2935/7709）：ProcessParentBlockHash 函数实现了EIP-2935/7709，它将父区块哈希存储在历史存储合约中，使智能合约能够访问历史区块哈希</p></li><li><p>提款队列处理（EIP-7002）：ProcessWithdrawalQueue 函数处理EIP-7002中定义的提款队列，用于处理从共识层到执行层的提款请求</p></li><li><p>合并队列处理 （EIP-7251）：ProcessConsolidationQueue 函数处理EIP-7251中定义的合并队列</p></li></ul><p>系统调用交易和普通交易不一样：</p><ul><li><p>它们由系统账户（ params.SystemAddress ）发起，而非普通用户</p></li><li><p>它们不消耗实际的 gas（使用固定的大量 gas 限制，目前是 30,000,000）</p></li><li><p>它们在区块处理的特定阶段执行，而不是作为普通交易</p></li></ul><p>预编译合约的执行和系统调用会按照不同的路径来执行，如果是预编译合约，会直接调用预编译合约的 Run 相关的方法来执行合约，系统调用依然需要加载合约代码并执行。</p><pre data-type="codeBlock" text="// 检查是否为预编译合约p, isPrecompile := evm.precompile(addr)if isPrecompile {    // 直接调用预编译合约    ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)} else {    // 创建合约实例并设置是否是系统调用标志    contract := NewContract(caller, addr, value, gas, evm.jumpDests)    contract.IsSystemCall = isSystemCall(caller)  // 根据调用者判断是否为系统调用    contract.SetCallCode(evm.resolveCodeHash(addr), code)    ret, err = evm.interpreter.Run(contract, input, false)} 
"><code>// 检查是否为预编译合约<span class="hljs-selector-tag">p</span>, isPrecompile := evm.<span class="hljs-built_in">precompile</span>(addr)if isPrecompile {    // 直接调用预编译合约    ret, gas, err = RunPrecompiledContract(<span class="hljs-selector-tag">p</span>, <span class="hljs-selector-tag">input</span>, gas, evm<span class="hljs-selector-class">.Config</span><span class="hljs-selector-class">.Tracer</span>)} else {    // 创建合约实例并设置是否是系统调用标志    contract := <span class="hljs-built_in">NewContract</span>(caller, addr, value, gas, evm.jumpDests)    contract.IsSystemCall = <span class="hljs-built_in">isSystemCall</span>(caller)  // 根据调用者判断是否为系统调用    contract.<span class="hljs-built_in">SetCallCode</span>(evm.<span class="hljs-built_in">resolveCodeHash</span>(addr), code)    ret, err = evm.interpreter.<span class="hljs-built_in">Run</span>(contract, input, false)} 
</code></pre><h2 id="h-evm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EVM 运行流程源码分析</h2><p>在上文我们已经完成对整个 EVM 的设计和关键组件的实现有了详细的了解，下面来看一下 EVM 执行一笔交易的完整流程。如果从用户提交一笔交易，从交易进入交易池到被 EVM 执行完成打包进区块的完整流程如下：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c3b692ceddbe3b6d2fd41a718861d0729cf221a16ab17582d1c78900c6506758.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果把以太坊可以看作是一个交易区块的状态机，那么 EVM 就可以看作是其中的状态转换函数，只能由交易来驱动状态的转变。交易执行的起点在 core/state_processor.go 中，通过 ApplyTransaction 方法或者 Process 作为入口，让 EVM 开始处理交易，这两个方法是 EVM 的入口：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/56d510f6c3889a7ea5f7a8168f0dbd8ec64e2e151776a09459fc3210d4d9257f.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>ApplyTransaction 和 Process 都可以作为交易的入口，但是适用的场景不一样：</p><ul><li><p>Process：Process 用于区块导入和验证过程，而且需要准备整个区块的执行的环境，包括 EVM 实例、区块上下文等等，处理执行交易外，还需要处理区块奖励、系统调用等等内容</p></li><li><p>ApplyTransaction：可用于模拟交易执行、测试或交易池验证，在调用 ApplyTransaction 时，需要实例化 EVM，并且准备好交易的上下文，只会执行单个交易，不涉及其他区块级别的操作</p></li><li><p>Process 和 ApplyTransaction 内部都会调用 ApplyTransactionWithEVM 来执行交易</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">区块打包或者验证流程</h3><p>这个流程由 core/state_processor.go 中的 Process 方法来完成。在执行交易之前，需要先初始化执行交易所需要的上下文，主要包括区块信息，包括区块头、区块 hash、区块号、gas 池（初始容量为区块的 Gas 上限）、签名验证器（用于验证签名和恢复交易发送者的地址）：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d2f70db27e2405a7d0b3c3411c7595a1ef5a358a1c55d1eee3d663f1185f65c0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后打开 StateDB，并且创建 EVM 实例：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3813dc022744491f6ccbc34c287eb0e6b693d15e75d42e5340576df9e99f8b2d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在执行环境准备完成之后，就会开始处理区块的具体业务逻辑，首先需要处理系统调用，首先处理信标链根（EIP-4788）将信标链（Beacon Chain）的区块根存储到执行层的预编译合约中，然后将父区块哈希存储到历史存储合约中，提供区块哈希的历史查询功能（EIP-2935/EIP-7709）。</p><p>处理完系统调用，之后，然后就会开始处理区块的中的交易，交易会按照区块中交易的打包顺序执行，在取出一笔交易之后，首先会将交易转换为 Message，并从 stateDB 中加载交易所需要的状态数据：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/203b676c0209e9ac6dcc915632c932b3a87c0323aa71e420f7fcec06d9a384a0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后进入 ApplyTransactionWithEVM 方法中执行交易。在这个方法中会调用 ApplyMessage 函数来执行具体的交易，并且在这个函数中会设置 EVM 交易的上下文信息：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/842269960096da527bfd8c6b95d8aac26d74b69753a609d7cafdb6feda646ec6.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后在 execute 方法中调用 EVM 的 Call 方法，Call 方法中会根据交易的具体情况，是单纯的转帐交易还是调用预编译合约或者是调用合约，然后做出对应的执行操作：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2c161ad1c2f96ed7e3ef6759d638ce35377d2ff8f331725875dbe3e811a81472.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果是预编译合约，那么就会去执行预编译合约，如果是合约调用，就会调用 EVM Interpreter 的 Run 方法去执行具体的合约：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/074067fb0adb710b915703d46a4e7c3f0e1059e3a328f1000f3f75787e540440.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在交易执行过程中，会按照上文所说的 gas 计算流程去计算 gas，在交易执行完成之后，如果用户的 gas 还没有用完，就会退还用户的 gas：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/311ed02a79c4d71d41322e0b6a8cc074329cab83540b45e28b0137734be2c3c3.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-applytransaction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">ApplyTransaction 流程</h3><p>这个流程相对简单，只需要将单笔交易转换成 Message，然后调用 AplyTransactionWithEVM 方法执行交易，具体流程和上文一致，只是在这种情况下，不需次去恶意处理系统调用等信息，而且，对于返回的状态变更结果也不需要持久化保存，所以这个流程可以看作是验证交易的有效性。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0cf8e51fc9f4f957a617be976d4d4fba314242f4e54888ff0768166fc6c2c157.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>EVM 作为以太坊这个交易驱动的状态机中的状态转换函数，只负责交易的执行，执行交易之后会输出对应的状态变更，但是 EVM 本身不参与状态的持久化存储。可以使用 Solidity 或者其他语言来编写合约，最终合约部署时，会将合约代码编译成字节码部署到链上，EVM 也读取字节码并根据调用情况解析成对应的操作码来执行。EVM 屏蔽了不同的硬件和操作系统，确保合约字节码的执行在任何地方都能保持一致，相当于在各个不同的计算平台上添加了一个中间层。以太坊中的 EVM 不是线程安全的，交易也无法在 EVM 中并行执行。虽然当前有 RISC-V 等 EVM 的替代方案正在讨论中，但 EVM 依然是目前最主流并且经过长时间线上验证过的方案。</p><h2 id="h-ref" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ref</h2><p>[1]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/zh/developers/docs/evm/">https://ethereum.org/zh/developers/docs/evm/</a></p><p>[2]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b">https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b</a></p><p>[3]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://notes.ethereum.org/@ipsilon/why-evm-has-jumpdest">https://notes.ethereum.org/@ipsilon/why-evm-has-jumpdest</a></p><p>[4]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://epf.wiki/#/wiki/EL/evm">https://epf.wiki/#/wiki/EL/evm</a></p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Geth 源码系列：交易设计及实现]]></title>
            <link>https://paragraph.com/@lxdao/geth-2</link>
            <guid>ua5xfqdAZMU5vRhk5La1</guid>
            <pubDate>Wed, 25 Jun 2025 13:20:40 GMT</pubDate>
            <description><![CDATA[本文作为 Geth 源码解析系列的第四篇，将穿透表层流程，深入交易生命周期的每个环节：从签名验证到交易池的双层架构（BlobPool 与 LegacyPool 的设计精要）从动态费率机制（EIP-1559）到负载均衡实现（BaseFee 的弹性调节逻辑）从广播打包的 P2P 传播优化到 EVM 执行的底层状态修改（StateDB 的脏状态追踪） 我们结合最新 Pectra 升级特性（如 SetCodeTxType 交易），逐层剖析源码中的关键实现： TxData 接口如何统一五类交易？ 交易池的 LazyTransaction 如何兼顾内存安全与效率？ 共识层与执行层如何通过 engineAPI 协同构建区块？ 本地交易为何享有打包优先级？ 无论你是希望理解交易池抗 DDoS 的驱逐机制，还是探究 ApplyTransaction 如何驱动状态机，本文将为你在源码级透视以太坊的交易引擎提供完整框架交易简介以太坊执行层可以看作是一个交易驱动的状态机，交易是唯一修改状态的方式。交易只能由 EOA 发起，并且会在交易中附加私钥的签名，交易执行之后就会更新以太坊网络的状态。以太坊网络中最...]]></description>
            <content:encoded><![CDATA[<p><strong>本文作为 Geth 源码解析系列的第四篇，将穿透表层流程，深入交易生命周期的每个环节：从签名验证到交易池的双层架构（BlobPool 与 LegacyPool 的设计精要）从动态费率机制（EIP-1559）到负载均衡实现（BaseFee 的弹性调节逻辑）从广播打包的 P2P 传播优化到 EVM 执行的底层状态修改（StateDB 的脏状态追踪）</strong></p><p>我们结合最新 Pectra 升级特性（如 SetCodeTxType 交易），逐层剖析源码中的关键实现：</p><p>TxData 接口如何统一五类交易？</p><p>交易池的 LazyTransaction 如何兼顾内存安全与效率？</p><p>共识层与执行层如何通过 engineAPI 协同构建区块？</p><p>本地交易为何享有打包优先级？</p><p>无论你是希望理解交易池抗 DDoS 的驱逐机制，还是探究 ApplyTransaction 如何驱动状态机，本文将为你在源码级透视以太坊的交易引擎提供完整框架</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>交易简介</strong></h2><p>以太坊执行层可以看作是一个交易驱动的状态机，交易是唯一修改状态的方式。交易只能由 EOA 发起，并且会在交易中附加私钥的签名，交易执行之后就会更新以太坊网络的状态。以太坊网络中最简单的交易就是 ETH 的转账，从一个帐户转帐到另一个帐户。</p><p>这里需要说明一下，随着 EOA 通过 EIP-7702 的升级可以支持合约的能力，以后合约账户和 EOA 的概念会逐渐模糊，但在当前的版本中，还是认为只有私钥控制的 EOA 才能发起交易。</p><p>以太坊支持不同类型的交易，在以太坊主网刚上线时，只支持一种交易，后续在以太坊不断升级的过程中，才陆续支持了不同类型的交易。当前主流的交易是可以支持动态费率 EIP-1559 的交易，大多数用户提交的都是这类交易。EIP-4844 中则引入了可以为 Layer2 或者其他链下扩容方案提供更加便宜的数据存储，而在最新的 Pectra 升级中，则通过 EIP-7702 引入了可以将 EOA 扩展为合约的交易形式。</p><p>随着以太坊的发展，后续可能还会支持其他的交易类型，但是交易总体的处理流程变化不会太大，都需要经过交易提交 → 交易校验 → 进入交易池 → 交易传播 → 打包进入区块的流程。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易结构的演进</strong></h3><p>从以太坊主网上线开始，以太坊交易结构可以总结为四次大的变化，分别从安全性和扩展性打好了基础，后续以太坊可以用低成本的方式增加交易类型。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>防止跨链重放攻击</strong></h3><p>最开始的交易结构如下所示，RLP 标识交易数据会被编码成 RLP 结构后再传播和处理：</p><pre data-type="codeBlock" text="RLP([nonce, gasPrice, gasLimit, to, value, data, v, r, s])
"><code><span class="hljs-built_in">RLP</span>([nonce, gasPrice, gasLimit, to, value, data, v, r, s])
</code></pre><p>这个结构最大的问题在于没有和链关联，在主网生成的交易可以被随意放到其他链上去执行，所以 EIP-155 中通过在签名v值中嵌入chainId（如主网ID=1），隔离不同链的交易，从而保证每条链的交易都无法在其他链重放。</p><p>涉及 EIP：</p><ul><li><p>EIP-155</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易扩展的标准化</strong></h3><p>随着以太坊的发展，最开始的交易格式已经无法满足一些场景的需求，所以需要增加新的交易类型，但是如果随意增加交易的类型，后续可能会面临管理复杂，无法标准化的问题。在 EIP-2718 中，定义了后续交易的格式，主要定义了 TransactionType || TransactionPayload  结构，其中：</p><ul><li><p>TransactionType 定义类交易类型，至多可以扩展到 128 种交易类型，足够新增交易类型使用</p></li><li><p>TransactionPayload 定义了交易的数据格式，当前使用 RLP 来编码数据，后续也有可能会升级到 SSZ 或者其他编码</p></li></ul><p>这次升级是在 Berlin 升级中完成，除了 EIP-2718 之外，这次升级中还通过 EIP-2930 引入了 Access List 交易类型，这个交易类型允许用户在交易中预先声明需要访问的合约和存储，可以降低交易执行过程中的 gas 消耗。</p><p>涉及 EIP：</p><ul><li><p>EIP-2718</p></li><li><p>EIP-2930</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>以太坊经济模型变革</strong></h3><p>在 London 升级中，EIP-1559 引入了 Base Fee 机制，让 ETH 发行的速度减缓甚至通缩，对于参与质押的节点来说，还有可能通过小费（maxPriorityFeePerGas）获得额外的收入。EIP-1559 交易继承了 Access List 机制，这已经是目前最主要的交易。并且在 Paris 的 The Merge 升级之后，以太坊从 PoW 转向 PoS，原先的挖矿经济模型已经不再适用，以太坊进入 Staking 时代。</p><p>另外在 EIP-1559 中，还通过引入 target 机制，可以动态调整 Base Fee，相当于为以太坊引入了负载均衡的能力，target 值是区块 Gas Limit 的一半，如果超出这个值，Base Fee 就会持续上涨，这样很多交易就会避开拥堵时间，这样可以让链的整体拥堵情况能减缓，用户体验更好。</p><p>涉及 EIP：</p><ul><li><p>EIP-1559</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>增加各类扩展交易</strong></h3><p>在 EIP-2718 和 EIP-1559 分别定义好扩展交易的标准和经济模型之后，就陆续有新的交易类型增加。在最近两次的升级中，分别增加了 EIP-4844 和 EIP-7702，前者增加了 Blob 交易类型，为链下扩容方案提供了理想化的存储方案，空间大，价格还低，而且也有着类似 EIP-1559 的经济模型和负载机制，EIP-7702 则可以将 EOA 改造成掌握私钥的智能合约账户，为后续帐号抽象的大规模采用做好准备。</p><p>涉及 EIP：</p><ul><li><p>EIP-4844</p></li><li><p>EIP-7702</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>交易模块架构</strong></h2><p>交易作为以太坊这个状态机的输入，几乎所有的主流程都围绕交易来进行，交易进入交易池之前需要校验交易的格式和签名等信息，进入交易池之后，需要在不同的节点之间传播，再被出块节点从交易池中选择，然后交易会在 EVM 上执行，并修改状态数据库，最后被打包进区块在执行层和共识层之间传递。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/61f11e5d1ec2a17e0fea6ecdc1fca85ebf6fba6667da1975d0171caf74dc8013.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>对于出块节点和非出块节点，处理交易的流程会有一些差别。对于出块节点，会负责从交易池中挑选交易，然后打包进区块，并更新本地的状态数据库，对于非出块节点，只需要将同步到的最新区块中的交易重新执行一遍，让本地的状态更新到最新。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易种类</strong></h3><p>目前以太坊总共支持五类交易。这些交易的主要结构都类似，不同交易会通过交易类型字段来区分，不同类型的交易中通过一些扩展字段来实现特定的用途。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/382e475eee4abef0b7726d8e089fc71010281ca95aa6e79f0423d549fd47e00e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p>LegacyTxType：创世区块沿用至今的基础格式，采用<strong>第一价格拍卖模型</strong>（用户手动设置gasPrice），EIP-155 升级后默认嵌入 chainId 防跨链重放攻击，当前在以太坊主网上的使用量已经比较少，以太坊当前兼容这个交易类型，后续会逐步淘汰</p></li><li><p>AccessListTxType：预热存储访问大幅降低 Gas成本，这个特性已经被后续的交易类型继承，直接使用这个交易类型的交易也较少</p></li><li><p>DynamicFeeTxType：更新了以太坊的经济模型的交易类型，引入 Base Fee 和 target 机制，继承了 AccessList 特性，这也是当前最主流的交易类型</p></li><li><p>BlobTxType：专为链下扩容涉及的交易类型，允许交易通过 blob 结构携带低成本的大量数据，降低链下扩容方案的成本，继承了 AccessList 和 DynamicFee 特性，并且交易中的 blob 有与 EIP-1559 类似的单独计费机制</p></li><li><p>SetCodeTxType：允许 <strong>EOA 转换为合约账户</strong>（也可以通过交易撤销合约能力），并可以执行 EOA 中对应的合约代码，继承了 AccessList 和 DynamicFee 特性</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易生命周期</strong></h3><p>当交易被打包进行区块之后，已经完成了对状态数据的修改，可以理解为交易的生命周期已经结束，在这个过程中，交易会经历四个周期：</p><ul><li><p>交易验证：EOA 提交的交易会经过一系列基础验证之后才会被加入到交易池中</p></li><li><p>交易广播：新提交到交易池的交易会被广播到其他的节点的交易池中</p></li><li><p>交易执行：出块节点会从交易池中挑选交易进行执行</p></li><li><p>交易打包：交易会按照一定的顺序（先区分是否是本地交易，然后再按照 gas fee 大小）打包进区块，会忽略那些会验证不通过的交易</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易池</strong></h3><p>交易池是临时存放交易的地方，交易在被打包之前，都会被存储在交易池中，交易池中的交易会被同步到其他节点，同时也会从其他节点的交易池中同步交易。用户提交的交易会首先进入到交易池中，然后经过共识层触发共识流程，驱动交易执行以及被打包进区块。</p><p>当前交易池的实现有两种类型：</p><ul><li><p>Blob 交易池（Blob TxPool）</p></li><li><p>其他交易的交易池（Legacy TxPool）</p></li></ul><p>由于 Blob 交易携带的数据与其他交易携带的数据处理流程不一样，所以会使用单独的交易池来处理，是其他类型的交易虽然类型不一致，但在不同节点之间同步以及被打包的流程基本一致，所以会在同一个交易池中被处理，交易池中交易都由外部 EOA 的所有者提交，向交易池中提交交易有两种方式：</p><ul><li><p>SendTransaction</p></li><li><p>SendRawTransaction</p></li></ul><p>SendTransaction 是客户端发送的是一个未签名的交易对象，节点会用交易中发起地址 from 对应的私钥来对交易进行签名。而 SendRawTransaction 则需要提前对交易签名，然后将签名完成的交易提交到节点。这种方式更常用，使用 Metamask、Rabby 等钱包使用的就是这种方式。</p><p>在这里以 SendRawTransaction 为例，在节点启动之后，节点会启动以一个 API 模块，用来处理外部的各类 API 请求，SendRawTransaction 就是其中的一个 API，源代码在 internal/ethapi/api.go :</p><pre data-type="codeBlock" text="func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {  tx := new(types.Transaction)  if err := tx.UnmarshalBinary(input); err != nil {    return common.Hash{}, err  }  return SubmitTransaction(ctx, api.b, tx)}
"><code>func (api <span class="hljs-operator">*</span>TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{  <span class="hljs-built_in">tx</span> :<span class="hljs-operator">=</span> <span class="hljs-keyword">new</span>(types.Transaction)  <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> <span class="hljs-built_in">tx</span>.UnmarshalBinary(input); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {    <span class="hljs-keyword">return</span> common.Hash{}, err  }  <span class="hljs-keyword">return</span> SubmitTransaction(ctx, api.b, <span class="hljs-built_in">tx</span>)}
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>核心数据结构</strong></h3><p>对于交易模块来说，核心的数据结构就两部分，一部分是用来表示交易本身的数据结构，另一部分就是表示临时存储交易的交易池结构。由于交易在交易池中需要在不同的节点之间传播，所以在交易池中的实现中会依赖底层的 p2p 协议。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易结构</strong></h3><p>使用了 core/types/transaction.go 的 Transaction 来统一表示了所有的交易类型：</p><pre data-type="codeBlock" text="type Transaction struct {  inner TxData    // 交易实际的数据会存储在这里  time  time.Time     //....}
"><code><span class="hljs-keyword">type</span> Transaction <span class="hljs-keyword">struct</span> {  inner TxData    <span class="hljs-comment">// 交易实际的数据会存储在这里  time  time.Time     //....}</span>
</code></pre><p>TxData 是一个 interface 类型，定义了所有交易类型都需要实现的属性获取方法，但是对于 LegacyTxType 这类交易，其中很多字段都没有，那就会使用之前已经存在的字段替代或者直接返回空：</p><pre data-type="codeBlock" text="type TxData interface {  txType() byte // 交易类型  copy() TxData // 创建交易数据的深度拷贝  chainID() *big.Int // 链ID，用于区分不同的以太坊网络  accessList() AccessList // 预编译的访问列表，用于优化gas消耗（EIP-2930引入）  data() []byte   // 交易的输入数据，用于合约调用或创建  gas() uint64    // Gas 限制，表示交易最多可以消耗的 gas 数量  gasPrice() *big.Int  // 每单位 Gas 的价格（用于 Legacy 交易）  gasTipCap() *big.Int // 小费上限（用于 EIP-1559 交易）  gasFeeCap() *big.Int // 总费用上限（用于 EIP-1559 交易）  value() *big.Int // 交易中发送的 ETH 数量  nonce() uint64 // 交易序号，用于防止重放攻击  to() *common.Address // 接收方地址，如果是合约创建则为nil  rawSignatureValues() (v, r, s *big.Int) // 原始签名值(v, r, s)  setSignatureValues(chainID, v, r, s *big.Int) // 设置签名值  effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int // 计算实际的gas价格（考虑baseFee）  encode(*bytes.Buffer) error // 将交易编码为字节流  decode([]byte) error // 从字节流解码交易  sigHash(*big.Int) common.Hash // 需要签名的交易哈希}
"><code>type <span class="hljs-title class_">TxData</span> interface {  txType() byte /<span class="hljs-regexp">/ 交易类型  copy() TxData /</span><span class="hljs-regexp">/ 创建交易数据的深度拷贝  chainID() *big.Int /</span><span class="hljs-regexp">/ 链ID，用于区分不同的以太坊网络  accessList() AccessList /</span><span class="hljs-regexp">/ 预编译的访问列表，用于优化gas消耗（EIP-2930引入）  data() []byte   /</span><span class="hljs-regexp">/ 交易的输入数据，用于合约调用或创建  gas() uint64    /</span><span class="hljs-regexp">/ Gas 限制，表示交易最多可以消耗的 gas 数量  gasPrice() *big.Int  /</span><span class="hljs-regexp">/ 每单位 Gas 的价格（用于 Legacy 交易）  gasTipCap() *big.Int /</span><span class="hljs-regexp">/ 小费上限（用于 EIP-1559 交易）  gasFeeCap() *big.Int /</span><span class="hljs-regexp">/ 总费用上限（用于 EIP-1559 交易）  value() *big.Int /</span><span class="hljs-regexp">/ 交易中发送的 ETH 数量  nonce() uint64 /</span><span class="hljs-regexp">/ 交易序号，用于防止重放攻击  to() *common.Address /</span><span class="hljs-regexp">/ 接收方地址，如果是合约创建则为nil  rawSignatureValues() (v, r, s *big.Int) /</span><span class="hljs-regexp">/ 原始签名值(v, r, s)  setSignatureValues(chainID, v, r, s *big.Int) /</span><span class="hljs-regexp">/ 设置签名值  effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int /</span><span class="hljs-regexp">/ 计算实际的gas价格（考虑baseFee）  encode(*bytes.Buffer) error /</span><span class="hljs-regexp">/ 将交易编码为字节流  decode([]byte) error /</span><span class="hljs-regexp">/ 从字节流解码交易  sigHash(*big.Int) common.Hash /</span><span class="hljs-regexp">/ 需要签名的交易哈希}
</span></code></pre><p>除了上面每个交易都需要实有的细节之外，每个新增加的交易都有自身的扩展部分。</p><p>在 Blob 交易中：</p><ul><li><p>BlobFeeCap：每个 blob 数据的最大费用上限，类似 maxFeePerGas，但是专门用于计算 blob 数据的费用</p></li><li><p>BlobHashes：存储所有 blob 数据的哈希值数组，这些数据会在执行层存储，用于证明 Blob 数据的完整性和真实性</p></li><li><p>Sidecar：包含实际的 blob 数据及其证明，这些数据不会在执行层存储，只会在共识层存储一段时间，也不会被编码到交易中</p></li></ul><p>在 SetCode 交易中：</p><ul><li><p>AuthList：是一个授权列表，用于实现合约代码的多重授权机制，用于帮助 EOA 获取智能合约的能力</p></li></ul><p>所有的交易类型都需要实现 TxData，然后每种交易的差异化处理会在交易类型的内部去实现。这样面向接口的一个好处就是后续可以很轻松地增加一种新的交易类型，而不需要去修改当前的交易流程流程。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易池结构</strong></h3><p>与交易结构类似，交易池也采用了同样的设计模式，使用 core/txpool/txpool.go 中的 TxPool 来统一管理交易池，其中 SubPool 是一个 interface，每个交易池的具体实现都需要实现这个 interface：</p><pre data-type="codeBlock" text="type TxPool struct {  subpools []SubPool // 交易池的具体实现  chain    BlockChain    // ...}type LegacyPool struct {  config      Config     // 交易池参数配置  chainconfig *params.ChainConfig // 区块链参数配置  chain       BlockChain // 区块链接口  gasTip      atomic.Pointer[uint256.Int] // 当前接受的最低 gas 小费  txFeed      event.Feed // 交易事件的发布订阅系统  signer      types.Signer // 交易签名验证器  pending map[common.Address]*list     // 当前可处理的交易  queue   map[common.Address]*list     // 暂时不可处理的交易  //...}type BlobPool struct {  config  Config                 // 交易池的参数配置  reserve txpool.AddressReserver //   store  billy.Database // 持久化数据存储，用于存储交易元数据和 blob 数据  stored uint64         //   limbo  *limbo         //   signer types.Signer //   chain  BlockChain   //   index  map[common.Address][]*blobTxMeta //   spent  map[common.Address]*uint256.Int //   //...}
"><code>type <span class="hljs-title class_">TxPool</span> struct {  subpools []<span class="hljs-title class_">SubPool</span> /<span class="hljs-regexp">/ 交易池的具体实现  chain    BlockChain    /</span><span class="hljs-regexp">/ ...}type LegacyPool struct {  config      Config     /</span><span class="hljs-regexp">/ 交易池参数配置  chainconfig *params.ChainConfig /</span><span class="hljs-regexp">/ 区块链参数配置  chain       BlockChain /</span><span class="hljs-regexp">/ 区块链接口  gasTip      atomic.Pointer[uint256.Int] /</span><span class="hljs-regexp">/ 当前接受的最低 gas 小费  txFeed      event.Feed /</span><span class="hljs-regexp">/ 交易事件的发布订阅系统  signer      types.Signer /</span><span class="hljs-regexp">/ 交易签名验证器  pending map[common.Address]*list     /</span><span class="hljs-regexp">/ 当前可处理的交易  queue   map[common.Address]*list     /</span><span class="hljs-regexp">/ 暂时不可处理的交易  /</span><span class="hljs-regexp">/...}type BlobPool struct {  config  Config                 /</span><span class="hljs-regexp">/ 交易池的参数配置  reserve txpool.AddressReserver /</span><span class="hljs-regexp">/   store  billy.Database /</span><span class="hljs-regexp">/ 持久化数据存储，用于存储交易元数据和 blob 数据  stored uint64         /</span><span class="hljs-regexp">/   limbo  *limbo         /</span><span class="hljs-regexp">/   signer types.Signer /</span><span class="hljs-regexp">/   chain  BlockChain   /</span><span class="hljs-regexp">/   index  map[common.Address][]*blobTxMeta /</span><span class="hljs-regexp">/   spent  map[common.Address]*uint256.Int /</span><span class="hljs-regexp">/   /</span><span class="hljs-regexp">/...}
</span></code></pre><p>当前实现了 SubPool 的两个交易池为：</p><ul><li><p>BlobTxPool：用于管理 Blob 交易</p></li><li><p>LegacyTxPool：用于管理 Blob 交易之外的其他交易</p></li></ul><p>之所以 Blob 交易需要和其他交易分开管理，是因为 Blob 交易中有可能会携带大量的 blob 数据，其他的交易都可以直接在内存中管理和同步，而 Blob 交易中的 blob 数据则需要持久化存储，所以不能使用和其他交易一样的管理方式。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>费用机制</strong></h2><p>由于以太坊本身无法处理<strong>停机问题</strong>，所以使用 Gas 机制来防止一些恶意的攻击，另外 Gas 本身也作为用户的手续费在使用，这是 Gas 最初的两个用途。</p><p>经过了这些年的发展之后，Gas 除了上面的两个用途，还是以太坊经济模型的重要组成部分，可以控制 ETH 的发行数量，还可以帮助以太坊完成通缩。甚至还可以动态调节以太坊网络的流量，提升用户使用体验。</p><p>以太坊的费用机制使用 Gas 来实现，有维护网络安全、实现经济模型平衡等多种作用。</p><h3 id="h-gas" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Gas</strong></h3><p>以太坊在处理交易时，在 EVM 上执行的每个操作都需要消耗 Gas，比如使用内存、读取数据、写入数据等等，有的操作消耗 Gas 多，有的消耗少，比如 ETH 的转账操作需要消耗 21,000 Gas。在每一笔交易中，都需要设定该交易最高需要消耗多少 Gas，如果 Gas 消耗完，那么交易就执行结束，而且这个过程中消耗的 Gas 不会退还，也正是这个机制可以用来处理以太坊的停机问题。</p><p>以太坊中区块的大小也是使用 Gas 来限制，而不是使用具体的大小单位，<strong>区块内所有交易实际消耗的 Gas 不能大于区块本身 Gas 限制</strong>。Gas 只是 EVM 执行过程中的计量单位，用于需要为每笔交易消耗的 Gas 支付 ETH，Gas 的价格通常使用 Gwei 来表示， 1 Ether = 10^9 Gwei。</p><p>在当前的以太坊网络中，当前的一个区块的大小限制是 36M Gas，但是在当前的社区中对于提升区块的 Gas 限制的声量很大，认为提升区块 Gas 限制到 60M 是比较合理的选择，这个数字会提升网络的容量，而且同时不会威胁到网络的安全，目前已经在测试网测试中。同时社区中也有人认为单纯使用 Gas 限制来控制的区块的大小不合理，需要引入字节大小的限制，目前这些正在社区讨论中。</p><h3 id="h-eip-1559" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>EIP-1559</strong></h3><p>在引入 EIP-1559 的机制之后，将之前的 GasPrice 直接拆分成了 Base Fee 和 Priority Fee（maxPriorityFeePerGas），其中 Base Fee 会全部被销毁，以控制以太坊中 ETH 的增长速度，Priority Fee 则会给出出块节点对应的验证者。用户可以在交易中设置 maxFeePerGas 来保证最终支付的费用是受限的。</p><p>如果要保证交易成功，那么就需要保证  maxFeePerGas ≥ Base Fee + Priority Fee，否则交易会执行失败，并且费用也不会退换。用户需要实际支出的费用为 (Base Fee+Priority Fee)×Gas Used，多余的费用会退还给交易发起的地址。</p><p>Base Fee 处在动态变化中，以区块中 Gas 的实际用量为基准，区块最大 Gas 限制的一半称之为 target，如果上一个区块的实际用量超过了 target，那么当前的区块的 Base Fee 就会增加，如果前一个区块的 Gas 用量低于 target，那么 Base Fee 就会减少，否则就不变。</p><h3 id="h-blob" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Blob 交易费用机制</strong></h3><p>Blob 交易的费用结算分为两部分，一部分是使用 EIP-1559 来和其他交易共同调整 Base Fee，另一部分是 Blob 交易中的 Blob 数据有一个独立的 Blob Fee 机制，其中 target 值是最大 Blob 数量的一半，也是根据 Blob 数据块的使用量来调整 Blob Fee，但是不单独设置 Priority Fee，因为 Blob 交易也可以直接设置交易中的 Priority Fee 来促使 Blob 交易被更快打包。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>交易处理流程源码分析</strong></h2><p>在上面详细介绍了以太坊中交易机制的设计及实现，接下来，我们将通过分析代码，详细介绍交易在 Geth 中具体实现，包括交易在整个生命周期中的处理流程。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易提交</strong></h3><p>无论是通过 SendTransaction 还是 SendURawTransaction 的方式提交交易，都会调用 internal/ethapi/api.go 中的 SubmitTransaction 函数向交易池提交交易。</p><p>在 这个函数中，会对交易做两个基本的检查，一个是检查 gas fee 是否合理，另一个是检查是交易是否满足 EIP-155 的规范，EIP-155 通过在交易签名中引入 chainID 参数，解决了跨链交易重放问题。该检查确保当节点配置开启 EIP155Required 时，所有向交易池提交的交易都必须符合这个标准。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8ebba79ab0ef802cf89fa66c368aaa8a5df07e2df0372835debd8decc2904f9d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在完成检查之后，就会把交易提交到交易池，由 eth/api_backend.go 中的 SendTx 来实现添加逻辑：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/43e6b211811653a81775217c22d3d3e786c611b7d566d25099673c7587cc96af.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在交易池中，会通过 Filter 方法来为交易匹配对应的交易池，当前有两个交易池实现，如果是 blob 交易，那么就会放入 BlobPool ，否则放入LegacyPool ：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bc22561cf49f94961e81ecdf2a1ea6f9372da28fc28b21e1e4ea354a8030ab77.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>到这里，EOA 提交的交易就已经被放入交易池，这个交易就会开始在交易池中传播，进入后续的交易打包和执行流程。</p><p>如果在交易打包之前，重新发送了一笔交易，新的交易设置了新的 gasPrice 和 gasLimit，就会把原来交易池中的交易删除，替换成了新的 gasPrice 和 gasLimit 之后重新返回到交易池中。这种方式也可以用来取消不想执行的交易。</p><pre data-type="codeBlock" text="func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {  if sendArgs.Nonce == nil {    return common.Hash{}, errors.New(&quot;missing transaction nonce in transaction spec&quot;)  }  if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {    return common.Hash{}, err  }  matchTx := sendArgs.ToTransaction(types.LegacyTxType)  // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.  price := matchTx.GasPrice()  if gasPrice != nil {    price = gasPrice.ToInt()  }  gas := matchTx.Gas()  if gasLimit != nil {    gas = uint64(*gasLimit)  }  if err := checkTxFee(price, gas, api.b.RPCTxFeeCap()); err != nil {    return common.Hash{}, err  }  // Iterate the pending list for replacement  pending, err := api.b.GetPoolTransactions()  if err != nil {    return common.Hash{}, err  }  for _, p := range pending {    wantSigHash := api.signer.Hash(matchTx)    pFrom, err := types.Sender(api.signer, p)    if err == nil &amp;&amp; pFrom == sendArgs.from() &amp;&amp; api.signer.Hash(p) == wantSigHash {      // Match. Re-sign and send the transaction.      if gasPrice != nil &amp;&amp; (*big.Int)(gasPrice).Sign() != 0 {        sendArgs.GasPrice = gasPrice      }      if gasLimit != nil &amp;&amp; *gasLimit != 0 {        sendArgs.Gas = gasLimit      }      signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType))      if err != nil {        return common.Hash{}, err      }      if err = api.b.SendTx(ctx, signedTx); err != nil {        return common.Hash{}, err      }      return signedTx.Hash(), nil    }  }  return common.Hash{}, fmt.Errorf(&quot;transaction %#x not found&quot;, matchTx.Hash())}
"><code>func (api <span class="hljs-operator">*</span>TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice <span class="hljs-operator">*</span>hexutil.Big, gasLimit <span class="hljs-operator">*</span>hexutil.Uint64) (common.Hash, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{  <span class="hljs-keyword">if</span> sendArgs.Nonce <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {    <span class="hljs-keyword">return</span> common.Hash{}, errors.New(<span class="hljs-string">"missing transaction nonce in transaction spec"</span>)  }  <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> sendArgs.setDefaults(ctx, api.b, <span class="hljs-literal">false</span>); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {    <span class="hljs-keyword">return</span> common.Hash{}, err  }  matchTx :<span class="hljs-operator">=</span> sendArgs.ToTransaction(types.LegacyTxType)  <span class="hljs-comment">// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.  price := matchTx.GasPrice()  if gasPrice != nil {    price = gasPrice.ToInt()  }  gas := matchTx.Gas()  if gasLimit != nil {    gas = uint64(*gasLimit)  }  if err := checkTxFee(price, gas, api.b.RPCTxFeeCap()); err != nil {    return common.Hash{}, err  }  // Iterate the pending list for replacement  pending, err := api.b.GetPoolTransactions()  if err != nil {    return common.Hash{}, err  }  for _, p := range pending {    wantSigHash := api.signer.Hash(matchTx)    pFrom, err := types.Sender(api.signer, p)    if err == nil &#x26;&#x26; pFrom == sendArgs.from() &#x26;&#x26; api.signer.Hash(p) == wantSigHash {      // Match. Re-sign and send the transaction.      if gasPrice != nil &#x26;&#x26; (*big.Int)(gasPrice).Sign() != 0 {        sendArgs.GasPrice = gasPrice      }      if gasLimit != nil &#x26;&#x26; *gasLimit != 0 {        sendArgs.Gas = gasLimit      }      signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType))      if err != nil {        return common.Hash{}, err      }      if err = api.b.SendTx(ctx, signedTx); err != nil {        return common.Hash{}, err      }      return signedTx.Hash(), nil    }  }  return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())}</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易广播</strong></h3><p>节点在接收到 EOA 提交的交易之后，需要在网络中进行传播，txpool（core/txpool/txpool.go ）提供了 SubscribeTransactions 方法，可以订阅交易池中的新事件，Blob 交易池和 Legacy 交易池实现订阅的方式不一致：</p><pre data-type="codeBlock" text="func (p *TxPool) SubscribeTransactions(ch chan&lt;- core.NewTxsEvent, reorgs bool) event.Subscription {  subs := make([]event.Subscription, len(p.subpools))  for i, subpool := range p.subpools {    subs[i] = subpool.SubscribeTransactions(ch, reorgs)  }  return p.subs.Track(event.JoinSubscriptions(subs...))}
"><code>func (p <span class="hljs-operator">*</span>TxPool) SubscribeTransactions(ch chan<span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span> core.NewTxsEvent, reorgs <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">event</span>.Subscription {  subs :<span class="hljs-operator">=</span> make([]<span class="hljs-keyword">event</span>.Subscription, len(p.subpools))  <span class="hljs-keyword">for</span> i, subpool :<span class="hljs-operator">=</span> range p.subpools {    subs[i] <span class="hljs-operator">=</span> subpool.SubscribeTransactions(ch, reorgs)  }  <span class="hljs-keyword">return</span> p.subs.Track(<span class="hljs-keyword">event</span>.JoinSubscriptions(subs...))}
</code></pre><p>BlobPool 区分了两种事件源：</p><ul><li><p>discoverFeed ：仅包含新发现的交易</p></li><li><p>insertFeed ：包含所有交易，包括因重组而重新进入池的交易</p></li></ul><pre data-type="codeBlock" text="func (p *BlobPool) SubscribeTransactions(ch chan&lt;- core.NewTxsEvent, reorgs bool) event.Subscription {  if reorgs {    return p.insertFeed.Subscribe(ch)  } else {    return p.discoverFeed.Subscribe(ch)  }}
"><code>func (p <span class="hljs-operator">*</span>BlobPool) SubscribeTransactions(ch chan<span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span> core.NewTxsEvent, reorgs <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">event</span>.Subscription {  <span class="hljs-keyword">if</span> reorgs {    <span class="hljs-keyword">return</span> p.insertFeed.Subscribe(ch)  } <span class="hljs-keyword">else</span> {    <span class="hljs-keyword">return</span> p.discoverFeed.Subscribe(ch)  }}
</code></pre><p>LagacyPool 不区分新交易和重组交易，它使用单一的 txFeed 来发送所有交易事件。</p><pre data-type="codeBlock" text="func (pool *LegacyPool) SubscribeTransactions(ch chan&lt;- core.NewTxsEvent, reorgs bool) event.Subscription {  return pool.txFeed.Subscribe(ch)}
"><code>func (pool <span class="hljs-operator">*</span>LegacyPool) SubscribeTransactions(ch chan<span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span> core.NewTxsEvent, reorgs <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">event</span>.Subscription {  <span class="hljs-keyword">return</span> pool.txFeed.Subscribe(ch)}
</code></pre><p>总的来说， SubscribeTransactions 通过事件机制将交易池与其他组件解耦，这个订阅可以被多个模块使用，比如交易广播、交易打包以及对外 RPC 都需要监听这个流程，然后做出对应的处理。</p><p>同时 p2p 模块 (eth/handler.go)  会持续监听新交易事件，如果接收到了新交易，那么就会发送广播，将交易广播出去：</p><pre data-type="codeBlock" text="// eth/handler.go 在产生新交易之后，会通过 p2p 网络广播出去func (h *handler) txBroadcastLoop() {  defer h.wg.Done()  for {    select {    case event := &lt;-h.txsCh: // 这里监听新交易信息      h.BroadcastTransactions(event.Txs)    case &lt;-h.txsSub.Err():      return    }  }}
"><code>// eth/handler.go 在产生新交易之后，会通过 p2p 网络广播出去func (h *handler) txBroadcastLoop() {  defer h.wg.Done()  <span class="hljs-keyword">for</span> {    <span class="hljs-keyword">select</span> {    <span class="hljs-keyword">case</span> <span class="hljs-keyword">event</span> := &#x3C;-h.txsCh: // 这里监听新交易信息      h.BroadcastTransactions(<span class="hljs-keyword">event</span>.Txs)    <span class="hljs-keyword">case</span> &#x3C;-h.txsSub.Err():      <span class="hljs-keyword">return</span>    }  }}
</code></pre><p>在广播交易时，需要对交易进行分类，如果是 blob 交易或者是超过了一定大小的交易，无法直接传播，对于普通的交易，则标记为可以直接传播。然后从当前节点的对等节点中去找那些没有这笔交易的节点。如果节点可以直接广播，则标记为 true，这个过程也是在 BroadcastTransactions 方法中实现的：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/64c3630485c087ee5e255d887e66f12947edef5e9bf5279a7598c4b4da1485cb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>依照上面的原则对交易分类完成，可以直接传播的交易就直接发送，blob 交易或者大交易则只广播 hash，等到需要用到这笔交易的时候再来获取</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/06061cc543e926416b625379b3fc90e792cfae80616529c6deb87efc5c1fef0a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>广播中只发送 hash 的交易会被放到 peer 节点的这个字段中：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/06061cc543e926416b625379b3fc90e792cfae80616529c6deb87efc5c1fef0a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>新交易会通过 p2p 模块广播出去，同时也会从 p2p 网络中接收新的交易。在 eth/backend.go 中初始化 Ethereum 实例时，会初始化 p2p 模块，添加交易池的接口。p2p 模块运行起来之后，会从 p2p 消息中解析出交易请求添加到交易池中。</p><p>具体来说，在实例化 handler 的时候，会指定好从其他节点获取交易的方式，会通过 eth/fetcher 中的 TxFetcher 去获取远端的交易，TxFetcher 会通过这里的 fetchTx 方法去获取远端的交易，实际是调用了 eth/protocols/eth 协议中实现的 RequestTxs 方法去获取交易：</p><pre data-type="codeBlock" text="// eth/backend.go New 函数if eth.handler, err = newHandler(&amp;handlerConfig{    NodeID:         eth.p2pServer.Self().ID(),    Database:       chainDb,    Chain:          eth.blockchain,    TxPool:         eth.txPool,    Network:        networkID,    Sync:           config.SyncMode,    BloomCache:     uint64(cacheLimit),    EventMux:       eth.eventMux,    RequiredBlocks: config.RequiredBlocks,  }); err != nil {    return nil, err  }    // eth/handler.go newHandler 函数，注册获取新交易的过程  fetchTx := func(peer string, hashes []common.Hash) error {    p := h.peers.peer(peer)    if p == nil {      return errors.New(&quot;unknown peer&quot;)    }    return p.RequestTxs(hashes) // 去其他节点请求交易  }  addTxs := func(txs []*types.Transaction) []error {    return h.txpool.Add(txs, false) // 将交易加入交易池  }  h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer)      // eth/handler_eth.go Handle 方法，在接收到新的交易之后，会添加到交易池中  for _, tx := range *packet {    if tx.Type() == types.BlobTxType {      return errors.New(&quot;disallowed broadcast blob transaction&quot;)    }  }  return h.txFetcher.Enqueue(peer.ID(), *packet, false)    // eth/fetcher/tx_fetcher.go 的 Handle 方法会调用上面注册的 addTxs 来将讲义添加到交易池  for j, err := range f.addTxs(batch) {    //....  }
"><code>// eth/backend.go New 函数<span class="hljs-keyword">if</span> eth.handler, err = newHandler(&#x26;handlerConfig{    NodeID:         eth.p2pServer.Self().ID(),    Database:       chainDb,    Chain:          eth.blockchain,    TxPool:         eth.txPool,    Network:        networkID,    Sync:           <span class="hljs-built_in">config</span>.SyncMode,    BloomCache:     uint64(cacheLimit),    EventMux:       eth.eventMux,    RequiredBlocks: <span class="hljs-built_in">config</span>.RequiredBlocks,  }); err != <span class="hljs-literal">nil</span> {    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err  }    // eth/handler.go newHandler 函数，注册获取新交易的过程  fetchTx := func(peer <span class="hljs-built_in">string</span>, hashes []common.Hash) <span class="hljs-built_in">error</span> {    p := h.peers.peer(peer)    <span class="hljs-keyword">if</span> p == <span class="hljs-literal">nil</span> {      <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"unknown peer"</span>)    }    <span class="hljs-keyword">return</span> p.RequestTxs(hashes) // 去其他节点请求交易  }  addTxs := func(txs []*types.Transaction) []<span class="hljs-built_in">error</span> {    <span class="hljs-keyword">return</span> h.txpool.Add(txs, <span class="hljs-literal">false</span>) // 将交易加入交易池  }  h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer)      // eth/handler_eth.go Handle 方法，在接收到新的交易之后，会添加到交易池中  <span class="hljs-keyword">for</span> _, tx := range *packet {    <span class="hljs-keyword">if</span> tx.Type() == types.BlobTxType {      <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"disallowed broadcast blob transaction"</span>)    }  }  <span class="hljs-keyword">return</span> h.txFetcher.Enqueue(peer.ID(), *packet, <span class="hljs-literal">false</span>)    // eth/fetcher/tx_fetcher.go 的 Handle 方法会调用上面注册的 addTxs 来将讲义添加到交易池  <span class="hljs-keyword">for</span> j, err := range f.addTxs(batch) {    //....  }
</code></pre><p>RequestTxs 方法通过发送 GetPooledTransactionsMsg 消息，然后收到其他节点发送的 PooledTransactionsMsg 的响应，由 backend 中的 Handle 方法来处理，在这个方法中调用了 txFetcher 的  Enqueue 方法，最终 Enqueue 方法调用了 adTxs 方法把从其他节点获取的交易添加到交易池：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/98d6e7fdfa81a60382118d9dcfcc2d0082e2be1f3f70d334a2ae91c328f023e1.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在交易池中还有一个延迟加载的设计，通过 core/txpool/subpool.go 中的 LazyTransaction 来实现，通过延迟加载机制减少内存使用并提高交易处理效率。它存储交易的关键元数据，只在真正需要时才加载完整交易数据，在以太坊处理大量交易时发挥着重要作用。这种设计特别适合交易池和区块打包这样的场景，其中大多数交易可能最终不会被包含在区块中，因此不需要完整加载所有交易数据。</p><pre data-type="codeBlock" text="type LazyTransaction struct {  Pool LazyResolver       // Transaction resolver to pull the real transaction up  Hash common.Hash        // Transaction hash to pull up if needed  Tx   *types.Transaction // Transaction if already resolved  Time      time.Time    // Time when the transaction was first seen  GasFeeCap *uint256.Int // Maximum fee per gas the transaction may consume  GasTipCap *uint256.Int // Maximum miner tip per gas the transaction can pay  Gas     uint64 // Amount of gas required by the transaction  BlobGas uint64 // Amount of blob gas required by the transaction}func (ltx *LazyTransaction) Resolve() *types.Transaction {  if ltx.Tx != nil {    return ltx.Tx  }  return ltx.Pool.Get(ltx.Hash)}
"><code><span class="hljs-keyword">type</span> LazyTransaction <span class="hljs-keyword">struct</span> {  Pool LazyResolver       <span class="hljs-comment">// Transaction resolver to pull the real transaction up  Hash common.Hash        // Transaction hash to pull up if needed  Tx   *types.Transaction // Transaction if already resolved  Time      time.Time    // Time when the transaction was first seen  GasFeeCap *uint256.Int // Maximum fee per gas the transaction may consume  GasTipCap *uint256.Int // Maximum miner tip per gas the transaction can pay  Gas     uint64 // Amount of gas required by the transaction  BlobGas uint64 // Amount of blob gas required by the transaction}func (ltx *LazyTransaction) Resolve() *types.Transaction {  if ltx.Tx != nil {    return ltx.Tx  }  return ltx.Pool.Get(ltx.Hash)}</span>
</code></pre><p>另外由于以太坊是一个 perminssionless 的网络，节点有可能会从网络中接收到一些恶意的请求，极端情况下，节点可能会面临 DDos 攻击，所以节点会使用一系列的方式来防止来自网络的恶意攻击：</p><ul><li><p>交易基础验证</p></li><li><p>节点资源限制</p></li><li><p>交易驱逐机制</p></li><li><p>p2p 网络层防护</p></li></ul><p>这里以 Legacypool 为例（Blobpool 也有类似的机制），在交易被添加进交易池之前，首先会经过一个基础的验证，在 core/txpool/validation.go 中的 ValidateTransaction 方法中，会对交易做一个基础的验证，包括交易类型、交易大小、Gas 等是否符合要求，如果不符合，就会拒绝接收交易。</p><p>这里的交易大小使用 Slot 来规定，在 core/txpool/legacypool/legacypool.go 中定义了 Slot：</p><pre data-type="codeBlock" text="const (  txSlotSize = 32 * 1024  txMaxSize = 4 * txSlotSize // 128KB)
"><code>const (  txSlotSize <span class="hljs-operator">=</span> <span class="hljs-number">32</span> <span class="hljs-operator">*</span> <span class="hljs-number">1024</span>  txMaxSize <span class="hljs-operator">=</span> <span class="hljs-number">4</span> <span class="hljs-operator">*</span> txSlotSize <span class="hljs-comment">// 128KB)</span>
</code></pre><p>每个交易不能超过 4 个 Slot，而且对于每个账户、整个节点都有最大 Slot 的限制，对于账户，达到限制之后就不能提交新的交易。对于节点，达到限制之后就需要剔除旧的交易，在 core/txpool/legacypool/legacypool.go 中的 truncatePending 方法中会公平驱逐交易，防止单个账户占用过多交易池资源：</p><pre data-type="codeBlock" text="type Config struct {  AccountSlots uint64  GlobalSlots  uint64 }
"><code><span class="hljs-keyword">type</span> Config <span class="hljs-keyword">struct</span> {  AccountSlots <span class="hljs-type">uint64</span>  GlobalSlots  <span class="hljs-type">uint64</span> }
</code></pre><p>在网络层上， 对于 blob 交易或者是超过了一定大小的交易，不会直接在网络上传播交易内容，只会传播交易 Hash，从而避免网络上传播的数据量过大，造成 DDos 攻击。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易打包</strong></h3><p>在交易提交到交易池之后，会在以太坊网络中的节点之间传播，在某个节点的验证者被选中为出块节点之后，验证者就会委托共识层和执行层构造区块。</p><p>验证者会首先从共识层触发区块构造流程，共识层在接收到区块构造请求之后，就会调用执行层的 engineAPI 来构造区块，engineAPI 的实现在 eth/catalyst/api.go 。共识层会先调用 ForkchoiceUpdated  API 来发送构造区块的请求，ForkchoiceUpdated 有多个版本，具体调用哪个版本依据当前网络版本来决定，调用完成之后会返回 PayloadID，然后根据这个参数调用 GetPayload 对应版本 API 来获取区块的构造结果。</p><p>无论调用的是 ForkchoiceUpdated 的哪个版本，最终都是调用 forkchoiceUpdated 方法来构造区块：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5736de6210f2e512a11ef124a728070f54a5a03e02b029b1eb6336e445e36512.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在 ForkchoiceUpdated 方法中会对执行层当前的状态做校验，如果当前执行层正在同步区块、或者获得最终性的区块不符合预期，那么该方法都会向共识层直接返回错误信息，构造区块失败：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e410ad8c4e0c1eedef5b10a9c88a7318c2ef564e3b43ae5f2b0c70468cb8aaca.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在对执行层的信息校验完成之后，就会调用 miner/miner.go 中的 BuildPayload 方法来构造区块。构造区块的具体操作都在 miner/payload_building.go 中的 generateWork 方法中完成，但这里需要注意，在调用这个方法之后，就会先产生了一个空的 payload，并把这个 payloadID 返回给共识层。同时会启动一个 goroutine 真正去完成区块的打包流程，这个 goroutine 会持续去交易池中找价值更高的交易，每次重新打包交易之后，都会更新 payload。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3ce6765881c172113bbec85e9bf1a2b00642978961ad61ce65d1f5169ddb72a7.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>打包交易的是通过 miner/worker.go 中的 fillTransactions 方法来完成，实际上就是调用 txpool 的 Pending 方法来获取待打包的交易：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ec43d7b74b532519582d609c9137a1041782408c63701f1833076bc600855041.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>共识层在 slot 结束之前会调用 getPayload API 来获取最终打包好的区块。如果提交的交易被打包在这个区块当中，那么交易就会被 EVM 执行，并改变状态数据库。如果这次没有被打包，那么就会等待下一次被打包。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>交易执行</strong></h3><p>在打包交易的过程中，同时也会将交易在 EVM 中执行，得到区块交易完成之后状态的变化，同样还是在 generateWork 函数中，准备好当前区块执行的环境变量，主要是获取最新区块和最新状态数据库：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/87fc68ed39c5c5be99d31fb81b8cba01b1194a58610c8b4fc3380c58d8f7df59.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这里的 state 就是代表状态数据库：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1aac2f7979651513cbc5ab60b2015dcb181746390e6c1fda53036993fd5ee3d9.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在这里形成了一个 StateDB → stateObjects→ stateAcount 的结构，分别代表完整的状态数据库、账号对象集合以及单个账户对象。其中 StateObject 中结构中，dirtyStorage 表示当前交易执行后已改变的状态，pendingStorage 表示当前区块执行之后已改变的状态，originStorage 表示原始的状态，所以这三个状态从新到旧是 dirtyStorage → pendingStorage → originStorage，这里关于存储的详细解析可以查看之前关于存储的详细解析：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/294d333510e5310ac23de5763d5e6da7e1cc2c54c27432d76532488b27c1524f.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在 eth/backend.go 的 New 方法中启动时会加载交易池的配置，其中有一个 Locals 的配置，这个配置中的地址会被视为本地地址，这些本地地址提交的交易会被优先处理。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9d5ad0ad6d6e169df0b1b974dd69ea00f290fd4007950096d42a799c865ddf50.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在获取到当前的环境变量之后，就可以执行交易了，首先会获取全部待打包的交易，并把其中的本地交易挑选出来，区分成本地交易和正常交易，然后会对本地交易和正常交易分别按照手续费从高到低打包交易。交易具体的执行都在 miner/worker.go 中 commitTransactions 方法中进行：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c47cc8431c5ba5cea36df7bc9830dec5e3083cd489937a03dc34f146c247862d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>最终都是调用 ApplyTransaction 函数，在这个函数中，会调用 EVM 执行交易，并修改状态数据库：</p><pre data-type="codeBlock" text="func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) {  msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)  if err != nil {    return nil, err  }  // Create a new context to be used in the EVM environment  return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), tx, usedGas, evm)}
"><code>func ApplyTransaction(evm <span class="hljs-operator">*</span>vm.EVM, gp <span class="hljs-operator">*</span>GasPool, statedb <span class="hljs-operator">*</span>state.StateDB, header <span class="hljs-operator">*</span>types.Header, <span class="hljs-built_in">tx</span> <span class="hljs-operator">*</span>types.Transaction, usedGas <span class="hljs-operator">*</span><span class="hljs-keyword">uint64</span>) (<span class="hljs-operator">*</span>types.Receipt, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{  <span class="hljs-built_in">msg</span>, err :<span class="hljs-operator">=</span> TransactionToMessage(<span class="hljs-built_in">tx</span>, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {    <span class="hljs-keyword">return</span> nil, err  }  <span class="hljs-comment">// Create a new context to be used in the EVM environment  return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), tx, usedGas, evm)}</span>
</code></pre><p><strong>交易验证</strong></p><p>上面讨论的情况都是交易被打包进区块的流程，大多数情况下，节点只会验证已经被打包好的区块，而不是自己打包区块。</p><p>共识层在同步到区块之后，使用 engine API 将同步到的最新区块传输到执行层。使用的是 engine_NewPayload 系列方法。这系列的方法最后都会调用 newPayload 方法,在这个方法中将共识层的 payload 组装成一个 block：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/07c4966a8c65db7a5b3f4ff61e094f4c5309307c42c9bf9c85c92a4500a70954.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>然后检查这个区块是否已经存在了，如果存在了，那么就直接返回取消有效的状态：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/55493365934560c4ad566eafc1ea01b8c5795e4ba761dd629f63bd11a1190dba.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果当前执行层还在同步状态，那么暂时就无法接收新的区块：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ad4829d833ce5d2af0768b020b2ebdd1ab751431a18f36e97082df47982bd07e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果上面的条件都满足，那么就开始将区块插入到区块链中，这里需要注意，在插入区块的时候不会直接指定链头，因为链头的决策会涉及到链分叉的选择，这个需要依靠共识层来决定：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9a61fcba79e03fd67493531545ab7cec3891f0bc3fe63cad4981bbf23e51838e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>共识层会调用 forkChoiceupdated API 来调用 core/blockchain.go 中的 SetCanonical  方法来决定区块头：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a4362740b68efe4e91f25bc342b6263ee0849403ab70355112bb6178a000ac60.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>还有一种情况会触发区块头的设置，那就时区块发生重组，区块重组会执行 core/blockchain.go 的 reorg 方法，在这个方法中同样会设置当前最新确定的区块头。</p><p>回到区块的执行过程，在 core/blockchain.go 中的 InsertBlockWithoutSetHead 方法会调用 insertChain 方法，在这个方法中，会做一系列条件的检查，检查完成之后，就会开始处理区块：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d3d9b513e84d512175b91c80a6a6693bfa63447a1d54fc99757441625da57c88.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在具体的 Process 里面，处理逻辑就很清晰了，和之前打包交易的流程类似，不断在 evm 中执行交易，然后修改状态数据库，与打包不同的地方在于，这里只是将新区块中的交易重放一遍，而不需要去交易池中获取。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1ac345b70ddd1bd90ece07fbb785cf656c95261867fbb39e4aa74054a82d3053.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>总结</strong></h2><p>交易是驱动以太坊状态变化的唯一方式，交易在以太坊中的处理需要经过多个阶段。交易需要先经过验证，再被提交到交易池，在不同的节点之间通过 p2p 网络传播，然后被出块节点打包进区块，最后其他节点同步区块，在本地执行区块中的交易，并同步状态变更。</p><p>随着以太坊协议的不断发展，从最开始只能支持一种交易，到目前可以支持 5 种交易。这些不同类型的交易可以让以太坊适应不同的角色，及可以作为 DApp 的运行平台，也可以作为 Layer2 或者其他链下扩容的结算层。最近新增加的 EIP-7702 为以太坊被大规模采用做好了技术上的准备。</p><h2 id="h-references" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>References</strong></h2><p>[1] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/zh/developers/docs/transactions/">https://ethereum.org/zh/developers/docs/transactions/</a></p><p>[2] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://hackmd.io/@danielrachi/engine_api">https://hackmd.io/@danielrachi/engine_api</a></p><p>[3]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b">https://github.com/ethereum/go-ethereum/commit/c8a9a9c0917dd57d077a79044e65dbbdd421458b</a></p><p>[4] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pumpthegas.org/">https://pumpthegas.org/</a></p><p>[5] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/EIPs/pull/9698">https://github.com/ethereum/EIPs/pull/9698</a></p><p><strong>·END·</strong></p><p>内容 |  Ray</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Geth 源码系列：p2p 网络设计及实现]]></title>
            <link>https://paragraph.com/@lxdao/geth-p2p</link>
            <guid>QpAowdG5OsX5NsaRAfEY</guid>
            <pubDate>Tue, 17 Jun 2025 09:41:39 GMT</pubDate>
            <description><![CDATA[为什么需要关注 Geth 的 P2P 层？去中心化的生命线：P2P 网络是以太坊无需信任的根基，承担区块传播、交易广播、状态同步等核心功能 性能的关键瓶颈：节点发现效率、数据同步速度直接影响全网健康度（如 SnapSync 将同步时间从数天缩短至小时级⏳） 升级演进的核心战场：从 Discv4 到 Discv5 的协议迭代，见证以太坊网络层的进化之路 这篇文章是 Geth 源码系列的第三篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在这第三篇文章中，将系统讲解 Geth 的 p2p 网络设计与相关源码，包括以太坊的 DevP2P 协议规范，以及 Geth 中 p2p 网络架构及代码实现。 以太坊作为一个去中心化平台，其核心功能依赖于 p2p（peer-to-peer）网络，p2p 网络负责执行层节点之间的通信，为其他子协议提供底层能力、传播区块和交易，并支持以太坊实现去中心化。 本文作者： zhou | Blockchain Developer, Web3 Engineer, Lecturerp2...]]></description>
            <content:encoded><![CDATA[<h2 id="h-geth-p2p" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么需要关注 Geth 的 P2P 层？</h2><p>去中心化的生命线：P2P 网络是以太坊无需信任的根基，承担区块传播、交易广播、状态同步等核心功能</p><p>性能的关键瓶颈：节点发现效率、数据同步速度直接影响全网健康度（如 SnapSync 将同步时间从数天缩短至小时级⏳）</p><p>升级演进的核心战场：从 Discv4 到 Discv5 的协议迭代，见证以太坊网络层的进化之路</p><p>这篇文章是 Geth 源码系列的第三篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在这第三篇文章中，将系统讲解 Geth 的 p2p 网络设计与相关源码，包括以太坊的 DevP2P 协议规范，以及 Geth 中 p2p 网络架构及代码实现。</p><p>以太坊作为一个去中心化平台，其核心功能依赖于 p2p（peer-to-peer）网络，p2p 网络负责执行层节点之间的通信，为其他子协议提供底层能力、传播区块和交易，并支持以太坊实现去中心化。</p><p><strong>本文作者</strong>： zhou | Blockchain Developer, Web3 Engineer, Lecturer</p><h2 id="h-p2p" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">p2p 网络设计概述</h2><h3 id="h-11-devp2p" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1.1 DevP2P 简介</h3><p>以太坊执行层 p2p 网络功能的实现基于 DevP2P 协议栈所定义的标准，p2p 网络层独立于共识机制（无论是之前的 PoW 还是当前的 PoS，后者由共识层协调）。Geth 的网络架构包含两个并行工作的协议栈：基于 UDP 的发现协议栈（主要用于网络节点间发现）和基于 TCP 的通信协议栈（主要用于节点间数据交换与同步）。</p><h3 id="h-12-devp2p" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1.2 DevP2P 协议栈</h3><p>DevP2P 并非指单一协议，而是为以太坊 p2p 网络定制的一套网络协议。其设计不局限于特定的区块链，但主要服务与以太坊生态的需求。其核心包括：</p><ul><li><p>节点发现层：基于 UDP 协议，用于在 p2p 网络上定位其他以太坊节点。包含如 DiscoveryV4、DiscoveryV5、DNS Discovery 等协议。</p></li><li><p>数据传输层：基于 TCP 的协议，为节点间提供加密且经过身份验证的通信会话。主要由 RLPx 传输协议提供支持，基于此协议还衍生出多种应用层协议。</p></li><li><p>应用层子协议：在 RLPx 建立的节点发现和安全连接的基础之上，处理节点间的具体数据交互和应用逻辑。这些协议使得节点能够进行区块链同步、交易传播、状态查询等操作。包含如核心的 ETH 协议（Ethereum Wire Protocol）、服务轻客户端的 LES 协议（Light Ethereum Subprotocol）、用于快照同步的 SNAP 协议，以及 WIT（Witness Protocol）、PIP (Parity Light Protocol) 。</p></li></ul><p>以下为 DevP2P 协议栈整体结构图：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f97c03443324d7488f31ce7e78bad5271fce9ea58d35eab38a87bc2153e2fc61.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>DevP2P 协议栈整体结构图 应用层子协议简要说明：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/db90cbff06abde57ee8a2c1574bdd37fef119a887ce39148bd94839251778558.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-13-devp2p-libp2p" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1.3 DevP2P 与 LibP2P 的关系</h3><p>LibP2P（library peer-to-peer）</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.libp2p.io/concepts/introduction/overview/">https://docs.libp2p.io/concepts/introduction/overview/</a></p><p>是一个 p2p 网络框架，与以太坊的 DevP2P 在功能定位上有相似之处，它通过一系列协议、规范和库为 p2p 应用的开发提供支持。尽管两者存在相似性，以太坊在其发展初期（约 2014–2015年）并未选择采用 LibP2P 作为其P2P层。</p><p>原因是以太坊团队开发 p2p 网络模块（最终形成了 DevP2P）的关键时期，LibP2P 虽然已经作为 IPFS 项目的一部分，但它在当时尚未成熟到能够被以太坊直接集成。为了满足以太坊自身的需求，以太坊的开发者们自行设计和实现了一套 p2p 协议。</p><p>与 DevP2P 不同，LibP2P 最开始的核心目标便是构建一个更具普适性、高度模块化的 P2P 网络基础，旨在服务多样化的去中心化应用，而非仅仅应用在某一特定平台或应用场景。</p><p>DevP2P 可以被视为一个协议集合，明确定义了以太坊所需的组件，如 ENR、discv5 和 RLPx。而 LibP2P 更像是一个编程库集合，提供了可组合的模块来构建各种 P2P 功能，包括传输、流多路复用、安全通道、节点发现、发布/订阅消息传递等。</p><p>目前在以太坊共识层，如 Lighthouse，Prysm，Teku，Nimbus 等客户端，主要采用 LibP2P，共识层利用了 LibP2P 的传输层实现（TCP, QUIC）、加密（Noise）、流多路复用（mplex, yamux）、节点发现（基于 ENR 的 discv5）以及强大的发布/订阅系统（gossipsub）来高效广播证明和区块。</p><h2 id="h-devp2p" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DevP2P 协议栈详解</h2><h3 id="h-21" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.1 发现层</h3><p>发现协议V4（Discovery Protocol v4）</p><p>发现协议V4</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/devp2p/blob/master/discv4.md">https://github.com/ethereum/devp2p/blob/master/discv4.md</a></p><p>（简称 DiscV4）是以太坊 DevP2P 协议栈中的核心组件之一，它是一种基于 Kademlia 算法思想的分布式哈希表（DHT），用于网络中的节点发现。Kademlia 的核心机制在于通过节点 ID 间的特定“距离”度量（通过 XOR 异或运算得到）来组织节点，并将其他节点的信息（如IP地址、端口和节点 ID）存储到称为“k 桶”（k-buckets）的路由表中。这些信息会被组织为为以太坊节点记录（Ethereum Node Records, ENR），其中包含了节点的网络地址（IP地址、TCP 与 UDP 端口）、由公钥生成的唯一节点 ID、公钥本身，以及用于验证记录有效性的签名和序列号。</p><p>因此，DiscV4 的目标是帮助节点在未知网络中其他节点地址的情况下，能够高效地发现并连接到这些对等节点。这种高效性得益于其迭代查询方法：当一个节点 A 要查找目标节点 B 时，它会向自己k 桶中已知且 ID “距离”目标 B 最近的一批节点发送查询请求。这些被查询的节点会回复它们各自路由表中离目标 B 更近的节点列表。节点 A 持续向这些新发现的、更近的节点发起查询，逐步逼近目标，通常能在对数时间复杂度（O(log N)，其中 N为网络节点总数）内定位到网络中的任意节点，这远比随机探测或全网广播更为高效且节省网络资源。</p><p>发现协议V5（Discovery Protocol v5）</p><p>发现协议V5</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md">https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md</a></p><p>（简称 Discv5，当前最新规范为v5.1）作为对 DiscV4 的重大升级版本，在保留了基于 UDP 的 Kademlia DHT 核心思想（例如通过节点 ID 的 XOR “距离”定义网络拓扑、使用“k桶”维护路由表，以及通过 PING/PONG 和迭代的 FINDNODE/NODES 消息进行节点发现）的同时，引入了多项关键改进以弥补前代协议在地址管理、信息承载和可扩展性上的不足。其关键更新在于原生实现了以太坊节点记录（ENR，遵循 EIP-778 标准）。</p><p>这与 Discv4 后期才通过扩展有限度支持 ENR 不同，DiscV5 将 ENR 作为其基础数据单元，不仅允许 ENR 携带更丰富、可验证的键值对元数据（如支持的协议、特定角色等），而且 ENR 直接包含在常规消息（例如 NODES 响应）中进行交换，无需像 DiscV4 那样需要特定的 ENRRequest。另一项改进是 DiscV5 引入了基于“主题”（Topic）的发现机制；节点可以通过 TOPICREGISTER 消息广播其提供的服务或对特定主题的兴趣，其他节点则能通过 TOPICQUERY 精确查找到这些具有特定能力的对等节点，实现了比 DiscV4 更精细化的服务发现能力。</p><p>此外，DiscV5 还定义了全新的、具有更强安全保障的 discv5-wire 底层协议，通过改进握手过程（使用临时密钥和认证标签 AuthTag）来建立加密通信通道，并对消息格式进行了更为严格和清晰的规范。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6d6c354fc9269ef020e7f4b427eb9522a5cd6a160cba212b84be06cc3544ce51.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>DNS 发现（DNS Discovery）</p><p>该机制作为基于 UDP 的 P2P 发现协议（Discv4/Discv5）的补充，旨在提供一个稳定且易于更新的节点列表（特别是引导节点）作为客户端连接网络的初始入口点。文档详细说明了如何将以太坊节点信息（通常是以太坊节点记录 ENR）编码成特定的字符串格式，并存储在 DNS 记录（通常是 TXT 记录）中。这些 DNS 域名和记录由可信的列表维护者管理和发布。客户端通过查询这些特定的 DNS 域名来获取 ENR 列表，然后可以使用列表中的节点启动 Discv5 或 Discv4 p2p 发现流程。</p><h3 id="h-22" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.2 数据传输层</h3><p>RLPx（Recursive Length Prefix Transport Protocol）作为 DevP2P 协议栈的底层传输协议，运行在 TCP 协议之上，专为以太坊节点间提供经过认证、加密且支持多路复用的安全通信通道。</p><p>在两个通过发现协议定位彼此并决定建立连接的节点之间，RLPx 通过初始握手流程来确保通信双方身份的真实性并协商加密参数，此过程利用非对称加密技术，节点会交换临时密钥并结合其长期身份密钥进行相互认证，最终通过椭圆曲线迪菲-赫尔曼密钥交换（ECDH）算法安全地生成一个共享密钥。在握手成功之后，该共享密钥会用于加密后续在该 TCP 连接上所有传输的数据，从而保障了通信内容的机密性。</p><p>RLPx 的一个关键特性是其多路复用能力，它允许在单一的 TCP 连接上同时承载和区分多个不同的上层应用协议（即 DevP2P 子协议，如 ETH 协议用于区块链同步，LES 协议服务于轻客户端等）的消息流，通常通过消息头部内嵌的协议类型 ID 来识别和路由。所有在 RLPx 之上传输的应用层消息都会被组织成特定格式的“帧”（frames）进行传输，这些帧不仅包含了加密后的消息负载（通常是已经过 RLP 编码的应用层数据），还附带有消息大小、协议类型等必要的元数据，并支持将较大的消息分割成多个帧进行分块传输。</p><p>总之，RLPx 通过加密握手、消息封装和多路复用机制，为以太坊节点提供了一个独立于具体应用逻辑的、通用的安全管道，是实现高效数据同步、交易广播等功能的基础。</p><h3 id="h-23" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.3 应用层子协议</h3><p>ETH（Ethereum Wire Protocol）</p><p>ETH 通过 RLPx 传输协议促进对等节点间以太坊区块链信息的交换。ETH 协议定义了节点如何交换与区块链相关的信息，例如区块头、区块体、交易数据以及状态信息。包括了协议的版本协商机制、不同消息类型的结构（使用RLP编码）、消息代码以及这些消息在诸如区块同步、交易广播等过程中的交互流程，确保网络中的节点能够有效地同步和验证区块链数据，从而共同维护以太坊网络的一致性和完整性。</p><p>SNAP（Ethereum Snapshot Protocol）</p><p>SNAP 运行在 RLPx 之上，用于在节点间传播以太坊的状态快照。其主要目的是提供近期状态的动态快照以供半实时数据检索，但不参与链的维护，需要与 ETH 协议协同工作。SNAP 支持检索状态树中的连续账户片段或特定存储树中的存储槽，并带有默克尔证明以便快速验证，同时也能批量检索字节码。该协议通过允许节点直接获取账户和存储数据并在本地重组状态树，减少了对中间默克尔树节点的下载需求，从而降低了网络带宽消耗和延迟。它主要作为新加入全节点的引导辅助工具，并依赖 ETH 协议。</p><p>LES（Light Ethereum Subprotocol）</p><p>LES 专为轻客户端设计，使它们能够安全地访问以太坊区块链而无需参与共识过程，主要通过下载区块头并按需获取其他区块链部分。该协议利用规范哈希树（CHT）进行快速同步和安全数据检索（如区块头、总难度），并采用 BloomBits 技术优化日志搜索。为了防止服务器过载并管理请求速率，LES 还引入了客户端流控制机制，通过缓冲区限制、最小充值速率和最大请求成本等参数进行调节，从而为轻客户端提供高效且安全的区块链交互方式。</p><p>PIP（Parity Light Protocol）</p><p>PIP 是 Parity Technologies 为其以太坊客户端设计的 LES 协议变种。它也使用规范哈希树（CHT），但每 2048 个区块生成一次。PIP 采用类似令牌桶的流量控制机制，客户端需镜像服务器的状态。协议消息包括状态、宣告、请求批处理、响应批处理、更新信用参数等。值得注意的是，PIP 的请求和响应消息是批处理的，不可单独发送，旨在优化客户端与服务器的交互轮次。协议支持多种请求/响应对，用于检索区块头、交易信息、区块体、账户及存储证明、合约代码和执行证明等。</p><p>WIT（Ethereum Witness Protocol）</p><p>WIT 运行在 RLPx 之上，用于在对等节点间交换以太坊状态见证。它旨在帮助客户端同步到链的顶端，并最终支持无状态客户端操作，但不参与链的维护，而是与 ETH 协议并行工作。协议的第 0 版主要提供见证的元数据（如区块执行期间读取的树节点哈希列表，包括存储、字节码和账户节点），以辅助节点通过 ETH 协议下载实际见证，从而更快地完成同步。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">以太坊节点生命周期和数据流图</h3><p>节点生命周期和数据流图</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/dfd8316a36974263264efe7cf38adb65ee1a2b6e4e4a3b141ddb090eea94ced7.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>流程图简要说明：</p><ol><li><p>节点启动：以太坊客户端（Geth）开始运行</p></li><li><p>加载配置：读取配置文件，包括网络 ID、创世区块信息、端口设置等</p></li><li><p>初始化 p2p 服务器：设置 p2p 服务器，准备监听和发起连接</p></li><li><p>节点发现：节点开始寻找网络中的其他对等节点</p></li></ol><ul><li><p>DNS Discovery：通过 DNS 种子节点列表获取初始节点信息</p></li><li><p>Kademlia（Discv4/v5）：使用节点发现协议发现其他节点</p></li></ul><p>5. 发现潜在节点：汇总来自不同发现机制的节点信息</p><p>6. 连接建立：</p><p>监听传入连接：接受来自其他节点的连接请求</p><p>主动拨号：向已发现的节点发起连接请求</p><p>7. 接受连接请求/发起连接：成功建立 TCP 连接</p><p>8. RLPx 加密握手：在 TCP 连接之上进行加密握手，确保通信安全，协商会话密钥</p><p>9. DevP2P 协议握手：RLPx 握手成功后，进行 DevP2P 层的握手。节点交换各自支持的子协议（如 eth, snap, les 等）及其版本。双方会就共同支持的最高版本协议进行协商</p><p>10. 子协议交互：</p><ul><li><p>ETH 协议交互：如果协商成功 eth 协议，节点将通过此协议交换区块、交易、链状态等信息</p></li><li><p>Snap 协议交互：如果协商成功 snap 协议，节点可以通过此协议更高效地同步状态数据，如账户、存储、字节码和 Trie 节点</p></li><li><p>其他子协议交互：如 les（Light Ethereum Subprotocol）等</p></li></ul><p>11. Peer 管理：节点会维护一个对等节点列表，跟踪其状态（如健康状况、支持的协议、最新的区块高度等），并根据需要添加新节点或移除断开/行为不当的节点</p><p>12.数据交换与处理：</p><ul><li><p>区块/交易同步与广播：通过 ETH 协议，节点下载新的区块和交易，验证它们，并将自己的新区块和交易广播给其他节点</p></li><li><p>状态同步：通过 snap 协议（或 eth 协议的某些消息），节点请求并接收账户数据、存储槽、合约字节码和状态树（Merkle Trie）的节点数据，用于快速构建或更新本地状态</p></li><li><p>数据处理与验证：接收到的所有数据（区块、交易、状态片段）都需要经过严格验证，确保其符合协议规则和链的一致性</p></li></ul><p>13. 更新本地链状态/数据库：验证通过的数据被用于更新本地的区块链数据库和状态树</p><p>14. 对外提供服务/响应查询：节点根据其角色（全节点、轻节点等）对外提供服务，如响应 RPC 请求、处理交易等</p><p>下面将对该流程图结合源码进行详细说明。</p><h2 id="h-geth" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Geth 代码具体实现</h2><p>下文将通过对 geth 中相关代码的分析来说明 devp2p 协议规范的具体实现。</p><h3 id="h-41" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4.1 节点初始化与发现</h3><p><strong>4.1.1 节点的启动与配置加载</strong></p><p>网络中的一个参与者（即节点）通常是一台运行以太坊客户端软件（如 Geth）的计算机。每个节点都拥有其独特的身份标识，主要包括 enode ID（节点 ID）和 ENR （Ethereum Node Record）。</p><p>具体来说，enode ID 是基于节点自身加密密钥对（通常为 secp256k1 公钥）通过特定哈希运算产生的唯一标识。而 ENR 则是由节点自行构建并签名的一个可扩展记录。它封装了节点的网络联络信息（如 IP 地址、用于 P2P 通信的 TCP/UDP 端口 — — 通常默认为 30303，以及节点 ID 和公钥）和其提供的服务能力，充当其在网络中的名片。这些 ENR 随后被节点发现协议（如 Discovery v4/v5）用来帮助节点互相定位和建立连接。执行层节点包含以下类型：</p><ul><li><p>引导节点（Bootnode）：这类节点的主要职责是帮助新加入的节点接入 p2p 网络。它们的 ENR 信息通常被硬编码在客户端的配置文件中，或通过 DNS 发现机制提供，作为新节点首次连接网络的入口点。</p></li><li><p>本地节点（LocalNode）：指用户当前正在直接运行和控制的那个以太坊节点实例。</p></li><li><p>远程节点（RemoteNode）：泛指在以太坊 p2p 网络中，除了本地节点以外的所有其他参与节点。</p></li></ul><p>Geth 节点的程序入口位于 cmd/geth/main.go 。紧接着会加载节点的配置文件，具体包括网络 ID、端口、种子节点、引导节点等。</p><p>这里的 main 函数会解析命令行参数，创建和配置节点。main 中的 app.Run 会默认执行 geth 函数。在 geth 函数中，涉及到了 makeFullNode 和 startNode ，其中 makeFullNode 主要加载配置（makeConfigNode）并创建节点的核心服务，包括以太坊服务（utils.RegisterEthService）。而 startNode 负责则启动已经配置好的节点。</p><p>配置加载主要在 makeFullNode 和 makeConfigNode 中完成，结合了默认配置、配置文件以及命令行参数。cmd/geth/makeFullNode → makeConfigNode → loadBaseConfig 负责加载基础配置。节点配置相关的结构体和默认值定义在 node/config.go ，引导节点列表硬编码在 params/bootnodes.go 中。</p><pre data-type="codeBlock" text="func loadBaseConfig(ctx *cli.Context) gethConfig {  // 加载默认配置  cfg := gethConfig{    Eth:     ethconfig.Defaults,    Node:    defaultNodeConfig(),    Metrics: metrics.DefaultConfig,  }  // 加载配置文件（如果有 TOML 文件的话）  if file := ctx.String(configFileFlag.Name); file != &quot;&quot; {    if err := loadConfig(file, &amp;cfg); err != nil {      utils.Fatalf(&quot;%v&quot;, err)    }  }  // 应用节点  utils.SetNodeConfig(ctx, &amp;cfg.Node)  return cfg} 
"><code>func loadBaseConfig(ctx *cli<span class="hljs-selector-class">.Context</span>) gethConfig {  // 加载默认配置  cfg := gethConfig{    Eth:     ethconfig.Defaults,    Node:    <span class="hljs-built_in">defaultNodeConfig</span>(),    Metrics: metrics.DefaultConfig,  }  // 加载配置文件（如果有 TOML 文件的话）  if file := ctx.<span class="hljs-built_in">String</span>(configFileFlag.Name); file != "" {    if err := <span class="hljs-built_in">loadConfig</span>(file, &#x26;cfg); err != nil {      utils<span class="hljs-selector-class">.Fatalf</span>("%v", err)    }  }  // 应用节点  utils<span class="hljs-selector-class">.SetNodeConfig</span>(ctx, &#x26;cfg<span class="hljs-selector-class">.Node</span>)  return cfg} 
</code></pre><p><strong>4.1.2 节点发现</strong></p><p>在一个动态的、不断有节点加入和离开的网络中，一个节点需要一种机制来找到其他可以连接的节点。以太坊的节点发现协议包含上文介绍过的三种发现协议，可通过下表快速回顾：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e1abe90c29de9f39ae4868b2095376bbface21f382dba09554111632dadb286e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>DNS Discovery 实现位于 p2p/dnsdisc 包中。核心是 dnsdisc.Client 结构体（定义在 p2p/dnsdisc/client.go ）。Client 负责与配置的 DNS 服务器交互，获取 DNS TXT 记录，这些记录指向一个 ENR 树。客户端会解析这个树来获取节点列表。p2p.Server 会使用 dnsdisc.Client 来轮询 DNS 列表，并将发现的节点添加到拨号候选池中。</p><p>Discv4 的实现位于 p2p/discover/v4_udp.go</p><ul><li><p>ping 用于发送 PING 消息检查节点是否在线，并获取其 ENR 序列号。</p></li><li><p>findnode 向目标节点发送 FINDNODE 请求，获取其 Kademlia 桶中距离特定目标最近的一组节点。</p></li><li><p>LookupPubkey 在DHT网络中查找与给定公钥最接近的节点。</p></li><li><p>ListenV4 用于启动 v4 发现协议的监听和处理。</p></li></ul><p>Discv5 的实现位于 p2p/discover/v5_udp.go 。</p><ul><li><p>ping v5 版本的 PING 操作。</p></li><li><p>Findnode v5 版本的 FINDNODE 操作，允许查询特定距离的节点。</p></li><li><p>Lookup v5 版本的节点查找。</p></li><li><p>ListenV5（实际行号可能根据版本变化）负责 v5 协议的运行。</p></li></ul><p>p2p/discover/table.go 实现了 Kademlia 路由表，用于存储和管理已发现的节点信息。p2p/discover/v4wire 和 p2p/discover/v5wire 定义了 Discovery v4/v5 协议的网络消息格式。</p><p>在 p2p/server.go 文件中，Server.setupDiscovery 方法会根据节点配置来初始化并启动节点发现服务，这可能包括 Discovery v4/v5。该方法会调用相应的 discover.ListenV4 或 discover.ListenV5 函数来启动 UDP 监听，以接收来自其他节点的发现消息。</p><p>当节点需要查找新的对等节点或定位某个特定节点时，会触发相应的查找操作，例如 discv5 中的 Lookup 或 discv4 中的 LookupPubkey。这些操作是一个迭代的查询过程：节点会向其路由表中已知的、距离目标最近的一批节点发送 FINDNODE 请求。收到对端节点回复的 NEIGHBORS（discv4）或 NODES （discv5）响应后，节点会将响应中新发现的、更近的节点信息更新到自己的路由表中，并继续向这些新节点发起查询。这个迭代过程会持续进行，直到找到目标节点，或者无法找到比当前已知节点更接近目标的节点为止。</p><p>此外，为了维护路由表的健康和时效性，节点会定期向表中的其他节点发送 PING 消息，以检查它们的在线状态并刷新相关信息。收到对方回复的 PONG 消息即确认该节点仍然活跃。在某些情况下，例如需要获取某个节点的最新详细信息时，节点还可以通过发送 REQUESTENR 消息来请求对方的最新 ENR 信息。</p><h3 id="h-42-p2p" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4.2 p2p 服务的启动与连接握手</h3><p><strong>4.2.1 初始化 p2p 服务</strong></p><p>根据配置初始化 p2p 服务，设置本地节点信息、加密密钥等。</p><p>以太坊 p2p 服务的初始化过程主要涉及 node/node.go 文件中的 node.New 函数和 node.Node.Start 方法。首先，在 node.New 函数内部，系统会依据传入的配置（config.P2P）来创建并配置一个 p2p.Server 的实例。</p><p>随后，在 node.Node.Start 方法中，openEndpoints 这一步骤会调用位于 p2p/server.go 文件中的 Server.Start 方法。Server.Start 方法是启动 p2p 服务各项核心功能的入口，包括启动网络监听、节点发现服务等。在 Server.Start 的执行过程中，会进行一系列关键的设置和初始化操作。例如，通过 srv.setupLocalNode 来配置和准备本地节点的信息，加载用于节点身份验证的私钥至 srv.PrivateKey，并通过 srv.setupDialScheduler 设置连接的拨号调度器。</p><p>最终，srv.setupListening 方法会被调用以启动监听循环。这个监听循环可以视为 p2p 服务的主事件循环，它持续运行，负责处理所有的 p2p 网络活动，包括响应节点发现请求、管理新连接的握手过程以及处理节点间的消息传递等。</p><p><strong>4.2.2 连接建立与通信</strong></p><p>以太坊节点间的连接建立是一个确保通信安全与有效的多阶段过程。在基础的连接管理层面，节点间的数据交换依赖于标准的 TCP 连接。节点可以通过“拨号”（Dialing）操作主动向目标节点的 IP 地址和 TCP 端口发起连接，同时也通过“监听”（Listening）特定 TCP 端口来接受入站连接请求。为了有效管理网络资源和连接稳定性，客户端通常会配置如最大连接数、最大尝试拨号节点数等参数。</p><p>底层的 TCP 连接建立由 Go 的 net 包处理。而 server.go 下的 conn 结构体封装了底层的 net.Conn，并增加了与 p2p 协议相关的状态和标志位，如 inboundConn 和 trustedConn 等。</p><pre data-type="codeBlock" text="// conn wraps a network connection with information gathered// during the two handshakes.type conn struct {  fd net.Conn      // 底层的网络连接  transport        // 定义了加密握手 doEncHandshake 和协议握手 doProtoHandshake 的接口，以及消息读写 MsgReadWriter。  node  *enode.Node// 远端节点的 enode.Node 表示  flags connFlag   // 连接的标志位，如 inboundConn , staticDialedConn , trustedConn 等。  cont  chan error // 用于在 run 循环和 SetupConn 之间传递错误。  caps  []Cap      // 协议握手后远端节点支持的能力（Capabilities）。  name  string     // 协议握手后远端节点的名称。} 
"><code>/<span class="hljs-regexp">/ conn wraps a network connection with information gathered/</span><span class="hljs-regexp">/ during the two handshakes.type conn struct {  fd net.Conn      /</span><span class="hljs-regexp">/ 底层的网络连接  transport        /</span><span class="hljs-regexp">/ 定义了加密握手 doEncHandshake 和协议握手 doProtoHandshake 的接口，以及消息读写 MsgReadWriter。  node  *enode.Node/</span><span class="hljs-regexp">/ 远端节点的 enode.Node 表示  flags connFlag   /</span><span class="hljs-regexp">/ 连接的标志位，如 inboundConn , staticDialedConn , trustedConn 等。  cont  chan error /</span><span class="hljs-regexp">/ 用于在 run 循环和 SetupConn 之间传递错误。  caps  []Cap      /</span><span class="hljs-regexp">/ 协议握手后远端节点支持的能力（Capabilities）。  name  string     /</span><span class="hljs-regexp">/ 协议握手后远端节点的名称。} 
</span></code></pre><p>对于入站连接的监听与接收，服务器端的 listenLoop 方法会持续监听在预先配置好的 TCP 地址和端口上。一旦其内部的 listener.Accept 成功接收到一个新的 TCP 连接请求，系统会先执行一些初步的有效性检查，比如核实请求方 IP 地址是否位于受限列表（黑名单）中，或者其连接尝试是否过于频繁等。只有通过了这些前置检查的连接，服务器才会为其启动一个新的 goroutine，并调用 Server.SetupConn 方法，引导该连接进入后续的详细设置与握手阶段。</p><p>与此同时，p2p 服务也会根据自身维护网络状态的需求（例如保持足够的对等节点数量、或连接到特定的静态节点等）主动发起出站连接。这项任务主要由位于 p2p/dial.go 文件中的拨号调度器（dialScheduler）来管理。该调度器会选择合适的目标节点并发起 TCP 连接尝试。当 TCP 连接成功建立后，与处理入站连接的后续步骤类似，系统同样会调用 Server.SetupConn 方法来对这个新建立的出站连接进行配置。</p><p>无论是入站还是出站连接，在基础的 TCP 通道打通并通过初步校验之后，都会进入由 Server.SetupConn 方法负责的连接设置流程。在此方法中，系统首先会为这个新连接创建一个代表 p2p 层连接状态的 p2p.conn 对象。紧接着，为了处理 RLPx 传输层协议的细节，会进一步创建一个 rlpx.Conn 对象，该对象封装了底层的原始网络连接（net.Conn）。最后，也是建立安全通信渠道的关键步骤，系统会调用此 conn 对象的 doEncHandshake 方法（该方法内部实际上会触发 rlpx.Conn.Handshake 的执行），以便与对等节点完成 RLPx 加密握手，从而正式构建起一个安全的通信会话。</p><p>连接一旦成功建立，双方节点必须进行后续的握手流程，以验证身份并协商通信参数。这个关键过程主要分为两步：</p><ul><li><p>RLPx握手：此步骤旨在通过 RLPx 协议（以太坊的底层传输协议）构建一个安全的、经过加密的通信通道。它通过以下交互实现：发起方首先发送一个包含其临时公钥和签名的 auth 消息；接收方验证签名后，亦生成临时密钥对，并回复一个包含其临时公钥的 ack 消息。基于双方交换的临时公钥及各自的临时私钥，它们通过椭圆曲线迪菲-赫尔曼密钥交换（ECDH）算法计算出共享密钥。此后，在该 TCP 连接上传输的所有数据都将使用此共享密钥进行对称加密，保障通信内容的机密性。</p></li><li><p>协议握手（也称应用层握手）：在 RLPx 加密通道安全建立之后，节点间还需进一步明确彼此支持的应用层子协议及其版本，例如 eth/66、snap/1 等。为此，双方会互发一个 Hello 消息，其中详细列出了各自的节点 ID、客户端软件版本以及所支持的协议列表（称为 Capabilities 或 Caps ）。通过比对这些信息，节点会选取双方共同支持的、版本号最高的协议作为在该连接上进行后续具体数据（如区块、交易等）交换的标准。</p></li></ul><p><strong>4.2.3 RLPx 握手</strong></p><p>RLPx 协议的握手过程是确保以太坊节点间 p2p 通信安全的关键步骤，其实现位于 p2p/rlpx/rlpx.go 。整个过程围绕 Conn 对象展开，该对象封装了底层的 TCP 连接并管理着包括加密密钥、MAC密钥在内的会话状态。握手的入口点是 Conn.Handshake 方法，它会根据节点是连接发起方还是接收方，分别调用 runInitiator 或 runRecipient 函数来执行具体的握手逻辑。在整个握手期间，rlpx.handshakeState 对象会临时存储必要的中间状态信息，如临时密钥对和 Nonces。</p><pre data-type="codeBlock" text="// protoHandshake is the RLP structure of the protocol handshake.type protoHandshake struct {  Version    uint64 // DevP2P 协议版本。  Name       string // 客户端名称和版本（例如 &quot;Geth/v1.10.0/...&quot;）.  Caps       []Cap  // 节点支持的子协议能力列表，每个 Cap 包含协议名称（如 &quot;eth&quot;）和版本（如 66）。  ListenPort uint64 // 节点的监听端口。  ID         []byte // secp256k1 节点的公钥（Enode ID）。  // Ignore additional fields（for forward compatibility）.  Rest []rlp.RawValue rlp：&quot;tail&quot;} 
"><code>// protoHandshake <span class="hljs-built_in">is</span> the RLP <span class="hljs-keyword">structure</span> <span class="hljs-keyword">of</span> the protocol handshake.type protoHandshake struct {  Version    uint64 // DevP2P 协议版本。  Name       <span class="hljs-type">string</span> // 客户端名称和版本（例如 <span class="hljs-string">"Geth/v1.10.0/..."</span>）.  Caps       []Cap  // 节点支持的子协议能力列表，每个 Cap 包含协议名称（如 <span class="hljs-string">"eth"</span>）和版本（如 <span class="hljs-number">66</span>）。  ListenPort uint64 // 节点的监听端口。  ID         []<span class="hljs-type">byte</span> // secp256k1 节点的公钥（Enode ID）。  // Ignore additional fields（<span class="hljs-keyword">for</span> forward compatibility）.  Rest []rlp.RawValue rlp：<span class="hljs-string">"tail"</span>} 
</code></pre><p>握手流程：</p><ol><li><p>发起方：runInitiator 生成临时椭圆曲线迪菲-赫尔曼（ECDH）密钥对和随机 Nonce initNonce，并计算与接收方的静态共享密钥 staticSharedSecret。接着，使用其临时 ECDH 私钥对静态共享密钥与 initNonce 的异或值进行签名。然后，将此签名、自身的静态公钥 InitiatorPubkey 和 initNonce 打包成 authMsgV4 消息。最后，使用接收方的静态公钥通过椭圆曲线集成加密方案（ECIES）加密 authMsgV4 并发送。</p></li><li><p>接收方：runRecipient 收到并解密 authMsgV4 后，利用消息中的 InitiatorPubkey、Nonce initNonce 及本地计算的静态共享密钥来验证签名，并恢复出发起方的临时公钥 remoteRandomPub。随后，接收方也生成自己的临时 ECDH 密钥对和随机 Nonce respNonce。它将自己的临时公钥 RandomPubkey 和 respNonce 构造成 authRespV4 消息。最后，使用发起方的静态公钥通过 ECIES 加密 authRespV4 并发送。</p></li><li><p>密钥派生：双方首先使用各自的临时私钥和对方的临时公钥计算 ECDHE 共享密钥 ecdheSecret。随后，结合 ecdheSecret、respNonce 和 initNonce，通过 Keccak256 哈希运算依次派生出主共享秘密 sharedSecret、AES 密钥 aesSecret 和 MAC 密钥 macSecret。最后，双方使用 macSecret 分别与 respNonce 和 initNonce 进行异或，并结合各自发送和接收的初始认证消息（authMsg 或 authRespMsg），初始化出站 egressMAC 和入站 ingressMAC 的 Keccak256 哈希实例，用于后续消息的完整性校验。</p></li><li><p>握手完成：至此，双方都拥有了对称的 AES 密钥和 MAC 密钥，可以用于后续 RLPx 帧数据的加密和完整性验证，标志着握手成功。在 p2p/server.go 的实现中，Server.checkpoint 会在 RLPx 握手成功后，将此连接推送到 checkpointPostHandshake 通道，以等待进行更高级别的 devp2p 协议层握手。</p></li></ol><p>握手成功后，节点间的应用层消息通过 RLPx 协议被封装成帧。较大的消息会被分割成一或多个帧进行传输，每个帧都包含了必要的元数据（如消息代码、长度）及加密后的消息体。整个过程实现了即使在不安全的网络上，两个节点也能安全地协商出会话密钥，用于后续通信的加密和完整性保护。</p><p><strong>4.2.4 DevP2P 应用子协议握手</strong></p><p>在 RLPx 传输层的加密握手成功之后，节点间会立即进行 devp2p 协议层面的握手。这个过程的核心是通过交换一个 Hello 消息（其消息码为 0x00）来完成的，目的是让双方节点了解彼此的能力和身份。</p><p>“Hello” 消息（protoHandshake 结构体）包含了节点的协议版本、客户端名称（如 “Geth/v1.10.0”）、支持的子协议和版本（Capabilities）、监听端口以及节点ID（公钥）。</p><p>在 p2p/peer.go 文件中，每个 Peer 对象通过其 run 方法来管理与对端节点的主通信循环。在此循环开始，即连接建立之初，会调用 Peer.handshake 方法。这个方法负责主动向对端发送本地节点的 Hello 消息，并同时等待接收来自对方的 Hello 消息。</p><p>当双方都完成 Hello 消息的交换后，服务器端的逻辑（在 Server.run 方法中，通过 checkpointPostHandshake 和 checkpointAddPeer 等检查点）会进一步验证握手结果的有效性。这些检查包括确认连接是否仍然存活、对方是否是节点自身（防止自连接）、当前连接数是否已达到上限等，具体的检查逻辑可以参考 Server.postHandshakeChecks 和 Server.addPeerChecks 等函数。</p><p>最后，双方节点会比较各自在 Hello 消息中声明的支持子协议列表。通过这个比较，它们会协商出共同支持的子协议及其版本，并以此为基础在后续的通信中运行这些选定的子协议，进行如区块同步、交易广播等具体的数据交换。</p><p>在 DevP2P 基础协议握手成功之后，如果双方节点都支持某些共同的子协议（例如 eth 协议），它们会为这些子协议进行特定的握手。</p><p>p2p/server.go 中当一个新对等节点成功完成基础握手并通过检查后（ checkpointAddPeer 逻辑）， Server.launchPeer 函数会被调用，其会为每个协商成功的子协议启动一个对应的处理例程。它会遍历 srv.Protocols ，并与对端节点的能力（ c.caps ）进行匹配。匹配中会创建一个 protoRW 实例，并通过 go p.run() 启动该协议的通信循环（见 peer.go 中的 Peer.startProtocols ）。</p><p>ETH 协议的特定握手与消息处理：</p><p>ETH 协议自身也有一个特定的握手环节，它紧随 DevP2P 的 Hello 消息交换之后进行。此握手通过双方交换 Status 消息完成，该消息包含了节点的网络ID、链的总难度（TD）、当前最佳区块哈希等关键区块链状态信息，用于快速评估节点间的兼容性和同步需求。这个 Status 交换逻辑主要在 eth/protocols/eth/handshake.go 的 Handshake 函数中实现。</p><p>Status 消息结构如下：</p><pre data-type="codeBlock" text="type StatusPacket struct {  ProtocolVersion uint32      // eth 协议的版本  NetworkID       uint64      // 网络的 ID（例如，1代表主网）  TD              *big.Int    // （Total Difficulty）链的总难度  Head            common.Hash // 头区块的哈希  Genesis         common.Hash // 创世区块的哈希  ForkID          forkid.ID   // 分叉的 ID} 
"><code>type <span class="hljs-title class_">StatusPacket</span> struct {  <span class="hljs-title class_">ProtocolVersion</span> uint32      /<span class="hljs-regexp">/ eth 协议的版本  NetworkID       uint64      /</span><span class="hljs-regexp">/ 网络的 ID（例如，1代表主网）  TD              *big.Int    /</span><span class="hljs-regexp">/ （Total Difficulty）链的总难度  Head            common.Hash /</span><span class="hljs-regexp">/ 头区块的哈希  Genesis         common.Hash /</span><span class="hljs-regexp">/ 创世区块的哈希  ForkID          forkid.ID   /</span><span class="hljs-regexp">/ 分叉的 ID} 
</span></code></pre><p>一旦 ETH 协议的 Status 握手成功，后续的通信便由 eth/protocols/eth/handler.go 中的 Handle 函数（及其内部的 handleMessage 方法）主导。Handle 函数会进入一个消息处理循环，负责接收和处理该 ETH 子协议下定义的各种消息。这些消息类型多样，例如用于广播新区块哈希的 NewBlockHashesMsg、请求区块头的 GetBlockHeadersMsg、传递区块体的 BlockBodiesMsg 以及处理交易的 TransactionsMsg 等（其具体消息代码和处理函数通常与协议版本对应，如 eth/68 版本）。通过这个循环，节点得以执行区块同步、交易传播等核心操作。</p><p>为了支持不同版本的 ETH 协议（如 eth/66, eth/67, eth/68 ），同样在 handler.go 中的 MakeProtocols 函数会为每个版本创建相应的 p2p.Protocol 定义。当一个新的对等节点连接并协商确定使用特定版本的 ETH 协议时，该 p2p.Protocol 定义中的 Run 方法即作为此子协议的执行入口。 Run 方法通常会初始化一个特定于 ETH 协议的 Peer 对象（这是对通用 p2p.Peer 的封装，增加了ETH协议相关的状态与功能），然后调用 backend.RunPeer(peer, Handle) 来启动该对等节点的 ETH 协议消息处理主循环，并将 Handle 函数作为核心回调传入。</p><p>SNAP 协议：</p><p>除了 ETH 协议，Geth 客户端还支持 SNAP 协议，主要用于加速节点的状态快照同步。其实现位于 eth/protocols/snap 目录。与 ETH 协议类似，SNAP 协议也有其特定的消息处理逻辑，主要由该目录下的 handler.go 中的 Handle 和 HandleMessage 函数负责，专门处理与状态快照数据请求、传输和验证相关的消息。</p><h3 id="h-43" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4.3 数据同步与处理</h3><p>数据处理与同步的主要结构如下图所示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/83d63e53c336610f61414efc84f5270cd87d2a4e05ac2e64b25899c05ede135f.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>4.3.1 数据同步</strong></p><p>数据同步是新节点加入网络或落后节点追赶最新链状态的核心过程。在 Geth 中，这个过程主要由 eth/downloader/downloader.go 模块负责。</p><p>同步启动与模式选择</p><p>同步过程主要通过 Downloader.synchronise 方法启动。Geth 支持多种同步模式，例如完整同步（FullSync）和快照同步（SnapSync），具体模式由 d.mode 变量控制，并可通过 Downloader.getMode 方法获取。值得一提的是，在 SnapSync 模式下，Geth 会先禁用 Trie 数据库的写入并暂停快照维护，以确保数据的一致性。</p><p>对等节点（Peer）管理</p><p>Geth 通过以下方法管理同步过程中的对等节点：Downloader.RegisterPeer 方法用于注册新的下载节点，这些节点保存在 d.peers（ peerSet 实例）中。相应的，Downloader.UnregisterPeer 方法则用于移除节点。此外，peerDropFn 回调函数会在节点行为不当（如发送无效数据）时被触发，以便及时将其移除。</p><p>数据获取与调度</p><p>数据获取的调度由 d.queue（ queue 实例）负责，它管理着需要下载的数据哈希。Geth 定义了诸如 MaxBlockFetch、MaxHeaderFetch、MaxReceiptFetch 等常量，用以限制单次请求可以获取的数据量。而 fetchRequest 结构体则表示一个正在进行的数据检索操作。</p><p>同步过程中的特定优化</p><p>在以太坊合并之后，Geth 引入了骨架同步（Skeleton Sync），用于高效地同步信标链的头部信息，并通过 Downloader.findBeaconAncestor 寻找共同祖先。同时，在 SnapSync 模式下，Geth 会选择一个 pivotHeader 作为状态同步的起点。</p><p>同步过程控制与事件通知</p><p>Geth 提供了一套完善的控制机制来管理同步的生命周期。d.cancelCh 通道可用于中途中止同步过程。d.syncStatsChainOrigin 和 d.syncStatsChainHeight 记录了同步的起始和目标高度，方便进度追踪。此外，d.mux（ event.TypeMux 实例）负责广播同步相关的事件，例如 StartEvent（同步开始）、DoneEvent（同步完成）和 FailedEvent（同步失败），这些事件对于客户端状态显示和外部监控至关重要。</p><p><strong>4.3.2 数据处理与验证</strong></p><p>在 Geth 客户端中，当从网络获取到数据后，需要对其进行处理、验证，并最终整合到本地的区块链中。</p><p>区块头（Header）处理</p><p>下载的区块头（types.Header）会通过 d.headerProcCh 通道传递给专门的头部处理器。headerTask 结构体则封装了一批下载的区块头及其预计算的哈希值。Geth 通过 maxHeadersProcess 常量限制一次性导入链中的区块头数量，以优化处理效率。</p><p>区块（Block）与收据（Receipt）处理</p><p>fetchResult 结构体负责收集部分下载结果，包括区块体、叔块、交易、收据和提款，直到所有部分都完整获取。数据准备就绪后，InsertChain 方法用于将一批区块插入本地链，而 InsertReceiptChain 方法则用于将一批区块及其对应的收据插入本地链。值得注意的是，ancientLimit 定义了被认为是“远古”数据的最大区块号，早于此号的数据在 SnapSync 模式下可能会直接写入专门的远古存储。chainCutoffNumber 和 chainCutoffHash 则定义了同步时可以跳过的链段截止点。</p><p>数据验证与错误处理</p><p>Geth 对获取到的数据进行严格验证，并定义了多种错误类型来指示验证失败，例如：</p><ul><li><p>errInvalidChain：检索到的哈希链无效。</p></li><li><p>errInvalidBody：检索到的区块体无效。</p></li><li><p>errInvalidReceipt：检索到的收据无效。</p></li></ul><p>如果 Geth 检测到无效数据或发现对等节点的恶意行为，会立即通过 dropPeer 回调函数移除对应的节点，以维护网络健康。badBlockFn 回调函数则用于在异步信标同步中，通知调用者某个区块因验证失败而被拒绝。</p><p>状态同步</p><p>d.SnapSyncer（一个 snap.Syncer 实例）专门负责处理快照同步中的状态数据下载和处理。d.stateSyncStart 通道用于启动状态同步过程，而 d.stateFetcher（一个在 New 方法中启动的 goroutine）则具体负责状态数据的获取。</p><p>数据提交与异常处理</p><p>d.committed 布尔值标记着数据是否已最终提交。在 SnapSync 模式中，数据可能直到同步完全完成前都不会被最终提交，以确保原子性。SnapSyncCommitHead 方法用于在快照同步中直接提交头区块。同步过程中可能出现的错误类型包括 errBusy（下载器正忙）、errTimeout（超时）和 errCanceled（同步被取消）等，Geth 会对这些异常情况进行妥善处理。</p><p><strong>4.3.3 更新本地链状态/数据库</strong></p><p>经过验证的数据会被写入本地数据库，从而更新节点的链状态。Geth 中本地链状态和数据库的更新主要通过以下机制实现：</p><p>区块和收据插入</p><ul><li><p>Downloader 结构体依赖一个 BlockChain 接口，该接口定义了插入区块和收据的方法。</p></li><li><p>BlockChain.InsertChain 将一批区块插入本地链。</p></li><li><p>BlockChain.InsertReceiptChain 将一批区块及其收据插入本地链。</p></li><li><p>对于早于 ancientLimit 的数据，会直接存入 ancient store。</p></li><li><p>BlockChain.InsertHeadersBeforeCutoff 在配置的 chainCutoffNumber 之前插入一批区块头到 ancient store。</p></li><li><p>processFullSyncContent 和 processSnapSyncContent （在 fetcher.go 中，但由 downloader 调用）函数负责处理下载到的区块和收据，并调用上述 BlockChain 接口的方法将它们插入数据库。</p></li></ul><p><strong>Snap Sync 状态更新</strong></p><ul><li><p>Downloader 包含一个 Downloader.SnapSyncer（snap.Syncer 类型），专门负责 Snap Sync 过程中的状态下载和应用。</p></li><li><p>在 Snap Sync 期间，状态数据（账户、存储、字节码等）会直接写入 Downloader.stateDB 。</p></li></ul><p>syncToHead 函数中，如果采用 Snap Sync 模式，会调用 rawdb.WriteLastPivotNumber 将 pivot 区块号写入数据库，以便在回滚时能够重新启用 Snap Sync。</p><ul><li><p>SnapSyncer.Progress() 方法用于获取 Snap Sync 的进度，包括已同步的账户、字节码、存储等数量。</p></li></ul><p>Ancient Store 和 Chain Cutoff</p><ul><li><p>Downloader.ancientLimit：定义了可以被视为 ancient 数据的最大区块号。在 Snap Sync 模式下，早于此限制的数据会直接写入 ancient store。</p></li><li><p>Downloader.chainCutoffNumber 和 Downloader.chainCutoffHash ：定义了同步时跳过区块体和收据的截止点。</p></li></ul><p>数据库提交</p><p>Downloader.committed 标志位：在 Snap Sync 模式下，如果 pivot 区块号不为 0，初始时 committed 为 false ，表示数据库中的状态可能是不完整的，直到同步完成。</p><p><strong>4.3.4 对等节点管理</strong></p><p>以太坊节点通过对等节点管理（Peer Management）来维护其 P2P 网络连接的稳定性和效率。一个成功与本节点建立连接并完成所有必需握手流程的远程节点，即成为一个对等节点（Peer）。有效的对等节点管理对于保障网络的连通性、促进数据的顺畅交换以及优化节点自身资源利用至关重要，这一管理过程主要涵盖了以下几个核心方面：</p><ul><li><p>对等节点的识别与连接池维护：在本地节点成功与远程节点完成所有握手步骤，确认其为对等节点后，节点会在本地维护一个当前所有这些活跃对等节点的列表或池（Connection Pool）。这个连接池是节点进行一切网络通信的动态基础。</p></li><li><p>连接操作与访问策略：节点会执行一系列连接操作，如通过主动“拨号”出站或“监听”并接受入站请求来添加新的对等节点。同时，节点也会根据通信错误、超时、对方离线或预设的访问策略（例如优先保障“受信任的” Peer 的连接，或对总连接数、单个 IP 连接数进行限制）来决定断开连接并移除相应的对等节点。</p></li><li><p>对等节点状态事件的响应：系统中对等节点的各种状态变化，例如新连接的成功建立、现有连接的意外断开或在通信过程中发生的错误，通常会触发内部的事件通知。这些事件使得客户端软件中的其他相关模块（如负责区块链同步的逻辑单元或交易池管理等）能够及时感知到 P2P 网络拓扑的实时动态，并据此作出必要的响应或调整。</p></li></ul><p>对等节点（Peer）管理</p><ul><li><p>Downloader.RegisterPeer 将新的 peer 注入到下载源集合中。</p></li><li><p>Downloader.UnregisterPeer 从已知列表中移除 peer，并尝试将该 peer 待处理的 fetch 任务返回到队列中。</p></li><li><p>Downloader.dropPeer 当检测到 peer 行为不当（例如发送无效数据）时，会调用此函数来移除该 peer。</p></li></ul><p>错误类型如下：</p><pre data-type="codeBlock" text="errBusy                     // 表示下载器正忙，无法处理新的同步请求errBadPeer                  // 表示来自不良 peer 的操作被忽略errTimeout                  // 表示操作超时errInvalidChain             // 表示检索到的哈希链无效errInvalidBody              // 表示检索到的区块体无效errInvalidReceipt           // 表示检索到的收据无效errCancelStateFetch         // 表示取消状态获取errCancelContentProcessing  // 表示取消内容执行errCanceled                 // 表示同步被取消errNoPivotHeader            // 表示未找到 pivot header 
"><code>errBusy                     /<span class="hljs-regexp">/ 表示下载器正忙，无法处理新的同步请求errBadPeer                  /</span><span class="hljs-regexp">/ 表示来自不良 peer 的操作被忽略errTimeout                  /</span><span class="hljs-regexp">/ 表示操作超时errInvalidChain             /</span><span class="hljs-regexp">/ 表示检索到的哈希链无效errInvalidBody              /</span><span class="hljs-regexp">/ 表示检索到的区块体无效errInvalidReceipt           /</span><span class="hljs-regexp">/ 表示检索到的收据无效errCancelStateFetch         /</span><span class="hljs-regexp">/ 表示取消状态获取errCancelContentProcessing  /</span><span class="hljs-regexp">/ 表示取消内容执行errCanceled                 /</span><span class="hljs-regexp">/ 表示同步被取消errNoPivotHeader            /</span><span class="hljs-regexp">/ 表示未找到 pivot header 
</span></code></pre><p>取消机制</p><ul><li><p>Downloader.cancelCh 一个 channel，用于取消正在进行的同步操作。</p></li><li><p>Downloader.Cancel （在 synchronise 函数的 defer 中调用）确保在同步过程退出时关闭 cancelCh ，并等待所有 fetcher goroutine 退出。</p></li><li><p>cancelLock 和 cancelWg 用于保护 cancelCh 的并发访问和确保 goroutine 正确退出。</p></li></ul><p><strong>处理无效数据和坏块</strong></p><ul><li><p>当从 peer 接收到的数据（如区块头、区块体、收据）验证失败时，会返回相应的错误（如 errInvalidChain , errInvalidBody , errInvalidReceipt ）。</p></li><li><p>Downloader.badBlock 用于异步 beacon sync 通知调用者，请求同步的源头 header 产生了一个包含坏块的链。</p></li><li><p>queue.DeliverHeaders , queue.DeliverBodies , queue.DeliverReceipts（在 queue.go 中）等函数会进行数据校验，如果校验失败，可能会导致 peer 被标记为不良或被移除。</p></li></ul><p>同步失败事件</p><ul><li><p>当同步过程因任何错误而意外终止时，Geth 会通过 d.mux.Post(FailedEvent{err}) 发布一个 FailedEvent 事件，其中包含了具体的错误信息。这使得 Geth 的其他模块或外部监控工具能够及时感知同步状态的变化并做出相应处理。 测试关键步骤 Geth 中包含了大量测试用例，我们可以对上文提到的关键步骤直接进行测试。</p></li></ul><h3 id="h-51" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">5.1 节点发现测试</h3><p>在目录 p2p/discover 下</p><p>v4_udp_test.go 此文件包含了针对基于 UDP 的 v4 发现协议的测试</p><blockquote><ul><li><p>TestUDPv4_findnode 测试 findnode 请求，这是节点发现的核心。它会检查是否能找到并返回最近的邻居节点。</p></li><li><p>TestUDPv4_pingTimeout 测试 ping 请求的超时处理， ping/pong 用于检查节点是否在线。</p></li><li><p>TestUDPv4_packetErrors 测试各种数据包错误处理，例如过期的包、未请求的回复等。</p></li><li><p>v5_udp_test.go 这个文件包含了针对基于 UDP 的 discv5 发现协议的测试。</p></li><li><p>TestUDPv5_lookupE2E 进行端到端的节点查找测试，确保节点可以成功发现网络中的其他节点。</p></li><li><p>TestUDPv5_pingHandling 测试对 PING 请求的处理和 PONG 回复的生成。</p></li><li><p>TestUDPv5_unknownPacket 测试当收到未知来源的数据包时的处理逻辑，这通常会触发握手流程。</p></li></ul></blockquote><p>table_test.go 包含对路由表（Kademlia 表）操作的测试，例如节点的添加、删除、查找等。</p><p>v4_lookup_test.go 这个文件用于 discv4 发现协议中的节点查找（lookup）逻辑测试。</p><h3 id="h-52" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">5.2 建立连接</h3><p>handshake_test.go 这个文件专门测试以太坊协议（eth protocol）的握手过程。</p><p>通常会包含测试握手消息的编码/解码、状态转换（例如，从等待状态到握手成功/失败）、版本协商、能力通告等。</p><p>v5_udp_test.go（也与握手相关）：</p><p>TestUDPv5_handshakeRepeatChallenge 测试在握手过程中重复收到挑战（challenge）时的处理逻辑，确保握手的健壮性。</p><p>TestUDPv5_unknownPacketKnownNode 测试当收到来自已知节点的未知数据包时的行为，这可能也与握手或会话重新建立有关。</p><h3 id="h-53-rlpx" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">5.3 RLPx 握手测试</h3><p>RLPx 测试（加密握手、消息收发等）主要位于 p2p/rlpx_test.go 文件：</p><ul><li><p>TestHandshake 测试握手，里面包括创建对等节点。</p></li><li><p>createPeers 创建对等节点，并尝试进行握手。</p></li><li><p>doHandshake 进行握手测试。</p></li></ul><h3 id="h-54" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">5.4 节点管理测试</h3><p>主要集中在 p2p/server_test.go 、 p2p/discover/v4_udp_test.go 、 p2p/discover/v4_lookup_test.go 、 p2p/discover/v5_udp_test.go 。</p><ul><li><p>例如 server_test.go 涵盖了节点的添加、移除、信任管理、连接建立等核心逻辑。</p></li><li><p>v4_udp_test.go 和 v5_udp_test.go 主要测试节点发现协议（如 Ping/Pong、FindNode、Neighbors、Whoareyou 等包的处理），包括节点查找、握手、包处理异常等。</p></li><li><p>v4_lookup_test.go 重点测试节点查找流程、查找迭代器、查找结果的正确性和排序。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>Geth 的网络核心构建在 DevP2P 协议栈之上，这是一个专为以太坊网络层通信设计的协议集合。节点首先通过多种机制发现对等节点，主要包括基于 Kademlia 的分布式哈希表（如 Discv4/v5 协议）和 DNS 种子节点。一旦发现潜在的对等节点，Geth 会尝试建立 TCP 连接。成功建立 TCP 连接后，会进行 RLPx 加密握手，以确保通信的机密性和完整性，并协商会话密钥。</p><p>在 RLPx 握手之后，节点会进行 DevP2P 协议层的握手。在这一阶段，节点会交换它们各自支持的子协议及其版本，例如 eth （以太坊主协议）、 snap （快速同步协议）、 les （轻客户端协议）等。双方会协商使用共同支持的最高版本的子协议进行后续通信。这个过程确保了不同能力的节点可以有效地交互。</p><p>协商成功后，节点便通过选定的子协议进行具体的数据交换。例如，通过 eth 协议，节点同步区块、交易、链状态等核心数据，这是以太坊网络功能的基础。而 snap 协议则允许节点更高效地同步状态数据，加速新节点的初始同步过程。Geth 会持续管理其对等节点列表，监控连接状态，并根据网络情况和节点行为动态调整连接。</p><p>总之，Geth 的网络模块是一个分层且模块化的系统，从底层的节点发现和安全连接建立，到上层的特定功能子协议协商与数据交换，共同构成了一个健壮且高效的去中心化网络通信框架。这种设计使得 Geth 能够灵活适应网络变化，并支持以太坊生态系统的持续发展。</p><h2 id="h-references" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">References</h2><p>[1]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum">https://github.com/ethereum/go-ethereum</a></p><p>[2]</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/devp2p">https://github.com/ethereum/devp2p</a></p><p>[3] GeeksforGeeksDifference Between libp2p, devp2p and RLPx — GeeksforGeeks</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.geeksforgeeks.org/what-is-the-difference-between-libp2p-devp2p-and-rlpx/">https://www.geeksforgeeks.org/what-is-the-difference-between-libp2p-devp2p-and-rlpx/</a></p><p>[4] Welcome to go-ethereum | go-ethereum</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://geth.ethereum.org/docs">https://geth.ethereum.org/docs</a></p><p>[5] 以太坊开发文档</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/zh/developers/docs/">https://ethereum.org/zh/developers/docs/</a></p><p>·END·</p><p>内容 | zhou Blockchain Developer, Web3 Engineer, Lecturer</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[EIP-7702 残酷共学优秀笔记总结]]></title>
            <link>https://paragraph.com/@lxdao/eip-7702</link>
            <guid>xRhfaGRpIEekORsGM4Hr</guid>
            <pubDate>Thu, 29 May 2025 11:32:47 GMT</pubDate>
            <description><![CDATA[经过 10 天的高强度学习挑战， 本期 EIP-7702 残酷共学圆满落幕！本期共吸引 43 名学习者参与，最终 18 人成功完成挑战，淘汰率达 58.14% ！其中，12 位伙伴以全勤战绩荣获“全勤超残酷”称号，展现了非凡的毅力与热忱！ 我们也整理了一些同学的优秀笔记，便于大家更好的理解 EIP-7702什么是 EIP-7702？来源：Jack_OuCJ EIP 7702 的核心是引入一种新的交易类型，允许账户自行设置和委托代码。它不再将完整的合约代码直接存储在账户中，而是存储一个委托指示符，该指示符由一个特定的前缀加上一个地址 ( (0xef0100 || address))组成。这个指针指向实际智能合约代码在链上的位置。 简而言之，钱包将指向链上的智能合约，该合约的逻辑决定账户的行为方式。 这种机制使 EOA 的行为更像智能账户。然而，两者之间一个显著的区别是，EOA 的私钥拥有对账户的完全控制权。如果 EOA 的私钥被泄露，攻击者将完全控制该账户。 它的核心目标是：让外部拥有账户（EOA）拥有合约账户（Contract Account）那样的灵活签名和权限管理能力；保持原...]]></description>
            <content:encoded><![CDATA[<p>经过 10 天的高强度学习挑战， 本期 EIP-7702 残酷共学圆满落幕！本期共吸引 43 名学习者参与，最终 18 人成功完成挑战，淘汰率达 58.14% ！其中，12 位伙伴以全勤战绩荣获“全勤超残酷”称号，展现了非凡的毅力与热忱！</p><p>我们也整理了一些同学的优秀笔记，便于大家更好的理解 EIP-7702</p><h2 id="h-eip-7702" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么是 EIP-7702？</h2><p>来源：Jack_OuCJ</p><p>EIP 7702 的核心是引入一种新的交易类型，允许账户自行设置和委托代码。它不再将完整的合约代码直接存储在账户中，而是存储一个委托指示符，该指示符由一个特定的前缀加上一个地址 ( (0xef0100 || address))组成。这个指针指向实际智能合约代码在链上的位置。</p><p>简而言之，钱包将指向链上的智能合约，该合约的逻辑决定账户的行为方式。</p><p>这种机制使 EOA 的行为更像智能账户。然而，两者之间一个显著的区别是，EOA 的私钥拥有对账户的完全控制权。如果 EOA 的私钥被泄露，攻击者将完全控制该账户。</p><p>它的核心目标是：</p><ul><li><p>让外部拥有账户（EOA）拥有合约账户（Contract Account）那样的灵活签名和权限管理能力；</p></li><li><p>保持原有地址不变、兼容现有工具；</p></li><li><p>在 Layer-1 层面原生支持多签、社交恢复、gas 费替付等功能，无需依赖中继或捆绑器（bundler）。</p></li></ul><p>来源：a39955720</p><p>EIP-7702 是一项新提案，允许将传统的 EOA 永久升级为像合约账户一样的账户，并具有更多功能，例如一次完成多项操作、让他人为您支付汽油费用或限制密钥只能进行某些操作。</p><p>这是通过一种新的交易格式（类型 0x04）来实现的，在这种格式中，交易附带有一个 “授权列表”，告诉连锁店： “这些账户现在授权给某个特定合同的代码，直到我分配一个新代码或它们被清除为止”。</p><p>来源：wayhome</p><p>EIP-7702 是一项旨在改进以太坊用户交互方式的提案，特别关注增强外部拥有账户 (EOA) 的功能。其核心目标是模糊 EOA 和智能合约账户之间的界限，赋予 EOA 可编程性和可组合性</p><p>主要机制：批量处理：一次交易，完成多项操作，告别重复确认，赞助交易：他人代付Gas费，降低交易成本，惠及用户，权限降级：精细化权限管理，保障账户安全</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">以太坊具有哪些账户类型</h2><p>来源：Jack_OuCJ</p><p>外部拥有账户（Externally Owned Account，EOA）</p><p>控制方式：由私钥签名控制</p><p>主要属性</p><ul><li><p>地址：20 字节</p></li><li><p>余额：ETH</p></li><li><p>nonce：用于防重放、交易排序</p></li><li><p>无代码（code）</p></li></ul><p>功能</p><ul><li><p>发起交易（转账、调用合约）</p></li><li><p>部署合约</p></li></ul><p>合约账户（Contract Account）</p><p>控制方式：由部署在链上的智能合约字节码控制</p><p>主要属性</p><ul><li><p>地址：20 字节</p></li><li><p>余额：ETH</p></li><li><p>nonce：仅在创建新合约时自增</p></li><li><p>有代码（code）：EVM 执行字节码</p></li></ul><h2 id="h-7702" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">7702 能用在什么地方？</h2><p>来源：universe-ron</p><p>Pectra 硬分叉時間點：官方 2025‑05‑07 上線。也就是說，任何拿 MetaMask、Rainbow 之類「傳統 EOA」的朋友，都能瞬間獲得「智慧合約錢包級」的超能力。對開發者和使用者都是大新聞。</p><p>我自己的痛點：我在做一個包含 DEX + NFT 市場的小專案。</p><p>現在最讓新人崩潰的流程是：</p><ul><li><p>第一次「Approve」鎖代幣（要付一次 gas）。</p></li><li><p>第二次才「Transfer / Swap」（再付一次 gas）。如果他們還沒存 ETH，就直接卡關。7702 把這些痛全部揉成一顆藥丸吞下去，一條龍搞定。</p></li></ul><p>錢包 UX 全面升級：想像一下，你跟朋友吃飯，有個 DApp 說「我幫你付 gas，你只要按一次確認」。過去要靠 4337 或自家伺服器，門檻高；7702 出來，人人都能辦到。</p><ol><li><p>我眼中的三大亮点</p></li></ol><p>重點：7702 = 「臨時」把 EOA 的程式碼指向代理合約。它不像 4337 得搞一大堆 Bundler、EntryPoint；只有在那一筆交易、那幾分鐘內生效，用完就收工，簡單粗暴。</p><h2 id="h-eip-7702-eip-4337" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">EIP-7702 和 EIP-4337 的对比</h2><p>来源：universe-ron</p><ul><li><p>我覺得兩者是「大哥 + 小弟」的關係，不是你死我活。</p></li><li><p>ERC‑4337：超完整、模組化、可擴充，像一台重裝機甲 — — 功能強，部署重。</p></li><li><p>EIP‑7702：輕量、即插即用，像袖珍瑞士刀 — — 不一定能打大 Boss，但日常超好用。</p></li><li><p>更棒的是，Smart EOA（開了 7702 超能力的地址）可以直接塞進 4337 的 UserOperation 當 sender。所以生態系不會破碎，大家繼續用熟悉的工具鏈。</p></li></ul><p>来源：Jack_OuCJ</p><p>EIP-4337 的优缺点</p><p>优点</p><ul><li><p>零协议升级门槛，快速上线</p></li><li><p>社区生态活跃，多家钱包厂商已支持</p></li><li><p>Paymaster 可定制、EntryPoint 可升级</p></li></ul><p>缺点</p><ul><li><p>架构复杂度高，引入 Bundler 和专用 mempool</p></li><li><p>每笔交易须先充值 EntryPoint Deposit</p></li><li><p>Gas 消耗与延迟均高于原生方案</p></li></ul><p>EIP-7702 的优缺点</p><p>优点</p><ul><li><p>原生支持，无额外中介，Gas 与延迟最低</p></li><li><p>架构最简，用户仅需按新格式发交易</p></li><li><p>功能弹性最大，任何验证均可链上定制</p></li></ul><p>缺点</p><ul><li><p>必须通过硬分叉/网络升级才能生效</p></li><li><p>节点、RPC、钱包等生态需全面升级</p></li></ul><p>来源：alexliao</p><p>EIP-7702</p><ul><li><p>允許 EOA 使用鏈上程式碼指標（code pointer） 來委託其行為。</p></li><li><p>設計較輕量（leaner integration）。</p></li><li><p>更適合特定用例的直接整合。</p></li></ul><p>EIP-4337</p><ul><li><p>採用 更全面的 Account Abstraction 架構。</p></li><li><p>使用 off-chain bundler 和 專用的 EntryPoint 智慧合約。</p></li><li><p>提供更完整的擴充性與功能。</p></li></ul><p>兩者關係都是在提升帳戶功能與抽象能力。EIP‑7702 與 EIP‑4337 並不互斥，可並存。各自適用於不同的需求與場景，合力推進 Ethereum 帳戶模型的演進。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">接下来我们将做什么</h2><p>我们即将开展 EIP-7702 休闲黑客松，从想法快速到 Demo，就在此刻</p><p>如果你有有趣的 Idea，欢迎在此处提交</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Coooder-Crypto/CasualHackathon-test">https://github.com/Coooder-Crypto/CasualHackathon-test</a></p><p>内容 | LXDAO</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/8fafaf1083e0654f476bec5d986055e60426725792a4314dbd31d138fa744295.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Geth 源码系列：Geth 整体架构]]></title>
            <link>https://paragraph.com/@lxdao/geth-geth</link>
            <guid>NIAMZ3az3QwqJ3GXZOpb</guid>
            <pubDate>Tue, 13 May 2025 02:04:03 GMT</pubDate>
            <description><![CDATA[这篇文章是 Geth 源码系列的第一篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第一篇文章中，将研究执行层客户端 Geth 的设计架构以及 Geth 节点的启动流程。Geth 代码更新的速度很快，后续看到的代码可能会有所不同，但是整体的设计大体一致，新的代码也可以用同样的思路阅读。以太坊客户端以太坊在进行 The Merge 升级之前，以太坊只有一个客户端，这个客户端及负责交易的执行，也会负责区块链的共识，保证区块链以一定的顺序产生新的区块。在 The Merge 升级之后，以太坊客户端分为了执行层和共识层，执行层负责交易的执行、状态和数据的维护，共识层则负责共识功能的实现，执行层和共识层通过 API 来通信。执行层和共识层有各自的规范，客户端可以使用不同的语言来实现，但是要符合对应的规范，其中 Geth 就是执行层客户端的一种实现。当前主流的执行层和共识层客户端有如下实现：执行层Geth：由以太坊基金会直接资助的团队维护，使用 Go 语言开发，是公认的最稳定、久经考验的客户端Nethermi...]]></description>
            <content:encoded><![CDATA[<p>这篇文章是 Geth 源码系列的第一篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在第一篇文章中，将研究执行层客户端 Geth 的设计架构以及 Geth 节点的启动流程。Geth 代码更新的速度很快，后续看到的代码可能会有所不同，但是整体的设计大体一致，新的代码也可以用同样的思路阅读。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">以太坊客户端</h2><p>以太坊在进行 The Merge 升级之前，以太坊只有一个客户端，这个客户端及负责交易的执行，也会负责区块链的共识，保证区块链以一定的顺序产生新的区块。在 The Merge 升级之后，以太坊客户端分为了执行层和共识层，执行层负责交易的执行、状态和数据的维护，共识层则负责共识功能的实现，执行层和共识层通过 API 来通信。执行层和共识层有各自的规范，客户端可以使用不同的语言来实现，但是要符合对应的规范，其中 Geth 就是执行层客户端的一种实现。当前主流的执行层和共识层客户端有如下实现：</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">执行层</h3><ul><li><p>Geth：由以太坊基金会直接资助的团队维护，使用 Go 语言开发，是公认的最稳定、久经考验的客户端</p></li><li><p>Nethermind：由 Nethermind 团队开发和维护，使用 C# 语言开发，早期获以太坊基金会和 Gitcoin 社区资助</p></li><li><p>Besu：最初由 ConsenSys 的 PegaSys 团队开发，现为 Hyperledger 社区项目，使用 Java 语言开发</p></li><li><p>Erigon：由 Erigon 团队开发和维护，获以太坊基金会、BNB Chain 资助。2017 年从 Geth 分叉而来，目标是提升同步速度和磁盘效率</p></li><li><p>Reth：由 Paradigm 主导开发，开发语言是 Rust，强调模块化和高性能，目前已经趋近成熟，可以在生产环境使用</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">共识层</h3><ul><li><p>Prysm：由 Prysmatic Labs 维护，是以太坊最早的共识层客户端之一，用 Go 语言开发，专注于可用性和安全性，早期获以太坊基金会资助</p></li><li><p>Lighthouse：由 Sigma Prime 团队维护，使用 Rust 语言开发，主打高性能和企业级安全，适用于高负载场景</p></li><li><p>Teku：早起由 ConsenSys 的 PegaSys 团队开发，后成为 Hyperledger Besu 社区的一部分，使用 Java 语言开发</p></li><li><p>Nimbus：由 Status Network 团队开发和维护，使用 Nim 语言开发，专为资源受限设备（如手机、物联网设备）优化，目标是在嵌入式系统中实现轻量化运行</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">执行层简介</h3><p>可以将以太坊执行层看作是一个由交易驱动的状态机，执行层最基础的职能就是通过 EVM 执行交易来更新状态数据。除了交易执行之外，还有保存并验证区块和状态数据，运行 p2p 网络并维护交易池等功能。</p><p>交易由用户（或者程序）按照以太坊执行层规范定义的格式生成，用户需要对交易进行签名，如果交易是合法的（Nonce 连续、签名正确、gas fee 足够、业务逻辑正确），那么交易最终就会被 EVM 执行，从而更新以太坊网络的状态。这里的状态是指数据结构、数据和数据库的集合，包括外部账户地址、合约地址、地址余额以及代码和数据。</p><p>执行层负责执行交易以及维护交易执行之后的状态，共识层负责选择哪些交易来执行。EVM 则是这个状态机中的状态转换函数，函数的输入会来源于多个地方，有可能来源于共识层提供的最新区块信息，也有可能来源于 p2p 网络下载的区块。</p><p>共识层和执行层通过 Engine API 来进行通信，这是执行层和共识层之间唯一的通信方式。如果共识层拿到了出块权，就会通过 Engine API 让执行层产出新的区块，如果没有拿到出块权，就会同步最新的区块让执行层验证和执行，从而与整个以太坊网络保持共识。</p><p>执行层从逻辑上可以分为 6 个部分：</p><ul><li><p>EVM：负责执行交易，交易执行也是修改状态数的唯一方式</p></li><li><p>存储：负责 state 以及区块等数据的存储</p></li><li><p>交易池：用于用户提交的交易，暂时存储，并且会通过</p></li><li><p>p2p 网络在不同节点之间传播 p2p 网络：用于发现节点、同步交易、下载区块等等功能</p></li><li><p>RPC 服务：提供访问节点的能力，比如用户向节点发送交易，共识层和执行层之间的交互</p></li><li><p>BlockChain：负责管理以太坊的区块链数据</p><p>下图展示了执行层的关键流程，以及每个部分的职能：</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/71f780a87dc7c762f45c7bf297910dcb171e73ef43fc047b0bcf7265163c3d15.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>对于执行层（这里暂时只讨论 Full Node），有三个关键流程：</p><ul><li><p>如果是新加入以太坊的节点，需要通过 p2p 网络从其他的节点同步区块和状态数据，如果是 Full Sync，会从创世区块开始逐个下载区块，验证区块并通过 EVM 重建状态数据库，如果是 Snap Sync，则跳过全部区块验证的过程，直接下载最新 checkpoint 的状态数据和以后的区块数据</p></li><li><p>如果是已经同步到最新状态的节点，那么就会持续通过 Engine API 从共识层获取到当前最新产出的区块，并验证区块，然后通过 EVM 执行区块中所有的交易来更新状态数据库，并将区块写入本地链</p></li><li><p>如果是已经同步到最新状态，并且共识层拿到了出块权的节点，就会通过 Engine API 驱动执行层产出最新的区块，执行层从交易池获取交易并执行，然后组装成区块通过 Engine API 传递给共识层，由共识层将区块广播到共识层 p2p 网络</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">源码结构</h3><p>go-ethereum 的代码结构很庞大，但其中很多代码属于辅助代码和单元测试，在研究 Geth 源码时，只需要关注协议的核心实现，各个模块功能如下。需要重点关注 core、eth、ethdb、node、p2p、rlp、trie &amp; triedb 等模块：</p><ul><li><p>accounts：管理以太坊账户，包括公私钥对的生成、签名验证、地址派生等</p></li><li><p>beacon：处理与以太坊信标链（Beacon Chain）的交互逻辑，支持权益证明（PoS）共识的合并（The Merge）后功能</p></li><li><p>build：构建脚本和编译配置（如 Dockerfile、跨平台编译支持）</p></li><li><p>cmd：命令行工具入口，包含多个子命令</p></li><li><p>common：通用工具类，如字节处理、地址格式转换、数学函数</p></li><li><p>consensus：定义consensus engine ，包括之前的工作量证明（Ethash）和单机权益证明（Clique）以及 Beacon engine 等</p></li><li><p>console：提供交互式 JavaScript 控制台，允许用户通过命令行直接与以太坊节点交互（如调用 Web3 API、管理账户、查询区块链数据）</p></li><li><p>core：区块链核心逻辑，处理区块/交易的生命周期管理、状态机、Gas计算等</p></li><li><p>crypto：加密算法实现，包括椭圆曲线（secp256k1）、哈希（Keccak-256）、签名验证</p></li><li><p>docs：文档（如设计规范、API 说明）</p></li><li><p>eth：以太坊网络协议的完整实现，包括节点服务、区块同步（如快速同步、归档模式）、交易广播等</p></li><li><p>ethclient：实现以太坊客户端库，封装 JSON-RPC 接口，供 Go 开发者与以太坊节点交互（如查询区块、发送交易、部署合约）</p></li><li><p>ethdb：数据库抽象层，支持 LevelDB、Pebble、内存数据库等，存储区块链数据（区块、状态、交易）</p></li><li><p>ethstats：收集并上报节点运行状态到统计服务，用于监控网络健康状态</p></li><li><p>event：实现事件订阅与发布机制，支持节点内部模块间的异步通信（如新区块到达、交易池更新）</p></li><li><p>graphql：提供 GraphQL 接口，支持复杂查询（替代部分 JSON-RPC 功能）</p></li><li><p>internal：内部工具或限制外部访问的代码</p></li><li><p>log：日志系统，支持分级日志输出、上下文日志记录</p></li><li><p>mertrics：性能指标收集（Prometheus 支持）</p></li><li><p>miner：挖矿相关逻辑，生成新区块并打包交易（PoW 场景下）</p></li><li><p>node：节点服务管理，整合 p2p、RPC、数据库等模块的启动与配置</p></li><li><p>p2p：点对点网络协议实现，支持节点发现、数据传输、加密通信</p></li><li><p>params：定义以太坊网络参数（主网、测试网、创世区块配置）</p></li><li><p>rlp：实现以太坊专用的数据序列化协议 RLP（Recursive Length Prefix），用于编码/解码区块、交易等数据结构</p></li><li><p>rpc：实现 JSON-RPC 和 IPC 接口，供外部程序与节点交互</p></li><li><p>signer：交易签名管理（硬件钱包集成）</p></li><li><p>tests：集成测试和状态测试，验证协议兼容性</p></li><li><p>trie &amp; triedb：默克尔帕特里夏树（Merkle Patricia Trie）的实现，用于高效存储和管理账户状态、合约存储</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">执行层模块划分</h3><p>外部访问 Geth 节点有两种形式，一种是通过 RPC，另外一种是通过 Console。RPC 适合开放给外部的用户来使用，Console 适合节点的管理者使用。但无论是通过 RPC 还是 Console，都是使用内部已经封装好的能力，这些能力通过分层的方式来构建。</p><p>最外层就是 API 用于外部访问节点的各项能力，Engine API 用于执行层和共识层之间的通信，Eth API 用于外部用户或者程序发送交易，获取区块信息，Net API 用于获取 p2p 网络的状态等等。 比如用户通过 API 发送了一个交易，那么这个交易最终会被提交到交易池中，通过交易池来管理，再比如用户需要获取一个区块数据，那么就需要调用数据库的能力去获取对应的区块。</p><p>在 API 的下一层就核心功能的实现，包括交易池、交易打包、产出区块、区块和状态的同步等等。这些功能再往下就需要依赖更底层的能力，比如交易池、区块和状态的同步需要依赖 p2p 网络的能力，区块的产生以及从其他节点同步过来的区块需要被验证才能写入到本地的数据库，这些就需要依赖 EVM 和数据存储的能力。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e7429d00d2d6733d580110bf7c7802127249bda8f0db8890b54b8ae1cc7662e8.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">执行层核心数据结构</h2><h3 id="h-ethereum" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Ethereum</h3><p>在 eth/backend.go 中的 Ethereum 结构是整个以太坊协议的抽象，基本包括了以太坊中的主要组件，但 EVM 是一个例外，它会在每次处理交易的时候实例化，不需要随着整个节点初始化，下文中的 Ethereum 都是指这个结构体：</p><pre data-type="codeBlock" text="type Ethereum struct {  // 以太坊配置，包括链配置  config         *ethconfig.Config  // 交易池，用户的交易提交之后先到交易池  txPool         *txpool.TxPool  // 用于跟踪和管理本地交易（local transactions）  localTxTracker *locals.TxTracker  // 区块链结构  blockchain     *core.BlockChain  // 是以太坊节点的网络层核心组件，负责处理所有与其他节点的通信，包括区块同步、交易广播和接收，以及管理对等节点连接  handler *handler  // 负责节点发现和节点源管理  discmix *enode.FairMix  // 负责区块链数据的持久化存储  chainDb ethdb.Database  // 负责处理各种内部事件的发布和订阅  eventMux       *event.TypeMux  // 共识引擎  engine         consensus.Engine  // 管理用户账户和密钥  accountManager *accounts.Manager  // 管理日志过滤器和区块过滤器  filterMaps      *filtermaps.FilterMaps  // 用于安全关闭 filterMaps 的通道，确保在节点关闭时正确清理资源  closeFilterMaps chan chan struct{}  // 为 RPC API 提供后端支持  APIBackend *EthAPIBackend  // 在 PoS 下，与共识引擎协作验证区块  miner    *miner.Miner  // 节点接受的最低gas价格  gasPrice *big.Int  // 网络 ID  networkID     uint64  // 提供网络相关的 RPC 服务，允许通过 RPC 查询网络状态  netRPCService *ethapi.NetAPI  // 管理P2P网络连接，处理节点发现和连接建立并提供底层网络传输功能  p2pServer *p2p.Server  // 保护可变字段的并发访问  lock sync.RWMutex   // 跟踪节点是否正常关闭，在异常关闭后帮助恢复  shutdownTracker *shutdowncheck.ShutdownTracker } 
"><code>type <span class="hljs-title class_">Ethereum</span> struct {  <span class="hljs-regexp">//</span> 以太坊配置，包括链配置  config         *ethconfig.<span class="hljs-title class_">Config</span>  /<span class="hljs-regexp">/ 交易池，用户的交易提交之后先到交易池  txPool         *txpool.TxPool  /</span><span class="hljs-regexp">/ 用于跟踪和管理本地交易（local transactions）  localTxTracker *locals.TxTracker  /</span><span class="hljs-regexp">/ 区块链结构  blockchain     *core.BlockChain  /</span><span class="hljs-regexp">/ 是以太坊节点的网络层核心组件，负责处理所有与其他节点的通信，包括区块同步、交易广播和接收，以及管理对等节点连接  handler *handler  /</span><span class="hljs-regexp">/ 负责节点发现和节点源管理  discmix *enode.FairMix  /</span><span class="hljs-regexp">/ 负责区块链数据的持久化存储  chainDb ethdb.Database  /</span><span class="hljs-regexp">/ 负责处理各种内部事件的发布和订阅  eventMux       *event.TypeMux  /</span><span class="hljs-regexp">/ 共识引擎  engine         consensus.Engine  /</span><span class="hljs-regexp">/ 管理用户账户和密钥  accountManager *accounts.Manager  /</span><span class="hljs-regexp">/ 管理日志过滤器和区块过滤器  filterMaps      *filtermaps.FilterMaps  /</span><span class="hljs-regexp">/ 用于安全关闭 filterMaps 的通道，确保在节点关闭时正确清理资源  closeFilterMaps chan chan struct{}  /</span><span class="hljs-regexp">/ 为 RPC API 提供后端支持  APIBackend *EthAPIBackend  /</span><span class="hljs-regexp">/ 在 PoS 下，与共识引擎协作验证区块  miner    *miner.Miner  /</span><span class="hljs-regexp">/ 节点接受的最低gas价格  gasPrice *big.Int  /</span><span class="hljs-regexp">/ 网络 ID  networkID     uint64  /</span><span class="hljs-regexp">/ 提供网络相关的 RPC 服务，允许通过 RPC 查询网络状态  netRPCService *ethapi.NetAPI  /</span><span class="hljs-regexp">/ 管理P2P网络连接，处理节点发现和连接建立并提供底层网络传输功能  p2pServer *p2p.Server  /</span><span class="hljs-regexp">/ 保护可变字段的并发访问  lock sync.RWMutex   /</span><span class="hljs-regexp">/ 跟踪节点是否正常关闭，在异常关闭后帮助恢复  shutdownTracker *shutdowncheck.ShutdownTracker } 
</span></code></pre><h3 id="h-node" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Node</h3><p>在 node/node.go 中的 Node 是另一个核心的数据结构，它作为一个容器，负责管理和协调各种服务的运行。在下面的结构中，需要关注一下 lifecycles 字段，Lifecycle 用来管理内部功能的生命周期。比如上面的 Ethereum 抽象就需要依赖 Node 才能启动，并且在 lifecycles 中注册。这样可以将具体的功能与节点的抽象分离，提升整个架构的扩展性，这个 Node 需要与 devp2p 中的 Node 区分开。</p><pre data-type="codeBlock" text="type Node struct {  eventmux      *event.TypeMux  config        *Config  // 账户管理器，负责管理钱包和账户  accman        *accounts.Manager  log           log.Logger  keyDir        string          keyDirTemp    bool            dirLock       *flock.Flock    stop          chan struct{}  // p2p 网络实例   server        *p2p.Server     startStopLock sync.Mutex  // 跟踪节点生命周期状态（初始化、运行中、已关闭）      state         int             lock          sync.Mutex  // 所有注册的后端、服务和辅助服务  lifecycles    []Lifecycle  // 当前提供的 API 列表   rpcAPIs       []rpc.API  // 为 RPC 提供的不同访问方式     http          *httpServer   ws            *httpServer   httpAuth      *httpServer   wsAuth        *httpServer   ipc           *ipcServer    inprocHandler *rpc.Server   databases map[*closeTrackingDB]struct{} } 
"><code><span class="hljs-keyword">type</span> Node <span class="hljs-keyword">struct</span> {  eventmux      <span class="hljs-operator">*</span><span class="hljs-keyword">event</span>.TypeMux  config        <span class="hljs-operator">*</span>Config  <span class="hljs-comment">// 账户管理器，负责管理钱包和账户  accman        *accounts.Manager  log           log.Logger  keyDir        string          keyDirTemp    bool            dirLock       *flock.Flock    stop          chan struct{}  // p2p 网络实例   server        *p2p.Server     startStopLock sync.Mutex  // 跟踪节点生命周期状态（初始化、运行中、已关闭）      state         int             lock          sync.Mutex  // 所有注册的后端、服务和辅助服务  lifecycles    []Lifecycle  // 当前提供的 API 列表   rpcAPIs       []rpc.API  // 为 RPC 提供的不同访问方式     http          *httpServer   ws            *httpServer   httpAuth      *httpServer   wsAuth        *httpServer   ipc           *ipcServer    inprocHandler *rpc.Server   databases map[*closeTrackingDB]struct{} } </span>
</code></pre><p>如果以一个抽象的维度来看以太坊的执行层，以太坊作为一台世界计算机，需要包括三个部分，网络、计算和存储，那么以太坊执行层中与这三个部分相对应的组件是：</p><ul><li><p>网络：devp2p</p></li><li><p>计算：EVM</p></li><li><p>存储：ethdb</p></li></ul><p><strong>devp2p</strong></p><p>以太坊本质还是一个分布式系统，每个节点通过 p2p 网络与其他节点相连。以太坊中的 p2p 网络协议的实现就是 devp2p。</p><p>devp2p 有两个核心功能，一个是节点发现，让节点在接入网络时能够与其他节点建立联系；另一个是数据传输服务，在与其他节点建立联系之后，就可以想换交换数据。</p><p>在 p2p/enode/node.go 中的 Node 结构代表了 p2p 网络中一个节点，其中 enr.Record 结构中存储了节点详细信息的键值对，包括身份信息（节点身份所使用的签名算法、公钥）、网络信息（IP 地址，端口号）、支持的协议信息（比如支持 eth/68 和 snap 协议）和其他的自定义信息，这些信息通过 RLP 的方式编码，具体的规范在 eip-778 中定义：</p><pre data-type="codeBlock" text="type Node struct {    // 节点记录，包含节点的各种属性    r  enr.Record    // 节点的唯一标识符，32字节长度      id ID              // hostname 跟踪节点的DNS名称    hostname string    // 节点的IP地址    ip  netip.Addr    // UDP端口      udp uint16         // TCP端口     tcp uint16      }// enr.Recordtype Record struct {    // 序列号    seq       uint64           // 签名    signature []byte           // RLP 编码后的记录    raw       []byte           // 所有键值对的排序列表    pairs     []pair       } 
"><code>type <span class="hljs-title class_">Node</span> struct {    <span class="hljs-regexp">//</span> 节点记录，包含节点的各种属性    r  enr.<span class="hljs-title class_">Record</span>    /<span class="hljs-regexp">/ 节点的唯一标识符，32字节长度      id ID              /</span><span class="hljs-regexp">/ hostname 跟踪节点的DNS名称    hostname string    /</span><span class="hljs-regexp">/ 节点的IP地址    ip  netip.Addr    /</span><span class="hljs-regexp">/ UDP端口      udp uint16         /</span><span class="hljs-regexp">/ TCP端口     tcp uint16      }/</span><span class="hljs-regexp">/ enr.Recordtype Record struct {    /</span><span class="hljs-regexp">/ 序列号    seq       uint64           /</span><span class="hljs-regexp">/ 签名    signature []byte           /</span><span class="hljs-regexp">/ RLP 编码后的记录    raw       []byte           /</span><span class="hljs-regexp">/ 所有键值对的排序列表    pairs     []pair       } 
</span></code></pre><p>在 p2p/discover/table.go 中的 Table 结构是 devp2p 实现节点发现协议的核心数据结构，它实现了类似 Kademlia 的分布式哈希表，用于维护和管理网络中的节点信息。</p><pre data-type="codeBlock" text="printf(&quot;type Table struct {  mutex        sync.Mutex  // 按距离索引已知节点          buckets      [nBuckets]*bucket  // 引导节点   nursery      []*enode.Node       rand         reseedingRandom     ips          netutil.DistinctNetSet  revalidation tableRevalidation    // 已知节点的数据库  db  *enode.DB   net transport  cfg Config  log log.Logger  // 周期性的处理网络中的各种事件  refreshReq      chan chan struct{}  revalResponseCh chan revalidationResponse  addNodeCh       chan addNodeOp  addNodeHandled  chan bool  trackRequestCh  chan trackRequestOp  initDone        chan struct{}  closeReq        chan struct{}  closed          chan struct{}  // 增加和移除节点的接口  nodeAddedHook   func(*bucket, *tableNode)  nodeRemovedHook func(*bucket, *tableNode)} world!&quot;); 
"><code><span class="hljs-built_in">printf</span>("type Table struct {  mutex        sync.Mutex  // 按距离索引已知节点          buckets      [nBuckets]*bucket  // 引导节点   nursery      []*enode.Node       rand         reseedingRandom     ips          netutil.DistinctNetSet  revalidation tableRevalidation    // 已知节点的数据库  db  *enode.DB   net transport  cfg Config  log log.Logger  // 周期性的处理网络中的各种事件  refreshReq      chan chan struct{}  revalResponseCh chan revalidationResponse  addNodeCh       chan addNodeOp  addNodeHandled  chan bool  trackRequestCh  chan trackRequestOp  initDone        chan struct{}  closeReq        chan struct{}  closed          chan struct{}  // 增加和移除节点的接口  nodeAddedHook   func(*bucket, *tableNode)  nodeRemovedHook <span class="hljs-built_in">func</span>(*bucket, *tableNode)} world!"); 
</code></pre><p><strong>ethdb</strong></p><p>ethdb 完成以太坊数据存储的抽象，提供统一的存储接口，底层具体的数据库可以是 leveldb，也可以是 pebble 或者其他的数据库。可以有很多的扩展，只要在接口层面保持统一。</p><p>有些数据（如区块数据）可以通过 ethdb 接口直接对底层数据库进行读写，其他的数据存储接口都是建立的 ethdb 的基础上，比如数据库有很大部分的数据是状态数据，这些数据会被组织成 MPT 结构，在 Geth 中对应的实现是 trie，在节点运行的过程中，trie 数据会产生很多中间状态，这些数据不能直接调用 ethdb 进行读写，需要 triedb 来管理这些数据和中间状态，最后才通过 ethdb 来持久化。</p><p>在 ethdb/database.go 中定义底层数据库的读写能力的接口，但没有包括具体的实现，具体的实现将由不同的数据库自身来实现。比如 leveldb 或者 pebble 数据库。在 Database 中定义了两层数据读写的接口，其中 KeyValueStore 接口用于存储活跃的、可能频繁变化的数据，如最新的区块、状态等。AncientStore 则用于处理历史区块数据，这些数据一旦写入就很少改变。</p><pre data-type="codeBlock" text="// 数据库的顶层接口type Database interface {    KeyValueStore    AncientStore}// KV 数据的读写接口type KeyValueStore interface {  KeyValueReader  KeyValueWriter  KeyValueStater  KeyValueRangeDeleter  Batcher  Iteratee  Compacter  io.Closer}// 处理老数据的读写的接口type AncientStore interface {  AncientReader  AncientWriter  AncientStater  io.Closer} 
"><code>// 数据库的顶层接口<span class="hljs-keyword">type</span> Database <span class="hljs-keyword">interface</span> <span class="hljs-punctuation">{</span>    KeyValueStore    AncientStore<span class="hljs-punctuation">}</span>// KV 数据的读写接口<span class="hljs-keyword">type</span> KeyValueStore <span class="hljs-keyword">interface</span> <span class="hljs-punctuation">{</span>  KeyValueReader  KeyValueWriter  KeyValueStater  KeyValueRangeDeleter  Batcher  Iteratee  Compacter  io.Closer<span class="hljs-punctuation">}</span>// 处理老数据的读写的接口<span class="hljs-keyword">type</span> AncientStore <span class="hljs-keyword">interface</span> <span class="hljs-punctuation">{</span>  AncientReader  AncientWriter  AncientStater  io.Closer<span class="hljs-punctuation">}</span> 
</code></pre><p><strong>EVM</strong></p><p>EVM 是以太坊这个状态机的状态转换函数，所有状态数据的更新都只能通过 EVM 来进行，p2p 网络可以接受到交易和区块信息，这些信息被 EVM 处理之后会成为状态数据库的一部分。EVM 屏蔽了底层硬件的不同，让程序在不同平台的 EVM 上执行都能得到一致的结果。这是一种很成熟的设计方式，Java 语言中 JVM 也是类似的设计。</p><p>EVM 的实现有三个主要的组件，core/vm/evm.go 中的 EVM 结构体定义了 EVM 的总体结构及依赖，包括执行上下文，状态数据库依赖等等； core/vm/interpreter.go 中的 EVMInterpreter 结构体定义了解释器的实现，负责执行 EVM 字节码；core/vm/contract.go 中的 Contract 结构体封装合约调用的具体参数，包括调用者、合约代码、输入等等，并且在 core/vm/opcodes.go 中定义了当前所有的操作码：</p><pre data-type="codeBlock" text="// EVMtype EVM struct {    // 区块上下文，包含区块相关信息    Context BlockContext    // 交易上下文，包含交易相关信息         TxContext    // 状态数据库，用于访问和修改账户状态                   StateDB StateDB    // 当前调用深度             depth int    // 链配置参数                   chainConfig *params.ChainConfig      chainRules params.Rules    // EVM配置              Config Config    // 字节码解释器                        interpreter *EVMInterpreter    // 中止执行的标志          abort atomic.Bool                    callGasTemp uint64    // 预编译合约映射                   precompiles map[common.Address]PrecompiledContract      jumpDests map[common.Hash]bitvec  }type EVMInterpreter struct {    // 指向所属的EVM实例    evm   *EVM    // 操作码跳转表                   table *JumpTable             // Keccak256哈希器实例，在操作码间共享    hasher    crypto.KeccakState    // Keccak256哈希结果缓冲区      hasherBuf common.Hash             // 是否为只读模式，只读模式下不允许状态修改    readOnly   bool    // 上一次CALL的返回数据，用于后续重用              returnData []byte        }type Contract struct {    // 调用者地址    caller  common.Address    // 合约地址       address common.Address       jumpdests map[common.Hash]bitvec      analysis  bitvec                      // 合约字节码    Code     []byte    // 代码哈希              CodeHash common.Hash    // 调用输入         Input    []byte              // 是否为合约部署    IsDeployment bool    // 是否为系统调用            IsSystemCall bool            // 可用gas量    Gas   uint64    // 调用附带的 ETH 数量                 value *uint256.Int       } 
"><code>/<span class="hljs-regexp">/ EVMtype EVM struct {    /</span><span class="hljs-regexp">/ 区块上下文，包含区块相关信息    Context BlockContext    /</span><span class="hljs-regexp">/ 交易上下文，包含交易相关信息         TxContext    /</span><span class="hljs-regexp">/ 状态数据库，用于访问和修改账户状态                   StateDB StateDB    /</span><span class="hljs-regexp">/ 当前调用深度             depth int    /</span><span class="hljs-regexp">/ 链配置参数                   chainConfig *params.ChainConfig      chainRules params.Rules    /</span><span class="hljs-regexp">/ EVM配置              Config Config    /</span><span class="hljs-regexp">/ 字节码解释器                        interpreter *EVMInterpreter    /</span><span class="hljs-regexp">/ 中止执行的标志          abort atomic.Bool                    callGasTemp uint64    /</span><span class="hljs-regexp">/ 预编译合约映射                   precompiles map[common.Address]PrecompiledContract      jumpDests map[common.Hash]bitvec  }type EVMInterpreter struct {    /</span><span class="hljs-regexp">/ 指向所属的EVM实例    evm   *EVM    /</span><span class="hljs-regexp">/ 操作码跳转表                   table *JumpTable             /</span><span class="hljs-regexp">/ Keccak256哈希器实例，在操作码间共享    hasher    crypto.KeccakState    /</span><span class="hljs-regexp">/ Keccak256哈希结果缓冲区      hasherBuf common.Hash             /</span><span class="hljs-regexp">/ 是否为只读模式，只读模式下不允许状态修改    readOnly   bool    /</span><span class="hljs-regexp">/ 上一次CALL的返回数据，用于后续重用              returnData []byte        }type Contract struct {    /</span><span class="hljs-regexp">/ 调用者地址    caller  common.Address    /</span><span class="hljs-regexp">/ 合约地址       address common.Address       jumpdests map[common.Hash]bitvec      analysis  bitvec                      /</span><span class="hljs-regexp">/ 合约字节码    Code     []byte    /</span><span class="hljs-regexp">/ 代码哈希              CodeHash common.Hash    /</span><span class="hljs-regexp">/ 调用输入         Input    []byte              /</span><span class="hljs-regexp">/ 是否为合约部署    IsDeployment bool    /</span><span class="hljs-regexp">/ 是否为系统调用            IsSystemCall bool            /</span><span class="hljs-regexp">/ 可用gas量    Gas   uint64    /</span><span class="hljs-regexp">/ 调用附带的 ETH 数量                 value *uint256.Int       } 
</span></code></pre><p><strong>其他模块实现</strong></p><p>执行层的功能通过分层的方式来实现，其他的模块和功能都是在这三个核心组件的基础之上构建起来的。这里介绍一下几个核心的模块。</p><p>在 eth/protocols 下有当前以太坊的p2p网络子协议的实现。有 eth/68 和 snap 子协议，这个些子协议都是在 devp2p 上构建的。</p><p>eth/68 是以太坊的核心协议，协议名称就是 eth，68 是它的版本号，然后在这个协议的基础之上又实现了交易池（TxPool）、区块同步（Downloader）和交易同步（Fetcher）等功能。snap 协议用于新节点加入网络时快速同步区块和状态数据的，可以大大减少新节点启动的时间。</p><p>ethdb 提供了底层数据库的读写能力，由于以太坊协议中有很多复杂的数据结构，直接通过 ethdb 无法实现这些数据的管理，所以在 ethdb 上又实现了 rawdb 和 statedb 来分别管理区块和状态数据。</p><p>EVM 则贯穿所有的主流程，无论是区块构建还是区块验证，都需要用 EVM 执行交易。</p><h3 id="h-geth" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Geth 节点启动流程</h3><p>Geth 的启动会分为两个阶段，第一阶段会初始化节点所需要启动的组件和资源，第二节点会正式启动节点，然后对外服务。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/18a380ba35107f055c390014437b4bb93e11b11d818e96fe7ed3c0e83bc9f32e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">节点初始化</h3><p>在启动一个 geth 节点时，会涉及到以下的代码：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9a054928d90fa7df051664a8dc417cf6700e2756ae51f5f8325f1f63d0b12b9e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>各模块的初始化如下：</p><ul><li><p>cmd/geth/main.go：geth 节点启动入口</p></li><li><p>cmd/geth/config.go（makeFullNode）：加载配置，初始化节点</p></li><li><p>node/node.go：初始化以太坊节点的核心容器</p></li><li><p>node.rpcstack.go：初始化 RPC 模块</p></li><li><p>accounts.manager.go：初始化</p></li><li><p>accountManager eth/backend.go：初始化 Ethereum 实例</p></li><li><p>node/node.go OpenDatabaseWithFreezer：初始化 chaindb</p></li><li><p>eth/ethconfig/config.go：初始化共识引擎实例（这里的共识引擎并不真正参与共识，只是会验证共识层的结果，以及处理 validator 的提款请求）</p></li><li><p>core/blockchain.go：初始化 blockchain</p></li><li><p>core/filterMaps.go：初始化 filtermaps</p></li><li><p>core/txpool/blobpool/blobpool.go：初始化 blob 交易池</p></li><li><p>core/txpool/legacypool/legacypool.go：初始化普通交易池</p></li><li><p>cord/txpool/locals/tx_tracker.go：本地交易追踪（需要配置开启本地交易追踪，本地交易会被更高优先级处理）</p></li><li><p>eth/handler.go：初始化协议的 Handler 实例</p></li><li><p>miner/miner.go：实例化交易打包的模块（原挖矿模块）</p></li><li><p>eth/api_backend.go：实例化 RPC 服务</p></li><li><p>eth/gasprice/gasprice.go：实例化 gas 价格查询服务</p></li><li><p>internal/ethapi/api.go：实例化 P2P 网络 RPC API</p></li><li><p>node/node.go(RegisterAPIs)：注册 RPC API node/node.go(RegisterProtocols)：注册 p2p 的 Ptotocols</p></li><li><p>node/node.go(RegisterLifecycle)：注册各个组件的生命周期</p></li><li><p>cmd/utils/flags.go(RegisterFilterAPI)：注册 Filter RPC API</p></li><li><p>cmd/utils/flags.go(RegisterGraphQLService)：注册 GraphQL RPC API（如果配置了的话）</p></li><li><p>cmd/utils/flags.go(RegisterEthStatsService)：注册 EthStats RPC API（如果配置了的话）</p></li><li><p>eth/catalyst/api.go：注册 Engine API</p></li></ul><p>节点的初始化会在 cmd/geth/config.go 中的 makeFullNode 中完成，重点会初始化以下三个模块</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f31a1e96176031ed76de02783adac3df2fd2009339b610b6e5ed15c3451da011.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在第一步会初始化 node/node.go 中的 Node 结构，就是整个节点容器，所有的功能都需要在这个容器中运行，第二步会初始化 Ethereum 结构，其中包括以太坊各种核心功能的实现，Etherereum 也需要注册到 Node 中，第三步就是注册 Engine API 到 Node 中。</p><p>其中 Node 初始化就是创建了一个 Node 实例，然后初始化 p2p server、账号管理以及 http 等暴露给外部的协议端口。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f2d37092b28db17ba9c9a4481ec2d75698195296a2e17e260d7eb57ea2fa6501.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Ethereum 的初始化就会复杂很多，大多数的核心功能都是在这里初始化。首先会初始化化 ethdb，并从存储中加载链配置，然后创建共识引擎，这里的共识引擎不会执行共识操作，而只是会对共识层返回的结果进行验证，如果共识层发生了提款请求，也会在这里完成实际的提款操作。然后再初始化 Block Chain 结构和交易池。</p><p>这些都完成之后就会初始化 handler，handler 是所有 p2p 网络请求的处理入口，包括交易同步、区块下载等等，是以太坊实现去中心化运行的关键组件。在这些都完成之后，就会将一些在 devp2p 基础之上实现的子协议，比如 eth/68、snap 等注册到 Node 容器中，最后 Ethereum 会作为一个 lifecycle 注册到 Node 容器中，Ethereum 初始化完成。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b6ff4e972b3193c96b65bb6477a8888bb7c8854be9e5df19944622d76a961b91.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>最后 Engine API 的初始化相对简单，只是将 Engine API 注册到 Node 中。到这里，节点初始化就全部完成了。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">节点启动</h3><p>在完成节点的初始化之后，就需要启动节点了，节点启动的流程相对简单，只需要将已经注册的 RPC 服务和 Lifecycle 全部启动，那么整个节点就可以向外部提供服务了。</p><p>总结 在深入理解以太坊执行层的实现之前，需要对以太坊有一个整体的认识，可以将以太坊整体看作是一个交易驱动的状态机，执行层负责交易的执行和状态的变更，共识层则负责驱动执行层运行，包括让执行层产出区块、决定交易的顺序、为区块投票、以及让区块获得最终性。由于这个状态机是去中心化的，所以需要通过 p2p 网络与其他的节点通信，共同维护状态数据的一致性。</p><p>在执行层不负责决定交易的顺序，只负责执行交易并记录交易执行之后的状态变化。这里的记录有两种形式，一种是以区块的方式将所有的状态变化都记录下来，另一种是在数据库中记录当前的状态。同时执行层也是交易的入口，通过交易池来存储还没有被打包进区块的交易。如果其他的节点需要获取区块、状态和交易数据，执行层就会通过 p2p 网络将这些信息发送出去。</p><p>对于执行层，有三个核心模块：计算、存储和网络。计算对应 EVM 的实现，存储则对应了 ethdb 的实现，网络对了 devp2p 的实现。有了这样的整体认识之后，就可以深入去理解每一个子模块，而不会迷失在具体的细节中。</p><h2 id="h-ref" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Ref</h2><p>[1]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/zh/what-is-ethereum/">https://ethereum.org/zh/what-is-ethereum/</a></p><p>[2]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://epf.wiki/#/wiki/protocol/architecture">https://epf.wiki/#/wiki/protocol/architecture</a></p><p>[3]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://clientdiversity.org/#distribution">https://clientdiversity.org/#distribution</a></p><p>[4]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/devp2p">https://github.com/ethereum/devp2p</a></p><p>[5]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/execution-specs">https://github.com/ethereum/execution-specs</a></p><p>[6]<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/consensus-specs">https://github.com/ethereum/consensus-specs</a></p><p>内容 | Ray</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/07740329788986bfbd542742bbcbb2630f218d339f42268c0299298f4bf6892d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Geth 源码系列：存储设计及实现]]></title>
            <link>https://paragraph.com/@lxdao/geth</link>
            <guid>Pjg619Kz4j6N88QCPQ0J</guid>
            <pubDate>Mon, 12 May 2025 10:40:19 GMT</pubDate>
            <description><![CDATA[在区块链的世界中，状态存储是每一个节点的「记忆核心」 — — 它记录着亿万账户的余额、合约的代码、交易的痕迹，甚至决定着一笔交易能否被正确执行。作为以太坊生态的基石，Geth 客户端如何以精密的架构设计承载海量状态数据？其存储系统如何在性能、安全与可扩展性之间找到平衡？ 这篇文章是 Geth 源码系列的第二篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在这第二篇文章中，将系统讲解 Geth 的存储结构设计与相关源码，介绍其数据库层次划分并详细分析各个层次中相应模块的核心功能。 以太坊作为全球最大的区块链平台，其主流客户端 Geth（Go-Ethereum）承担了绝大部分节点运行与状态管理的职责。Geth 的状态存储系统，是理解以太坊运行机制、优化节点性能、以及推动未来客户端创新的基础。 本文作者： po Web3buidler.tech Core Contributor, EthStorage Engineer Geth 底层数据库总览 自 Geth v1.9.0 版本起，Geth 将其数据库分为...]]></description>
            <content:encoded><![CDATA[<p>在区块链的世界中，状态存储是每一个节点的「记忆核心」 — — 它记录着亿万账户的余额、合约的代码、交易的痕迹，甚至决定着一笔交易能否被正确执行。作为以太坊生态的基石，Geth 客户端如何以精密的架构设计承载海量状态数据？其存储系统如何在性能、安全与可扩展性之间找到平衡？</p><p>这篇文章是 Geth 源码系列的第二篇，通过这个系列，我们将搭建一个研究 Geth 实现的框架，开发者可以根据这个框架深入自己感兴趣的部分研究。这个系列共有六篇文章，在这第二篇文章中，将系统讲解 Geth 的存储结构设计与相关源码，介绍其数据库层次划分并详细分析各个层次中相应模块的核心功能。</p><p>以太坊作为全球最大的区块链平台，其主流客户端 Geth（Go-Ethereum）承担了绝大部分节点运行与状态管理的职责。Geth 的状态存储系统，是理解以太坊运行机制、优化节点性能、以及推动未来客户端创新的基础。</p><p>本文作者：</p><p>po</p><p>Web3buidler.tech Core Contributor, EthStorage Engineer</p><p>Geth 底层数据库总览 自 Geth v1.9.0 版本起，Geth 将其数据库分为两部分：快速访问存储（KV数据库，用于最近的区块和状态数据）和称为 freezer 的存储（用于较旧的区块和收据数据，即“ancients”）。</p><p>这样划分的目的是减少对昂贵、易损的 SSD 的依赖，将访问频率较低的数据迁移到成本更低、耐用性更高的磁盘上。与此同时，这种拆分也能减轻 LevelDB/PebbleDB 的压力，提高其整理和读取性能，使得在给定的缓存大小下，更多状态树节点能够常驻内存，从而提升整体系统效率。</p><ul><li><p>快速访问存储: Geth 用户可能对底层数据库的选项较为熟悉 — — 可以通过 — db.engine 参数进行配置。目前的默认选项是 pebbledb，也可以选择 leveldb。这两个都是 Geth 所依赖的第三方键值数据库，负责存储路径为 datadir/geth/chaindata（所有的区块和状态数据）和 datadir/geth/nodes（数据库元数据文件，体积很小）的文件。通过 — history.state value 设置快速访问保存的最近历史状态区块数，默认为 90,000 个区块。</p></li><li><p>freezer 或 ancients存储（历史数据），其目录路径通常为 datadir/geth/chaindata/ancients。由于历史数据基本是静态的，不需要高性能 I/O，因此可以节省宝贵的 SSD 空间，用于存储更活跃的数据。</p></li></ul><p>本文的重点是状态数据，它存储在 KV 数据库中。因此，文中提到的底层数据库默认是指这个 KV 存储，而非 freezer。</p><h3 id="h-geth" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Geth 存储结构：五个逻辑数据库</h3><p>Geth 的底层使用 LevelDB/PebbleDB 存储所有以 RLP 编码后的数据，但其逻辑上划分出五种用途不同的数据库：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3cd04c4a4e2045be6a4f97643c6160282a7b1e258f26ecd1ad82479171ab1b29.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>每种数据通过 key 前缀(core/rawdb/schema.go)区分，逻辑上实现职责分离。通过 geth db inspect 可以查看 Geth 存储的所有以太坊数据(块高22,347,000)，可以看到磁盘空间占用最大的是区块、收据和状态数据。</p><pre data-type="codeBlock" text="+-----------------------+-----------------------------+------------+------------+|       DATABASE        |          CATEGORY           |    SIZE    |   ITEMS    |+-----------------------+-----------------------------+------------+------------+| Key-Value store       | Headers                     | 576.00 B   |          1 || Key-Value store       | Bodies                      | 44.00 B    |          1 || Key-Value store       | Receipt lists               | 42.00 B    |          1 || Key-Value store       | Difficulties (deprecated)   | 0.00 B     |          0 || Key-Value store       | Block number-&gt;hash          | 42.00 B    |          1 || Key-Value store       | Block hash-&gt;number          | 873.78 MiB |   22347001 || Key-Value store       | Transaction index           | 13.48 GiB  |  391277094 || Key-Value store       | Log index filter-map rows   | 12.98 GiB  |  132798523 || Key-Value store       | Log index last-block-of-map | 2.73 MiB   |      59529 || Key-Value store       | Log index block-lv          | 45.05 MiB  |    2362175 || Key-Value store       | Log bloombits (deprecated)  | 0.00 B     |          0 || Key-Value store       | Contract codes              | 9.81 GiB   |    1587159 || Key-Value store       | Hash trie nodes             | 0.00 B     |          0 || Key-Value store       | Path trie state lookups     | 19.62 KiB  |        490 || Key-Value store       | Path trie account nodes     | 45.88 GiB  |  397626541 || Key-Value store       | Path trie storage nodes     | 176.23 GiB | 1753966511 || Key-Value store       | Verkle trie nodes           | 0.00 B     |          0 || Key-Value store       | Verkle trie state lookups   | 0.00 B     |          0 || Key-Value store       | Trie preimages              | 0.00 B     |          0 || Key-Value store       | Account snapshot            | 13.34 GiB  |  290797237 || Key-Value store       | Storage snapshot            | 93.42 GiB  | 1295163402 || Key-Value store       | Beacon sync headers         | 622.00 B   |          1 || Key-Value store       | Clique snapshots            | 0.00 B     |          0 || Key-Value store       | Singleton metadata          | 1.36 MiB   |         20 || Ancient store (Chain) | Hashes                      | 809.85 MiB |   22347001 || Ancient store (Chain) | Bodies                      | 639.98 GiB |   22347001 || Ancient store (Chain) | Receipts                    | 244.19 GiB |   22347001 || Ancient store (Chain) | Headers                     | 10.69 GiB  |   22347001 || Ancient store (State) | History.Meta                | 37.58 KiB  |        487 || Ancient store (State) | Account.Index               | 5.80 MiB   |        487 || Ancient store (State) | Storage.Index               | 7.47 MiB   |        487 || Ancient store (State) | Account.Data                | 6.46 MiB   |        487 || Ancient store (State) | Storage.Data                | 2.70 MiB   |        487 |+-----------------------+-----------------------------+------------+------------+|                                    TOTAL            |  1.23 TIB  |            |+-----------------------+-----------------------------+------------+------------+ 
"><code><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">|</span>       DATABASE        <span class="hljs-operator">|</span>          CATEGORY           <span class="hljs-operator">|</span>    SIZE    <span class="hljs-operator">|</span>   ITEMS    <span class="hljs-operator">|</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Headers                     <span class="hljs-operator">|</span> <span class="hljs-number">576.00</span> B   <span class="hljs-operator">|</span>          <span class="hljs-number">1</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Bodies                      <span class="hljs-operator">|</span> <span class="hljs-number">44.00</span> B    <span class="hljs-operator">|</span>          <span class="hljs-number">1</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Receipt lists               <span class="hljs-operator">|</span> <span class="hljs-number">42.00</span> B    <span class="hljs-operator">|</span>          <span class="hljs-number">1</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Difficulties (deprecated)   <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Block number<span class="hljs-operator">-</span><span class="hljs-operator">></span>hash          <span class="hljs-operator">|</span> <span class="hljs-number">42.00</span> B    <span class="hljs-operator">|</span>          <span class="hljs-number">1</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Block hash<span class="hljs-operator">-</span><span class="hljs-operator">></span>number          <span class="hljs-operator">|</span> <span class="hljs-number">873.78</span> MiB <span class="hljs-operator">|</span>   <span class="hljs-number">22347001</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Transaction index           <span class="hljs-operator">|</span> <span class="hljs-number">13.48</span> GiB  <span class="hljs-operator">|</span>  <span class="hljs-number">391277094</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Log index filter<span class="hljs-operator">-</span>map rows   <span class="hljs-operator">|</span> <span class="hljs-number">12.98</span> GiB  <span class="hljs-operator">|</span>  <span class="hljs-number">132798523</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Log index last<span class="hljs-operator">-</span><span class="hljs-built_in">block</span><span class="hljs-operator">-</span>of<span class="hljs-operator">-</span>map <span class="hljs-operator">|</span> <span class="hljs-number">2.73</span> MiB   <span class="hljs-operator">|</span>      <span class="hljs-number">59529</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Log index <span class="hljs-built_in">block</span><span class="hljs-operator">-</span>lv          <span class="hljs-operator">|</span> <span class="hljs-number">45.05</span> MiB  <span class="hljs-operator">|</span>    <span class="hljs-number">2362175</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Log bloombits (deprecated)  <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Contract codes              <span class="hljs-operator">|</span> <span class="hljs-number">9.81</span> GiB   <span class="hljs-operator">|</span>    <span class="hljs-number">1587159</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Hash trie nodes             <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Path trie state lookups     <span class="hljs-operator">|</span> <span class="hljs-number">19.62</span> KiB  <span class="hljs-operator">|</span>        <span class="hljs-number">490</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Path trie account nodes     <span class="hljs-operator">|</span> <span class="hljs-number">45.88</span> GiB  <span class="hljs-operator">|</span>  <span class="hljs-number">397626541</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Path trie <span class="hljs-keyword">storage</span> nodes     <span class="hljs-operator">|</span> <span class="hljs-number">176.23</span> GiB <span class="hljs-operator">|</span> <span class="hljs-number">1753966511</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Verkle trie nodes           <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Verkle trie state lookups   <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Trie preimages              <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Account snapshot            <span class="hljs-operator">|</span> <span class="hljs-number">13.34</span> GiB  <span class="hljs-operator">|</span>  <span class="hljs-number">290797237</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Storage snapshot            <span class="hljs-operator">|</span> <span class="hljs-number">93.42</span> GiB  <span class="hljs-operator">|</span> <span class="hljs-number">1295163402</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Beacon sync headers         <span class="hljs-operator">|</span> <span class="hljs-number">622.00</span> B   <span class="hljs-operator">|</span>          <span class="hljs-number">1</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Clique snapshots            <span class="hljs-operator">|</span> <span class="hljs-number">0</span><span class="hljs-number">.00</span> B     <span class="hljs-operator">|</span>          <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Key<span class="hljs-operator">-</span>Value store       <span class="hljs-operator">|</span> Singleton metadata          <span class="hljs-operator">|</span> <span class="hljs-number">1.36</span> MiB   <span class="hljs-operator">|</span>         <span class="hljs-number">20</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (Chain) <span class="hljs-operator">|</span> Hashes                      <span class="hljs-operator">|</span> <span class="hljs-number">809.85</span> MiB <span class="hljs-operator">|</span>   <span class="hljs-number">22347001</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (Chain) <span class="hljs-operator">|</span> Bodies                      <span class="hljs-operator">|</span> <span class="hljs-number">639.98</span> GiB <span class="hljs-operator">|</span>   <span class="hljs-number">22347001</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (Chain) <span class="hljs-operator">|</span> Receipts                    <span class="hljs-operator">|</span> <span class="hljs-number">244.19</span> GiB <span class="hljs-operator">|</span>   <span class="hljs-number">22347001</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (Chain) <span class="hljs-operator">|</span> Headers                     <span class="hljs-operator">|</span> <span class="hljs-number">10.69</span> GiB  <span class="hljs-operator">|</span>   <span class="hljs-number">22347001</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (State) <span class="hljs-operator">|</span> History.Meta                <span class="hljs-operator">|</span> <span class="hljs-number">37.58</span> KiB  <span class="hljs-operator">|</span>        <span class="hljs-number">487</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (State) <span class="hljs-operator">|</span> Account.Index               <span class="hljs-operator">|</span> <span class="hljs-number">5.80</span> MiB   <span class="hljs-operator">|</span>        <span class="hljs-number">487</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (State) <span class="hljs-operator">|</span> Storage.Index               <span class="hljs-operator">|</span> <span class="hljs-number">7.47</span> MiB   <span class="hljs-operator">|</span>        <span class="hljs-number">487</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (State) <span class="hljs-operator">|</span> Account.Data                <span class="hljs-operator">|</span> <span class="hljs-number">6.46</span> MiB   <span class="hljs-operator">|</span>        <span class="hljs-number">487</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> Ancient store (State) <span class="hljs-operator">|</span> Storage.Data                <span class="hljs-operator">|</span> <span class="hljs-number">2.70</span> MiB   <span class="hljs-operator">|</span>        <span class="hljs-number">487</span> <span class="hljs-operator">|</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">|</span>                                    TOTAL            <span class="hljs-operator">|</span>  <span class="hljs-number">1.23</span> TIB  <span class="hljs-operator">|</span>            <span class="hljs-operator">|</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">+</span> 
</code></pre><h2 id="h-6-db" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">源码视角下的存储分层：6 种 DB</h2><p>总体而言，Geth 中包含 StateDB、state.Database、trie.Trie、TrieDB、rawdb 和 ethdb 六个数据库模块，它们如同一棵“状态生命树”的各个层级。最顶层的 StateDB 是 EVM 执行阶段的状态接口，负责处理账户与存储的读写请求，并将这些请求逐层下传，最终由最底层负责物理持久化的 ethdb 读/写物理数据库。</p><p>接下来，我们将依次介绍这六个数据库模块的职责及它们之间的协作关系。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e5f6b7f03de244fa14c3826586bbb9aff9b5fd49c5b7799533ed7640e0f4ad16.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-21-statedb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.1 StateDB</h3><p>在 Geth 中，StateDB 是 EVM 与底层状态存储之间的唯一桥梁，负责抽象和管理合约账户、余额、nonce、存储槽等信息的读写，对所有其他数据库（TrieDB, EthDB）的状态相关读写都由 StateDB 中的相关接口触发，可以说 StateDB 是所有状态数据库的大脑。它并不直接操作底层的 Trie 或底层数据库（ethdb），而是提供一个简化的内存视图，让 EVM 能以熟悉的账户模型进行交互。因此，多数依赖 Geth 的项目其实也不会关心底层的 EthDB 或 TrieDB 是怎么实现的 — — 它们能正常工作就够了，没必要动。大多数基于 Geth 的分叉项目都会修改StateDB 结构，以适应自己的业务逻辑。例如，Arbitrum 修改了 StateDB 以管理它们的 Stylus 程序；EVMOS 修改了 StateDB 来追踪对其有状态预编译合约（stateful precompile）的调用。</p><p>源码中，StateDB 的主要定义位于 core/state/statedb.go。它的核心结构维护了一系列内存状态对象（stateObject），每个stateObject对应一个账户（包含合约存储）。它还包含一个 journal（事务日志）用于支持回滚，以及用于追踪状态更改的缓存机制。在交易处理和区块打包过程中，StateDB 提供临时状态变更的记录，只有在最终确认后才会写入底层数据库。</p><p>StateDB 的核心读写接口如下，基本都是账户模型相关的 API:</p><pre data-type="codeBlock" text="// 读相关func (s *StateDB) GetBalance(addr common.Address) *uint256.Int func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash // 写入dirty状态数据func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) // 将EVM执行过程中发生的状态变更(dirty数据) commit到后端数据库中func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) 
"><code>// 读相关<span class="hljs-keyword">func</span> <span class="hljs-punctuation">(</span>s *StateDB<span class="hljs-punctuation">)</span> GetBalance<span class="hljs-punctuation">(</span>addr common.Address<span class="hljs-punctuation">)</span> *uint256.Int <span class="hljs-keyword">func</span> <span class="hljs-punctuation">(</span>s *StateDB<span class="hljs-punctuation">)</span> GetStorageRoot<span class="hljs-punctuation">(</span>addr common.Address<span class="hljs-punctuation">)</span> common.Hash // 写入dirty状态数据<span class="hljs-keyword">func</span> <span class="hljs-punctuation">(</span>s *StateDB<span class="hljs-punctuation">)</span> SetStorage<span class="hljs-punctuation">(</span>addr common.Address, storage map[common.Hash]common.Hash<span class="hljs-punctuation">)</span> // 将EVM执行过程中发生的状态变更<span class="hljs-punctuation">(</span>dirty数据<span class="hljs-punctuation">)</span> commit到后端数据库中<span class="hljs-keyword">func</span> <span class="hljs-punctuation">(</span>s *StateDB<span class="hljs-punctuation">)</span> commitAndFlush<span class="hljs-punctuation">(</span><span class="hljs-keyword">block</span> uint64, deleteEmptyObjects bool, noStorageWiping bool<span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span>*stateUpdate, error<span class="hljs-punctuation">)</span> 
</code></pre><p><strong>生命周期</strong></p><p>StateDB 的生命周期只持续一个区块。在一个区块被处理并提交之后，这个 StateDB 就会被废弃，不再具有作用</p><ul><li><p>EVM 第一次读取某个地址时，StateDB 会从Trie→TrieDB→EthDB 数据库中加载它的值，并将其缓存一个新的状态对象(stateObject.originalStorage)中。这一阶段被视为“干净的对象”（clean object）。</p></li><li><p>当交易与该账户发生交互并改变其状态时，对象就变成“脏的”（dirty）。stateObject 会同时追踪该账户的原始状态和所有修改后的数据，包括其存储槽及其干净/脏状态。</p></li><li><p>如果整个交易最终成功被打包到区块，StateDB.Finalise() 会被调用。这个函数负责清理已 selfdestruct 的合约，并重置 journal (事务日志)以及 gas refund 计数器。</p></li><li><p>当所有交易都执行完毕后，StateDB.Commit() 被调用。在这之前，状态树 Trie 实际上还未被更改。直到这一步，StateDB 才会将内存中的状态变更写入存储 Trie，计算出每个账户的最终 storage root，从而生成账户的最终状态。接下来，所有“脏”的状态对象会被写入 Trie 中，更新其结构并计算新的 stateRoot。</p></li><li><p>最后，这些更新后的节点会被传递给 TrieDB，它会根据不同的后端（PathDB/HashDB） 缓存这些节点，并最终将它们持久化到磁盘 （LevelDB/PebbleDB） — — 前提是这些数据没有因为链重组被丢弃。</p></li></ul><h3 id="h-22-statedatabase" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.2 State.Database</h3><p>state.Database 是 Geth 中连接 StateDB 与底层数据库（EthDB 与 TrieDB）的重要中间层，它为状态访问提供了一组简洁的接口和实用方法。虽然它的接口比较薄，但在源码中，它扮演了多个关键角色，尤其是在状态树访问与优化方面。</p><p>在 Geth 源码中（core/state/database.go），state.Database 接口由 state.cachingDB 这一具体数据结构实现。它的主要作用包括：</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">提供统一的状态访问接口</h3><p>state.Database 是构建 StateDB 的必要依赖，它封装了打开账户 Trie 和存储 Trie 的逻辑，例如：</p><pre data-type="codeBlock" text="func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error)func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) 
"><code>func (db <span class="hljs-operator">*</span>cachingDB) OpenTrie(root common.Hash) (Trie, <span class="hljs-function"><span class="hljs-keyword">error</span>)<span class="hljs-title">func</span> (<span class="hljs-params">db *cachingDB</span>) <span class="hljs-title">OpenStorageTrie</span>(<span class="hljs-params">stateRoot common.Hash, <span class="hljs-keyword">address</span> common.Address, root common.Hash, trie Trie</span>) (<span class="hljs-params">Trie, <span class="hljs-keyword">error</span></span>) 
</span></code></pre><p>这些方法隐藏了底层 TrieDB 的复杂性，开发者在构建某个区块的状态时，只需调用这些方法获取正确的 Trie 实例，而不必直接操作 hash 路径、trie 编码或底层数据库。</p><p><strong>暂存和复用合约代码（code cache）</strong></p><p>合约代码的访问代价较高，且往往在多个块中重复使用。为此，state.Database 中实现了代码缓存逻辑，避免重复从磁盘加载合约字节码。这一优化对提高区块执行效率至关重要：</p><pre data-type="codeBlock" text="func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte 
"><code>func (db <span class="hljs-operator">*</span>CachingDB) ContractCodeWithPrefix(<span class="hljs-keyword">address</span> common.Address, codeHash common.Hash) []<span class="hljs-keyword">byte</span> 
</code></pre><p>这个接口允许按地址和代码哈希快速命中缓存，若未命中才回退到底层数据库加载。</p><p><strong>长生命周期，跨多个区块复用</strong></p><p>与 StateDB 的生命周期仅限于单个区块不同，state.Database 的生命周期和整个链（core.Blockchain）保持一致。它在节点启动时构造，并贯穿整个运行周期，作为 StateDB 的“忠实伙伴”，为其在每个区块处理时提供支持。</p><p><strong>为未来的 Verkle Tree 迁移做准备</strong></p><p>虽然当前 state.Database 看似只是“代码缓存 + trie 访问封装”，但它在 Geth 架构中的定位非常前瞻性。一旦未来的状态结构切换至 Verkle Trie，它将成为迁移过程的核心组件：处理新旧结构之间的桥接状态。</p><h3 id="h-23-trie" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.3 Trie</h3><p>在 Geth 中，状态树 Trie（Merkle Patricia Trie）本身并不存储数据，但 Trie 承担计算状态根哈希和收集修改节点的核心职责，并起到衔接 StateDB与底层存储之间的桥梁作用，是以太坊状态系统的中枢结构。</p><p>当 EVM 执行交易或调用合约时，并不会直接操作底层的数据库，而是通过 StateDB 间接与 Trie 交互。Trie 接收账户地址和存储槽位的查询与更新请求，并在内存中构建状态变化路径。这些路径最终通过递归哈希运算，自底向上生成新的根哈希（state root），这个根哈希是当前世界状态的唯一标识，并被写入区块头中，确保状态的完整性和可验证性。</p><p>在一个区块执行完毕并进入提交阶段（StateDB.Commit），Trie 会将所有修改过的节点“塌缩”为一个必要的子集，并传递给 TrieDB，由其进一步交由后端的节点数据库（如 HashDB 或 PathDB）持久化。由于 Trie节点以结构化形式编码，它既支持高效读取，也使得状态在不同节点之间可以安全地同步和验证。因此，Trie 不只是一个状态容器，更是连接上层 EVM 与底层存储引擎的纽带，使得以太坊状态具备一致性、安全性和模块化可扩展性。</p><p>源码中，Trie主要定位于 trie/trie.go中, 它提供了如下核心接口:</p><pre data-type="codeBlock" text="type Trie interface {  GetKey([]byte) []byte  GetAccount(address common.Address) (*types.StateAccount, error)  GetStorage(addr common.Address, key []byte) ([]byte, error)  UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error  UpdateStorage(addr common.Address, key, value []byte) error  DeleteAccount(address common.Address) error  DeleteStorage(addr common.Address, key []byte) error  UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error  Hash() common.Hash  Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)  Witness() map[string]struct{}  NodeIterator(startKey []byte) (trie.NodeIterator, error)  Prove(key []byte, proofDb ethdb.KeyValueWriter) error  IsVerkle() bool}
"><code><span class="hljs-keyword">type</span> Trie <span class="hljs-class"><span class="hljs-keyword">interface</span> </span>{  GetKey([]<span class="hljs-keyword">byte</span>) []<span class="hljs-keyword">byte</span>  GetAccount(<span class="hljs-keyword">address</span> common.Address) (<span class="hljs-operator">*</span>types.StateAccount, <span class="hljs-function"><span class="hljs-keyword">error</span>)  <span class="hljs-title">GetStorage</span>(<span class="hljs-params">addr common.Address, key []<span class="hljs-keyword">byte</span></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)  <span class="hljs-title">UpdateAccount</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> common.Address, account *types.StateAccount, codeLen <span class="hljs-keyword">int</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">UpdateStorage</span>(<span class="hljs-params">addr common.Address, key, value []<span class="hljs-keyword">byte</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">DeleteAccount</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> common.Address</span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">DeleteStorage</span>(<span class="hljs-params">addr common.Address, key []<span class="hljs-keyword">byte</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">UpdateContractCode</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> common.Address, codeHash common.Hash, code []<span class="hljs-keyword">byte</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">Hash</span>(<span class="hljs-params"></span>) <span class="hljs-title">common</span>.<span class="hljs-title">Hash</span>  <span class="hljs-title">Commit</span>(<span class="hljs-params">collectLeaf <span class="hljs-keyword">bool</span></span>) (<span class="hljs-params">common.Hash, *trienode.NodeSet</span>)  <span class="hljs-title">Witness</span>(<span class="hljs-params"></span>) <span class="hljs-title">map</span>[<span class="hljs-title"><span class="hljs-keyword">string</span></span>]<span class="hljs-title"><span class="hljs-keyword">struct</span></span></span>{}  NodeIterator(startKey []<span class="hljs-keyword">byte</span>) (trie.NodeIterator, <span class="hljs-function"><span class="hljs-keyword">error</span>)  <span class="hljs-title">Prove</span>(<span class="hljs-params">key []<span class="hljs-keyword">byte</span>, proofDb ethdb.KeyValueWriter</span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">IsVerkle</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">bool</span></span>}
</span></code></pre><p>以节点查询 trie.get 为例，它会根据节点类型递归地查找账户或合约存储对应的节点，查找时间复杂度是 log(n)，n 为路径深度。</p><pre data-type="codeBlock" text="func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) {  switch n := (origNode).(type) {  case nil:    return nil, nil, false, nil  case valueNode:    return n, n, false, nil  case *shortNode:    if !bytes.HasPrefix(key[pos:], n.Key) {      // key not found in trie      return nil, n, false, nil    }    value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key))    if err == nil &amp;&amp; didResolve {      n.Val = newnode    }    return value, n, didResolve, err  case *fullNode:    value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)    if err == nil &amp;&amp; didResolve {      n.Children[key[pos]] = newnode    }    return value, n, didResolve, err  case hashNode:    child, err := t.resolveAndTrack(n, key[:pos])    if err != nil {      return nil, n, true, err    }    value, newnode, _, err := t.get(child, key, pos)    return value, newnode, true, err  default:    panic(fmt.Sprintf(&quot;%T: invalid node: %v&quot;, origNode, origNode))  }} 
"><code>func (t *Trie) <span class="hljs-keyword">get</span>(origNode node, <span class="hljs-keyword">key</span> []<span class="hljs-type">byte</span>, pos int) (value []<span class="hljs-type">byte</span>, newnode node, didResolve bool, err <span class="hljs-keyword">error</span>) {  switch n := (origNode).(type) {  <span class="hljs-keyword">case</span> nil:    <span class="hljs-keyword">return</span> nil, nil, <span class="hljs-literal">false</span>, nil  <span class="hljs-keyword">case</span> valueNode:    <span class="hljs-keyword">return</span> n, n, <span class="hljs-literal">false</span>, nil  <span class="hljs-keyword">case</span> *shortNode:    <span class="hljs-keyword">if</span> !bytes.HasPrefix(<span class="hljs-keyword">key</span>[pos:], n.<span class="hljs-keyword">Key</span>) {      // <span class="hljs-keyword">key</span> <span class="hljs-built_in">not</span> found <span class="hljs-keyword">in</span> trie      <span class="hljs-keyword">return</span> nil, n, <span class="hljs-literal">false</span>, nil    }    value, newnode, didResolve, err = t.<span class="hljs-keyword">get</span>(n.Val, <span class="hljs-keyword">key</span>, pos+len(n.<span class="hljs-keyword">Key</span>))    <span class="hljs-keyword">if</span> err == nil &#x26;&#x26; didResolve {      n.Val = newnode    }    <span class="hljs-keyword">return</span> value, n, didResolve, err  <span class="hljs-keyword">case</span> *fullNode:    value, newnode, didResolve, err = t.<span class="hljs-keyword">get</span>(n.Children[<span class="hljs-keyword">key</span>[pos]], <span class="hljs-keyword">key</span>, pos+<span class="hljs-number">1</span>)    <span class="hljs-keyword">if</span> err == nil &#x26;&#x26; didResolve {      n.Children[<span class="hljs-keyword">key</span>[pos]] = newnode    }    <span class="hljs-keyword">return</span> value, n, didResolve, err  <span class="hljs-keyword">case</span> hashNode:    child, err := t.resolveAndTrack(n, <span class="hljs-keyword">key</span>[:pos])    <span class="hljs-keyword">if</span> err != nil {      <span class="hljs-keyword">return</span> nil, n, <span class="hljs-literal">true</span>, err    }    value, newnode, _, err := t.<span class="hljs-keyword">get</span>(child, <span class="hljs-keyword">key</span>, pos)    <span class="hljs-keyword">return</span> value, newnode, <span class="hljs-literal">true</span>, err  <span class="hljs-keyword">default</span>:    panic(fmt.Sprintf(<span class="hljs-string">"%T: invalid node: %v"</span>, origNode, origNode))  }} 
</code></pre><h3 id="h-24-triedb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.4 TrieDB</h3><p>TrieDB 是 Trie 与磁盘存储之间的中间层，专注于 Trie 节点的存取与持久化。每一个 Trie 节点（无论是账户信息还是合约的存储槽）最终都会通过 TrieDB进行读写。</p><p>程序启动时会创建一个 TrieDB 实例，在节点关闭时会被销毁。它在初始化时需要传入一个 EthDB 实例，EthDB 实例负责具体的数据持久化操作。</p><p>目前，Geth 支持两种 TrieDB 后端实现：</p><p>HashDB：传统方式，以哈希为键。</p><p>PathDB：新引入的 Path-based 模型(Geth 1.14.0 版本后默认配置)，以路径信息作为键，优化了更新与修剪性能。</p><p>源码中，TrieDB 主要位于 triedb/database.go。</p><p><strong>Trie 节点的读取逻辑</strong></p><p>我们先来看节点的读取流程，因为它相对简单。</p><p>所有的 TrieDB 后端都必须实现一个 database.Reader 接口，其定义如下：</p><pre data-type="codeBlock" text="type Reader interface {    Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)} 
"><code><span class="hljs-keyword">type</span> Reader <span class="hljs-class"><span class="hljs-keyword">interface</span> </span>{    Node(owner common.Hash, path []<span class="hljs-keyword">byte</span>, hash common.Hash) ([]<span class="hljs-keyword">byte</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>)} 
</span></code></pre><p>这个接口提供了基本的节点查询功能，它会根据路径（path）和节点哈希（hash）从 trie 树中定位并返回该节点。注意，返回的是原始字节数组 — — TrieDB 对节点的内容并不关心，也不知道这是不是账户节点、叶子节点还是分支节点(这由上层的 Trie 来解析)。</p><p>接口中的 owner 参数用于区分不同的 trie：</p><p>如果是账户 trie，则 owner 留空。</p><p>如果是合约的存储 trie，则 owner 是该合约的地址，因为每个合约都有自己独立的存储 trie。</p><p>换句话说，TrieDB 是底层节点的读写总线，为上层的 Trie 提供统一的接口，不涉及语义，只关心路径和哈希。它让 Trie 与物理存储系统之间解耦，使得不同存储模型可以灵活替换而不影响上层逻辑。</p><p><strong>TrieDB 之 HashDB</strong></p><p>TrieDB 历史上采用的节点持久化方式是：</p><p><strong>将每个 Trie 节点的哈希（Keccak256）作为键，将该节点的 RLP 编码作为值</strong>，并写入底层的 key-value 存储中。这种方式现在被称为 HashDB。</p><p>这种设计方式非常直接，但有几个显著的优点：</p><ul><li><p>支持多棵 Trie 并存：只需知道根哈希，就能遍历恢复整个 Trie。每个账户的存储、账户 Trie、不同历史状态的根哈希都可以分别管理。</p></li><li><p>子树去重（Subtrie Deduplication）：由于相同的子树具有相同的结构和节点哈希，它们在 HashDB 中会自然共享，不需要重复存储。这对于以太坊的大状态树尤为重要，因为大部分状态在区块之间是保持不变的。</p></li></ul><p>要注意的是，<strong>普通的 Geth 节点并不会在每个区块之后将 Trie 完整写入磁盘，这种完整持久化只发生在 “归档模式”</strong>（ — gcmode archive）下，而大多数主网节点并不使用归档模式。</p><p>那普通模式下，状态是怎么写入磁盘的呢？实际上，状态更新会先缓存在内存中，延迟写入磁盘。这个机制叫做“延迟刷盘”（delayed flush），触发条件包括：</p><p>⏱️ 定时刷盘：默认每隔 5 分钟（等价于处理完约 5 分钟内的区块）会自动写入一次。</p><p>💾 缓存容量达到上限：当状态缓存填满，必须刷盘释放内存。</p><p>⛔ 节点关闭时：为了数据完整性，所有缓存都会刷盘。</p><p>尽管 HashDB 的结构设计很简单，但它在内存管理方面非常复杂，特别是对失效节点的垃圾回收机制：假设某个合约在一个区块被创建，在下一个区块就被销毁 — — 此时与该合约有关的状态节点（包括合约账户和其独立的存储 Trie）都已经没用了，如果不清理，它们会白白占用内存。因此，HashDB 设计了引用计数和节点使用追踪机制来判断哪些节点不再使用并从缓存中清除。</p><p>TrieDB 之 PathDB</p><p>PathDB 是 TrieDB 的一种新后端实现。它改变了 Trie 节点在磁盘上持久化和内存中维护的方式。如前所述，HashDB 是通过节点的哈希进行索引存储的。而这种方法让清除（prune）状态中不再使用的部分变得非常困难。为了解决这一长期问题，Geth 引入了 PathDB。</p><p>PathDB 与 HashDB 有几个显著区别：</p><ul><li><p>Trie 节点在数据库中是按照其路径（path）作为键进行存储的。某账户或 storage key 节点的路径为该账户地址哈希或 storage key 在 trie 树上与其他节点公共前缀部分；某个合约的 storage Trie 中的节点，其路径前缀包含该账户地址哈希。</p></li></ul><pre data-type="codeBlock" text="account trie node key = Prefix(1byte) || COMPACTED(node_path)storage trie node key = Prefix(1byte) || account hash(32byte) || COMPACTed(node_path) 
"><code>account trie node key = <span class="hljs-built_in">Prefix</span>(<span class="hljs-number">1</span>byte) || <span class="hljs-built_in">COMPACTED</span>(node_path)storage trie node key = <span class="hljs-built_in">Prefix</span>(<span class="hljs-number">1</span>byte) || account <span class="hljs-built_in">hash</span>(<span class="hljs-number">32</span>byte) || <span class="hljs-built_in">COMPACTed</span>(node_path) 
</code></pre><ul><li><p>HashDB会定期把每个区块的完整状态刷盘。这意味着即使是你并不关心的旧区块，也会残留完整状态。而 PathDB 始终只在磁盘上维护一棵 Trie。每个区块只更新同一棵 Trie。因为使用路径作为键，节点的修改仅需覆盖旧节点即可；被清除的节点也可以安全删除，因为没有其他 Trie 会引用它们。</p></li><li><p>被持久化的这棵 Trie 并非链的最新头部，而是落后头部至少 128 个区块。最近 128 个区块的 Trie 更改则分别存在内存中，用于应对短链重组（reorg）；</p></li><li><p>如果出现更大的 reorg，PathDB会利用 freezer 中预存的每个区块的 state diff（状态差异）进行逆应用（rollback），将磁盘状态回滚至分叉点。</p></li></ul><h3 id="h-25-rawdb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.5 RawDB</h3><ul><li><p>在 Geth 中，rawdb 是一个底层数据库读写模块，它直接封装了对状态、区块链数据、Trie 节点等核心数据的存取逻辑，是整个存储系统的基础接口层。它并不直接暴露给 EVM 或业务逻辑层，而是作为内部工具服务于如 TrieDB、StateDB、BlockChain 等模块的持久化操作。rawdb 和 trie 一样，并不直接存储数据本身，它们都是对底层数据库的抽象封装层，负责定义存取规则，而非执行最终的数据落盘或读取。可以把 rawdb 看作是 Geth 的“硬盘驱动器”，它定义了所有核心链上数据的键值格式和访问接口，负责确保不同模块可以统一、可靠地读写数据。虽然在直接开发中很少会使用它，但它是整个 Geth 存储层最基础、最关键的一环。</p></li></ul><p><strong>核心功能</strong></p><p>源码中，rawdb 主要定位于 core/rawdb/accessors_trie.go。rawdb 提供了大量 ReadXxx 和 WriteXxx 等读写方法，用于标准化地访问不同类型的数据。例如：</p><p>区块数据(core/rawdb/accessors_chain.go)：ReadBlock, WriteBlock, ReadHeader等 状态数据(core/rawdb/accessors_trie.go)：WriteLegacyTrieNode, ReadTrieNode等 总体元数据：如总难度、最新头区块哈希、创世信息等 这些方法通常以约定好的 key 前缀（如 h 表示 header, b 表示 block, a 表示 AccountTrieNode）组织数据在底层数据库中（LevelDB 或 PebbleDB）。</p><p><strong>与 TrieDB 的关系</strong></p><p>TrieDB 本身并不直接操作硬盘，它把具体的读写委托给 rawdb。而 rawdb 又会调用更底层的 ethdb.KeyValueStore 接口，这可能是 LevelDB、PebbleDB 或内存数据库。例如，写入 Trie相关的数据（账户、存储槽等）时:</p><p>基于HashDB 的 Trie 节点采用rawdb.WriteLegacyTrieNode 等方法负责将以 (hash, rlp-encoded node) 的形式写入数据库。 基于PathDB的 Trie 节点则采用WriteAccountTrieNode, WriteStorageTrieNode 等方法将以(path, rlp-encoded node)的形式写入数据库。</p><h3 id="h-26-ethdb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.6 EthDB</h3><p>在 Geth 中，ethdb 是整个存储系统的核心抽象，它扮演着“生命之树”的角色 — — 深深扎根于磁盘，向上传递支持至 EVM 与执行层各个组件。其主要目的是屏蔽底层数据库实现的差异，为整个 Geth 提供统一的键值读写接口。正因如此，Geth 在任意地方都不直接使用具体的数据库（如 LevelDB、PebbleDB、MemoryDB等），而是通过 ethdb 提供的接口进行数据访问。</p><p>接口抽象与职责划分</p><p>源码中，ethdb 主要定位于ethdb/database.go。ethdb 中最核心的接口是 KeyValueStore(),它定义了常见的键值操作方法：</p><pre data-type="codeBlock" text="type KeyValueStore interface {  Has(key []byte) (bool, error)  Get(key []byte) ([]byte, error)  Put(key []byte, value []byte) error  Delete(key []byte) error} 
"><code><span class="hljs-keyword">type</span> KeyValueStore <span class="hljs-class"><span class="hljs-keyword">interface</span> </span>{  Has(key []<span class="hljs-keyword">byte</span>) (<span class="hljs-keyword">bool</span>, <span class="hljs-function"><span class="hljs-keyword">error</span>)  <span class="hljs-title">Get</span>(<span class="hljs-params">key []<span class="hljs-keyword">byte</span></span>) (<span class="hljs-params">[]<span class="hljs-keyword">byte</span>, <span class="hljs-keyword">error</span></span>)  <span class="hljs-title">Put</span>(<span class="hljs-params">key []<span class="hljs-keyword">byte</span>, value []<span class="hljs-keyword">byte</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>  <span class="hljs-title">Delete</span>(<span class="hljs-params">key []<span class="hljs-keyword">byte</span></span>) <span class="hljs-title"><span class="hljs-keyword">error</span></span>} 
</span></code></pre><p>这套接口非常简洁，覆盖了基础读写操作。而扩展接口 ethdb.Database 则在此基础上加入了对 freezer 冷存储的读写支持（AncientStore），主要用于链数据（如历史区块、交易回执）的管理：新近区块保存在 KV 存储中，较老的则迁移至 freezer。</p><p>此外，ethdb 还提供了多种具体实现版本：</p><p>LevelDB：最早期的默认实现，稳定成熟。 PebbleDB：目前推荐使用的默认实现，更快、资源效率更高。</p><p>RemoteDB：用于远程状态访问场景，在轻节点、验证者或模块化执行环境中尤为重要。 MemoryDB：完全内存实现，常用于 dev 模式和单元测试。</p><p>这让 Geth 能够灵活地在不同场景间切换存储后端，比如开发调试使用 MemoryDB，主网上线使用 PebbleDB。</p><p><strong>生命周期与模块贯通</strong></p><p>每个 Geth 节点启动时，都会创建唯一的 ethdb 实例，这个对象贯穿程序始终，直到节点关闭。在结构设计上，它被注入到 core.Blockchain 中，进而传递到 StateDB、TrieDB 等模块，成为全局共享的数据访问入口。</p><p>正因为 ethdb 抽象了底层数据库细节，Geth 的其他组件才能专注于各自的业务逻辑，比如：</p><p>StateDB 只关心账户和存储槽；</p><p>TrieDB 只关心如何存储和查找 Trie 节点；</p><p>rawdb 只关心如何组织链数据的键值布局； 这些上层组件都无需感知数据是存在哪个具体数据库引擎里。</p><h2 id="h-db" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">六种 DB 的创建顺序和调用链</h2><p>本节从 Geth 节点启动开始，梳理这 6 种 DB 的启动流程和调用关系。</p><h3 id="h-31" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3.1 创建顺序</h3><p>整体创建顺序为ethdb → rawdb/TrieDB → state.Database → stateDB → trie，源码中具体调用链如下:</p><pre data-type="codeBlock" text="【节点初始化阶段】MakeChain└── MakeChainDatabase└── node.OpenDatabaseWithFreezer└── node.openDatabase└── node.openKeyValueDatabase└── newPebbleDBDatabase / remotedb↓ethdb.Database↓rawdb.Database (封装 ethdb)└── rawdb.NewDatabaseWithFreezer(ethdb)↓trie.Database (TrieDB)└── trie.NewDatabase(ethdb)└── backend: pathdb.New(ethdb) / hashdb.New(ethdb)↓state.Database (cachingDB)└── state.NewDatabase(trieDB)↓【区块处理阶段】chain.InsertChain└── bc.insertChain└── state.New(root, state.Database)↓state.StateDB└── stateDB.OpenTrie()└── stateDB.OpenStorageTrie()↓trie.Trie / SecureTrie 
"><code>【节点初始化阶段】MakeChain└── MakeChainDatabase└── node.OpenDatabaseWithFreezer└── node.openDatabase└── node.openKeyValueDatabase└── newPebbleDBDatabase <span class="hljs-operator">/</span> remotedb↓ethdb.Database↓rawdb.Database (封装 ethdb)└── rawdb.NewDatabaseWithFreezer(ethdb)↓trie.Database (TrieDB)└── trie.NewDatabase(ethdb)└── backend: pathdb.New(ethdb) <span class="hljs-operator">/</span> hashdb.New(ethdb)↓state.Database (cachingDB)└── state.NewDatabase(trieDB)↓【区块处理阶段】chain.InsertChain└── bc.insertChain└── state.New(root, state.Database)↓state.StateDB└── stateDB.OpenTrie()└── stateDB.OpenStorageTrie()↓trie.Trie <span class="hljs-operator">/</span> SecureTrie 
</code></pre><h3 id="h-32" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3.2 生命周期一览</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d51fd36ea2bb1754dfa2ac9076f4ff64040f182d4db612b475d4944845f0b757.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>区块执行完毕后 StateDB 会调用 func (s StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool)，并触发如下存储状态更新:</p><p>通过ret, err := s.commit(deleteEmptyObjects, noStorageWiping)收集 Trie 状态树涉及到的所有更新</p><pre data-type="codeBlock" text="func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {    ...    newroot, set := s.trie.Commit(true)    root = newroot    ...    } 
"><code>func (s <span class="hljs-operator">*</span>StateDB) commit(deleteEmptyObjects <span class="hljs-keyword">bool</span>, noStorageWiping <span class="hljs-keyword">bool</span>) (<span class="hljs-operator">*</span>stateUpdate, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{    ...    newroot, set :<span class="hljs-operator">=</span> s.trie.Commit(<span class="hljs-literal">true</span>)    root <span class="hljs-operator">=</span> newroot    ...    } 
</code></pre><p>其中调用到的 trie.Commit 方法会把所有的节点(不论是 short 节点还是 full 节点)塌缩为 hash 节点t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root, t.uncommitted &gt; 100)，并收集所有脏节点返回给 StateDB</p><p>StateDB 利用收集到的所有脏节点更新 TrieDB 缓存层：</p><p>HashDB在内存中维护了dirties map[common.Hash]cachedNode 这个对象来缓存这些更新，并更新相应的 trie 节点引用，缓存有大小限制</p><p>PathDB 则在内存中维护了 tree layerTree 这个对象并增加一层 diff 来缓存这些更新，最多可缓存 128 层 diff</p><pre data-type="codeBlock" text="func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {    ...    // If trie database is enabled, commit the state update as a new layer    if db := s.db.TrieDB(); db != nil {      start := time.Now()      if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil {        return nil, err      }      s.TrieDBCommits += time.Since(start)    }    ... 
"><code>func (s <span class="hljs-operator">*</span>StateDB) commitAndFlush(<span class="hljs-built_in">block</span> <span class="hljs-keyword">uint64</span>, deleteEmptyObjects <span class="hljs-keyword">bool</span>, noStorageWiping <span class="hljs-keyword">bool</span>) (<span class="hljs-operator">*</span>stateUpdate, <span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{    ...    <span class="hljs-comment">// If trie database is enabled, commit the state update as a new layer    if db := s.db.TrieDB(); db != nil {      start := time.Now()      if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil {        return nil, err      }      s.TrieDBCommits += time.Since(start)    }    ... </span>
</code></pre><p>当 HashDB 或 PathDB 缓存超限时，则会触发 flush，通过 rawdb 提供的相关接口将缓存写入 ethdb 的实际持久层：</p><p>全节点 HashDB 模式下，由于 key 是 hash，所以同一个账户如果被修改，由于底层数据库通过 key 无法感知是否是同一个账户，不能轻易删除该 key 及其对应的值，否则可能会影响其他账户状态，所以只会把新修改的 KV 写入 DB，而无法删除旧状态，因此全节点状态很难被修剪。例如两个不同的合约地址 A 和 B 实际保存相同的合约代码，他们在 HashDB 中共享同一个(key 为 hash，value 为合约代码)的存储，若 EVM 执行后销毁其中一个合约 A，另外一个合约 B 代码和合约 A 代码在数据库中的 key 一样，所以不能随意删除数据库中 hash 为 key 的值，否则会导致 B 合约后面读取不到该合约代码了。</p><p>全节点 PathDB 模式下，由于 key 是 path，所以同一个账户在底层 DB 对应的 key 是一样的，会把同一个账户对应的状态覆盖掉，因此更容易裁剪全节点的状态。因此现在 Geth 全节点默认采用的是 PathDB 模式</p><p>由于 归档(archive)节点需要存储每一个区块对应的状态，此时 HashDB 则更具优势，因为不同区块下很多账户的数据实际上并未修改，基于 hash 作为 key 相当于自动具备裁剪的特性；而此时 PathDB 则需要保存每个区块下所有账户的状态，导致状态会超级大，因此 Geth 的 archive 节点只支持 HashDB 模式</p><h3 id="h-hashdb-pathdb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">实例：全节点下 HashDB 和 PathDB 实际落盘对比</h3><p>假设左边的 Trie 是 MPT 的初始状态，其中红色的是将被修改的节点；右边的则是 MPT 的新状态，绿色表示之前的 4 个红色节点被修改了。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2cc764977614b99c5a29e92d33b71f21b3f6aa329089a775513291d340ad7707.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在 HashDB 模式下，由于 C/D/E 节点更改后 hash 必定会发生变化，因此尽管 C/D/E 节点对应的三个账户之前已经落盘了，这三个账户对应的新节点 C’/D’/E’ 还是需要落盘，且一旦持久化之后就很难删除这些旧节点了。磁盘更新之前(左图)和之后(右图)的状态如下，其中 collapsed Node 可以简单理解为节点存储的值。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1c2d4be95f7f961835a0b98f85ff05fc3a86dd6d2976751472e96f5614e775bb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>在 PathDB 模式下，虽然 C/D/E 节点对应的值发生了变化，但是由于底层存储的 key(path) 不变，在持久化是可以直接替换这三个节点对应的值为 C’/D’/E’ 就可以了，磁盘数据并不会有过多冗余（虽然有些相同的合约可能会在不同的 path下都保存了一份，但是影响不大）。磁盘更新之前(左图)和之后(右图)的状态如下。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/62e25049afa29b7c5b3dbb4ff3bd9d7da4700c37466e4b03a2fc4e8eec06da5c.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-hashdb-pathdb" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">实例: HashDB 和 PathDB 读取账户对比</h3><p>在 core/rawdb/accessors_trie.go中增加如下 debug 代码，测试 stateDB 读取0xB3329fcd12C175A236a02eC352044CE44dC2C3ba账户(account hash:0xaea7c67da6a9bdb230dd07d0e96626e5e57c9cba04dc8039c923baefe55eacd1)涉及到的Trie节点数据库读取:</p><pre data-type="codeBlock" text="func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {  fmt.Println(&quot;PathDB read:&quot;, hexutil.Encode(accountTrieNodeKey(path)))  data, _ := db.Get(accountTrieNodeKey(path))  return data}func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {  fmt.Println(&quot;HashDB read:&quot;, hash)  data, err := db.Get(hash.Bytes())  if err != nil {    return nil  }  return data} 
"><code>func ReadAccountTrieNode(db ethdb.KeyValueReader, path []<span class="hljs-keyword">byte</span>) []<span class="hljs-keyword">byte</span> {  fmt.Println(<span class="hljs-string">"PathDB read:"</span>, hexutil.Encode(accountTrieNodeKey(path)))  data, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> db.Get(accountTrieNodeKey(path))  <span class="hljs-keyword">return</span> data}func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []<span class="hljs-keyword">byte</span> {  fmt.Println(<span class="hljs-string">"HashDB read:"</span>, hash)  data, err :<span class="hljs-operator">=</span> db.Get(hash.Bytes())  <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {    <span class="hljs-keyword">return</span> nil  }  <span class="hljs-keyword">return</span> data} 
</code></pre><p>PathDB 读取到的 Trie 节点如下，可看出读取的是账户地址 hash 的前 8 位相应 path 的节点:</p><pre data-type="codeBlock" text="#0x41为前缀，多加的0是nibbles（半字节） 的对齐需要PathDB read: 0x410aPathDB read: 0x410a0ePathDB read: 0x410a0e0aPathDB read: 0x410a0e0a07PathDB read: 0x410a0e0a070cPathDB read: 0x410a0e0a070c06PathDB read: 0x410a0e0a070c0607PathDB read: 0x410a0e0a070c06070d 
"><code><span class="hljs-meta prompt_">#</span><span class="bash">0x41为前缀，多加的0是nibbles（半字节） 的对齐需要PathDB <span class="hljs-built_in">read</span>: 0x410aPathDB <span class="hljs-built_in">read</span>: 0x410a0ePathDB <span class="hljs-built_in">read</span>: 0x410a0e0aPathDB <span class="hljs-built_in">read</span>: 0x410a0e0a07PathDB <span class="hljs-built_in">read</span>: 0x410a0e0a070cPathDB <span class="hljs-built_in">read</span>: 0x410a0e0a070c06PathDB <span class="hljs-built_in">read</span>: 0x410a0e0a070c0607PathDB <span class="hljs-built_in">read</span>: 0x410a0e0a070c06070d</span> 
</code></pre><p>HashDB 读到的 Trie 节点如下，可以看出读取是的 hash 为 key 对应的节点:</p><pre data-type="codeBlock" text="HashDB read: 0xb01e32b0c38555bb27f1a924b8408824f97dd8d70f096b218d397906a9095385HashDB read: 0x99d38ce254e6c35a49504345a30e94b4ea08338279385bae33feaaa11c3a0a00HashDB read: 0xfcc42d902aa9107b83ee7839a8bc61b370cc5eac9ee60db1af7165daf6c3f76bHashDB read: 0x3232bc99a88337d2aea2e8c237eb5b4ebb9366ff5bdd94b965ac6f918bd6303fHashDB read: 0x04ae6f0462f6c0c7e5827dc46fcd69329483d829c39f624744f7b55c09c2cc96HashDB read: 0x22a16c466cc420e8ed97fd484cecc8f73160ee74a56cfc87ff941d1b56ff46f8HashDB read: 0xae26238e219065458f314e456265cd9c935e829ba82aebe6d38bacdbb14582f3HashDB read: 0xe9ce7770c224e563b0c407618b7b7d8614da3d5da89f3960a3bec97e78fc0ae0HashDB read: 0x2c7d134997a5c3e0bf47ff347479ee9318826f1c58689b3d9caeac77287c3af8 
"><code>HashDB read: <span class="hljs-number">0xb01e32b0c38555bb27f1a924b8408824f97dd8d70f096b218d397906a9095385</span>HashDB read: <span class="hljs-number">0x99d38ce254e6c35a49504345a30e94b4ea08338279385bae33feaaa11c3a0a00</span>HashDB read: <span class="hljs-number">0xfcc42d902aa9107b83ee7839a8bc61b370cc5eac9ee60db1af7165daf6c3f76b</span>HashDB read: <span class="hljs-number">0x3232bc99a88337d2aea2e8c237eb5b4ebb9366ff5bdd94b965ac6f918bd6303f</span>HashDB read: <span class="hljs-number">0x04ae6f0462f6c0c7e5827dc46fcd69329483d829c39f624744f7b55c09c2cc96</span>HashDB read: <span class="hljs-number">0x22a16c466cc420e8ed97fd484cecc8f73160ee74a56cfc87ff941d1b56ff46f8</span>HashDB read: <span class="hljs-number">0xae26238e219065458f314e456265cd9c935e829ba82aebe6d38bacdbb14582f3</span>HashDB read: <span class="hljs-number">0xe9ce7770c224e563b0c407618b7b7d8614da3d5da89f3960a3bec97e78fc0ae0</span>HashDB read: <span class="hljs-number">0x2c7d134997a5c3e0bf47ff347479ee9318826f1c58689b3d9caeac77287c3af8</span> 
</code></pre><p>总体来说，PathDB 和 HashDB 均是保持 Trie 数据结构来存储状态数据，只是 PathDB 以 Trie 节点的 path 作为 key，而 HashDB 则是以 Trie 节点值对应的 hash 作为 key，两者均存储值相同均为 Trie 节点的值。</p><h2 id="h-db" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DB 相关读写操作流程追踪</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">交易执行阶段</h3><p>所有账户和Storage值通过StateDB.GetState等方法经过Trie→TrieDB(pathdb/hashdb)→RawDB→Level/PebbleDB 读取到 StateDB 内存中 随后EVM执行状态变更（如调用 Statedb.SetBalance() ）也保留在 StateDB 的内存中 包括：余额变更、nonce 更新、storage 修改</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">单个区块执行完毕更新缓存</h3><p>调用 StateDB.Commit() → 收集脏节点转化为修改的 Trie 节点组，并计算新 StateRoot 内部调用 Trie.Commit() → 调用 TrieDB.Update()将更改保存在 TrieDB 缓存层 PathDB 最多有 128 个块的 diff 缓存层限制 HashDB 的缓存层有大小限 超过上述限制则进一步触发 TrieDB.Commit 实际落盘到底层数据库 3. 单个区块执行完毕 Header / Receipts 提交：</p><p>除状态以外，区块头、body、交易回执等数据通过 RawDB.Write*(ethdb) 等接口写入 ethdb 层</p><h3 id="h-triedbcommit-batch-db" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">多个区块执行后缓存超限触发实际落盘 TrieDB.Commit → batch → DB</h3><p>节点是归档节点 node 或超过 flushInterval 或超过 TrieDB 的缓存限制或节点关闭前，开始触发commit，并最终落盘。如下是 PathDB 模式下落盘核心代码</p><pre data-type="codeBlock" text="func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {  ...  rawdb.WriteLegacyTrieNode(batch, hash, node.node) // 多个修改的trie节点加入 batch（未落盘）  if batch.ValueSize() &gt;= ethdb.IdealBatchSize { // 达到 IdealBatchSize 后触发写盘      batch.Write()            // 落盘      batch.Replay(uncacher)   // 通知 uncacher 清理内存      batch.Reset()            // 重置 batch  }  ... 
"><code>func (db <span class="hljs-operator">*</span>Database) commit(hash common.Hash, batch ethdb.Batch, uncacher <span class="hljs-operator">*</span>cleaner) <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{  ...  rawdb.WriteLegacyTrieNode(batch, hash, node.node) <span class="hljs-comment">// 多个修改的trie节点加入 batch（未落盘）  if batch.ValueSize() >= ethdb.IdealBatchSize { // 达到 IdealBatchSize 后触发写盘      batch.Write()            // 落盘      batch.Replay(uncacher)   // 通知 uncacher 清理内存      batch.Reset()            // 重置 batch  }  ... </span>
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>Geth 中的这 6 个数据库模块各自承担不同层级的职责，形成了一条自底向上的数据访问链。通过多层抽象与多级缓存，上层模块无需关心底层的具体实现，从而实现了底层存储引擎的可插拔性与较高的 I/O 性能。</p><p>最底层的 ethdb 抽象了物理存储，屏蔽具体数据库类型，支持如 LevelDB、Pebble、RemoteDB 等多种后端；其上一层是 rawdb，负责对区块、区块头、交易等核心链上数据结构的编码、解码与封装，简化了链数据的读写操作。TrieDB 管理状态树节点的缓存与持久化，支持 hashdb 与 pathdb 两种后端，用于实现不同的状态修剪策略和存储方式。</p><p>再往上，trie.Trie 是状态变化的执行容器与根哈希的计算核心，承担实际的状态构建与遍历操作；state.Database 封装对账户和合约存储 Trie 的统一访问，并提供合约代码缓存；而最顶层的 state.StateDB 是在区块执行过程中与 EVM 对接的接口，提供账户与存储的读缓存和写支持，使得 EVM 无需感知底层 Trie 的复杂结构。</p><p>这些模块通过职责分离与接口隔离，协同构建了一个既灵活又高效的状态管理体系，使 Geth 能在复杂链状态与交易执行中保持良好性能与可维护性。</p><h2 id="h-references" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">References</h2><p>[1] go-ethereum 源码</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereum/go-ethereum">https://github.com/ethereum/go-ethereum</a></p><p>[2] The Tale of 5 DBs</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://s1na.substack.com/p/the-tale-of-5-dbs-24-07-26">https://s1na.substack.com/p/the-tale-of-5-dbs-24-07-26</a></p><p>[3] Path-based storage &amp; Inline prune — NodeReal</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nodereal.io/blog/en/geth-path-based-storage-model-and-newly-inline-state-prune/">https://nodereal.io/blog/en/geth-path-based-storage-model-and-newly-inline-state-prune/</a></p><p>[4] RLP 编码规范</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/">https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/</a></p><p>[5] Ethereum data structures and encoding</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/developers/docs/data-structures-and-encoding/">https://ethereum.org/en/developers/docs/data-structures-and-encoding/</a></p><p>内容 |</p><p>po</p><p>Web3buidler.tech Core Contributor, EthStorage Engineer</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/362c344b8fe69bdffcca3915466a5d998711815d07123099b76bec63f19c67a6.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LXDAO 第二期休闲黑客松圆满落幕：线上线下联动，创意无限碰撞！]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-16</link>
            <guid>9IrNUfWRNxrkzEA167cc</guid>
            <pubDate>Sat, 10 May 2025 12:34:13 GMT</pubDate>
            <description><![CDATA[LXDAO Casual Hackathon 第二期 AI 开发工具黑客松圆满落幕。这场黑客松再次让我们见证了 AI 如何助力开发者释放潜能，将天马行空的想法快速落地为切实可行的项目，为技术世界带来了新的活力与惊喜。让我们一起来看看这次活动的精彩内容吧！ 4 月 20 日 晚 21 点，随着 Demo Show 的结束，LXDAO 休闲黑客松第二期 AI 开发工具黑客松圆满闭幕。在本期 LXDAO 休闲黑客松中共计有 18 支参赛队伍贡献并展示了其卓越的创新成果与出色的创意成果。错过直播的小伙伴可以点击链接查看回放，感受这场黑客松带来的技术震撼与无限可能！ 🎙️ 回放链接： Opening Day 回放： Demo Show 回放： LXDAO Casual Hackathon 在技术迅猛发展和商业化浪潮的冲击下，传统黑客松的探索精神、创造热情和自由氛围似乎正逐渐被淹没。为此，LXDAO Casual Hackathon 致力于在轻松有趣的环境中，重拾那份最初的 “玩” 的心，让每个人都能跳脱日常，充分享受编程带来的乐趣与自由。为什么是 “休闲” 黑客松？围绕特定主题，一起畅想...]]></description>
            <content:encoded><![CDATA[<p>LXDAO Casual Hackathon 第二期 AI 开发工具黑客松圆满落幕。这场黑客松再次让我们见证了 AI 如何助力开发者释放潜能，将天马行空的想法快速落地为切实可行的项目，为技术世界带来了新的活力与惊喜。让我们一起来看看这次活动的精彩内容吧！</p><p>4 月 20 日 晚 21 点，随着 Demo Show 的结束，LXDAO 休闲黑客松第二期 AI 开发工具黑客松圆满闭幕。在本期 LXDAO 休闲黑客松中共计有 18 支参赛队伍贡献并展示了其卓越的创新成果与出色的创意成果。错过直播的小伙伴可以点击链接查看回放，感受这场黑客松带来的技术震撼与无限可能！</p><p>🎙️ 回放链接：</p><p>Opening Day 回放：</p><div data-type="youtube" videoId="2VO0dmOwVWE">
      <div class="youtube-player" data-id="2VO0dmOwVWE" style="background-image: url('https://i.ytimg.com/vi/2VO0dmOwVWE/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=2VO0dmOwVWE">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>Demo Show 回放：</p><div data-type="youtube" videoId="GO5InxROkWA">
      <div class="youtube-player" data-id="GO5InxROkWA" style="background-image: url('https://i.ytimg.com/vi/GO5InxROkWA/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=GO5InxROkWA">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>LXDAO Casual Hackathon 在技术迅猛发展和商业化浪潮的冲击下，传统黑客松的探索精神、创造热情和自由氛围似乎正逐渐被淹没。为此，LXDAO Casual Hackathon 致力于在轻松有趣的环境中，重拾那份最初的 “玩” 的心，让每个人都能跳脱日常，充分享受编程带来的乐趣与自由。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么是 “休闲” 黑客松？</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">围绕特定主题，一起畅想未来</h3><p>每一届 LXDAO Casual Hackathon 都会围绕一个或多个特别策划的主题进行创作，例如关注 Web3 未来、元宇宙探索、去中心化应用创新等。大家可以基于既定的主题随意发挥，从探索概念到实现原型，每一秒都充满了未知和惊喜。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">周末或短期活动，远程也能愉快搞事情</h3><p>活动通常在周末或 2–3 天的短周期内举行，为的是让开发者能专注地爆发灵感。更重要的是，所有人都可以通过线上远程参与，无论你身处何处，都能和世界各地的朋友一起联手“整活儿”，共享彼此的想法与成果。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">轻松氛围 + 无压力创作</h3><p>LXDAO Casual Hackathon 倡导 “先好好玩，再认真做”。这里没有过度严格的评委打分或漫长的赛制流程，而是鼓励每个人以乐趣为先，自由地试验想法、激发灵感。大家可以对任何技术栈或创意进行脑洞大开，释放最纯粹的创造力。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">每个项目都会获奖，人人都被看见</h3><p>在 LXDAO 的休闲黑客松里，没有所谓的“失败” — — 我们坚信每一个创意都值得被关注。为了鼓励更多大胆尝试和点子碰撞，我们为每个项目都设置了专属奖励与纪念，以此向敢于探索的人们致敬。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">享受过程，与同好者结伴同行</h3><p>LXDAO Casual Hackathon 不只是一场比赛，也是一个学习、社交、共创的场域。你将在这里找到志同道合的小伙伴，一起碰撞想法，结下友谊。更多时候，完成项目只是开始，真正的乐趣在于与彼此的持续协作与交流。</p><p>AI 开发工具黑客松</p><p>AI 开发工具黑客松是由 LXDAO 组织并主办的 LXDAO Causual Hackathon 品牌系列的第二期活动，旨在通过组织并学习 AI 开发工具以有效推进想法的实施或落地为可行的项目，并降低 MVP 的落地成本。</p><p>在本期黑客松中，最终由以下 18 支队伍并在 Demo Show 中将其创意一一呈现，接下来就让我们一同欣赏他们的精彩表现吧！</p><h3 id="h-ai-ybsbarker-ybsbarker" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 理财奖：YBSBarker YBSBarker</h3><p>是一个由生息稳定币构成的链上收益策略可视化工具。它帮助用户轻松配置链上资产，通过可视化的方式展示收益策略。项目灵感来源于 Ethena (ENA) 等 DeFi 协议，旨在简化复杂的利率交换操作。YBSBarker 提供了一个策略创建器，用户可以通过拖拽元素生成和发布个性化策略，并在策略广场上展示。该工具不仅适合小白用户，也能通过策略复制和交易实现盈利。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f43d8e5a88145bef463239fcb5e1f6850918b1dfeb8622675853ec7b2811f9af.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-intensive-co-learning" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 助教奖：Intensive-Co-Learning</h3><p>Bot Intensive-Co-Learning Bot 是一个为残酷共学项目设计的学习助理工具，旨在帮助管理者高效统计和管理学员的学习数据。该工具通过自动化流程实现了学员学习情况的日常统计、周报生成、优秀笔记推荐等功能，并支持定时任务的灵活配置（如每日、每周或精确到具体时间）。此外，Bot 还能实现与群组的交互，包括消息发送、项目激活与停用等操作。项目还集成了 AI 模型分析功能，自动生成学习简报，极大地提升了学习管理的效率，目前已在 LXDAO 的各残酷共学频道试用。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/61c863671c990bf27fc6a060465cf40febe8317e069a53fab0bbab05e338b8c1.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 心理分析奖：良心量表</h3><p>良心量表项目旨在提供一个免费的心理测评工具，解决市面上许多量表在测评后期突然收费的不良体验。项目的核心功能是一个开放的心理健康评估量表，用户可以通过回答问题来获得精神状况的评估结果。测评完成后，结果会交给 AI 进行分析，并提供类似心理医生的建议。项目还包括一个结果展示页面，方便用户查看自己选择的答案和评分。未来计划增加更多功能和问卷，并实现 AI 聊天功能，让用户可以与 AI 互动以获得更详细的建议。量表的问题基于开源论文和已有研究，确保其科学性和可靠性。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/eaf00ba66d8ca1bd6ad3d31200d60a799521b4c670f705d088a00ca3ee37011a.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-wownavs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 收藏家奖：Wownavs</h3><p>超级飞侠战队开发的 Wownavs 项目可以将用户书签转化为在线网页，支持独立的 SEO 优化和 AI 代码生成。用户可以上传到主站或下载 HTML 文件进行独立部署。项目解决了市面上现有框架的臃肿和收费问题，通过浏览器插件实现书签分类和动态生成站点地图。Wownavs 还集成了 AI 功能，允许用户通过自然语言描述生成代码，提升用户体验和操作效率。项目特别适合企业内部培训和个人影响力扩展，显著优化了 SEO 效果。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/191a5ab4a5690b8669105c59bca9562c1ddffe451ce4fd24956ccd1cd9459758.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-pumpkin" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 最佳宠物奖：Pumpkin</h3><p>Pumpkin 是一个会说话的虚拟猫，由喵喵队开发。项目灵感来源于让猫咪能够与人互动，通过 AI 技术实现自然对话。技术上使用了 OpenAI 的 API 和 Google Cloud 的语音转文字及文字转语音功能。项目流程包括捕捉用户语音转换为文本，利用 AI 生成对话，再将文本转换为猫咪的声音。动画设计展示不同状态下的猫咪动作，目前 Demo 展示中使用橘色方块代替猫咪动画。Pumpkin 具有个性化设定，能够回答问题并表现出猫咪的特征。未来计划加入更多互动细节，使猫咪更加真实可爱。欢迎大家参与拓展功能，共同完善项目。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1858718123255335f62825f64a09b152c5bbb1c37deb06e8dd7b4c607aa49d27.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 财经奖：股票新闻 AI 笔记</h3><p>项目通过国外 API 获取股票数据，并利用 AI 分析新闻情绪。用户可以在 note 中查看新闻摘要，并与 AI 互动，获取专业分析和建议。尽管 API 稳定性不佳，项目成功展示了实时新闻抓取和情绪分析功能。未来计划包括训练 AI 阅读财报，提升用户理解。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a08fa12f20fe84162d7662eec5953c3c78faffb475e1f175c4576f868da956d8.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-ui-zynka" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 最佳 UI 奖： Zynka 心格系统</h3><p>Joey 开发的 Zynka，旨在帮助用户记录和管理灵光一现的想法和灵感。通过使用 AI 和其他工具，用户可以在日常生活中随时记录灵感，或将有趣的观点从视频和播客中保存到应用中。未来计划加入 AI 功能，帮助总结和分析灵感，并探索项目驱动的方式，将灵感转化为实际行动。应用界面简洁美观，交互流畅，致力于成为用户的数字化知识库和创意推动工具。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/25b543d9a61ae9a45246973071d74e5cd730200304ead62a922abd22a5dfc983.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 美食奖：卡路里助手</h3><p>Marcus 开发的食物卡路里识别助手，通过拍照识别食物并计算卡路里摄入量，帮助用户管理饮食和体重。目前 MVP 的版本支持图片上传、食物识别、卡路里计算和健康食物推荐。未来计划包括更精准的菜品识别、营养素分析和实时识别功能。项目利用 AI 辅助撰写文档和竞品分析，适合健身和减肥人士，显著提升饮食管理效率。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/033ace8b7dc73935f1aba1ceed0ce6637eb8e271ee4cb02c31a5ebca9b70a3b0.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 乡建成长奖：成长社区</h3><p>Libroo 团队开发的成长社区活动平台，旨在提高乡村儿童参与自然和美育教育活动的积极性，并管理志愿者活动。项目提供儿童身份和成就系统，通过积分兑换虚拟形象装备，并计划结合 MetaMask 进行身份验证。用户可查看和管理活动，参与者可通过平台获得活动徽章（NFT）。尽管团队的成员们的开发经验有限，但利用 AI 工具成功创建了功能性界面，并计划通过智能合约和 API 实现数据上链。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/df6a59f6ac28071ea8d65179df253161cfbe8d1aa9a9a054b8ace9264641e160.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 理财奖：加密日报助手</h3><p>黑曼巴团队开发的加密日报助手旨在通过抓取中英文财经新闻和市场动态，生成每日投资日报，提高信息获取效率。项目解决了用户在社交媒体上被算法干扰的问题。1.0 版本主要连接国内新闻 API，提供基本框架；计划的 2.0 版本将根据用户持仓匹配新闻，并提供 AI 投资建议和事件驱动预警。尽管开发成本较高，团队希望通过合作进一步完善系统，帮助用户在投资决策中获取多元视角。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/66a7353a0322cada003cc5c408abe963d7933325458495259a6439515586ea36.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-matchp" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 赛事组织奖：MatchP</h3><p>MatchProtocol 项目由 Nobody 战队开发，旨在通过区块链技术简化和优化大型赛事的组织与管理。该协议支持评分类、竞技类和拉票类赛事，解决传统赛事中的不公平和资源匹配效率低等问题。通过公开透明的规则和去中心化的资源匹配，MP 提供了一种创新的赛事管理方式。项目还集成了 AI 小助手，用于提升赛事信息获取效率，并引入代币系统用于评分和互动。MP 目标是形成一个风险共担、利益共享的去中心化赛事社区。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7bd7966488975b82dddba17c0318524ed73d1d1932f71bce9eed4c9176d0f3b5.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 沉浸式英语学习奖：拍照单词</h3><p>吴彦组团队的李大猫开发了 AI 单词卡片生成器，通过拍照识别日常物品，生成对应的英文单词卡片。项目采用小模型进行图像识别和词汇生成，支持本地推理，无需依赖大语言模型。虽然目前尚未实现移动端的完全本地化，但 PC 端已能运行。此工具旨在帮助用户更便捷地学习英语，尤其在旅行中遇到不熟悉的物品时，可快速识别并学习相关词汇。项目在短时间内完成，未来计划优化移动端兼容性。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c85bc7824ce25e0d5e8974ac03f03f4cc1b9a19b9983079534a0e4857b9b00b9.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-kitship" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 最佳关系维护奖：KitShip</h3><p>KitShip 是一个关系管理应用，帮助用户维护亲密关系。通过简单的 UI 设计和交互，用户可以添加联系人、记录共同事件、设置约定和管理合照。项目由高三学生散修使用 WinSurf 开发，利用 DeepSeek 提供的配色建议。项目名称 KitShip 来源于“Keep In Touch”和“关系（Relationship）”的缩写，结合关系状态的概念。用户可以自定义联系人信息，并通过 app 记录和追踪关系发展。开发过程中，尽管遇到许多技术挑战并且没有经验，散修使用并且分享了一些有效策略，成功实现了项目功能。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a8f0bb76c830e476510ccf818cdcf8b3f3d69b86eaa15c562c467809578b1af3.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 猪脚饭奖 ：良心点评</h3><p>在一次意外的食物中毒事件中，汤圆获得启发并做出了良心点评，项目希望解决大众点评中的虚假好评问题，通过真实用户评价回归良心文化。用户可以选择仅与好友共享评价，避免流量驱动的虚假信息。项目引入 Meme 系统，用于反映商家价值，类似于股票。结合 Notion，用户可定制化分享食物体验，增强评论的故事性。评价仅限朋友间分享，确保信息可靠。数据存储在本地，保障个人数据安全。项目在短时间内开发完成，未来计划探索更多结合 MemeCoin 机制。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/81784dbfdc1bf8b6ee9e34f932feb9dfb5dc7a8cf15f2c159e43ccdf9e8e5a54.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-nice-moment" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 超棒规划奖：Nice Moment</h3><p>Nice Moment 是由 NPC 团队开发的个人时间管理工具，旨在整合时间、任务、日记与资源，帮助用户更好地理解和管理生活。工具通过周历视图管理任务，自动生成习惯记录，并区分计划与实际记录，以便用户了解每日活动。项目目前已有初步 Demo，未来计划优化界面和功能。夏洛特在项目中加深了对设计与开发关系的理解，并希望进一步学习前端开发知识。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b40bdf8e022d464faebcc02d9c477a7743964f2d3024a4f057d059a1f735906d.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-ticker-semon" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 高阶炒股奖：Ticker Semon</h3><p>开发的 Ticker 基金网格交易助手，旨在帮助没有投资经验的用户通过网格交易策略进行基金投资。该工具可以设置买入和卖出的提醒条件，根据基准价和当前估值的涨跌幅，给出买入或卖出建议。用户可以查看历史操作记录和收益分析。项目目前为 Demo 版本，计划未来移植到微信小程序，并结合提醒功能，通过微信推送交易建议。开发过程中使用了 Manus 工具进行快速原型制作。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e3b586d7e38386e59b1ff7fc9ff1d2caf85927427640de4b4d3c5908f3c10c5b.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 极客操作奖： AI 四象限任务管理</h3><p>铁柱同学受到此次黑客松气氛的感染，在工作之余从自身的需求出发，开发了一套任务管理工具，通过命令行实现任务管理与 AI 对接，简化任务创建和状态更新。用户可在终端添加任务、查看任务列表，并利用 AI 进行复杂任务拆解和优先级调整。工具已打包为状态栏应用，适合开发人员使用，未来计划包括智能任务流转和任务提醒功能。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6cd8012054830adc8585a2a294d9cea17214b3aa1938090d8cce17014d068896.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 纪录生活奖：时链</h3><p>Chen 和团队成员们开发的时链是一款帮助用户记录日常事件并自动分类的工具。用户可以通过简单的语句记录生活事件，AI 将其归类到习惯、情绪、学习等类别，并为用户提供长期复盘功能。目前实现了一个网页 Demo，支持基本的记录和分类功能。尽管开发过程中遇到了多次环境搭建和技术实现的困难（如插件系统、网络问题等），团队最终通过调整方案完成了网页版本的展示。未来计划包括支持更复杂的分类、多端适配，以及结合用户个性化数据（如星座或性格特质）提供更深入的反馈，帮助用户更好地了解自己并优化生活和工作效率。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b285c7197e570ef909dd935615d30f9674988e790589d614bf3b31833a7b6aa4.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">本期赛后感悟</h2><h3 id="h-or" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">汤圆 | 良心点评开发者</h3><p>lxdao 的休闲黑客松是我找到的最适合新人的黑客松了!无需找到队友，无需技术背景，无需熬夜写代码，只要一个需求，一个想法，和百分百的激情! — — 这就是最良心的黑客松!</p><h3 id="h-jp3000-or-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Jp3000 | 股票新闻 AI 笔记开发者</h3><p>很高兴参加本次由 lxdao 举办的休闲黑客松第二期，这也是我第一次参加黑客松这种活动，我感到非常的激动也很紧张，这些可能都来自我i人的本性，想把事情做好，不求喝彩，但求成功混过。</p><p>不过 lxdao 举办的这次休闲黑客松，还是让我一个新手感到惊喜，最开始想象中都是各种技术大牛，大家旁征博引，然后说一些没听过的技术名词，可能都需要找资料的那种，不过从一步一步的交流中，体验中，发现大家也是普通人，一群有童心的人，一群可爱的人，更像是一群有相同爱好但刚聚在一起的朋友，从玩相识，休闲黑客松做到了真正的没有门槛，就是一起玩，不管你提出的是先进的前沿科技还是个人的天马行空，都不会有嘲笑，有的都是帮助，和试试看的鼓励。</p><p>虽然是第一次参加这种活动，我一个i人也从中体验到，交流也不是那么困难，就是很随性，有点百无禁忌的感觉，</p><p>你知道我最大的遗憾是什么吗？居然是没有提出更大的白日梦，没有使用更多的想象力。</p><p>如何有下次，我想告诉那些可能对自己不自信的朋友，多一点尝试的勇气，玩起来，玩的的尽兴，无所谓高手，低手，这里都是朋友，期待下一次相聚。</p><h3 id="h-or-kitship" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">散修 | KitShip 开发者</h3><p>“这次黑客松我尝试用 Windsurf 配合 Deepseek 进行产品搭建，一直想做一个关系管理的 App 使与朋友保持联系更有规律：一开始让 DS 产出 coding prompts，但我很快发现这样好像行不通，R1 会给出很多自己关于项目的逻辑和提案，Windsurf 应付不来，后来自己一步步告知 ws 产品需求后才逐渐成型，bug 和环境问题减少很多；整个黑客松过程我觉得是非常 Chill 的，确实很休闲，中途也有很多小伙伴加入进来，大家在一两天时间做出来的产品都很有趣，让人很想体验。我认为 AIcodingHackathon 即是放大了参赛者需求挖掘，产品概念设计的体验和思考，有值得发展的Idea在头脑中冒出来是很令人享受的事。“休闲黑客松”不只是宣扬产品理念的重要性，更是讲一种松弛和创造平衡的生活状态。”</p><h3 id="h-charnice-moment" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Char丨Nice Moment</h3><p>开发者“在休闲黑客松开始之前，一直想要拥有一个更好的个人工具，但想法在笔记里停留了很久，总觉得困难，迟迟没有开始。感谢这个契机让我尝试花两三天真的“开始去做”，并且收获颇多。一方面感受到了 vibe coding 的魅力，让非开发背景的我也能实现自己的想法，把想法实现的过程真的很美妙；另一方面，也开始更多地思考设计与开发的关系，也许在设计中需要有意识地前置一些开发的规则；同时，这种低成本的开发、调试过程，也让在测试中不断打磨产品想法成为可能。总之，learning by doing，先做了再说！”</p><p>从创意原型到可持续价值的跨越 在本期黑客松中，参赛团队在 48 小时的极限开发中，不仅展现了惊人的技术执行力，更通过巧妙的场景设计，让每个项目都具备了从原型走向真实应用的潜力。这些作品既是对前沿技术的探索，也是对用户需求的深度回应，在去中心化协作、社交娱乐、工具效率等领域开辟了新的可能性。</p><p>例如，佐爷开发的 YBSBarker，通过可视化的策略配置和用户生成的策略市场，为 DeFi 用户提供了便捷的资产管理方式，未来有望成为小白用户进入 DeFi 世界的理财助手；超级飞侠战队的“Wownavs”，通过将书签快速生成 SEO 优化的网页，结合 AI 生成的自定义代码，为内容管理和分享提供了新思路，让个人知识管理更加高效。同时，残酷共学 Bot 项目通过自动化的学习统计和 AI 分析，为接下来的残酷共学项目提供了高效的运营支持，不仅帮助管理员降低管理成本，也为学习者创造了更加智能化的学习体验；像 KitShip 通过记录亲密关系中的重要事件和互动日志，帮助用户更好地维护人际关系，尤其是异地恋情的管理；良心量表项目则通过开源的心理测评问卷和 AI 分析，提供了免费的心理健康评估工具，为用户带来了更贴心的心理健康服务；而 Pumpkin 则以 AI 驱动的电子宠物为核心，为用户带来了情感陪伴与趣味互动。</p><p>这些项目看似是“周末创意”，但背后隐藏着对垂直场景的深刻洞察。无论是帮助用户管理资产、学习进度，还是维护人际关系，它们都在用技术重新定义效率与温度的平衡。我们期待这些原型能快速迭代，真正融入用户的生活与工作场景，延续黑客松“灵感落地”的精神。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语致谢</h2><p>随着 LXDAO Casual Hackathon 第二期休闲黑客松的圆满落幕，我们收获的不仅是令人瞩目的创意成果，更是对黑客松初心的深刻回归与重塑。参赛者们借助 AI 开发工具，将创意化为现实，将想法落地为产品，每一个项目都无一不彰显出探索与创造的力量。</p><p>在此，我们特别感谢本次活动的品牌赞助方 Onekey 对活动的大力支持。Onekey 为完成度较高的项目提供了硬件钱包作为奖品，不仅为参赛团队提供了实际的激励，也为活动的成功举办增添了更多的意义和价值。感谢 Onekey 的慷慨支持，让技术与创意的碰撞有了更广阔的舞台。</p><p>未来，LXDAO 将继续秉承初心，重现黑客松最纯粹的魅力，让黑客松的精神在技术的星河中熠熠生辉。期待在下一场 LXDAO 休闲黑客松中，与各位共同见证技术与创意的奇妙碰撞，开启新的探索之旅！</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">合作支持</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">主办方</h3><p>LXDAO 是一个专注研发的 DAO 组织，致力于探索构建支持有价值的公共物品和开源项目的无限循环。</p><h3 id="h-onekey" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">特别支持 Onekey</h3><p>感谢 Onekey 钱包的赞助，收到黑客松奖励的小伙伴也特别激动，希望 LXDAO 休闲黑客松越来越好。</p><p>内容 | Ache</p><p>编辑 &amp; 排版 | Ache、环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/d1ce98fd8793290c445d7db2d0e0270b78325c8f4978f3eb0539501da4c8193e.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LXDAO 研究员征集令：探索和共创]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-15</link>
            <guid>gSPl8tVwhbJrtGy6MFfd</guid>
            <pubDate>Fri, 02 May 2025 06:37:01 GMT</pubDate>
            <description><![CDATA[LXDAO 研究工作小组（Research WG）诚挚邀请所有对和共同探索 Web3 领域的前沿议题，推动行业理论与实践的发展。研究方向我们鼓励任何和开源项目和技术的长期可持续性发展、公共治理及协作支持等问题相关的研究提案，包括但决不限于以下研究方向及提案示例：DAO 治理及协作研究协调机制研究（Coordination Research）目标：优化 DAO 协作模式当我们在聊 Coordination 的时候，我们到底在聊什么？治理投票模式比较研究目标：比较分析主流 DAO 投票机制并提出优化方案社会工程风险研究目标：开源项目治理攻击防护方案技术研究Geth 源码研究已发起 Pod：LXDAO New Pod: 深入以太坊核心：Geth 源码研究项目启动！EIP 研究重点方向：Pectra升级 (EIP-7251/3074)跨链互操作性研究参考：CCIP vs LayerZero 技术对比AI + Web3 应用研究参考：MCP 实验ZK 技术研究参考：zkEVM 性能比较开源项目激励及可持续发展模式研究开源项目盈利模式及可持续性研究目标：开源项目孵化方法论、激励机制、融资/贡...]]></description>
            <content:encoded><![CDATA[<p><strong>LXDAO 研究工作小组（Research WG）诚挚邀请所有对和共同探索 Web3 领域的前沿议题，推动行业理论与实践的发展。</strong></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>研究方向</strong></h2><p>我们鼓励任何和开源项目和技术的长期可持续性发展、公共治理及协作支持等问题相关的研究提案，<strong>包括但决不限于以下研究方向及提案示例</strong>：<strong>DAO 治理及协作研究</strong></p><ul><li><p>协调机制研究（Coordination Research）</p><ul><li><p>目标：优化 DAO 协作模式</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mp.weixin.qq.com/s?__biz=MzI2NzExNTczMw==&amp;mid=2653294834&amp;idx=1&amp;sn=922e3b8f1633e96a6d2720891bcb6c63&amp;scene=21#wechat_redirect">当我们在聊 Coordination 的时候，我们到底在聊什么？</a></p></li></ul></li><li><p>治理投票模式比较研究</p><ul><li><p>目标：比较分析主流 DAO 投票机制并提出优化方案</p></li></ul></li><li><p>社会工程风险研究</p><ul><li><p>目标：开源项目治理攻击防护方案</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>技术研究</strong></h3><ul><li><p>Geth 源码研究</p><ul><li><p>已发起 Pod：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mp.weixin.qq.com/s?__biz=MzI2NzExNTczMw==&amp;mid=2653295865&amp;idx=2&amp;sn=c4b482a23ba8025e1fafdc8d2df5c7b9&amp;scene=21#wechat_redirect">LXDAO New Pod: 深入以太坊核心：Geth 源码研究项目启动！</a></p></li></ul></li><li><p>EIP 研究</p><ul><li><p>重点方向：Pectra升级 (EIP-7251/3074)</p></li></ul></li><li><p>跨链互操作性研究</p><ul><li><p>参考：CCIP vs LayerZero 技术对比</p></li></ul></li><li><p>AI + Web3 应用研究</p><ul><li><p>参考：MCP 实验</p></li></ul></li><li><p>ZK 技术研究</p><ul><li><p>参考：zkEVM 性能比较</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>开源项目激励及可持续发展模式研究</strong></h3><ul><li><p>开源项目盈利模式及可持续性研究</p><ul><li><p>目标：开源项目孵化方法论、激励机制、融资/贡献/分配方案</p></li><li><p>参考：不同的激励模式比较研究，如 Quadratic Funding, DeepFunding,  Retro PGF， Gitcoin 效果研究</p></li></ul></li><li><p>Web2 → Web3 开源开发者的迁移路径研究</p><ul><li><p>参考：开发者转化路径指南</p></li></ul></li><li><p>Web3 生态热点研究</p><ul><li><p>EVM 及 L2 生态研究</p></li><li><p>不同链应用生态、技术比较研究</p></li></ul></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>如何参与</strong></h3><p>我们鼓励社区成员参与或发起新的研究方向，LXDAO 将尽力提供所需的技术和资金支持。我们将以 Pod 小组形式提供前期的研究资金支持，并在未来根据研究的价值和影响力给予社区的回溯性激励。如果您对我们已开启的研究方向感兴趣，或者有自己感兴趣的研究议题，欢迎点击下列研究小组成员招募问卷链接报名参加！也可以点击文末阅读原文填写报名问卷🔗</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tally.so/r/mOrBLM">https://tally.so/r/mOrBLM</a></p><p>如有任何疑问，欢迎直接联系我们，说明您感兴趣的研究方向和您可以贡献的专业知识。</p><p>📫 联系 LXDAO 研究工作小组：Ye  (TG @wyeeeh)  白丁（TG @HYbaidiing）</p><p>📌 <strong>了解更多</strong>：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://forum.lxdao.io/c/research/">https://forum.lxdao.io/c/research/</a></p><p><strong>注：本招募长期有效，欢迎随时提交您的研究兴趣或提案。我们将定期审核并支持有价值的研究项目。</strong></p><p>内容 | Ye</p><p>编辑 &amp; 排版 | 环环</p><p>设计 | Daisy</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Futarchy：当预测市场成为治理武器，一场颠覆 DAO 决策范式的治理实验]]></title>
            <link>https://paragraph.com/@lxdao/futarchy-dao</link>
            <guid>nFro7ZKFGKtTpg43ZA8W</guid>
            <pubDate>Fri, 25 Apr 2025 05:58:27 GMT</pubDate>
            <description><![CDATA[2025 年 3 月，Optimism 发起了一场具有里程碑意义的链上治理实验。通过 Futarchy 机制分配 500,000 枚 OP 代币激励，这场为期 21 天的治理实验不仅检验了预测市场在公链生态治理中的可行性，更揭示了去中心化决策机制进化的复杂张力。Futarchy 治理实验Optimism 在三月份推出了一项很新颖的 Futarchy 治理实验，Futarchy 的字面翻译是预测实验，在区块链中，Futarchy 是一种通过预测市场指导决策的治理模式，利用金融市场的预测能力和参与者的真实货币投入，激励更准确的预测和分析。在本次实验中，Optimism 用 Futarchy 的方式来分配共 500k OP（100k * 5）的激励，以探索公链方激励生态发展的激励发放新模式，实验的大部分进度已经完成，LXDAO 成员 Loxia 作为实验的参与者之一，对该治理方式的未来表示谨慎乐观。 MetaDAO 提出的 Futarchy 简单来说就是当有人提出治理目的（如”空投代币激励用户”），Futarchy 会定义”通过”与”否决”两个条件代币市场。参与者需抵押真实资产换取对应...]]></description>
            <content:encoded><![CDATA[<p>2025 年 3 月，Optimism 发起了一场具有里程碑意义的链上治理实验。通过 Futarchy 机制分配 500,000 枚 OP 代币激励，这场为期 21 天的治理实验不仅检验了预测市场在公链生态治理中的可行性，更揭示了去中心化决策机制进化的复杂张力。</p><h2 id="h-futarchy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Futarchy 治理实验</h2><p>Optimism 在三月份推出了一项很新颖的 Futarchy 治理实验，Futarchy 的字面翻译是预测实验，在区块链中，Futarchy 是一种通过预测市场指导决策的治理模式，利用金融市场的预测能力和参与者的真实货币投入，激励更准确的预测和分析。在本次实验中，Optimism 用 Futarchy 的方式来分配共 500k OP（100k * 5）的激励，以探索公链方激励生态发展的激励发放新模式，实验的大部分进度已经完成，LXDAO 成员 Loxia 作为实验的参与者之一，对该治理方式的未来表示谨慎乐观。</p><p>MetaDAO 提出的 Futarchy 简单来说就是当有人提出治理目的（如”空投代币激励用户”），Futarchy 会定义”通过”与”否决”两个条件代币市场。参与者需抵押真实资产换取对应代币进行交易 — — 若看好提案将推高代币价格，就买入”通过”市场代币；反之则押注”否决”市场。最终通过比较两个市场的加权平均价格决定提案命运，同时参与者可赎回抵押资产，但决策结果直接影响其持币价值。这种设计巧妙地将个人利益与集体目标绑定：</p><p>想获利就必须深入研究提案对组织代币价格的长期影响，而非凭直觉或跟风投票。MetaDAO 的实践显示，即便恶意提案者试图操纵市场，也会因需要高价收购”通过”代币而得不偿失。MetaDAO 认为当每个决策都经过真金白银的博弈淬炼，集体智慧才有机会战胜人性弱点。</p><h2 id="h-futarchy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Futarchy 的由来</h2><p>Futarchy 是一种由经济学家 Robin Hanson 提出的政府形式。在这种治理模式下，由民选官员来界定国家福祉的衡量标准，而预测市场则被用来决定哪些政策会带来最积极的影响。《纽约时报》在 2008 年将“Futarchy”列为一个流行词。后来，这个概念也被引入区块链和 DAO 的讨论之中。</p><p>Futarchy 的宣传口号是：</p><p>“在价值上投票，在信念上下注”（vote on values, bet on beliefs）。这句话的意思是：</p><p>公民应该用民主程序来表达“我们想要什么”（即“价值”）。</p><p>然后用预测市场来决定“什么政策最有可能实现这些目标”（即“信念” — — 对因果关系的判断）。</p><p>经济学家 Tyler Cowen 表示：“我不会看好 Futarchy 的未来，或者它一旦被实施后能否成功。罗宾说，‘在价值上投票，在信念上下注’，但我认为价值与信念并不能如此轻易地被分开。”</p><p>Cowen 认为人类的价值观和信念是高度交织的，很难将“目标”与“实现目标的方式”彻底分开。例如，一个人可能声称自己追求社会平等（价值），但他对某些政策（信念）的支持，实际上是出于意识形态偏好，而非对政策效果的理性预测。</p><p>换句话说，预测市场无法完全屏蔽人类情感、认知偏差和价值导向的干扰，因此 Futarchy 的运作机制可能无法实现其理论上的理性与高效。</p><h2 id="h-futarchy-for-optimism" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Futarchy for Optimism</h2><p>Futarchy 治理实验的设计者认为：</p><ul><li><p>当决策者因其准确性受到奖惩时（准确 → 奖励，不准确 → 惩罚），他们会倾向于做出更深思熟虑、非偏见的决策； 同时，一个无需许可的 futarchy 模式可以吸引更多人参与（群众智慧），而不是局限于中心化的决策机构。</p></li><li><p>同时为了使实验更加开放，也为了获得更多数据测试实验，实验方开放了参与权限，任何拥有 telegram 账户或 Farcaster 账户的人都可以参与，所有的预测者都会获得 50 OP-PLAY 的入场筹码（是 OP-PLAY，代币不具备实际价值，是仅供实验用的假筹码），而 OP 治理的实际参与者会获得更多的 OP-PLAY 筹码。</p></li></ul><p>那么这一轮 Futarchy 围绕的预测问题是什么呢？</p><p>假如某个项目拿到 100k OP 激励，哪个/些协议将在三个月之后获得最大的 TVL 增长。</p><p>此次参与 Futarchy 的项目有 23 个，每个参与实验的人需要预测这 23 个项目在”拿到 100k OP 激励“之后的 TVL 增量，在实验开始之时，所有项目的初始预测 TVL 都是一样的（同一个起跑线，作为参考，在测试实验的项目选择中），随着时间进行，用户将 OP-PLAY 抵押，通过对不同的项目买看涨期权（UP token）和看跌期权（DOWN token）来展开博弈，预测结果最高的五个项目每个项目获得 100k OP 的激励。</p><p>在实验结束后，参与者 通过 OP-PLAY 参与预测市场选出了五个项目，作为对比，Grants Council 也选出自己的五个受资助项目：</p><p>在 21 天的涨跌博弈中，通过 Futarchy 选出的前五个 100K OP 资助项目：</p><ul><li><p>Rocket Pool: $59.4M</p></li><li><p>SuperForm: $48.5M</p></li><li><p>Balancer &amp; Beets: $47.9M</p></li><li><p>Avantis: $44.3M</p></li><li><p>Polynomial: $41.2M</p></li></ul><p>与此同时 Grants Council 选出的五个受资助项目（如有重叠只发一次）：</p><ul><li><p>Extra</p></li><li><p>Finance</p></li><li><p>Gyroscope</p></li><li><p>Reservoir</p></li><li><p>QiDAO</p></li><li><p>Silo</p></li></ul><h2 id="h-futarchy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Futarchy 模式在治理中的局限</h2><p><strong>本次 TVL 判断指标的局限性</strong>：</p><p>“如果 ETH 的价格上涨，那些锁了很多 ETH 的协议会在 TVL 上看起来增长很大，即使它们什么都没做。” — @joanbp, 3 月 13 日</p><p>“我们似乎是在用 Futarchy 决定谁该获得赠款，但如果 TVL 增长只是反映市场价格变化，那这个指标就不能反映项目是否善用了赠款。” — @joanbp, 3 月 13 日</p><p><strong>预测实验的指标的设立角度也非常重要</strong>：</p><p>“我们应该选择那些 — — 即使参与者想 ‘操纵’ — — 也只能通过做对生态有益的事情才能‘赢’的指标。” — @Sky, 3 月 17 日</p><p>模拟代币带来的偏差（如果真实代币价值不足也会出现偏差）</p><p>“这是‘假钱’，不是‘真钱’。很多人会在最后一刻双边下注，只是为了不亏。”</p><p>— @thefett, 3 月 19 日</p><p>*41% 参与者在末期进行风险对冲（双边下注避免亏损）</p><p>“我感觉我并没有带来什么特别的见解，反而是稀释了那些真正懂项目的人的影响力。”</p><p>— @Milo, 3 月 20 日</p><p><strong>用户体验并不佳，且影响了博弈有效性</strong>：</p><p>预测市场的成功与否很大程度取决于用户参与深度。但本次实验体验门槛偏高，信息不透明，操作繁琐，极大影响了参与者的判断力与参与度。</p><p>用户普遍反馈的问题包括：</p><ul><li><p>不知道总共有多少代币。</p></li><li><p>单次下注要 6 次链上交互。（因此我并没有在这次实验中做过几次交易，界面过于复杂）</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9bcc1bfeb583558976863b06f7ae591ed2530c77e43323bf9183619f611e4c37.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p>押错项目是否亏损解释不清。</p></li><li><p>排行榜盈亏逻辑无法理解。</p></li></ul><p>“我一开始以为 PLAY 是用掉的，结果每个项目都重置，搞不懂我总共花了多少。” — @Milo, 3 月 20 日</p><p>“一个预测要签六个交易，有点太过了。” — @Milo, 3 月 20 日</p><p>“排行榜我看不懂，有时候感觉我应该是盈利的，结果显示亏 46%。” — @joanbp, 3 月 19 日</p><p>在 Butter 官方出具的数据报告中显示，本次实验：</p><ul><li><p>总交易量 5,898 笔，但 41% 地址在最后三天才参与，显示用户学习成本过高。</p></li><li><p>单次预测需 6 次链上交互（见界面截图），导致平均每人仅交易 13.6 次。</p></li><li><p>尽管有 2,262 名访问者，但转化率仅 19%，OP 治理贡献者参与率仅 13.48%</p></li><li><p>45% 项目未向预测者披露计划，信息不对称导致预测偏差（如 Balancer 预测值超项目自估 $26.4M）</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><h3 id="h-futarchy" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">博弈指标的设立会对 Futarchy 实验产生决定性的影响</h3><p>好的指标应该具有：</p><ul><li><p>可度量性：数据清晰、容易验证；</p></li><li><p>方向正确：能引导参与者去做“就算为了赢钱也在推动系统正向发展”的事；</p></li><li><p>不易游戏化：难以被单纯的金融技巧或价格波动“做大做强”。</p></li></ul><p>比如在本次 Futarchy 实验中，以美元计的 TVL 极易被 ETH 等主流币价波动影响，使得预测结果更像在“赌币价”，而非评估谁真正有增长能力。</p><p>Butter 出具的官方报告显示，截至 2025年4月9 日的中期 TVL 数据已暴露指标局限性：</p><ul><li><p>Rocket Pool (预测 TVL 增长 59.4M) 实际 TVL 增长为 59.4M，实际 TVL 增长为 0</p></li><li><p>SuperForm (预测 48.5M) 实际下跌1.2M</p></li><li><p>Balancer&amp; Beets(预测 47.9M) 实际下跌 13.7M</p></li></ul><p>所有 Futarchy 选中项目的实际 TVL 总跌幅达 $15.8M，而同期 Grants Council 选中项目中:</p><ul><li><p>Extra Finance (预测 39.7M) 实际增长 8M</p></li><li><p>QiDAO(预测 26.9M)实际增长 10M 这验证了社区质疑 — — TVL 指标与市场价格强相关，未能有效反映项目真实运营能力。</p></li></ul><h3 id="h-futarchy" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Futarchy 的“最佳预测员”结果不完全客观</h3><ul><li><p>在此次实验中更多反映参与者的 OP-PLAY 交易能力，而不是”预测能力“的评选，因为在本次实验中，所有标的均有较大幅度的日级别涨跌，参与者有相当大的操作空间（匿名账户 @joanbp 通过高频交易（406 笔/3 天）登顶）</p></li><li><p>在最后的 OP-PLAY 交易胜率排行榜中，作为公认的 OP 生态专业人士，Badge Holders 的分组胜率是最低的。</p></li><li><p>前 20 名预测者中仅 4 人持有 OP 治理身份（skydao.eth/alexsotodigital.eth等）</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">预测影响决策的悖论：</h3><p>Futarchy 的特性在于预测即决策，集体预期会直接影响结果（比如本实验中哪个项目得到赠款）。这和一般预测市场纯粹预测外部事件不同，产生了一些独特的动力挑战。正如 OP 论坛中讨论的，一个投票人在 Futarchy 中有两种取向：</p><p>其一，随大流押中热门项目以确保这些项目获赠款（自己预测正确但未必有高回报，因为多数人都这么押）；</p><p>其二，标新立异选被低估的项目，如果后来证明少数派是对的则个人收益最大。这种兼具投票和投注双重属性的机制让参与者有点无所适从。同时，当预测本身塑造了未来（因为资金流向会影响项目发展），Futarchy存在一定<strong>自我实现或自我挫败</strong>的循环：大家都压某项目好，资源给了它，它自然更有机会成功；反之不被看好的即便本可成功也因得不到资源而失败。这种闭环使得 Futarchy 实验需要谨慎解读其预测准确性，并在设计上考虑如何缓解这种自证循环的偏差。</p><p>在这场 Futarchy 实验中，我们不仅看到了治理机制如何被“博弈化”，也看清了 Degen 在预测市场中的潜力 — — 他们不再只是逐利的过客，而是潜在的专业治理者。只有当制度设计能将 Degen 的能量锚定于公共目标，让投机成为共建，让押注成为判断，Futarchy 才有机会激活属于 Web3 的再生式治理精神（Regen）。本次实验唤醒了一种可能：治理不必是清教徒式的理性协商，也可以是深度游戏化的共识形成。觉醒 Degen 的 Regen 血脉，也许正是未来 DAO 治理的进化方向。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">引用</h2><p>[1] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Futarchy">https://en.wikipedia.org/wiki/Futarchy</a></p><p>[2] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gov.optimism.io/t/experimenting-with-futarchy-for-optimism-grant-allocation-decisions/9678">https://gov.optimism.io/t/experimenting-with-futarchy-for-optimism-grant-allocation-decisions/9678</a></p><p>[3] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ggresear.ch/t/futarchy-vs-grants-council-optimisms-futarchy-experiment/57">https://ggresear.ch/t/futarchy-vs-grants-council-optimisms-futarchy-experiment/57</a></p><p>[4] <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/@netrovert/futarchy-redefining-dao-governance-5f554d523dee">https://medium.com/@netrovert/futarchy-redefining-dao-governance-5f554d523dee</a></p><p>内容 | Loxia</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/362c344b8fe69bdffcca3915466a5d998711815d07123099b76bec63f19c67a6.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LXDAO Pod 指南：让 Pod 更好的建设开源产品可持续性]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-pod-pod</link>
            <guid>GoTa05tjAspl0Vt3NYX7</guid>
            <pubDate>Wed, 23 Apr 2025 07:52:47 GMT</pubDate>
            <description><![CDATA[我们希望通过 LXDAO Pod，更好的探索开源产品目前遇见的问题，以及是如何去更好的解决这些问题的为什么需要这份指南？我们发现许多社区成员对 Pod 提案流程不够清晰，部分提案与 LXDAO 核心方向偏离。本指南旨在：明确 LXDAO 的战略方向与 Pod 范围提供标准化提案框架，提升提案质量简化评估流程，加速创意落地LXDAO 战略聚焦方向在过去的 Pod 申请中，我们发现社区提出 Pod 的内容和方向并不明确，我们提出了以下几种方向，便于大家更好的梳理 Pod 内容 所有 Pod 提案建议围绕以下核心主线展开：2.1 治理自动化与协作优化目标：减少治理内耗，实现「自动化协作」 提案方向举例：治理工具开发（如提案分析面板、自动化 SOP 工具）DAO 协作机制研究（如匿名协作、激励分配模型）社区入口优化（Onboarding 流程自动化）治理研究（如治理协调，治理数据分析，治理去中心化）2.2 公共物品研究目标：探索 Web3 公共物品可持续模式 提案方向举例：《Web3 公共物品报告》（技术/资金/治理维度）Web2→Web3 开源开发者迁移路径研究开源产品项目孵化方法论开...]]></description>
            <content:encoded><![CDATA[<p><strong>我们希望通过 LXDAO Pod，更好的探索开源产品目前遇见的问题，以及是如何去更好的解决这些问题的</strong></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么需要这份指南？</h2><p>我们发现许多社区成员对 Pod 提案流程不够清晰，部分提案与 LXDAO 核心方向偏离。本指南旨在：</p><ul><li><p>明确 LXDAO 的战略方向与 Pod 范围</p></li><li><p>提供标准化提案框架，提升提案质量</p></li><li><p>简化评估流程，加速创意落地</p></li></ul><h2 id="h-lxdao" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">LXDAO 战略聚焦方向</h2><p>在过去的 Pod 申请中，我们发现社区提出 Pod 的内容和方向并不明确，我们提出了以下几种方向，便于大家更好的梳理 Pod 内容</p><p>所有 Pod 提案建议围绕以下核心主线展开：</p><h3 id="h-21" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.1 治理自动化与协作优化</h3><p>目标：减少治理内耗，实现「自动化协作」</p><p>提案方向举例：</p><ul><li><p>治理工具开发（如提案分析面板、自动化 SOP 工具）</p></li><li><p>DAO 协作机制研究（如匿名协作、激励分配模型）</p></li><li><p>社区入口优化（Onboarding 流程自动化）</p></li><li><p>治理研究（如治理协调，治理数据分析，治理去中心化）</p></li></ul><h3 id="h-22" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.2 公共物品研究</h3><p>目标：探索 Web3 公共物品可持续模式</p><p>提案方向举例：</p><ul><li><p>《Web3 公共物品报告》（技术/资金/治理维度）</p></li><li><p>Web2→Web3 开源开发者迁移路径研究</p></li><li><p>开源产品项目孵化方法论</p></li><li><p>开源产品融资/贡献/分配方案分析</p></li></ul><h3 id="h-23-dao" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.3 DAO 工具基建</h3><p>目标：打造 LXDAO 专属协作栈</p><p>提案方向举例：</p><ul><li><p>官网/人才池/Dashboard 开发</p></li><li><p>现有工具迭代（FairSharing/DAICO.fun）</p></li><li><p>轻量级插件开发（如 GitHub 贡献自动追踪）</p></li></ul><h3 id="h-24-lx-buildpath" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2.4 LX BuildPath 生态</h3><p>目标：吸引开发者参与公共物品建设</p><p>关于 LXBuildPath 的整体范围，你可以围绕以下产品做一些迭代，同时可以提出一些优化方向，也可以提出类似的 Pod</p><p>什么是 LXBuildPath: LXBuildPath 是围绕 LXDAO 目前的产品提出的叙事以及可持续性叙事方向，能更好的帮助开源进行可持续性</p><ul><li><p>Intensive Co-learning：为开发者提供学习各种技能的教育平台。</p></li><li><p>WhatToBuild：提供各类创业或项目想法灵感，引导开发者动手实践。</p></li><li><p>Causal Hackathon：快速实现创意与想法的黑客松活动。</p></li><li><p>Open Bounties：任务驱动的贡献模式，让更多人“边做边学边赚”。</p></li><li><p>FairSharing：远程匿名协作，公平分配收益。</p></li><li><p>DAICO.fun：通过分阶段解锁资金（NFT/Token）的方式，灵活支持项目或报告编写等小型任务。</p></li><li><p>SimpleRetroFunding：追溯性奖励，按照实际贡献追溯奖励。</p></li></ul><p><strong>提案方向举例：</strong></p><ul><li><p>课程设计（如 ZK/Governance 101）</p></li><li><p>黑客松主题策划（如「公共物品快速验证」「AI 工具黑客松」）</p></li><li><p>任务包设计（新手友好型任务）</p></li></ul><h2 id="h-pod" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">优秀 Pod 提案的一些标准</h2><p><strong>开源性</strong>：代码/文档必须开源，符合 LXDAO 开放原则</p><p><strong>里程碑驱动</strong>：明确阶段目标（建议按周/月划分）</p><p><strong>可持续潜力</strong>：能发展为长期项目或产生持续影响</p><h2 id="h-pod" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Pod 提案内容（参考）</h2><h3 id="h-pod-web3" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Pod 提案：Web3 开源产品资助模式全景分析报告</h3><p><strong>1. 基础信息</strong></p><ul><li><p><strong>项目名称</strong>：PG-Fund-Map（Public Goods Funding Map）</p></li><li><p><strong>负责人</strong>：@LXDAO_ID | Telegram: @xxx</p></li><li><p><strong>关联主线</strong>：开源产品研究（2.2） + DAO 工具建设（2.3）</p></li><li><p><strong>钱包地址</strong>：0x…（已完成 LXDAO Onboarding）</p></li></ul><p><strong>2. 项目描述</strong></p><p><strong>What</strong>：</p><p>制作对比分析主流 Web3 基金会公共物品资助模式的报告，并开发实时数据看板。</p><p><strong>Why</strong>：</p><p>当前缺乏系统性分析：Gitcoin/OP/Arbitrum等 资助标准、效果差异不透明</p><p>LXDAO 需借鉴最佳实践：优化自身资助策略，吸引优质 Builder</p><p>开源产品领域需要数据基建：为开发者提供申请指南，为 DAO 提供分配参考</p><p><strong>How</strong>：</p><p>数据层：抓取链上+链下资助数据（金额/项目数/领域分布）</p><p>分析层：建立评估框架（通过率/存活率/杠杆效应等指标）</p><p>可视化：Dune 仪表盘+交互式对比工具</p><p><strong>3. 资金申请</strong></p><p>首期申请：450 USDT</p><ul><li><p>数据采集成本：150 USDT（API 调用/数据清洗）</p></li><li><p>分析报告撰写：200 USDT</p></li><li><p>Dune 看板开发：100 USDT</p></li></ul><p>追溯奖励：根据报告传播效果追加（预计上限 300 USDT）</p><p><strong>4. Milestone计划</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d4b682d7776128be1384020c4b1459f5d8305807da2cf2e03b7dc9e0b1fa2466.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>5. 项目需求</strong></p><ul><li><p>数据支持：需要访问 LXDAO 历史资助数据（如有）</p></li><li><p>技术协作：Dune SQL 查询语法顾问</p></li><li><p>传播资源：官网/推特报道支持</p></li></ul><p>内容 | Marcus</p><p>编辑 &amp; 排版 | 环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/d1ce98fd8793290c445d7db2d0e0270b78325c8f4978f3eb0539501da4c8193e.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[【Space 回顾】并行 EVM：以太坊生态的下一个爆发点？]]></title>
            <link>https://paragraph.com/@lxdao/space-evm</link>
            <guid>STB4QuPJgJxrG7kevw5B</guid>
            <pubDate>Tue, 15 Apr 2025 02:30:51 GMT</pubDate>
            <description><![CDATA[本次 Space 由 LXDAO（专注 Web3 公共物品与开源项目的 DAO）和ETHPanda（连接华语建设者与以太坊国际生态的社区）联合主办，围绕并行 EVM 技术展开深度讨论。 https://x.com/LXDAO_Official/status/1907429832054702481?sessionid=嘉宾阵容Zhixiong Pan：ChainFeeds Co-Founder@nake13Box: Monad 亚太区 DevRel，区块链全栈开发者，高性能链技术倡导者@BoxMrChenWishlonger：Pharos CTO, 打造最快 EVM 兼容 L2（200k TPS），主推 RWA 资产上链@wishlongerPignard：蚂蚁链研究员，公链研发专家，科普博主「小猪 Web3」@pignard_web3Samuel：Reddio Solution Engineer@snjign260588主办方介绍LXDAO 推动有价值的开源项目进入良性，可持续的生态循环。重点领域：开发者教育、开源工具、治理协议及基础设施。 ETHPanda 华语以太坊建设者社区...]]></description>
            <content:encoded><![CDATA[<p><strong>本次 Space 由 LXDAO（专注 Web3 公共物品与开源项目的 DAO）和ETHPanda（连接华语建设者与以太坊国际生态的社区）联合主办，围绕并行 EVM 技术展开深度讨论。</strong></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/LXDAO_Official/status/1907429832054702481?sessionid=">https://x.com/LXDAO_Official/status/1907429832054702481?sessionid=</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>嘉宾阵容</strong></h2><ul><li><p><strong>Zhixiong Pan</strong>：ChainFeeds Co-Founder@nake13</p></li><li><p><strong>Box</strong>: Monad 亚太区 DevRel，区块链全栈开发者，高性能链技术倡导者@BoxMrChen</p></li><li><p><strong>Wishlonger</strong>：Pharos CTO, 打造最快 EVM 兼容 L2（200k TPS），主推 RWA 资产上链@wishlonger</p></li><li><p><strong>Pignard</strong>：蚂蚁链研究员，公链研发专家，科普博主「小猪 Web3」@pignard_web3</p></li><li><p><strong>Samuel</strong>：Reddio Solution Engineer@snjign260588</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>主办方介绍</strong></h2><p><strong>LXDAO</strong></p><p>推动有价值的开源项目进入良性，可持续的生态循环。重点领域：开发者教育、开源工具、治理协议及基础设施。</p><p><strong>ETHPanda</strong></p><p>华语以太坊建设者社区，致力于通过教育和技术创新连接华语开发者与国际以太坊生态。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>内容回顾</strong></h2><p>各位嘉宾老师依次进行了自我介绍，分享了自己在以太坊生态领域的经验及所关注的领域。最后，会议围绕以太坊的并行化问题展开了讨论。潘志雄老师认为，虽 EVM 的标准过时且未针对特定场景设计，但目前并没有更好的标准出现，因此他并不觉得并行化来得太晚。而 BOX 则持不同观点，认为并行化并不急迫，因为以太坊目前的性能已经足够满足需求，且未来可能会有更大的改进空间。</p><p><strong>并行化在以太坊生态中的重要性和时机分析</strong></p><p>在讨论以太坊的并行化策略时，Wishlonger 认为虽然目前进行并行化可能稍晚，但并不影响其重要性。他强调并行化不仅提升了性能，还激发了更多创新应用和交易类型。此外，他提到以太坊生态作为快速领域最大的社区，开发者数量庞大，因此即使现在开始并行化改进，也不会为时过晚。接下来，话题转向并行 EVM 技术的发展，Pignard 指出尽管技术前沿且复杂，但观察外部情况可能更有益。他还比较了 Solana 与以太坊在 C 端市场的竞争态势，并预测并行 EVM 公链将有机会抢占一部分市场份额。最后，Pignard 对 MegaETH 的技术方案表示认可，尤其是其针对以太坊性能不足的具体优化措施。整体而言，会议中嘉宾就以太坊的并行化、并行 EVM 技术和市场竞争状况提供了深入的分析和展望。</p><h3 id="h-monad" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Monad 并行技术分析与未来展望</strong></h3><p>BOX 分享了 Monad 的并行化方案及其特性。他指出，尽管之前有些项目尝试过并行化，但其性能提升并不明显，仅约 2-3 倍。随着研究深入，发现不仅需要并行处理数据，还需要并行数据库等。在 Monad 中，采用了乐观并行和自建数据库架构的方式。然而，BOX 认为这种并行化并非复杂难以理解，而是很基础的技术，如流水线和时间分片等，只是在特定场景下才重要。他还提到，Monad 已采用两种并行方案：执行层并行和共识层与执行层的分离异步执行。最后，盒子老师表示，虽然目前有多种并行化方案被提出，包括 AI 和 GPU 等，但 Monad 可能会选择更多不同的并行层方案，因为这是一种类似于外挂的方案，其护城河不会很深，仅在极端性能情况下可能更有意义。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>探讨技术护城河与并行优化策略</strong></h3><p>在讨论 Monad 系统如何平衡异步执行与去中心化，以及如何保证安全性时，出现了一些误解。一位参与者询问关于异步执行与性能优化的关系，另一位老师则指出并行处理不仅涉及效率问题，还包括了共识层面和硬件支持等多方面因素。这位老师进一步解释了提升性能的多个维度：从提高出块速度、支持更大账户规模到存储层面的并行化。他还提到了将并行级别分为几个等级，包括共识优化、流水线并行处理交易、存储层面的优化等。此外，他强调了实践层面的挑战，如确保数据库稳定性和数据一致性。最后，他分享了未来可能的创新方向，包括利用异构硬件并行、虚拟机并行及开发框架的改进，以提升用户体验和开发效率。</p><h3 id="h-reddio-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>探讨 Reddio 技术与 AI 及区块链的融合潜力</strong></h3><p>Samuel 阐述了 Reddio 的独特之处，如使用 GPU 加速和重写 IBM 操作码等，并分析了与 AI 结合后可能拓展的新玩法和机遇。潘志雄则从金融设施、AI 技术成熟度等方面出发，探讨了将 EVM 、AI 及模块化区块链概念结合可能带来的新场景、机遇与挑战，以及未来产品形态。</p><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>探讨 AI 与区块链结合的新可能</strong></h3><p>潘老师 和 Pignard 分别分享了他们对吞吐量提升和AI与区块链结合的看法。Pignard 认为，尽管技术细节如何改变并不重要，关键在于AI与区块链的融合。他提到了 MCP (可扩展内容处理)协议，这是一个由 srp 公司推出的开源解决方案，用于解决不同系统间的数据交互问题。MCP 的出现引发了关于如何在区块链网络上部署以解决搜索单点问题和抗审查能力的讨论。目前只有部分异步区块链支持此操作，而对于并行业务链来说，由于 EVM 的设计限制，难以实现。另一个方向是在链下进行 MCP 搜索，使其具备与区块链交互的能力，旨在降低用户门槛。这类似于 DVI 变种的概念，通过将工具封装在 MCP server 中，避免重复工作。</p><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>探讨 AI 与区块链结合的未来趋势</strong></h3><p>Space 聚焦于 AI 与区块链的结合应用，邀请了三名老师分享他们对该领域的看法和经验。首先，Samuel 介绍了他们通过探索模块化区块链，搭建了一套用户可以自定义的区块链技术，并在运营中取得了良好效果。随后，讨论转向了并行化处理的问题，强调了排序的重要性以及性能消耗。接着，BOX 分享了他对于 AI 与区块链结合的个人观点，尽管看好这一创新方向，但他更倾向于支持 AI 项目的创新。Wishlonger 则从高性能公链的角度出发，探讨了如何为开发者提供更好的基础设施支持，特别是在异构硬件支持能力和工具链开发方面。最后，讨论转向了开发者迁移问题，指出现在的并行链设计考虑了开发迁移成本，保持了对以太坊工具链的高度兼容性，并鼓励开发者编写高效并行的代码。整体上，会议围绕 AI 与区块链的技术融合、开发效率及迁移支持等方面进行了深入讨论。</p><h3 id="h-monad-evm" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>探讨 Monad 迁移与并行 EVM 发展展望</strong></h3><p>会议讨论了并行 EVM 和 EVM 的发展及其对当前供应链竞争格局的影响。潘老师认为，目前还难以预测并行 EVM 的发展情况，需要长期观察。同时，他强调每个 EVM 项目都有其专长和技术特点，开发者可以根据需求和兴趣进行选择。其他嘉宾也表示，目前大家还在探索阶段，需要观望并行化对供应链格局的影响。最终，大家一致认为，未来的发展方向取决于各业务侧和生态侧的建设和发展情况。</p><p>内容 | LXDAO</p><p>编辑 &amp; 排版 | Marcus、环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Next.js 与中间件漏洞：失效的授权机制]]></title>
            <link>https://paragraph.com/@lxdao/next-js</link>
            <guid>b97q9Q3iNmX83iiuMX1V</guid>
            <pubDate>Mon, 07 Apr 2025 14:43:08 GMT</pubDate>
            <description><![CDATA[原作：Rachid.A 原文： https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware 本期文章由 LXDAO 成员 Yewlne 翻译。 译文正文 最近，我与化名为 inzo_ 的 Yasser Allam 决定联手开展研究。在讨论了多个潜在目标后，我们决定将焦点放在 Next.js https://github.com/vercel/next.js GitHub 上拥有 13 万 Stars，目前每周下载量超过 940 万次）。这是一个我非常熟悉的框架，与它有美好的创作经历，正如我之前的研究成果 https://zhero-web-sec.github.io/research-and-things/ 所证明的那样。因此，本文中的"我们"自然指代我们两人。 Next.js 是一个基于 React 的全功能 JavaScript 框架，拥有丰富的特性 — — 是深入研究细节的理想场所。怀着信念、好奇与韧性，我们踏上旅程，探索那些鲜为人知的角落，寻找藏匿其中的宝藏。 ...]]></description>
            <content:encoded><![CDATA[<p>原作：Rachid.A</p><p>原文：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware">https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware</a></p><p>本期文章由 LXDAO 成员 Yewlne 翻译。</p><p>译文正文 最近，我与化名为 inzo_ 的 Yasser Allam 决定联手开展研究。在讨论了多个潜在目标后，我们决定将焦点放在 Next.js</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/vercel/next.js">https://github.com/vercel/next.js</a></p><p>GitHub 上拥有 13 万 Stars，目前每周下载量超过 940 万次）。这是一个我非常熟悉的框架，与它有美好的创作经历，正如我之前的研究成果</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://zhero-web-sec.github.io/research-and-things/">https://zhero-web-sec.github.io/research-and-things/</a></p><p>所证明的那样。因此，本文中的&quot;我们&quot;自然指代我们两人。</p><p>Next.js 是一个基于 React 的全功能 JavaScript 框架，拥有丰富的特性 — — 是深入研究细节的理想场所。怀着信念、好奇与韧性，我们踏上旅程，探索那些鲜为人知的角落，寻找藏匿其中的宝藏。</p><p>不久之后，我们就在中间件中发现了一个重大问题。其影响范围广泛，所有版本均受影响，且利用此漏洞无需任何前置条件 — — 我们将很快进行详细展示。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">目录</h2><ul><li><p><strong>Next.js 中间件</strong></p></li><li><p><strong>授权神器：宝藏级老代码</strong></p><ul><li><p><strong>执行顺序与 </strong><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://middlewareInfo.name"><strong>middlewareInfo.name</strong></a></p></li></ul></li><li><p><strong>授权神器：昨日已成诗，今朝更值得</strong></p><ul><li><p><strong>/src 目录</strong></p></li><li><p><strong>最大递归深度</strong></p></li></ul></li><li><p><strong>漏洞利用</strong></p><ul><li><p><strong>绕过授权/重写</strong></p></li><li><p><strong>绕过 CSP</strong></p></li><li><p><strong>通过缓存投毒实现 DoS（What？）</strong></p></li><li><p><strong>澄清</strong></p></li></ul></li><li><p><strong>安全公告 — CVE-2025–29927</strong></p></li><li><p><strong>免责声明</strong></p></li><li><p><strong>结语</strong></p></li></ul><h3 id="h-nextjs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Next.js 中间件</h3><p>中间件允许你在请求完成之前执行代码。然后，你可以根据传入的请求，通过重写、重定向、修改请求或响应头，或直接返回响应的方式来修改响应内容（摘自 Next.js 文档）。</p><p>作为一个完整的框架，Next.js 拥有自己的中间件（middleware） — — 这是一个重要且被广泛使用的特性。它的应用场景众多，其中最重要的包括：</p><ul><li><p>路径重写（Path rewriting）</p></li><li><p>服务器端重定向（Server-side redirects）</p></li><li><p>向响应添加头信息（如 CSP 等）元素</p></li><li><p>最重要的是：身份验证（Authentication）和授权（Authorization）</p></li></ul><p>中间件的一个常见用途是进行<strong>授权</strong>，这涉及基于特定条件来<strong>保护特定路径</strong>。</p><p>身份验证和授权：在授予对特定页面或 API 路由的访问权限之前，确保用户身份并检查会话 Cookie（Next.js 文档）。</p><p><strong>示例</strong>：当用户尝试访问 /dashboard/admin 时，请求首先会通过中间件，中间件会检查用户的会话 cookie 是否有效以及是否具有必要的权限。如果验证通过，中间件便转发请求；否则，中间件会将用户重定向到登录页面：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5a8f17ecdbbbaa38e86f0e0c271f0a936731c74331ce7d8ec26c80b2e8d2c300.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">授权神器：宝藏级老代码</h3><p>正如一位伟人曾经说过的，”talk is cheap, show me the bug”，让我们避免过多的叙述，直接切入主题；我们在浏览框架的旧版本（v12.0.7）时，我们发现了这段代码</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/vercel/next.js/blob/v12.0.7/packages/next/server/next-server.ts">https://github.com/vercel/next.js/blob/v12.0.7/packages/next/server/next-server.ts</a></p><p>当 Next.js 应用程序使用中间件时，会调用 runMiddleware 函数。除了其主要功能外，该函数还会获取 x-middleware-subrequest 头部的值，并用它来判断是否应该应用中间件。该头部值会使用冒号（:）作为分隔符被拆分成列表，然后检查这个列表是否包含 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://middlewareInfo.name">middlewareInfo.name</a> 值。这意味着，如果我们在请求中添加带有正确值的 x-middleware-subrequest 头部，那么中间件 — — 无论其用途如何 — — 将被完全忽略，请求将通过 NextResponse.next() 被转发，并且将完成到原始目的地的路径，而不受中间件的任何影响。这个头部及其值就像一把”万能钥匙”，可以绕过所有规则。此时我们已经意识到发现了一个惊人的问题，接下来需要完成最后几个拼图。</p><p>要让我们的”万能钥匙”生效，它的值必须包含 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://middlewareInfo.name">middlewareInfo.name</a>，但这个值究竟是什么呢？</p><p>执行顺序与 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://middlewareInfo.name">middlewareInfo.name</a> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://middlewareInfo.name">middlewareInfo.name</a> 的值非常容易推测，它仅仅是中间件所在的路径。要了解这一点，我们需要简单了解一下中间件在旧版本中的配置方式。</p><p>首先，在 12.2 版本之前 — — 这个版本中中间件约定发生了变化 — — 文件必须命名为 _middleware.ts。此外，app 路由器（router）仅在 Next.js 的版本 13 中才引入。当时存在的唯一路由器是 pages 路由器，因此该文件必须放在 pages 文件夹内（特定于路由器）。</p><p>有了这些信息，我们就能推断出中间件的确切路径，从而猜测 x-middleware-subrequest 头部的值。这个值只是由目录名称（即当时存在的唯一路由器名称）和文件名组成，遵循当时以下划线开头的命名约定：</p><p>x-middleware-subrequest: pages/_middleware 当我们尝试绕过那些被配置为系统性地将访问尝试从 /dashboard/team/admin 重定向到 /dashboard 的中间件时：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9cbc3d56727c96d0347447cf39b12a44ef8cca5f7a923ecfb04e511865fa1652.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>成功了，我们侵入了 ⚔️</p><p>我们现在可以完全绕过中间件，从而绕过任何基于它的保护系统，最典型的就是授权，就像我们上面的例子。这个发现相当惊人，但还有其他需要考虑的点。</p><p>12.2 之前的版本允许嵌套路由在目录树的任何位置（从pages文件夹开始）放置一个或多个_middleware文件，并且它们有执行顺序，正如我们在从Web Archive中检索到的旧文档截图中所看到的：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cc82e51fead1c80f78b618f66e5090beafd29b8e7b8a06345392783e2b479bff.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>这对我们的漏洞利用意味着什么？</strong></p><p>可能性 = 路径中的层级数量</p><p>因此，要访问/dashboard/panel/admin（受中间件保护），middlewareInfo.name的值有三种可能性，相应地x-middleware-subrequest的值也有三种可能性：</p><p><code>pages/_middleware</code></p><p>或</p><p><code>pages/dashboard/_middleware</code></p><p>或</p><p><code>pages/dashboard/panel/_middleware</code></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">授权神器：昨日已成诗，今朝更值得</h3><p>到目前为止，我们认为只有版本 13 之前的版本容易受到攻击，因为中间件已在源代码中被移动，并且我们还没有覆盖它的所有方面。我们推测维护者一定已经注意到了这个漏洞，并在版本 13 的重大更改之前修复了它，所以我们向框架维护者报告了这个漏洞并继续了我们的研究。</p><p>令我们大为惊讶的是，在最初发现后两天，我们发现所有版本的 Next.js — — 从版本 11.1.4 开始 — — 都存在漏洞！ 代码不再位于同一位置，漏洞利用的逻辑也略有变化。</p><p>如前所述，从版本 12.2 开始，文件不再包含下划线，必须简单命名为 middleware.ts。此外，它不再位于 pages 文件夹中（这对我们来说很方便，因为从版本 13 开始，引入了 app 路由器，这本会使可能性加倍）。</p><p>With that in mind, the payload for the first versions starting with version 12.2 is very simple:</p><p>考虑到这一点，从版本 12.2 开始的第一个版本的有效负载非常简单：</p><pre data-type="codeBlock" text="x-middleware-subrequest: middleware
"><code>x<span class="hljs-operator">-</span>middleware<span class="hljs-operator">-</span>subrequest: middleware
</code></pre><h3 id="h-src" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">/src 目录</h3><p>还需要考虑到 Next.js 提供了创建 /src 目录的可能性：</p><p>(Next.js documentation) 作为在项目根目录中拥有特殊 Next.js app 或 pages 目录的替代方法，Next.js 还支持将应用程序代码放在 src 目录下的常见模式。（Next.js 文档）</p><p>在这种情况下，payload 将是：</p><pre data-type="codeBlock" text="x-middleware-subrequest: src/middleware
"><code>x<span class="hljs-operator">-</span>middleware<span class="hljs-operator">-</span>subrequest: src<span class="hljs-operator">/</span>middleware
</code></pre><p>因此，<strong>无论路径中有多少层级，总共只有两种可能性</strong>。这简化了针对相关版本的漏洞利用难度。</p><p>在最新版本中，它又有了一点变化（我们保证，最后一次）。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">最大递归深度</h3><p>在更新的版本中，逻辑又略有变化，请看这段代码：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1874c3361cd3210bdf7a62d041460f6e55becb58f67010508aac156336f9a2a4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>v15.1.7</p><p>和之前一样，系统会检索 x-middleware-subrequest 头部的值，并使用冒号作为分隔符形成一个列表。但这次，请求直接转发的条件 — — 即忽略中间件规则 — — 有所不同：</p><p>常量depth的值必须大于或等于常量 MAX_RECURSION_DEPTH 的值（即 5）。在赋值过程中，每当列表 subrequests（即由:分隔的头部值）中的某个值等于 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://params.name">params.name</a>（即中间件的路径）时，常量depth就会增加 1。如前所述，这里只有两种可能性：middleware 或 src/middleware。</p><p>因此，为了绕过中间件，我们只需要在请求中添加以下头部/值：</p><pre data-type="codeBlock" text="x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware 
"><code>x<span class="hljs-operator">-</span>middleware<span class="hljs-operator">-</span>subrequest: middleware:middleware:middleware:middleware:middleware 
</code></pre><p>或</p><pre data-type="codeBlock" text="x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware
"><code>x<span class="hljs-operator">-</span>middleware<span class="hljs-operator">-</span>subrequest: src<span class="hljs-operator">/</span>middleware:src<span class="hljs-operator">/</span>middleware:src<span class="hljs-operator">/</span>middleware:src<span class="hljs-operator">/</span>middleware:src<span class="hljs-operator">/</span>middleware
</code></pre><p><strong>这段代码最初是用来做什么的？</strong></p><p>这段代码似乎是为了防止递归请求陷入无限循环。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nextjs.org/blog/cve-2025-29927">https://nextjs.org/blog/cve-2025-29927</a></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">漏洞利用</h3><p>既然我们知道您喜欢这类内容，这里有一些来自 Bug Bounty Program 的真实案例。</p><p><strong>绕过授权/重写</strong></p><p>在这个例子中，当我们尝试访问/admin/login时，收到404响应。从响应头中可以看出，中间件执行了路径重写，以防止未经授权或不适当的用户访问：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9f758951f2059b16ffae6babf495f83ab602d2360cd45b1e359b65ed1e7999b0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>但使用我们的授权神器 ：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/976ef0884e3bb948fdc4f513a28ab90e65708fcffec9b9e17469bb9b9653e914.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>我们可以毫无障碍地访问该端点，中间件被完全忽略。目标 Next.js 版本：15.1.7</p><p><strong>绕过 CSP</strong></p><p>这次网站使用中间件来设置 — — 除了其他功能外 — — CSP和cookie：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d6809126be6706b22a3de6eb98d506bdf1572aaa60bb2ad3d0e958be12a2aa4b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>让我们绕过它：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4ecd90a441f5473f26df6cf788ad37f751fe061187f7af5b312739ebb697127d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Target next.js version: 15.0.3Target next.js 版本：15.0.3</p><p><strong>注意</strong>： 请留意两个目标的payload差异，其中一个使用了src/目录，而另一个没有。</p><p><strong>通过缓存投毒实现 DoS（What？）</strong></p><p>是的，通过这个漏洞也可能实现缓存投毒 DoS 攻击。这显然不是我们首先要寻找的，但如果没有敏感路径受到保护，且没有更有趣的可利用点，那么某些情况可能会导致缓存投毒拒绝服务（CPDoS）：</p><p>假设一个网站根据用户地理位置重写用户路径，添加（/en、/fr等），且没有在根路径（/）上提供页面或资源。如果我们绕过中间件，就会避开重写，最终到达根页面。由于开发者并未打算让用户访问根页面，因此没有提供相应页面，我们会得到404（或根据重写配置/类型不同，可能是500）。</p><p>如果该网站使用了缓存/CDN 系统，可能会强制缓存404响应，导致页面不可用，<strong>严重影响站点可用性</strong>。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fdfe0a3d9d0b67eeadaa62ebe7eac7f98b8e660efced2228ec5dea2c7fa6743e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>澄清</strong></p><p>自安全公告发布以来，我们收到了一些人的咨询，他们担心自己的应用程序安全，并且不太理解攻击的范围。需要明确的是，易受攻击的元素是中间件。如果您没有使用中间件（或至少没有将其用于敏感目的），那么无需担心（不过，请检查上面提到的 DoS 方面），因为绕过中间件不会绕过任何实际的安全机制。</p><p>否则，后果可能是灾难性的，我们建议您迅速实施安全公告中的指导措施。</p><h3 id="h-cve-2025-29927" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">安全公告 — CVE-2025–29927</h3><p><strong>补丁</strong></p><ul><li><p>对于 Next.js 15.x，此问题已在 15.2.3 中修复</p></li><li><p>对于 Next.js 14.x，此问题已在 14.2.25 中修复</p></li><li><p>对于 Next.js 版本 11.1.4 到 13.5.6，我们建议查阅以下解决方法。</p></li></ul><p><strong>解决方案</strong></p><p>如果无法升级到安全版本，我们建议你阻止包含 x-middleware-subrequest 请求头的外部用户请求访问你的 Next.js 应用。</p><p><strong>严重性</strong></p><p>CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N（严重程度：9.1/10，临界级）</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffw">https://github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffw</a></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">更多信息</h3><p>在撰写本文时，部署在 Vercel 和 Netlify 上的应用显然已不再受此漏洞影响（更新：由于存在大量误报，Cloudflare 已将该规则调整为仅在用户主动启用时生效 — — 这些误报未能有效区分来自合法用户的请求与潜在攻击者的请求）。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/nextjs/status/1903522002431857063">https://x.com/nextjs/status/1903522002431857063</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">免责声明</h2><p>本研究发布仅用于教育目的，旨在帮助开发者理解问题的根本原因，或为研究人员 / 漏洞猎人在未来的研究工作中提供启发。本文作为安全公告的补充材料，提供了有关漏洞本质的进一步说明和澄清 — — 因为公告中已公开了导致该漏洞的请求头（以及相关的提交差异）。</p><p>我们明确声明不支持对本文进行任何不道德的使用。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>正如本文所强调的，这个漏洞在 Next.js 源代码中已经存在了数年，随着中间件及其版本的演变而变化。任何软件都可能出现严重漏洞，但当它影响到最流行的框架之一时，就变得特别危险，可能对更广泛的生态系统造成严重后果。如前所述，在撰写本文时，Next.js 每周下载量接近 1000 万次。它广泛应用于从银行服务到区块链等关键领域。当漏洞影响到用户依赖的成熟功能（如授权和身份验证）时，风险就更大了。</p><p>Vercel 团队花了几天时间来解决这个漏洞，但值得注意的是，一旦他们意识到问题，修复就被提交、合并到几小时内实现到新版本中（包括向后移植）。</p><p>编译 | Yewlne</p><p>编辑 &amp; 排版 | Yewlne、环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[MCP：AI 的去中心化革命与以太坊的完美融合]]></title>
            <link>https://paragraph.com/@lxdao/mcp-ai</link>
            <guid>OgeWU0LDSQVn1QocaKd8</guid>
            <pubDate>Wed, 26 Mar 2025 09:19:42 GMT</pubDate>
            <description><![CDATA[AI 进化史上的 “USB-C 时刻”，2024 年 11 月，Anthropic 发布的 MCP 协议正在引发硅谷地震。这个被喻为”AI 界 USB-C”的开放标准，不仅重构了大模型与物理世界的连接方式，更暗藏着破解 AI 垄断困局、重构数字文明生产关系的密码。当我们还在争论 GPT-5 的参数规模时，MCP 已悄然铺就通向 AGI 时代的去中心化之路…… LXDAO 联合发起人 Bruce 最近就 MCP 分享他最新的思考成果，原文: https://x.com/brucexu_eth/status/1901555983111110994 Bruce：最近在研究 Model Context Protocol（MCP）。这是继 ChatGPT 之后，在 AI 领域第二个让我非常兴奋的东西，因为它有希望解决我思考多年的三个问题： 非科学家和天才，普通人如何参与 AI 行业并获得收入？ AI 和 Ethereum 有什么双赢的结合之处？ 如何实现 AI d/acc？避免中心化的大公司垄断、审查，AGI 毁灭人类？ MCP 是什么？ MCP 是一个开放标准框架，可以简化 LLM 与外...]]></description>
            <content:encoded><![CDATA[<p>AI 进化史上的 “USB-C 时刻”，2024 年 11 月，Anthropic 发布的 MCP 协议正在引发硅谷地震。这个被喻为”AI 界 USB-C”的开放标准，不仅重构了大模型与物理世界的连接方式，更暗藏着破解 AI 垄断困局、重构数字文明生产关系的密码。当我们还在争论 GPT-5 的参数规模时，MCP 已悄然铺就通向 AGI 时代的去中心化之路……</p><p>LXDAO 联合发起人 Bruce 最近就 MCP 分享他最新的思考成果，原文:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/brucexu_eth/status/1901555983111110994">https://x.com/brucexu_eth/status/1901555983111110994</a></p><p><strong>Bruce</strong>：最近在研究 Model Context Protocol（MCP）。这是继 ChatGPT 之后，在 AI 领域第二个让我非常兴奋的东西，因为它有希望解决我思考多年的三个问题：</p><p>非科学家和天才，普通人如何参与 AI 行业并获得收入？ AI 和 Ethereum 有什么双赢的结合之处？ 如何实现 AI d/acc？避免中心化的大公司垄断、审查，AGI 毁灭人类？ MCP 是什么？ MCP 是一个开放标准框架，可以简化 LLM 与外部数据源和工具的集成。如果我们把 LLM 比作 Windows 操作系统，Cursor 等应用是键盘和硬件，那么 MCP 就是 USB 接口，支持将外部数据和工具灵活插入，然后用户可以读取使用这些外部数据和工具。</p><p>MCP 提供了三种能力对 LLM 进行扩展：</p><ul><li><p>Resources（知识扩展）</p></li><li><p>Tools（执行函数，调用外部系统）</p></li><li><p>Prompts（预编写提示词模板）</p></li></ul><p>MCP 可以由任何人进行开发和托管，以 Server 的方式提供，可以随时下线停止服务。</p><h2 id="h-mcp" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">为什么需要 MCP</h2><p>目前 LLM 使用尽可能多的数据进行大量的运算并生成大量的参数，将知识融入到模型里面，从而实现对话输出相应知识。但是存在比较大的几个问题：</p><ul><li><p>大量的数据和运算需要大量的时间和硬件，用于训练的知识通常是过时的。</p></li><li><p>大量参数的模型，很难在本地设备进行部署和使用，但实际上使用者大部分场景可能并不需要全部信息完成需求。</p></li><li><p>部分模型采用爬虫的方式读取外部信息进行运算以实现时效性，但是由于爬虫的限制和外部数据的质量，可能产出误导性更强的内容。</p></li><li><p>由于 AI 并没有很好的给创作者带来利益，很多网站和内容开始实施反 AI 措施，生成大量垃圾信息，将会导致 LLM 的质量逐步下降。</p></li><li><p>LLM 很难扩展到方方面面的外部功能和操作，例如准确调用 GitHub 接口实现一些操作，它会按照可能过时的文档生成代码，但无法确保可以精准执行。</p></li></ul><p>胖 LLM 和瘦 LLM + MCP 的架构演进 我们可以将目前的超大规模模型视为胖 LLM，其架构可以以下面简单图示表示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/62e3edea3e16d52337d84cfaad831784c0dab6d900fb7da1df24744c4c56c72f.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>用户输入信息之后，通过 Perception &amp; Reasoning 层对输入进行拆解和推理，然后调用庞大的参数进行结果生成。</p><p>基于 MCP 之后，LLM 可能聚焦在语言解析本身，剥离出去知识和能力，变成瘦 LLM:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7870e8b93659b2d2e5f5cd2f1b6a4e5d045dabcd76a9fa29698dd27839a9ac05.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>瘦 LLM 的架构下，Perception &amp; Reasoning 层将会关注如何将全方面的人类物理环境信息解析成为 tokens，包括但不限于：语音、语气、气味、图像、文字、重力、气温等，然后通过 MCP Coordinator 编排和协调多达数百的 MCP Servers 完成任务。瘦 LLM 的训练成本和速度将会极速提升，对于部署设备的要求变得很低。</p><h2 id="h-mcp" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MCP 如何解决三大问题</h2><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">普通人如何参与 AI 行业？</h3><p>任何有独特才能的人，都可以创建自己的 MCP Server 对 LLM 提供服务。例如一个鸟类爱好者可以将自己多年的鸟类笔记通过 MCP 对外提供服务。当有人使用 LLM 搜索跟鸟类相关的信息，就会调用到当前鸟类笔记 MCP 服务。创作者也会因此获得收入分成。</p><p>这是一种更为精准和自动化的创作者经济循环，服务内容更加标准化，调用的次数、输出的 token 都可以很精准的统计。LLM 提供商甚至可以同时调用多个鸟类笔记 MCP Servers 让用户选择和评分来确定谁的质量更好获得更高匹配权重。</p><h3 id="h-ai-ethereum" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AI 和 Ethereum 的双赢结合</h3><p>a. 我们可以基于 Ethereum 构建一个 OpenMCP.Network 创作者激励网络。MCP Server 需要托管和提供稳定的服务，用户对 LLM 提供商付费，LLM 提供商将实际的激励通过网络分配到被调用的 MCP Servers 上从而维持整个网络的可持续性和稳定性，激发 MCP 的创作者持续创作和提供高质量内容。这一套网络将需要使用智能合约实现激励的自动化、透明、可信和抗审查。运行过程中的签名、权限验证、隐私保护都可以使用以太坊钱包、ZK 等技术实现。</p><p>b. 开发 Ethereum 链上操作相关的 MCP Servers，例如 AA 钱包调用服务，用户将支持在 LLM 里面通过语言实现钱包付款而不暴露相关私钥和权限给 LLM。</p><p>c. 还有各种开发者工具，进一步简化 Ethereum 智能合约开发和代码生成。</p><h3 id="h-ai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">实现 AI 去中心化</h3><p>a. MCP Servers 将 AI 的知识和能力去中心化，任何人都可以创建和托管 MCP Servers，注册到例如 OpenMCP.Network 这样的平台上面之后按照调用获得激励。没有任何一个公司可以掌握全部的 MCP Servers。如果一个 LLM 提供商给予不公平的激励到 MCP Servers，创作者将支持屏蔽该公司，用户得不到优质结果后将会更换其他 LLM 提供商实现更公平的竞争。</p><p>b. 创作者可以对自己的 MCP Servers 实现细粒度的权限控制以保护隐私和版权。瘦 LLM 提供商应该通过提供合理的激励来让创作者贡献高质量的 MCP Servers。</p><p>c. 瘦 LLM 能力差距将慢慢抹平，因为人类的语言是有遍历上限的，演进也很缓慢。LLM 提供商将需要把目光、资金瞄向高质量的 MCP Servers，而非重复使用更多显卡炼丹。</p><p>d. AGI 的能力将得到分散和降权，LLM 仅作为语言处理和用户交互，具体能力分布在各个 MCP Servers 里面。AGI 将不会威胁到人类，因为关闭 MCP Servers 之后就只能进行基础语言对话。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总体回顾</h2><p>LLM + MCP Servers 的架构演进，本质上是将 AI 能力的去中心化，降低了 AGI 毁灭人类的风险。 LLM 的使用方式，使其对 MCP Servers 的调用次数和输入输出可以做到 token 级别的统计和自动化，为 AI 创作者经济系统的搭建奠定了基础。 好的经济系统可以驱动创作者主动贡献创作高质量 MCP Servers，从而带动整个人类的发展，实现正向飞轮。创作者不再抵御 AI，AI 也会提供更多岗位和收入，将类似 OpenAI 这样的垄断商业公司的利润合理分配。 这一套经济系统，结合其特性和创作者的需求，非常适合基于 Ethereum 实现。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">未来展望</h2><p>下一步的剧本演进 MCP 或者类 MCP 的协议将会层出不穷，几家大公司将开始竞争标准的定义。 MCP Based LLM 将会出现，专注于解析和处理人类语言的小模型，附带 MCP Coordinator 接入 MCP 网络。LLM 将支持自动发现和调度 MCP Servers，无需复杂手工配置。 MCP Network 服务提供商将出现，每家有自己的经济激励系统，MCP 创作者将自己的 Server 注册和托管即可得到收入。 如果 MCP Network 的经济激励系统使用 Ethereum 构建，基于智能合约，那么 Ethereum 网络的 transactions 保守估计将增加约 150 倍（按照非常保守的每天 1 亿次 MCP Servers 的调用量，目前 12s 一个 Block 包括 100 txs 计算）。</p><p><strong>内容 | Bruce</strong></p><p><strong>编辑 &amp; 排版 | 环环</strong></p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LXDAO S11 落幕收官，S12 全新起航]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-s11-s12</link>
            <guid>o8oZUH8jZnzQwEAUOpom</guid>
            <pubDate>Wed, 26 Mar 2025 09:03:20 GMT</pubDate>
            <description><![CDATA[S11 核心数据速览在 S11 各工作组和项目组的共同协作与努力下，LXDAO 社区也在逐渐稳步增长中，同时在本季度中，我们也获得了多笔捐赠以推动着 LXDAO 社区与项目的持续运转。S11 主要成果简报Forge 工作组Coordnation 研究小组成立，开展了相关协调的基础研究，并发布了第一篇以协调研究的十个核心问题的报告； · Coordnation 研究小组工作 Notion： https://www.notion.so/Coordination-Research-Group-150dceffe40b801aa824dc0d046cb809?pvs=4 · 报告详情： https://forum.lxdao.io/t/topic/2630FairSharing 完成激励池合约开发和修复 BUG，2.0 版本持续推进中； · 官网： https://app.fairsharing.xyz/残酷共学 MVP 版本正式完成上线，并有序开展残酷共学活动中； · 官网：https://intensivecolearn.ing/ · 更多详情可见： 残酷共学项目正式上线啦！OPCN...]]></description>
            <content:encoded><![CDATA[<h2 id="h-s11" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">S11 核心数据速览</h2><p>在 S11 各工作组和项目组的共同协作与努力下，LXDAO 社区也在逐渐稳步增长中，同时在本季度中，我们也获得了多笔捐赠以推动着 LXDAO 社区与项目的持续运转。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2522b6eb26cae90f30f07cc414de7981099620d9891d28cf2d5a0eda2ee7a318.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-s11" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">S11 主要成果简报</h2><h3 id="h-forge" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Forge 工作组</h3><ol><li><p>Coordnation 研究小组成立，开展了相关协调的基础研究，并发布了第一篇以协调研究的十个核心问题的报告； · Coordnation 研究小组工作 Notion： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.notion.so/Coordination-Research-Group-150dceffe40b801aa824dc0d046cb809?pvs=4">https://www.notion.so/Coordination-Research-Group-150dceffe40b801aa824dc0d046cb809?pvs=4</a> · 报告详情： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://forum.lxdao.io/t/topic/2630">https://forum.lxdao.io/t/topic/2630</a></p></li><li><p>FairSharing 完成激励池合约开发和修复 BUG，2.0 版本持续推进中； · 官网： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.fairsharing.xyz/">https://app.fairsharing.xyz/</a></p></li><li><p>残酷共学 MVP 版本正式完成上线，并有序开展残酷共学活动中； · 官网：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://intensivecolearn.ing/">https://intensivecolearn.ing/</a> · 更多详情可见： 残酷共学项目正式上线啦！</p></li><li><p>OPCN 作为 LXDAO 的合作项目，在本季度中成为 Optimism S7 GovNERD，并启动了 Optimism 中文第一次空投。 · Delegate 链接： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://optimism.curiahub.xyz/delegate/optimismcn.eth">https://optimism.curiahub.xyz/delegate/optimismcn.eth</a></p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">治理工作组</h3><ol><li><p>完成了专家审议制度、目标解锁奖励制度等论坛提案； · 提案详情： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://forum.lxdao.io/t/lxdao-milestone-incentive-unlocking-system/2635">https://forum.lxdao.io/t/lxdao-milestone-incentive-unlocking-system/2635</a> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://forum.lxdao.io/t/proposal-establishing-an-expert-review-mechanism-to-optimize-proposal-processes/2542">https://forum.lxdao.io/t/proposal-establishing-an-expert-review-mechanism-to-optimize-proposal-processes/2542</a></p></li><li><p>发起并完成第二期叙事残酷共学。 · 更多了解： 加密思潮残酷共学：逃离叙事十日试炼</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">运营工作组</h3><p>在 S11 季度中，我们总计发布了 120 篇文章，并精心组织了 12 场线上活动， 2 场线下活动，包括但不限于揭秘 ERC-7802、Web3 开发者趋势、AI x Web3、 Consensus 大会见闻分享会等主题 Space 以及 Arbirtum、Optimism 、加密思潮残酷共学、Web3 入门到放弃残酷共学等活动；</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/eb6a21df17e8487950f127063b663febe835d07b91740538076b06874a72e959.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>其中更是成功举办了 LXDAO 休闲黑客松第一期：AI 开发工具黑客松，而在本期黑客松中，更是有共计有 10 支参赛，28 位创客贡献并向我们展示了其卓越的创新成果与出色的创意成果！</p><p>更多了解：一个周末，10 支队伍，28 位创客，一场纯粹的创意狂欢丨 LXDAO 休闲黑客松第一期圆满落幕</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/18df93733e164db5b40a009eabc826b213ec6769e86696160158f1c53324d271.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-lxdao-s12" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">LXDAO S12 工作计划</h2><p>在 S12 中，LXDAO 将重点推进 LX BuildPath 围绕的主线产品建设：LX BuildPath 叙事及活动，并对工作组以及社区事务进行改善，具体如下：</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">简化治理和内耗</h3><ol><li><p>工作组预算合并：所有工作组根据职责范围提出预算，工作组之间自由商议分配和使用</p></li><li><p>工作组会议修改: 合并在一起进行会议讨论</p></li><li><p>资源倾斜支持项目：面向具体的项目提供支持，支持具体的 Pod/项目组</p></li><li><p>LXP 通缩：取消 LXPU 的制度，仅发 U or tokens，LXP 缩减到 10% 自动等比空投，继续通缩</p></li><li><p>提供一个快速的 Pod 小组的构建：Pod：快速、轻量反应小组，自主自发，快速的工作组提案推进产生，无需社区提案，包括早期项目和 Research，Milestone 的方式解锁和回收</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">低成本自驱</h3><ol><li><p>工作组辅助而非监督，以自驱为主，工作组协助团队成功，而非作为团队成功</p></li><li><p>通过 Retro funding 和 Milestone 按照实际产出进行解锁，避免资源浪费</p></li><li><p>面向事情参与而非金钱，但是取得好的结果后可以获得足够的激励</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">减少日常事务</h3><ol><li><p>缩减会议，尝试仅保留周六社区周会，不开设工作组周会或者多开放一个合并的工作周会，仅有两个会</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">工作组成员</h3><p>Dev Coordinator (Dev)： Hardman(Tg@hardman_eth), Keylen(Tg@Keylen3_14), Marcus(Tg@Marcuszheng)</p><p>Designer (Dev)： Char(Tg@chareate)</p><p>Gov Coordinator (Gov)： SueT(Tg@sylvianeo)</p><p>Accounting (Gov)： Ache(Tg@wodeche1)</p><p>Activity Operation (Ops)： Arain(Tg@Arainfang)</p><p>Content Operation (Ops)： Huanhuan(Tg@Saihuan)</p><p>Community Operations (Ops)： Vdel(Tg@rrrguhuhiubj)</p><p>BD Coordinator (BD)： Bruce(Tg@brucexu_eth), Aarin(Tg@Arainfang), Marcus(Tg@Marcuszheng)</p><p>Research Coordinator (Research)： Wye(Tg@wyeeeh), 白丁(Tg@HYbaidiing)</p><p>更多相关职位详情可见： LXDAO S12 工作组成员正式公布！</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mp.weixin.qq.com/s/w_y3arei5rO2akRDNJR7_g">https://mp.weixin.qq.com/s/w_y3arei5rO2akRDNJR7_g</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>在此，我们由衷感谢每一位选择并愿与 LXDAO 共同成长的建设者与支持者们。我们深知，未来的道路或许并不平坦，但正是由于有你们的支持与信任，赋予了我们继续前行的信心与勇气，未来，我们将继续秉承着初心，推动公共物品和开源项目的可持续发展！</p><h2 id="h-lxdao" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">欢迎加入 LXDAO</h2><h3 id="h-lxdao" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">如何加入 LXDAO</h3><ol><li><p>在官网注册成员身份：在 LXDAO 官网点击 Join us ，注册属于你的 Member Profile，官网地址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://lxdao.io/onboarding/intro">https://lxdao.io/onboarding/intro</a></p></li><li><p>于社区周会上进行自我介绍：LXDAO 每周六上午 10 点（UTC+8）的社区周会公开自我介绍。</p></li><li><p>获得 Badge：在自我介绍完成之后，LXDAO 会给你颁发一个 Badge，获得 Badge 后你就是 LXDAO 的正式 Member 啦！</p></li></ol><h3 id="h-lxdao" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一键掌握 LXDAO 动态</h3><p>官方网站了解详情：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://lxdao.ioLXDAO">https://lxdao.ioLXDAO</a></p><p>论坛想法发起，参与项目：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://forum.lxdao.io">https://forum.lxdao.io</a></p><p>社媒平台获取最新动态</p><p>推特：@LXDAO_Official</p><p>公众号：LXDAO</p><p>Youtube：@lxdao_official</p><p>哔哩哔哩：LXDAO_</p><p>内容 | LXDAO</p><p>编辑 &amp; 排版 | Soleil</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LXDAO 翻译：智械如翼，人思为舵]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-14</link>
            <guid>sHTR5lQX7WCEIRpfN18t</guid>
            <pubDate>Wed, 19 Mar 2025 09:56:13 GMT</pubDate>
            <description><![CDATA[原作：Vitalik Buterin 原文： https://vitalik.eth.limo/general/2025/02/28/aihumans.html#4 本期文章由 LXDAO 成员 Yewlne 翻译。译文正文特别感谢 Devansh Mehta、Davide Crapis 和 Julian Zawistowski 提供的反馈和评论。 如果你询问人们为什么喜欢民主结构，无论是政府、工作场所，还是基于区块链的 DAO 组织，他们通常会给出相似的答案：它们避免了权力的集中，给用户提供了强有力的保证，因为没有哪个人能够凭空改变系统的趋势，同时还能汇集众人的智慧，从而做出更好的决策。 但如果你问人们为什么不喜欢民主结构，他们通常会给出相同的抱怨：普通选民缺乏深度思考，因为每个选民对结果的影响微乎其微，只有少数选民会在决定前认真思考，此外我们通常会看到民主还会导致低参与度（低参与度会使整个系统容易受到攻击）或者大家都倾向于信任并模仿某个意见领袖，从而导致事实上的集权。 本文旨在探讨一种新范式，该范式有望借助 AI 来为我们提供民主结构的有点，同时规避其缺点。“AI 是引擎，人...]]></description>
            <content:encoded><![CDATA[<p><strong>原作：Vitalik Buterin</strong></p><p><strong>原文：</strong></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vitalik.eth.limo/general/2025/02/28/aihumans.html#4">https://vitalik.eth.limo/general/2025/02/28/aihumans.html#4</a></p><p><strong>本期文章由 LXDAO 成员 </strong><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/yewlne7"><strong>Yewlne</strong></a><strong> 翻译。</strong></p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>译文正文</strong></h1><p>特别感谢 Devansh Mehta、Davide Crapis 和 Julian Zawistowski 提供的反馈和评论。</p><p>如果你询问人们为什么喜欢民主结构，无论是政府、工作场所，还是基于区块链的 DAO 组织，他们通常会给出相似的答案：它们避免了权力的集中，给用户提供了强有力的保证，因为没有哪个人能够凭空改变系统的趋势，同时还能汇集众人的智慧，从而做出更好的决策。</p><p>但如果你问人们为什么不喜欢民主结构，他们通常会给出相同的抱怨：普通选民缺乏深度思考，因为每个选民对结果的影响微乎其微，只有少数选民会在决定前认真思考，此外我们通常会看到民主还会导致低参与度（低参与度会使整个系统容易受到攻击）或者大家都倾向于信任并模仿某个意见领袖，从而导致事实上的集权。</p><p>本文旨在探讨一种新范式，该范式有望借助 AI 来为我们提供民主结构的有点，同时规避其缺点。<strong>“AI 是引擎，人类是方向盘”</strong>。该模式下，人类仅需要向系统提供少量信息，或许仅有几百份数据，但每份数据都经过深思熟虑，且质量非常高。然后 AI 将这些数据视为“目标函数（objective function）”，并不断做出大量决策，尽力实现这些目标得以实现。<strong>特别地，本文还将探讨一个有趣的问题：我们能否在不将单一 AI 置于核心地位的前提下实现这一点，而是依赖于一个开放竞争的市场，让任何 AI（或人机混合体）都能自由参与其中？</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8efad602688c75dc91b6937264cf5b5eee9badfb4fb3576ebfe9fff72d3af10b.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>目录</strong></h1><ul><li><p>为什么不干脆让一个 AI 来负责呢？</p></li><li><p>Futarchy</p></li><li><p>蒸馏人类判断</p></li><li><p>深度资助 DeepFunding</p></li><li><p>增加隐私</p></li><li><p>引擎与方向盘设计的好处</p></li></ul><h1 id="h-ai" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>为什么不干脆让一个 AI 来负责呢？</strong></h1><p><strong>将人类偏好融入到基于 AI 的机制，最简单的方案是构建一个单一的 AI 模型，并以某种方式输入人类的偏好。</strong></p><p>有很多简单的做法，譬如：你可以只把包含人类指令的文本文件加入到系统提示（System prompt） 里。接着，利用各种“agentic AI 框架”赋予 AI 访问互联网的能力，并把你组织资产和社交媒体的控制权交给它，就大功告成了。</p><p>经过几轮迭代，这种方法对许多应用场景来说可能已经足够，我也深信在不久的将来会出现许多类似的框架，让 AI 们读取一群人所提供的指示（甚至是实时的群聊），然后据此采取行动。</p><p>然而，若将这种架构用作<strong>机构</strong>的<strong>长期性</strong>治理机制，就并不理想。<strong>长期存在的机构</strong>通常需要具备的一个宝贵特性，就是“可信中立性（<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nakamoto.com/credible-neutrality/">credible neutrality</a>）”。在我介绍这个概念的帖子里，我列出了四项对可信中立性非常重要的属性：</p><ol><li><p>不要将特定的人或特定的结果写进治理机制中。</p></li><li><p>执行过程应开源并可被公开验证。</p></li><li><p>保持简单。</p></li><li><p>不要频繁变动。</p></li></ol><p>无论哪一个 LLM（或 AI Agentic 框架）都达不到上面四项要求中的任一项。模型在训练过程中会不可避免地会学习了大量与特定人或特定结果相关的偏好。这会导致 AI 有时在一些意想不到的问题上表现出偏好。例如，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/DanHendrycks/status/1889344074098057439">最近有研究显示</a>，一些主流大型语言模型对巴基斯坦人的生命的评价远高于对美国人生命的评价（!!）。即便训练的<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://promptengineering.org/llm-open-source-vs-open-weights-vs-restricted-weights/">权重是公开</a>的，也依然算不上真正的“开源”，因为我们并不清楚在模型深处隐藏着怎样的“<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arstechnica.com/information-technology/2023/12/is-chatgpt-becoming-lazier-because-its-december-people-run-tests-to-find-out/">魔鬼</a>”。与“简单”恰恰相反，一个 LLM 的柯尔莫哥洛夫复杂度（Kolmogorov complexity）高达数百亿比特，和<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://law.stackexchange.com/questions/22502/how-many-lines-of-text-in-all-currently-active-federal-laws-of-us">美国所有法律（包含联邦、州与地方）加起来</a>的复杂度大致相当。而且由于 AI 改进速度极快，你可能每隔三个月就必须切换新的模型。</p><p>正因如此，我更倾向在许多场景中探索另一种思路：<strong>让一个简单的机制成为“游戏规则”，而由 AI 来担任“玩家”</strong>。这与市场经济能奏效原理异曲同工：市场的规则其实是一个相对简单落后的产权体系，各种边界情况通过法院系统通过不断积累和调整判例来裁定，而最后呈现的智慧都来自在“边界”试探的企业家们。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0bf31b5576dd3c08781ffd4399c846c9ffe3d6aa18e59138087c5221545b1bc8.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>具体到每个“游戏参与者”，它可以是 LLM 模型、可以是一群 LLM 相互协作并调用各种互联网服务、也可以是各种 AI + 人类的组合，乃至许多其他形态；作为机制设计者，你并不需要知道究竟参与者是哪种形态。理想情况下，我们希望这个机制能像自动装置那样运行 — — <strong>如果该框架的目标是决定要资助什么项目，那么它应当像比特币或以太坊的区块奖励那样，在运作方式上尽可能地自动化。</strong></p><p>这种方法的好处在于：</p><ol><li><p>它避免了将任何单一模型固化到机制之中；相反地，你获得的是一个由不同参与者和架构组成的开放市场，它们各自带着不同的偏见。开源模型也好、闭源模型也好、Agent 集群、人类 AI 混合体、“电子改造人”（cyborgs）、甚至“无限猴子（<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Infinite_monkey_theorem">Infinite monkey theorem</a>）”等等，都可以参与；<strong>机制本身并不歧视任何形式。</strong></p></li><li><p><strong>该机制本身是开源的。</strong></p></li><li><p>虽然具体的“玩家”并非都开源，但“游戏”是开源的 — — 这是一种已经相当成熟的模式（例如政党和市场经济都如此运作）。</p></li></ol><ul><li><p><strong>机制足够简单，因而几乎没有多少途径让设计者把他们的偏见塞进设计当中。</strong></p></li><li><p>机制不会改动，即便构建在其上的 AI 参与者的架构，可能每隔三个月就得重新调整一次，一直到奇点来临也不例外。</p></li></ul><p>该引导机制的目标在于忠实呈现指导委员会的内在目标。只需要提供很少的信息，但这些信息必须是高质量的。</p><p><strong>你可以将这个机制理解为利用了“输出答案”与“验证答案”之间的不对称性。</strong></p><p><strong>这就像数独（sudoku）很难解，但很容易验证结果对错一样</strong>。需要你（i）先创建一个开放的市场，让众多参与者来当“解题者”（ii）然后，只需维护一个由人类管理的机制，完成一个简单得多的工作：验证已有输出的解答是否正确。</p><h1 id="h-futarchy" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Futarchy</strong></h1><p>Futarchy 最初由 Robin Hanson 提出，其口号是“<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mason.gmu.edu/~rhanson/futarchy.html">对价值投票，但对信念下注</a>”。即一个投票机制会选择一组目标（可以是任何东西，但前提是它们必须可量化），并将它们整合成一个指标 M。当你需要做出决策时（为简单起见，我们先以决策仅有“是/否”作为例子），就设立条件市场（conditional markets），让人们对以下内容进行下注：(i) 最终选择“是”还是“否”；(ii) 如果选“是”，则 M 的价值是多少（否则为零）；(iii) 如果选“否”，则 M 的价值是多少（否则为零）。有了这三项变量，你就可以根据市场的下注情况，推断市场认为 YES 还是 NO 对 M 的数值更有利。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7530865bc35523876685e658315cca599b4980e28e91b7ff7aea8824622a2c5e.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>“公司股价”（或者，某加密货币价格）是最常被引用的指标，因为价格直观易懂又便于衡量。不过，这种机制也能支持多种指标：如每月活跃用户数、特定群体自评的幸福感中位数、衡量去中心化程度的某种量化指标等等。</p><p>Futarchy 最初是在前 AI 时代被发明的。然而，它与之前所描述的“求解者复杂、验证者简单”范式非常契合，Futarchy 中的交易者也可以是 AI（或人类与 AI 的组合）。在这里，“解答者”（即预测市场交易者）的角色是判断每一个输出方案在未来会如何影响某个指标的数值。这很难。如果判断正确，解答者就能赚钱；如果判断错误，他们就会亏钱。而“验证者”（对度量指标进行投票的人，如果他们发现指标被“操纵”或过时，就会对其进行调整，并在将来某个时刻确定该指标的真实数值）只需要回答一个相对简单的问题：“当前指标的数值是多少？”</p><p><strong>蒸馏人类判断（Distilled human judgement）</strong></p><p>蒸馏人类判断是一类机制，其运行方式如下：有一个非常庞大的问题集合（想象有一百万道题）需要作答。以下是一些直观的例子：</p><ul><li><p>在这个列表中的每个人，为某项目或某项工作作出了多大贡献，应得到多少额度的认可？</p></li><li><p>哪些评论违反了某社媒平台（或某衍生社区）的规定？</p></li><li><p>在这些给到的以太坊地址中，哪些真正代表了独立且真实存在的人类？</p></li><li><p>下列物理对象中，哪些对周围环境的美学有积极或消极影响？</p></li></ul><p>你可以设置一个“评审团”来回答这些问题，但这需要他们在每个回答上投入大量精力。你只需要让评审团只回答其中极少数的问题（例如，如果问题总数有一百万条，评审团也许只回答其中的一百条）。你<strong>甚至可以让评审团回答间接问题</strong>：不一定要直接问“Alice 在整体贡献中应占百分之几？”，也可以问“与 Bob 相比，Alice 应获得更多贡献度吗？如果更多，多几倍？” 在设计评审团机制时，你可以借鉴真实世界中成熟的模式，例如拨款委员会、法庭（评定判决的价值）或评估体系等等，当然，评审团成员也完全可以使用各种最新的 AI 研究工具来帮助他们输出答案。</p><p>接下来，你<strong>允许任何人对整个问题集合提交一个数值回答列表</strong>（例如，提供各个参与者应当得到多少贡献度的估计）。我们鼓励参与者使用 AI 来完成这项工作，但他们可以使用任何方法：AI、人类与 AI 的组合、具备互联网搜索能力、雇佣他人、或调用其他 AI 能力的 AI，乃至“机械改造猴子”之类等，都可以。</p><p>一旦完整答案列表的提交者和评审团都交出了各自的结果，就会将这些全量答案与评审团的答案进行对比，<strong>并从中选出最符合评审团答案的一个或若干个组合，作为最终结果。</strong></p><p>蒸馏人类判断虽与 Futarchy 不同，但也存在一些重要的相似点：</p><ul><li><p>在 <strong>futarchy</strong> 中，“<strong>解答者</strong>”是在做<strong>预测</strong>，而这些预测会与“<strong>真实数据</strong>”相比对（以决定对解答者的奖励或惩罚）。这里的“真实数据”是<strong>由评审团所管理的预言机输出的指标</strong>。</p></li><li><p><strong>在蒸馏人类判断中</strong>，“<strong>解答者</strong>”<strong>针对海量问题给出答案</strong>，而他们的预测同样会与“<strong>真实数据</strong>”进行对比。不同之处在于，<strong>这里的“真实数据”是一部分小规模且高质量的答案，由评审团提供</strong>。</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cc84b28a886fb49532b424a0457b248def519fd715fe3318097cbe92ecfbaca2.webp" alt="蒸馏人类判断在“贡献度分配”中的一个示例可以参见此处的 Python 代码。脚本会让你扮演评审团的角色，并且在代码中已经预先包含了部分由 AI（以及人类）生成的完整答案列表。该机制会找出哪种全量答案的线性组合与陪审团的答案最为匹配。在这一示例中，最终胜出的组合是 0.199×Claude + 0.801 × Deepseek，这一组合比任意单个模型的回答都更能贴合评审团的结果。这些系数同时也代表会分配给提交者的奖励比例。" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">蒸馏人类判断在“贡献度分配”中的一个示例可以参见此处的 Python 代码。脚本会让你扮演评审团的角色，并且在代码中已经预先包含了部分由 AI（以及人类）生成的完整答案列表。该机制会找出哪种全量答案的线性组合与陪审团的答案最为匹配。在这一示例中，最终胜出的组合是 0.199×Claude + 0.801 × Deepseek，这一组合比任意单个模型的回答都更能贴合评审团的结果。这些系数同时也代表会分配给提交者的奖励比例。</figcaption></figure><p>在“打败索伦（defeating Sauron）”这个示例中，“人类作为方向盘”体现在两个方面。首先，每个具体问题都需要高质量的人类判断，虽然这仍然依赖于陪审团作为“技术官僚”来评估表现。同时还存在一种隐含的投票机制，用于决定“打败索伦”是否真的是一个合理的目标（也许我们应该选择与他结盟，或者割让某条关键河流以东的土地来换取和平等等）。另外，还有许多采用蒸馏人类判断的用例，其评审团任务会更加直接地涉及价值判断：比如，可以想象一个去中心化的社交媒体平台（或衍生社区），评审团的工作就是对随机抽取的帖子进行标记，判断它是否遵守社区规则。</p><p>蒸馏人类判断（Distilled Human Judgement）范式中的一些未定因素：</p><p><strong>如何进行抽样？全量答案</strong>提交者负责<strong>提供大量答案输出</strong>，而评审团的角色是<strong>提供高质量答案</strong>。因此，我们需要合理选择评审团成员，并合理分配评审团需要评估的问题，使得模型与评审团答案的匹配度尽可能准确地反映其整体表现。在这一过程中，需要考虑以下因素：</p><ul><li><p><strong>专业性与偏见的权衡（Expertise vs bias tradeoff）</strong></p></li><li><p>经验丰富的评审团成员通常在其专业领域内具备更高的判断力，因此让他们自主选择要评估的内容，可能会带来更高质量的输入。然而，赋予评审团过多的裁定权可能会导致两种问题：过多的选择可能引入偏见（例如，陪审员可能偏爱与自己有联系的内容），或导致采样存在漏洞（某些内容可能会系统性地被忽略）。</p></li><li><p>反“古德哈特定律”策略（Anti-Goodharting）在AI系统中，总会存在试图“操纵”评分机制的内容，例如：某些仓库可能有大量看似精美但实际无用的代码，以博取更高评价。这意味着，评审团可能可以察觉到这些欺骗行为，而静态 AI 模型往往无法做到，除非进行了额外的优化。一种可能的解决方案是引入挑战机制（challenge mechanism），允许个体举报疑似操纵行为，并确保评审团会对这些举报内容进行评估。（从而激励 AI 开发者确保能正确识别这些问题）。如果陪审团认同标记者的观点，举报者将获得奖励；反之，则会面临处罚。</p></li><li><p>使用什么评分函数？在当前的深度资助（deep funding）试点中，一个正在使用的方法是让评审团回答：“A 和 B 之中，谁应该获得更多的贡献度？如果更多，应该是几倍？”对于评分函数，当前的一个计算方式是：</p></li></ul><pre data-type="codeBlock" text="score(x) = sum((log(x[B]) - log(x[A]) - log(juror_ratio)) ** 2 for (A, B, juror_ratio) in jury_answers)
"><code><span class="hljs-built_in">score</span>(x) = <span class="hljs-built_in">sum</span>((log(x[B]) - <span class="hljs-built_in">log</span>(x[A]) - <span class="hljs-built_in">log</span>(juror_ratio)) ** <span class="hljs-number">2</span> for (A, B, juror_ratio) in jury_answers)
</code></pre><ul><li><p>也就是说，对于每个陪审团提供的答案，该函数衡量全量答案中 A 和 B 的比值与陪审团给定的比值之间的偏差（在对数空间中），并以偏差平方作为差距惩罚。这表明<strong>评分函数的设计空间非常广泛</strong>，而具体使用哪种评分函数，与评审团被问及的具体问题密切相关。</p></li><li><p>如何奖励完整答案列表提交者？在理想情况下，希望让多个参与者都能获得非零奖励，以避免机制被单个提交者垄断。同时，我们还希望确保：一个参与者不能通过提交相同（或稍作修改）的答案多次来增加自己的奖励。一个有设计前景的方法是：直接计算最佳符合评审团答案的完整答案列表的线性组合，要求其系数非负且总和为 1，然后按照这些系数来分配奖励。此外，也可以探索其他方法。</p></li></ul><p>总体而言，该机制的目标是：利用那些已被证明有效、能最大限度减少偏见并经受住时间考验的人类判断机制（例如：法庭的对抗结构 — — 诉讼双方掌握大量信息却可能偏颇，而法官则信息有限但通常公正），并借助一个开放的 AI 市场，作为这些机制的高保真且低成本的预测器（这类似于大语言模型“蒸馏”的过程）。</p><h1 id="h-deep-funding" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>深度资助（Deep Funding）</strong></h1><p>深度资助是一种蒸馏人类判断（Distilled Human Judgement, DHJ）的应用，专门用于计算图结构中边的权重，即“X 的贡献中有多少百分比归因于 Y？”</p><p>最简单的方式是通过示例来说明：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/192416497c5ade056e6ec9e97b6e82ce980045bcd4fef9bd5ee140beecbdf84d.webp" alt="两级深度资助示例：以太坊的思想起源（Python 代码示例可参考这里）" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">两级深度资助示例：以太坊的思想起源（Python 代码示例可参考这里）</figcaption></figure><p>在这个示例中，目标是分配对以太坊哲学贡献的归属。以下是一个计算过程示例：</p><ul><li><p>模拟的深度资助轮次显示，赛博朋克运动（Cypherpunk Movement）获得了 20.5% 的贡献度，而技术进步主义（Techno-Progressivism）获得了 9.2%。</p></li><li><p>在每个节点内部，我们需要进一步问：这部分贡献有多少是原创的（即其自身应获得贡献度）？有多少是对上游影响的重新组合？例如，在赛博朋克运动（Cypherpunk Movement）中，有 40% 是新的原创贡献，而 60% 来自上游影响。</p></li><li><p>然后可以继续追溯这些节点的上游影响：自由意志主义的最小政府（Libertarian minarchism）与无政府主义（anarchism）贡献了 17.3% 给赛博朋克运动。瑞士直接民主（Swiss direct democracy）贡献了 5%。</p></li><li><p>值得注意的是，自由意志主义最小政府（Libertarian Minarchism）和无政府主义（Anarchism）不仅影响了赛博朋克运动（Cypherpunk Movement），还直接影响了比特币的货币哲学（Bitcoin’s monetary philosophy）。因此，它们通过两条路径间接影响了以太坊的哲学思想。</p></li><li><p>要计算自由意志主义最小政府和无政府主义对以太坊的总贡献，需要沿着每条路径计算权重，并将两条路径的贡献值相加：0.205×0.6×0.173+0.195×0.648×0.201≈0.04660.205×0.6×0.173+0.195×0.648×0.201≈0.04660.205×0.6×0.173+0.195×0.648×0.201≈0.04660.205×0.6×0.173+0.195×0.648×0.201≈0.0466这意味着，如果你打算捐赠 $100 来奖励那些为以太坊哲学做出贡献的人，那么根据这一模拟的深度资助轮次，自由意志主义最小政府和无政府主义的贡献份额为 4.66 美元。</p></li></ul><p>这种方法适用于那些工作建立在先前成果之上的领域，并且其结构清晰可追溯。两个自然的应用场景包括：学术界（类似于引用图谱，Citation Graphs）、开源软件（类似于库依赖关系和分叉机制，Library Dependencies &amp; Forking）</p><p>一个运行良好的深度资助系统，其目标是创建和维护一个全球贡献图（Global Contribution Graph）。在这个系统中：资助者（Funder） 可以向某个具体的项目节点发送资金。资金会根据图中的分支权重，自动传递到其依赖项（Dependencies），并且递归地继续分配到更上游的贡献者。这样，贡献者能够持续获得激励，而不仅仅依赖于一次性捐赠或早期奖励。</p><p>你可以想象一个去中心化协议，通过内置的深度资助机制（deep funding gadget）来发行其代币。在这个过程中，协议内的去中心化治理将选出一个评审团，该评审团负责运行深度资助机制，而协议会自动发行代币并将其存入与自身对应的节点。通过这种方式，协议能够以程序化的方式奖励其所有直接和间接贡献者，这类似于比特币或以太坊的区块奖励机制，它们曾奖励过某一特定类型的贡献者（矿工）。此外，评审团可以通过调整边的权重来持续定义其所认可的贡献类型。该机制可作为一种去中心化且可持续的长期方案，替代传统的挖矿、代币销售或一次性空投模式。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>增加隐私保护</strong></h1><p>在许多情况下，做出合理判断往往需要访问私密信息，例如组织的内部聊天记录、社区成员私下提交的信息等。对于较小规模的场景来说，“仅使用单个 AI” 的一个显著优势在于，相较于将信息公开给所有人来说，让一个 AI 访问这些数据更容易被接受。</p><p>为了在这些场景中实现提炼的人类判断或深度资助机制，我们可以尝试使用密码学技术，让 AI 在安全的前提下访问私密信息。具体而言，可以利用<strong>多方计算（MPC）、全同态加密（FHE）、可信执行环境（TEEs）等技术机制，使私密信息能被 AI 实用，但仅限于那些其唯一输出是“完整答案列表提交”，并将私密信息直接纳入机制处理的系统</strong>。</p><p>如果采取这种方法，那么需要将可访问私密信息的对象<strong>严格限定为 AI 模型</strong>，而不能包括人类或“AI + 人类”的组合，因为人类无法直接接触这些数据。此外，这些 AI 模型必须运行在特定的计算环境中，例如<strong>多方计算（MPC）、全同态加密（FHE）或可信硬件（trusted hardware）</strong>。当前的一个重要研究方向是<strong>探索在现实条件下可行的实用方案</strong>。</p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>“引擎 + 方向盘” 设计的优势</strong></h1><p>这种设计方案带来了许多有前景的优势。其中最重要的一点是，它能够构建一人类投票者负责设定总体方向，但不需要处理过多繁琐的决策的 DAO。它找到了一个皆大欢喜的平衡点，个人无需直接做 N 个决策，但同时他们拥有的权力也不局限于仅仅做出一个决策（传统委托方式通常仅要求做出一项决策），而且这种方式更能激发出那些难以直接表达的内在偏好。</p><p>此外，这类机制似乎还具备激励均衡（incentive smoothing）的特性。这里的“激励均衡”指的是以下两个因素的结合：</p><ul><li><p><strong>扩散性（Diffusion）</strong>：投票机制的任何单一决策都不会对某个特定个体的利益造成过大的影响。</p></li><li><p><strong>混淆性（Confusion）</strong>：投票决策与其对各方利益的影响之间的关联更加复杂，难以被精确计算或预测。</p></li></ul><p>“混淆（Confusion）”和“扩散（Diffusion）”这两个术语<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Confusion_and_diffusion">借鉴自密码学</a>，都是加密算法和哈希函数安全性的关键属性。</p><p>一个现实世界中激励均衡<strong>的典型例子是法治（rule of law）</strong>。政府的顶层决策者不会直接做出类似于“给 Alice 的公司拨款 2 亿美元”或“对 Bob 的公司罚款 1 亿美元”这样的具体决定，而是<strong>制定对广泛主体一视同仁的规则</strong>，这些规则再由独立的执行机构进行解释和实施。当这种机制运作良好时，它能<strong>大幅降低贿赂和腐败的驱动力</strong>，而一旦规则被破坏（如现实中经常发生的情况），这些问题便会迅速恶化。</p><p>AI 显然将会在未来占据重要的位置，而这也必然会成为未来治理的重要组成部分。然而，如果将 AI 纳入治理也会存在明显的风险：AI 存在偏见，在训练过程中可能会被恶意篡改，<strong>而且 AI 技术发展如此迅猛，“让 AI 掌舵”实际上可能就意味着“让负责升级 AI 的人掌舵”</strong>。蒸馏人类判断则提供了一条替代路径，让我们能够以开放的自由市场方式利用 AI 的力量，同时依然保持由人类主导的民主治理。</p><p>如果你有兴趣更深入地探索和参与这些机制，强烈建议你查看当前正在进行的深度资助回合，详情请访问： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://cryptopond.xyz/modelfactory/detail/2564617%E3%80%82">https://cryptopond.xyz/modelfactory/detail/2564617</a></p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[破茧而出：加密思潮十日谈结营纪实]]></title>
            <link>https://paragraph.com/@lxdao/raW76ki3TWQIbUr9lhpl</link>
            <guid>raW76ki3TWQIbUr9lhpl</guid>
            <pubDate>Wed, 19 Mar 2025 09:34:48 GMT</pubDate>
            <description><![CDATA[作者：Loxia 本纪实展现了 LXDAO 举行的加密思潮十日谈中的思想高光时刻。残酷共学参与者们从《主权个人》的预言出发，深入探讨了 Regen 思潮、区块链、AI 与现代信用在当下交汇的多重可能，构建起从去中心化理想到公共共识、从自由市场到未来治理的宏大图景。这不仅是一场知识的激烈碰撞，更是一场对传统秩序发起的深刻挑战与重构，彰显了大家对未来自由与持续进步的不懈追求。 围绕着密码朋克宣言和 Regen 思潮（Degen 的可持续进化版本，专注于再生，持续，重建）延伸出的思考资料，参与十日谈的伙伴们思考了很多，交流了很多，在这里我把笔记打散，重新排列成七个部分：《主权个人》的祛魅easyshellworld: 回忆 《主权个人》（The Sovereign Individual）是在加密货币出来之时代。如果严格意义讲该书，并没有成功预言到互联网主流的变化。曾经的 PC 互联网时代，很多东西确实如书中所说，垄断暴力的经济回报如何在信息时代下降，个体力量兴起等等（具体可见书中二、三章）。但是到移动时代的实际情况，由 Facebook 的等兴起泛熟人社会网络社交开始，打破网络的曾经纯...]]></description>
            <content:encoded><![CDATA[<figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d75e1c0279224752eacfe2cd85180eac3593627530aa63742b53858bf79246ac.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>作者：Loxia</strong></p><p><strong>本纪实展现了 LXDAO 举行的加密思潮十日谈中的思想高光时刻。残酷共学参与者们从《主权个人》的预言出发，深入探讨了 Regen 思潮、区块链、AI 与现代信用在当下交汇的多重可能，构建起从去中心化理想到公共共识、从自由市场到未来治理的宏大图景。这不仅是一场知识的激烈碰撞，更是一场对传统秩序发起的深刻挑战与重构，彰显了大家对未来自由与持续进步的不懈追求。</strong></p><p>围绕着密码朋克宣言和 Regen 思潮（Degen 的可持续进化版本，专注于再生，持续，重建）延伸出的思考资料，参与十日谈的伙伴们思考了很多，交流了很多，在这里我把笔记打散，重新排列成七个部分：</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">《主权个人》的祛魅</h2><p><strong>easyshellworld</strong>: 回忆 《主权个人》（The Sovereign Individual）是在加密货币出来之时代。如果严格意义讲该书，并没有成功预言到互联网主流的变化。曾经的 PC 互联网时代，很多东西确实如书中所说，垄断暴力的经济回报如何在信息时代下降，个体力量兴起等等（具体可见书中二、三章）。但是到移动时代的实际情况，由 Facebook 的等兴起泛熟人社会网络社交开始，打破网络的曾经纯虚拟化。向日常 real life 深度介入。好像整个网络时代彻底变了，慢慢由一朵朵的云在控制。只少数地方还能算上独立的地方。在移动网络时代，新几大巨头形成，谁逃过它们服务，甚至一些东西控制比以前更加空前增强，个人已经完全透明。本以为《主权个人》里面说的快成梦幻泡影了。但是在 2008 年金融危机后，2009 年 bitcoin 出现，视乎又有东西在挣脱新的枷锁。让网络在想办法重新去中心化，随之而以来一些系列基础设施出现，IPFS，EVM，libp2p 等等。涵盖了计算，存储，带宽等等。新的 Dao 组织开始实列化。解决了数字身份（did）,隐私保护，金融主权等一些现实基础问题。新时代又能做什么呢？未来加密世界会走向何方？</p><p>可能当今世界还是延续了二战后的框架，由 180 多国家与组织所构成，充满了矛盾与冲突。无法做到绝对的利出一孔。但是同时形成了很多缝隙。同时也是给加密世界给出一定生存空间。在这些缝隙与分歧中去寻找共识。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">无限花园的探索</h2><p><strong>AlexWaker</strong>: 我之前读过《有限与无限的游戏》这本书，对其的印象可以概括为“有趣但无聊”。“有趣”是因为我觉得这本书在抽象层面确实给了我不少震撼的观点，“无聊”怕是因为我没有看到这本书的内容对我有什么功利性的帮助。我相信马克思的观点“哲学不能只为了解释世界，更应该改造世界”，因此对于过于“形而上”的东西并不是很热衷。</p><p>但是，我从来没有将这个观点与区块链和 Web3 行业联系起来过。尽管在 Web3 被批判为“投机”、“噱头”之际，我隐约觉得把这个行业“持续地玩下去”才是硬道理，但依旧没有显式地联系到这本书。直到今天阅读了这篇文章，我有种顿悟的感觉。作为河南人，“赢”这个概念似乎从小就被老师灌输到了潜意识中。河南学子所有的目标都是为了高考，似乎只有高考赢了，你的人生才有意义。作为“卷王”，我似乎在高考前也没有怀疑过这个观点。我们总是热衷于“解决问题”、“达成目标”，当我们实现了目的之后，这件事就已经结束了。我们将这种思维应用到生活中，仿佛生活就是一件件事情等着我们去解决，但怎样是个尽头吗？每个人的财富自由么？</p><p><strong>Lillian</strong>: “無限花園”這種提倡所有參與者“以共好取代競爭”的非零和遊戲是以太坊基金會的偉大願景，也是和其他公鏈不一樣的地方。 我在 Devcon 期間，整個會場就是以無限花園為主軸。Aya Miyaguchi 的開場也是用“曼陀羅系統”這個東南亞在歷史上長達千年的特有政治體系作為無限花園概念的延伸 曼陀羅系統是每個國家沒有固定疆域，某地區的住民很可能同時為兩個國家以上的公民。 核心理念是在同一個框架內，容納多個政體，彼此交流、共享權力。即使成員民族多元，依然擁有共同的核心價值與身份認同。</p><p>Aya Miyaguchi 借用這個概念來比喻在無限花園裡，沒有單一領導者，以太坊基金會僅是其中一個組織並非整個生態的中心，其所提供的養分與資源，用來支持其他組織的發展：例如 L2BEAT、 Geodework 、 Argot Collective 、 0xPARC 和 Nomic Foundation 等。</p><h2 id="h-dacc" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">什么是 d/acc 呢？</h2><p><strong>JL_Lelouch</strong>:《Vitalik Buterin：我的技术乐观主义》中 AI 总是被认为与其他的技术不同，它有极大可能带来风险并导致失控。人们对 ai 的观念似乎两极分化，一部分人坚信它能超越人类智慧的极限，在各方面辅助人类，另一部分则认为它最终会走向控制人类的道路。人工智能会自行诞生出意义来吗？政治集权在数字化的当今愈发容易实现，人们逐渐都被纳入监管之下，这在之后是否会演变成少数精英利用技术控制整个国家，尤其是战争当中。</p><p>“不需要政治和意识形态工作和战争动员”意味着最高战争指挥官只需考虑战局本身，无需关心‘棋盘上的骑士和车’在想什么。战争变成了纯粹的技术比拼。</p><p>防御性治理，当冲突发生的可能性降低时，治理模式会更加自由开放。 人们更愿意对公共利益的益处投票，而不愿对害处投票，大家天然回避负面问题，以至于这类问题更容易到一个中央团体解决。</p><p>我热爱科技，因为科技拓展了人类的潜能。</p><p>计算机如何发展到人类智慧极限以上呢？怎么才能判断那是更智慧的生命体？智慧发展到一定程度是会更加包容的吗？还是说人们对人工智能可能的恐惧不在于它的智慧，而在于它不够智慧，却又格外强大？ 部分人或许愿意将脑意识与计算机结合，部分人或许不愿意，那最终人类会因此分层为不同的人种吗？</p><p><strong>Lillian</strong>: d/acc的長遠目標，是要建立人類具有“能動性”的世界。 能動性但表人類擁有消極與積極自由。</p><ul><li><p>消極自由：他人無法對能塑造自身命運的能力進行干涉。</p></li><li><p>積極自由：確保人類有知識與能力去實踐。</p></li></ul><p>他提到幾個方法：</p><ul><li><p><strong>Liability</strong>：讓使用者負責，用“稅”的概念讓開發者負責等等。 quote: 一个非常符合 d/acc 理念的想法是，对人工智能在执行某些灾难性有害行动过程中所接管（例如通过黑客攻击）的任何设备的所有者或操作者追究责任。这将创造一种非常广泛的激励，促使人们努力使世界（特别是计算和生物领域）的基础设施尽可能安全。</p></li><li><p>工业规模硬件上的全球「软暂停」按钮。這個想法很有趣，而且可以融入區塊鏈多簽的技術。多方機構每隔一段時間簽名才能讓機器繼續運行。但這樣的方法需積極行為所以只能發展在工業規模的硬體上 Quote: 平等地限制每个人，并努力实际合作来组织实施，而不是一方试图支配所有人。</p></li></ul><p>最後提到公共物品資助如何能幫助 d/acc 的發展。其中提到他最近的 deepfunding 項目， 如何用“依賴關係圖”及用開放的人工智能模型市場，訓練AI模型成為“精煉後的人類決策”。e/acc 的效率加速主義可能會帶讓AI發展成超級物種，與人類競爭。 而 d/acc 的 去中心化、民主與防禦的手段才有可能能在不遏制技術發展的情況下，朝對人類有長遠益處的方向前進。 我覺得所謂防禦其實是要加強「韌性」，在歌舞昇平的世界，即使在預料之外的災難發生時，也能有從容面對的骨氣與實力。</p><p><strong>luffythink</strong>: 当区块链 + AI 系统自动化执行伦理规则（如智能合约强制下架违规内容），人类是否丧失了“灵活判断”的空间？AI 算法是否会继承训练数据中的偏见，导致歧视性的结果？</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">摆脱叙事，逃离叙事</h2><p><strong>DarkingLee</strong>: “我不太喜欢 d/acc 这个概念，它包含了太多涵义，如果深究，几乎每一个都非常复杂，最终这些概念将演变为无脑化的标签。因此，这个概念更像一个缝合怪。什么是加速？什么是差异化？什么是多元？什么是防御？什么是意图？什么是自由？什么是民主？这里每一个概念都相当复杂，这必然将导致大量的误解，也许这是精英主义社区的协调问题的根源之一。”</p><p><strong>JL_Lelouch</strong>: 民主政府往往被妖魔化为“中心化”，而区块链则被神化为“去中心化”的理想。所以，请不要这样做。区块链没有主观系统，没有法律保护，没有福利系统等等 — — 它们绝对无法比较。 “福利”是指所有人的需求都得到满足，无论他们拥有何种手段。“中立”是指任何人都可以满足自己的需求，但他们可能需要手段来实现这一点。与此相关的是，加密货币中所谓的“公共物品”大部分都是“普通物品”。它们不具有排他性，因此任何人都可以使用它们；然而，在大多数情况下，加密货币是竞争性的 — 即供应有限，并且通常会拍卖谁可以使用上述服务。然而，最大的问题是福利是主观的。代码和加密不能是主观的 — — 只有我们可以。加密货币的独特优势 — — 中立性。加密货币真正的 USP 是稳定在僵化的区块链上的不可变协议。没有代币，没有治理，只有永久运行的客观代码，让那些有能力使用它们的人受益。加密的灵魂是什么？是建立在僵化的区块链上的不可变协议。这个概念的完美演绎可能是不可能的，但目标应该是尽可能接近。 但现实是，加密不仅仅是中立的 — — 它更像是一场社会运动，大量借鉴了我们历史上的运动。它更多的是关于人而不是代码。加密中的不同协议在不同程度上类似于传统社区、宗教、社交俱乐部、政府、合作社等；而有些则更倾向于中立/不变的一面。总是有一个范围。必须找到福利和中立之间的平衡。</p><p><strong>JL_Lelouch</strong>: MEV 创造的公共现实政治就是这样：任何权力结构都是可以接受的，只要它不妨碍任何人提出和实施替代方案的能力。“除非大社会转变为大社区，否则公众将一直处于衰落状态。只有沟通才能创造一个伟大的社区。我们的巴别塔不是语言的巴别塔，而是符号和象征的巴别塔，没有这些符号和象征，就不可能有共同的体验。”“最高级、最困难的探究，以及微妙、精致、生动和反应灵敏的交流艺术，必须掌握传输和流通的物理机制，并为其注入生命。当机器时代如此完善其机器时，它将成为一种生活方式，而不是其专制的主人。民主将自成一体，因为民主是一种自由和丰富共融的生活。”</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">信用与循环</h2><p><strong>easyshellworld</strong>: 在所有奥派的理论体系中都有一个前提条件，就是有效充分竞争的开放市场下（其实就是没有任何规矩与限制市场，当然这点也不完全现实）。如果从这么看，放大到国际市场，其实货币的竞争就是这原始样子。因此，如果按照奥派的理论，在宏观市场上不可能出现格雷欣法则（“劣币驱逐良币”）。</p><p>在凯恩斯主义者或 MMT 主义者眼里这事又可能是一个局面。通俗点就是，他们手里又没钱（甚至可能负债累累）但是又想出于一些目的（比如拉动经济，拯救市场，增加公共福利等等）干一些事情。那么办呢？他们就会祭出货币数量论，货币流通速率，货币创造，财政政策优先，通货膨胀控制，货币主权等一套套理论，进行一系列的数量管控（其实某些措施与手段就现实看是先进的手段）。首先就是构建封闭市场（破奥派与其他自由派的开放市场），制定详细的新的游戏规则，拉高某些主权信用权重（其实现代货币背后只剩信用），再进行下一步信用扩张（严谨上理论不可能创造信用，信用不来源自己，而是来源于别人），从而创造出来新的货币（这有钱去干事情了）。再回过头来看，他们首先破开放市场，然后构建特定条件（法律强制兑换等等）。这就不符合了格雷欣法则 — — 劣币驱逐良币！最后！引用《赤字迷思》里面一段话：说到底，作为货币发行者的 GOV 想要的是真实的产品，而不是货币本身。GOV 想要的不是我们的税款，而是我们的时间。</p><p>信用 — — 现代货币早已全是信用化货币，甚至 BTC 也在开始向信用化转变与集中（比如：把 BTC 存在交易所里，交易所拿着这 BTC 去借贷或其他）。关键最终的信用来源都是源于他人的授权（授信，比如：他人承诺借给你多少额度借款，自身才能增加多少额度信用，如果他人抽回这个额度，同时也就丧失这个信用），从不来自于自身。今天 Bybit 也遭遇信用危机（当然它的这个事情是由其他事件，引发信用危机）。其实这些年的出现金融危机哪一场不是因为信用滥用或信用扩张。说白了，就是负债危机（还不上了呗）。再想想凯恩斯或 MMT 的做法，其实就是再拉人头多借点(等通货膨胀或者科技爆发，再还上)，不能现在就是 Dead (关键前提要还有信用)。最后是凯恩斯那一句名言：长期来看，我们都死了”（”In the long run, we are all dead”）</p><p>回溯 — — 自 2008 年金融危机来，金融危机专家伯南克（他在大学就是教金融危机的）为拯救经济衰退直接开启坐直飞机撒钱模式（芝派大佬弗里德曼的一语双关的评论，伯南克升官也快），开启量化宽松（QE），大量热钱四处涌出，基础物价上升抬头。造成了公众对银行业，华尔街（首先拿到钱的人，而且很多事情本是它们玩砸了，现在它们又活过来了，还赚钱了）极端不信任。2009 年，中本聪发布了那个 bitcoin 的白皮书，旨在构建一个无需中间信任（其实就是信任银行）的电子现金货币系统。这儿好像与现代货币的信用无关。但是出一种新而又古老的信用形式共识。bitcoin 利用工作量证明（POW）构建一条时间不可逆向（block）的共识诚账本链（chiain of block）。只要相信这个共识账本的人投入工作量证明或者钱就能使用无需中间信任的共识现金系统，某种意义其实也可以算是一种授信，由传统授信给银行，授信给主权国家，变成了可以授信给一个共识账本。大约在 2012 年，ripple 系统（最早叫 opencoin ,号称也是 blockchain 技术，新一代 bitcoin 云云，早期背后其实是 SQL，还丢过 block，不过现在还是市值排第三）出现。ripple 提出一个 IOU 概念（其实就是打白条，债权）。在 ripple 里面任何人都可以授信别人接受别人一定额度 IOU (有点像现在代币 token)。这些 IOU 都能在 ripple 的网关里面自由撮合交易，只要有人买卖就行，价格看双方当事人意愿就行。任何账户都能接受别人授信发行，而且没什么限制，也没什么具体上限,只要别人愿意接受多少额度（早期就是这样的，这儿感觉视乎完美体现信用思想，这也是与后面 token 不同地方，其实发它比发 token 容易多了，鼠标点几下就行，现在的 ripple 好像模式变了）。到 2015 年 7 月以太坊主网上线，构建 EVM 虚拟机，11 月提出 ERC20 标准，以 solidity 代码形式详细描述如何发行 token，而且以后的 ERC20 token 代码以智能合约形式只要上了主网发布以后就自然形成约束了功能与限制（当然能够升级合约的，重启限制）。规范了后面代币发行。某种意义，授信其实是给到具体智能合约。后面 eos，tron,solana 等等公链，在代币发行思想上基本大同小异，主要逻辑功能也差不多。再说一下，权益证明(POS) — — 与者根据持有的代币数量和时间（即“权益”）获得记账权,最早是 2012 年由 Sunny King 和 Scott Nadal 提出的，应用在 Peercoin (其实它当时采用还是 PoW 和 PoS 的混合机制)。真正把 POS 发扬广大是 BM 父子（Daniel Larimer 与 Stan Larimer，搞了几个项目 BitShares 跑路（彻底去中心化），Steem 跑路），但是在 EOS 项目上名气大获得成功，他们家其实主要采用是 DPOS (委托权益证明),说白就是别人把手里钱（代币）借给别人（节点）进行帮忙投票，记账，验证什么的（收益节点按比例分成给出钱的人）。其实 POS 完全是授信予节点的过程。现在某种意义 POS 已成为 web3 的主流（ETH 已经切换到 POS,很多后来公链的共识都多少糅合了 POS）。其实整个加密市场一直也是在信用与信任打转转。不过还多一个东西广泛的的共识。</p><p>香火 — — 现在又回到凯恩斯主义者与 MMT 主义者身上了，他们不是没钱了。现代货币都是靠信用，信用不可能凭空产生，他们总得找人授信！就算是神，也得接受人们的香火朝拜才能有力量吧。在原来的大环境下，流通性资产只能去银行，股市，债券等地方，最终基本会流向在“凯恩斯主义者”或“ MMT 主义者”与其支持者手中。那这么玩！MMT 理论自然成立了，内循环构建完备。就算不行把流动性资产投向他们都不行，会采取各种手段限制其他流通（限制实物交易量，对贵金属流通进行管制，设卡抽取重税），拉高变量（比如：利息，其实利息是由市场决定的，但是主权货币都是他们印的，花代价变量可调节），推动重资产沉淀通胀（比如房产或其他稀缺资源）。总之就是在圈内玩变量。现在加密世界出现了，也许因为加密与网络化，可以不授信于他们了，有了其他选择，可以到一个更大自由市场。这好像才是真正断了他们香火与篱笆。其实从历史看这不是什么新鲜事，在没有垂直与网络治理年代，其实也有法国第三阶层崛起，不投票不纳税的事情。过去与现在也许是一对矛与盾上技术升级。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">未来是怎样的？</h2><p><strong>OneShellWorld</strong>: 可能，也算终极幻想！其实以现代技术为基底，某种意义也能出现一种超级结算系统。说简单就是人人可能都发 token 代币。这个代币与他个人信用或她的生产产品、劳务背书。比如某人要出售 3 把斧头（他是生产斧头的），需要购置两个凳子。传统做法，使先把斧头换成货币，再用货币去换凳子（当然里面有差价由货币量化了）。在超级结算系统中，他拿着自己手里的斧头 token 直接去 swap 凳子 token 就行系统会自动经过节点与多中间商进行撮合交易（当然涉及报价问题，可能需要找零，或者斧子 token 不足的问题），中间是不是就没了，传统的货币了。但是！也可能出现一种情况，本来日常生产产品与劳务，自己本身用不完，也没什么其他东西需要换的。这个时候，人们很可能拿着自己手里这些“多余”产品 token，去换“理财产品” token，或者投资其他高利项目,来赚取回报。这个又是一个授信过程了。流动资产又会被集中，主流人们手里又是一堆“理财产品” token。好像故事又回去了。</p><p><strong>Lillian</strong>: 在越來越充滿各種可能性與未知時，我們更需要加強協作與保護隱私，讓人成為人工智能的方向盤，帶領人工智能這個引擎到達讓更多人共好，而不是少部分人得利的未來。</p><p><strong>AlexWaker</strong>: AI 到底能不能完全取代人类？目前来看似乎是不能的。然而狂热的“AI 教徒”却在奋不顾身地实现着“通用 AI”的宏伟目标，似乎要取代所有人类的工作。我并不知道这在技术上是否可能，但我希望不能，至少给我们平凡人一些存在的价值意义。AI 时代，“劳动价值论”或许会更加流行。人类生产的东西 — — 相比 AI 的产出显得那么拙劣 — — 凝聚了人类的劳动，它才显得珍贵。</p><p><strong>DarkingLee</strong>: 《主权个人》预言的“数字游民”本质上是新型的信源节点 — — 他们利用加密技术（抗噪编码）和全球带宽（互联网）绕过传统的中心化信道（民族国家），实现点对点价值传输。这种“主权”的转移，恰如香农定理中通过提升信噪比（S/N）突破信道极限：当个体能够直接访问高带宽低噪声的全球网络时，传统权力结构（低效中继器）必然会被边缘化。</p><p>未来文明可能呈现“分形信道”的特征：既有主权国家的广域低速网络（维持基础秩序），也有 DAO 组织的局部高速链路（承载创新）。而是否能在噪声中筛选出创造性的信息（如 MEME 币背后的社群共识），将决定人类是否陷入热力学熵寂，还是迈向信息论意义上的“负熵文明”。</p><p><strong>Oscar</strong>: 在 97% 的人类历史中，所有人都拥有大致相同的权力和获取商品的途径。</p><p>平等的、合作的人类社区是可能的。广泛的分享和协商一致的决策并不违背 “人性”（不管你认为人性是什么）。事实上，在人类历史的大部分时间里，我们都生活在这样的社会里。但这样的社会并不具有内在的稳定性。这些社会实践有赖于积极的防御。</p><p>我们不可能回到更新世的平等，很多人也不会接受那种生活的社会亲密性和物质简单性。但我们确实有新的社会技术。而值得警惕的是，很多国家正在展示这些新的社会技术如何被用来加强精英的监控。让我们希望它们能够被重新配置，以支持更多自下而上的行动，以减轻财富和权力不平衡的一些影响。</p><p>任重 DAO 不远。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">我们往何处去？</h2><p><strong>AlexWaker</strong>: 昨天看到一句话：“最好的工作是自由市场中持续学习者的创造性表达”（The best jobs are creative expressions of continuous learners in free markets）这句话出自《纳瓦尔宝典》(The Almanackof Naval Ravikant)。我想这句话把我所有想说的都给概括了：“自由的代价是永远的警惕”。最令人羡慕的工作就是自由工作者，他们可以无拘无束地在天地间任何地方工作。然而，这种自由需要永恒地警惕，他们必须持续学习，跟进市场迭代。</p><p>善于持续学习之人，是自由市场的精灵，而我愿意成为这样的人。</p><p><strong>AlexWaker</strong>: 我突然觉得，本次残酷十日共学的意义，或许就是让我完全摆脱 AI，自由自在地写作。一句话中主谓宾、定状补的合理运用，“的”“地”“得”的正确用法，这些被人忽略的细节，我想我们需要捡起来。</p><p><strong>AlexWaker</strong>: 技术迭代的规律是什么？为什么伟大不能被计划？</p><p><strong>Oscar</strong>: 希望未来能看到产品市场契合点的整合、可持续扩展解决方案的成熟（使用有效性证明等），更多的混合消费者应用程序，具有无缝用户体验和深思熟虑的应用场景，而不是模糊的宏伟愿景和空谈。</p><p><strong>Oscar</strong>: 当区块链 + AI 系统自动化执行伦理规则（如智能合约强制下架违规内容），人类是否丧失了“灵活判断”的空间？AI 算法是否会继承训练数据中的偏见，导致歧视性的结果？</p><p><strong>DarkingLee</strong>: 我们应该比理想主义更投机，比投资分子更理想。</p><p>最后，现在的行业极其脆弱，不能把希望寄托到一个商人的喊单，要对抗虚无主义，加密货币需要正外部性，加密货币需要真实应用，只有扎根寻找到可持续的真实需求，发展新的应用，才能找回密码朋克的初心，才能达成 Regen 的愿景！</p><p>共勉！</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c804245bc92a5f6a0b03c41ee1e6383b5b4be99be399c53410768bddc18cd749.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结营通讯</h2><p>在本次加密思潮十日谈残酷共学中共有 24 人参加，打卡挑战成功 8 人，其中全勤超残酷 3 人，残酷淘汰率高达 66.7%！</p><p>经讨论和商议，本期残酷共学中的 60 LXPU 奖励将发放给 OneShellWorld，作为对笔记中多次突破材料文档的边界，越狱认知的激励，其笔记内容可通过下方链接查看。</p><p>OneShellWorld 笔记：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/IntensiveCoLearning/Ten-Days-on-Crypto-Ideology/blob/main/easyshellworld.md">https://github.com/IntensiveCoLearning/Ten-Days-on-Crypto-Ideology/blob/main/easyshellworld.md</a></p><p>更多本期残酷共学笔记：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/IntensiveCoLearning/Ten-Days-on-Crypto-Ideology">https://github.com/IntensiveCoLearning/Ten-Days-on-Crypto-Ideology</a></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f14741a608d11bf8c3c46344b38a2c7c6ea94b32cf0e19193af399bf86ccca08.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>内容 | Loxia</p><p>编辑 &amp; 排版 | Yewlne、环环</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e1e81c5baa4f4ef52aba648116ab4370da854c3e46b6e8fef36816ffba6a5f7d.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[网络教育新范式？深度探讨 Network School 体验与未来]]></title>
            <link>https://paragraph.com/@lxdao/network-school</link>
            <guid>6oKPb8jzBrpn1pvU0aYT</guid>
            <pubDate>Wed, 26 Feb 2025 12:10:25 GMT</pubDate>
            <description><![CDATA[在互联网与区块链技术的交汇处，传统的教育方式正在面对一场深入骨髓的变革。由 Balaji 等人发起的 Network School，以“Learn, Burn and Earn”为核心理念，尝试将去中心化思维运用到教育和社区建设中，为来自全球的学习者和创作者打造一个跨国界的“流动式课堂”。本文邀请了三位亲历者 — — Adria、Twone 和定慧，从他们的学习动机与心得，到对 Network State 理念的思考，带你全方位了解这一场兼具理想主义与实践性的教育实验。为什么选择 Network School作为全球领先的发达国家之一，美国在加密货币领域的政策制定无疑值得我们深入审视和研究。美国的加密货币政策不仅会直接 当 Adria 在社交媒体上看到“我们搞了一个岛”这句招募宣言时，她立刻感觉这正是自己想要的地方。与其说这是经过反复权衡后的决定，不如说是瞬间的冲动与直觉吸引。对她而言，相比传统教育中更为被动的“知识接收”，Network School 更鼓励主动创造，每个人都可以在交流中扮演老师的角色。 另一方面，Twone 最初在阅读《The Network State》一书...]]></description>
            <content:encoded><![CDATA[<p><strong>在互联网与区块链技术的交汇处，传统的教育方式正在面对一场深入骨髓的变革。由 Balaji 等人发起的 Network School，以“Learn, Burn and Earn”为核心理念，尝试将去中心化思维运用到教育和社区建设中，为来自全球的学习者和创作者打造一个跨国界的“流动式课堂”。本文邀请了三位亲历者 — — Adria、Twone 和定慧，从他们的学习动机与心得，到对 Network State 理念的思考，带你全方位了解这一场兼具理想主义与实践性的教育实验。</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/db86daa9fb3fa94d06291b75e919d514bb5d75355f22fc5b7f164797a0038ecf.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-network-school" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>为什么选择 Network School</strong></h2><p>作为全球领先的发达国家之一，美国在加密货币领域的政策制定无疑值得我们深入审视和研究。美国的加密货币政策不仅会直接</p><p>当 Adria 在社交媒体上看到“我们搞了一个岛”这句招募宣言时，她立刻感觉这正是自己想要的地方。与其说这是经过反复权衡后的决定，不如说是瞬间的冲动与直觉吸引。对她而言，相比传统教育中更为被动的“知识接收”，Network School 更鼓励主动创造，每个人都可以在交流中扮演老师的角色。</p><p>另一方面，Twone 最初在阅读《The Network State》一书并关注兄弟社区四海后，便对数字游民、去中心化治理等话题产生了浓厚兴趣。他在清迈的本地化社区体验让他看到了Network School在线下“落地”的可能性。</p><p>定慧则一直深耕 Network State 领域，系统研究过多个相关项目，并期待在“线上 + 线下”综合环境中验证这些想法的可行性。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>学习之外的人脉与灵感</strong></h2><p>Adria 虽只在 Network School 停留了 16 天，却收获了远超预期的见闻。每天仅有两场官方讲座，更多时候是由学员们自发组织的分享、交流以及运动活动。在这样自由开放的氛围里，她结识到许多有趣的“大神”，例如做 AI 动画工具的朋友，帮助她拓展创作思路。与此同时，与来自世界各地的伙伴切磋和讨论，也让她看到了更广阔的文化图景。</p><p>Twone 将这种多元体验归纳为“Learn、Burn、Build、Play”四大模块，每个人都能按照兴趣灵活参与。无论是小规模工作坊还是大型主题活动，都为他带来了新鲜的灵感碰撞，也锻炼了他更主动、更自信地展示自己。</p><p>定慧则坦言，语言最初带来的焦虑感，让她意识到提高英语听力的重要性；可正因这种国际化的高密度交流，她在项目调研时获得了来自俄罗斯、欧洲等地区的即时反馈，视野随之打开。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>分阶段建设与培养</strong></h2><p>在第二期 Network School 的 newsletter 中，官方提出通过阶段性培养与筛选，逐步推进 Network State 理念。Adria 认为，这在本质上是寻找那些对未来充满热忱且能认同去中心化价值观的人，因为 Network State 的目标相当理想化，需要志同道合者一起前行。Twone 同样指出，这是一次“价值观凝聚”的过程。面对日益多元化的未来社会，Network State 要走多远，取决于核心成员能否彼此联结、协作，最终形成真正的社区合力。定慧 补充，地理和经济现实或让进程慢于预期，但只要技术和外部资源能够跟进，Network School 与 Network State 依然存在广阔发展空间。</p><h2 id="h-web3" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>学员对 Web3 生态的推动</strong></h2><p>由于相当多学员本身活跃于 Web3 领域，Network School 内时常涌现关于 Token 或 DeFi 的项目研讨。有时候，一条“内幕消息”甚至能在社群内快速传播，让更多人“下场体验”加密世界的高风险与高收益。Adria 曾看到有人借 Network School 的名号发行 Meme Token，也有 Token 上线后价格骤跌，让部分人亏损。她认为，这一系列事件既体现出学员们的创造活力，也折射出投机属性，需要理性看待。同时 Twone 提醒大家别给“Network School 学员”贴上绝对的标签，“他们是否能持续推动 Web3，最终还是要看各自的方向与选择。”但不可否认，这种高密度、跨文化的碰撞，对于培育未来创新土壤大有裨益。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>给未来申请者的建议</strong></h2><p>针对想要报名的朋友，Adria 首先建议先问清自己：“你来这里主要为了什么？”若想系统学习门类知识，Network School 可能并不适合；但若你渴望跨领域灵感碰撞、结识充满创造力的同伴，这里的氛围会让你大呼过瘾。Twone 提醒：“保持经济独立、时间灵活”很重要。如果没有远程工作或足够积蓄，面对学费与当地生活成本会相当吃力。此外，语言能力是与全球伙伴交互的基础，英语水准较高才能更有效参与。定慧 也强调：“越早申请机会越大。”她观察到首批录取名单中，很多人都是在招募信息发布后 48 小时内就提交了申请表。</p><h2 id="h-network-state" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Network State 的未来展望</strong></h2><p>三位学员普遍认为，Network State 仍在早期探索阶段，具体能否对传统主权国家形成大规模替代或补充，要视宏观环境与政策监管而定。Adria 表示期待这种“全球分布式结点”能够扎根世界各地，连接成跨国网络。Twone 认为会有更多小型、多元化社会并存，共同与传统国家治理模式互动。定慧 则强调，这需要十年甚至更长时间的投入，兼具技术落地与人文信仰，以及对混乱与不确定环境的适应能力，才能真正实现 Balaji 所描绘的愿景。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>总结</strong></h2><p>Network School 与 Network State 的出现，代表了一种超越传统教育与社会结构的新尝试。无论是源自对区块链与去中心化理念的信仰，还是对全球化创新教育的渴望，都让越来越多的人愿意投入时间与资源，亲身体验并推动这一进程。正如文中三位学员所言，他们的故事或许只是冰山一角，但所折射出的热情与多元探索，正是这场全球新兴运动的真正能量所在。无论 Network State 未来能否成为一种主流模式，它对当代社会的启示意义已不可忽视。</p><p>内容 | 古忆</p><p>编辑 &amp; 排版 | Yewlne、环环</p><p>设计 | Daisy</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b65f3129c35a8c60ed33be8c60dec767535c57042afff902c89bbfbd4bb1c925.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[LXDAO 翻译：如果 Web3 真能管事儿 ]]></title>
            <link>https://paragraph.com/@lxdao/lxdao-web3</link>
            <guid>GRVxAbub702EA8dCKzVc</guid>
            <pubDate>Mon, 17 Feb 2025 00:34:16 GMT</pubDate>
            <description><![CDATA[本期带来来自 BlueYard Capital 发布在 Youtube 的视频 《Jon Wu (Aztec) @ If Web3 is to Work… A BlueYard Conversation》原视频链接： 柠檬问题与信任危机今天我不打算过多谈论技术，我要讨论的是我们在加密领域面临的一个社会问题。这次演讲的标题是“社会共识与自我监管”。我想先问一下大家，有没有人听说过“柠檬问题”？这个词有印象吗？ 好吧，不太有印象，不太有。 那么，美国俚语中的“柠檬”指的是一辆不可靠的车，而且是你事先不知道它会不可靠的车。我不太确定这个词的起源，但“柠檬”就是这个意思。 嗯，好车、可靠的车被称为“桃子”。这个我之前也不知道，是查了才知道的，挺可爱的。 “柠檬问题”基本上是二手车经销商的问题。你去二手车市场，看起来有点像这样，感觉有点骗人的味道，因为你不知道你买的车会是“桃子”还是“柠檬”。这也是当今加密领域的一个大问题 — — 一切看起来都可能是“桃子”，但实际上很多协议都是“柠檬”。所以，当你买车或使用一个协议时，有一定的概率它是“桃子”，也有一定的概率它是“柠檬”。那么你愿意为此支...]]></description>
            <content:encoded><![CDATA[<p><strong>本期带来来自 BlueYard Capital 发布在 Youtube 的视频 《Jon Wu (Aztec) @ If Web3 is to Work… A BlueYard Conversation》原视频链接：</strong></p><div data-type="youtube" videoId="o17GnPJXxgU">
      <div class="youtube-player" data-id="o17GnPJXxgU" style="background-image: url('https://i.ytimg.com/vi/o17GnPJXxgU/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=o17GnPJXxgU">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>柠檬问题与信任危机</strong></h2><p>今天我不打算过多谈论技术，我要讨论的是我们在加密领域面临的一个社会问题。这次演讲的标题是“社会共识与自我监管”。我想先问一下大家，有没有人听说过“柠檬问题”？这个词有印象吗？</p><p>好吧，不太有印象，不太有。</p><p>那么，美国俚语中的“柠檬”指的是一辆不可靠的车，而且是你事先不知道它会不可靠的车。我不太确定这个词的起源，但“柠檬”就是这个意思。</p><p>嗯，好车、可靠的车被称为“桃子”。这个我之前也不知道，是查了才知道的，挺可爱的。</p><p>“柠檬问题”基本上是二手车经销商的问题。你去二手车市场，看起来有点像这样，感觉有点骗人的味道，因为你不知道你买的车会是“桃子”还是“柠檬”。这也是当今加密领域的一个大问题 — — 一切看起来都可能是“桃子”，但实际上很多协议都是“柠檬”。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/91b74bd7d7ced20150053e35c9d8f985ebe88a59a2dabe467287bc0eb815583b.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>所以，当你买车或使用一个协议时，有一定的概率它是“桃子”，也有一定的概率它是“柠檬”。那么你愿意为此支付的价格是多少？你愿意为可能成为“桃子”或“柠檬”的东西支付的期望值加权平均价格是多少？</p><p>你愿意为此支付的价格是多少？这就像某种加权平均，我们都可以内化这个概念 — — 有一定的“柠檬”概率乘以“柠檬”的价值，加上“桃子”概率乘以“桃子”的价值。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/30a70c97699fb5e365a50a1ddc2b8aab643502a86a650aee248be964e3ced0ed.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>你可能会直觉地认为，你愿意支付的价格介于你知道它是“桃子”时愿意支付的价格和你知道它是“柠檬”时愿意支付的价格之间。那么，为什么这是一个奇怪的动态，我们为什么要谈论水果呢？</p><p>那么，这对二手车经销商有什么激励作用呢？如果你知道每个人都会支付介于“桃子”和“柠檬”之间的价格，你的激励是什么？</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3bf9575a0fe65f3f3377c052f37f4b6ec89ca61f4556ffc6d4afc491745bfc9c.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>你的激励应该是只卖“柠檬”，对吧？如果人们愿意支付比“柠檬”更高的价格，你没有理由卖“桃子”，你可以直接把“柠檬”卖给他们。</p><p>这通常被称为骗局。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/449ac179c37442b0b17160f882d30cddffd36d455c9b43817870c916bf8ce4f2.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>嗯，我想暂停一下，这是加密领域今天面临的一个大问题 — — 柠檬问题。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/08e39c67eae6f2e0a02b0090dcd7004bfa674a68354d7d93c7cbe4467e49382c.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>嗯，今天加密领域的动态是，由于这个柠檬问题，“桃子”的概率实际上下降了，越来越少的人愿意种植“桃子”，因为“桃子”很昂贵，而“柠檬”经销商涌入市场，因为他们觉得“哇，我可以直接把‘柠檬’卖给那些愿意为我的产品支付比它实际价值更高价格的人，因为他们被误导认为这是‘桃子’。”总的来说，用户参与生态系统的意愿下降了，这很合理。</p><p>现在我可以在脑海中听到你们中的一些人，或者你们想象中的对话者说：“这就是无许可的代价，我们必须接受好坏参半，就像加密领域的 30% 折扣一样，你知道这就是现实。”</p><p>但这并不是一次性成本，柠檬问题不是一次性成本，它实际上是一个死亡螺旋。</p><p>因为当我们信任度降低时，“桃子”更难胜过“柠檬”，“桃子”退出市场，我们只剩下“柠檬”，这不是一个好地方。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/dff0322756d98b46e9d21de90b0a76ab8bc116c0394de55de28b47ba23e71938.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>所以我们需要以某种方式帮助消费者识别“柠檬”，我想说，如果我们不这样做，Gary 会 — — 事实上他已经很努力了 — — 所以这就是为什么我推动，如果我们想保持我们在加密领域发展的精神并解决柠檬问题，我们需要某种形式的自我监管。</p><p>让我们把这个和做得好的东西做个比较，这可能会引起争议</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>赌场模式：构建安全与公平的信任机制</strong></h2><p>好吧，我在说什么？</p><p>所以我是说加密领域就是个赌场吗？</p><p>不，我是说加密领域<strong>甚至不如</strong>赌场，</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/69cf928e7cc93efd7b31e76d942fd28cd65b2601da9e96a9fcabf792f9090d46.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>我们至少需要做得和赌场一样好。如果加密货币能行，</p><p>我们<strong>至少</strong>需要做赌场做得好的事情，</p><p>我认为这值得一看，这是我接下来要讲的。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0eb3fd17e3b9b84088a16bd671c201ec2cf6bf98a5d79ad99c6f43f77650065d.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>赌场以公平和安全著称，他们大力宣传这一点。他们为什么要这么做？他们不遗余力地证明赌场没有被操纵，当然除了它显然被操纵的方式。</p><p>让我给你们举几个例子，这是一台自动计牌机。</p><p>嗯，他们为什么要这么做？为什么他们改用这个而不是让发牌员手动发牌？</p><p>他们想向你证明你没有被欺骗，当然除了你在结构上被欺骗的方式，但他们想向你证明这是可验证的随机性。</p><p>他们禁止作弊者，并与其他赌场分享作弊者信息。为什么他们愿意联合起来对付作弊者？如果我是火烈鸟赌场（拉斯维加斯的一个赌场），我发现了一个作弊者，为什么我要与赢家分享这些信息？</p><p>他们有这些骰子卡尺来确保骰子重量均匀，所有这些都是为了说服消费者，你没有被骗，你在公平地玩，尽管胜算对你不利，但你不会被骗或受骗。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/48c3287d09fe603fe289628c9555e8f57afcb64eadd10e58e5bf67428d554138.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>所以我们需要以去中心化的方式做到这一点。我们知道一个事实，这周我没有在任何对话中听到的三个字母 — — FTX，没有人谈论它，我们喜欢假装它只是一个噩梦，你知道，坏人真的侵蚀了整个生态系统的信任，不仅仅是他们针对的人，而是所有人。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>零知识证明驱动的自我监管与社会共识</strong></h2><p>但我们有技术来证明安全和合法性，我们只需要在社会层采用它，所以这周必要的挥手 — — 零知识，对吧？这是我们都知道的词。</p><p>我们有能力证明完整性，证明身份、声誉和计算的完整性。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/121bb46d4ff79e6b0cf166453b7645f4b052d491b918f4fb8e859ec76532230a.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>问题不在于技术，我们不断参加这些会议，不断谈论技术，部分问题实际上在于社会共识和意识形态。</p><p>我们知道我们有能力围绕保护应用程序和用户创建新的社会共识形式，我们需要接受这是我们必须要做的事情，我们需要自我监管，然后才能被他人监管。</p><p>所以我认为我们在意识形态上非常极端，要么完全无许可，要么完全许可，非黑即白，非此即彼。</p><p><strong>但实际上，在这之间有非常广泛的社会共识光谱</strong>。</p><p>让我给你们举个例子，ZK 和 ASIC 最终将要研究的东西可以解锁什么 — — 这简直是意识形态的诅咒，你知道只有能证明资金合法性的第三方识别代币持有者才能进入一个池子。这可以既是无许可的，也是有许可的。我可以建立一个有这些规则的池子，你可以选择是否进入，所以我们有这种自由意志家长主义的概念。</p><p>有人，某个地方，比如这个房间里的社会共识，将决定这是我们安全运作的方式，然后用户决定他们想做什么，而不是我们完全非黑即白，如果有任何许可，即使是社会的，即使是民主的，我们也不能允许。</p><p>另一个例子是 Vitalik 和我们的联合创始人 Zach Williamson 一直在研究的去中心化清洁提供者的概念，这是一个社会图，个人在其中证明你的资金和交易的合法性，他们观察行为并说这不是我们想与之相关的东西。这与中心化非常不同，与审查非常不同，这是一种民主形式的社会共识，是我们所有人说我们不会容忍我们生态系统中的某些行为。</p><p>这里的目标是仍然允许用户在各种协议设计中表达他们的偏好，这不是为了限制自由，而是给用户比我现在说的更多的选择。</p><p>所以 ZK 在基础层实现了这种无许可，同时在应用层提供了许可的社会共识。</p><p>这些都是更多的例子，你知道有很多关于储备证明的讨论，防钓鱼，选择加入的合规池子，合法资金证明。</p><p>但这一切都是为了说，我们需要把 zachXBT 变成 ZK，我们需要使用数学和社会共识，而不是信任或集中式合规。</p><p>所以总结一下，我们需要 ZK 来解锁三大改进。</p><p>首先，我们需要在允许自我监管和合规的同时保留用户选择，我们作为一个社区和生态系统还没有真正谈论过自我监管，我们只是希望并祈祷别人不会注意到。</p><p>我们不会达到目标，如果我们允许这种情况发生，Web3 不会成功。<strong>我们需要向某人证明我们在互相照顾和照顾我们的用户，所以我们需要向用户证明我们作为一个社区在支持他们。</strong></p><p><strong>我们不要试图把意识形态强加给用户，让我们给他们选择他们想去的地方，这最终是这个空间的意义所在，它是关于自由，关于自主。</strong></p><p>最后，我们需要提高安全性，我们需要让它可靠，我们需要让加密成为必需品而不是可选品。我们忘记了政府至少据称是由选民组成的，为什么 Uber 和 Airbnb 曾经是非法的，现在又合法了？因为有人走到国会台阶上说“除非我死了，否则你不能拿走我的 Uber”，有人这么做了，个人这么做了，我不知道你们是否记得这件事。</p><p>我们让加密成为必需品并融入我们经济生活结构的一种方式是<strong>确保它是可靠和安全的，并且我们支持我们的用户</strong>。</p><p>这就是我们如何把“柠檬”变成“桃子”的方法。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e6c616721a2c1cc1d67f9fd30cd3470603f3001462923d4db1af92721ff8afff.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>编译 | Loxia</p><p>编辑 &amp; 排版 | Yewlne、环环</p><p>设计 | Daisy</p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b65f3129c35a8c60ed33be8c60dec767535c57042afff902c89bbfbd4bb1c925.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[如何参加 Deepfunding 比赛？参赛代码一步步解析]]></title>
            <link>https://paragraph.com/@lxdao/deepfunding</link>
            <guid>JqFIaVGivKd1OpTUG6UX</guid>
            <pubDate>Sun, 26 Jan 2025 13:19:38 GMT</pubDate>
            <description><![CDATA[DeepFunding 的 Mini-contest #1 & #2 已吸引约 200 名参赛者，其中多位选手提供了值得学习的方法。本文将分析两位突出参赛者 davidgasquez 和 Allan Niemerg 的代码和策略。 什么是 DeepFunding？见 https://mp.weixin.qq.com/s?__biz=MzI2NzExNTczMw%3D%3D&mid=2653294746&idx=1&sn=3323532273f52a64da043255a7ace184&scene=21#wechat_redirect 下面将以 davidgasquez 和 Allan Niemerg 的代码为例，为你解析 Deepfunding 参赛和分配思路！ repo： https://github.com/davidgasquez/predictive-funding-challenge https://github.com/aniemerg/mini-contest数据资源Mini-contest #1 训练集：https://github.com/deepfunding...]]></description>
            <content:encoded><![CDATA[<p><strong>DeepFunding 的 Mini-contest #1 &amp; #2 已吸引约 200 名参赛者，其中多位选手提供了值得学习的方法。本文将分析两位突出参赛者 davidgasquez 和 Allan Niemerg 的代码和策略。</strong></p><p>什么是 DeepFunding？见</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mp.weixin.qq.com/s?__biz=MzI2NzExNTczMw%3D%3D&amp;mid=2653294746&amp;idx=1&amp;sn=3323532273f52a64da043255a7ace184&amp;scene=21#wechat_redirect">https://mp.weixin.qq.com/s?__biz=MzI2NzExNTczMw%3D%3D&amp;mid=2653294746&amp;idx=1&amp;sn=3323532273f52a64da043255a7ace184&amp;scene=21#wechat_redirect</a></p><p>下面将以 davidgasquez 和 Allan Niemerg 的代码为例，为你解析 Deepfunding 参赛和分配思路！</p><p><strong>repo</strong>：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/davidgasquez/predictive-funding-challenge">https://github.com/davidgasquez/predictive-funding-challenge</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/aniemerg/mini-contest">https://github.com/aniemerg/mini-contest</a></p><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>数据资源</strong></h1><ul><li><p>Mini-contest #1 训练集：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/deepfunding/mini-contest/blob/main/dataset.csv">https://github.com/deepfunding/mini-contest/blob/main/dataset.csv</a></p></li><li><p>Mini-contest #2 训练集：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://cryptopond.xyz/modelfactory/detail/306250?tab=1">https://cryptopond.xyz/modelfactory/detail/306250?tab=1</a></p></li><li><p>开源 Repo 关系依赖图谱：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://cosmograph.app/run/?data=https://raw.githubusercontent.com/opensource-observer/insights/refs/heads/main/community/deep_funder/data/unweighted_graph.csv&amp;source=seed_repo_name&amp;target=package_repo_name&amp;gravity=0.25&amp;repulsion=1&amp;repulsionTheta=1&amp;linkSpring=1&amp;linkDistance=10&amp;friction=0.1&amp;renderLabels=true&amp;renderHoveredLabel=true&amp;renderLinks=true&amp;linkArrows=true&amp;curvedLinks=true&amp;nodeSizeScale=0.5&amp;linkWidthScale=1&amp;linkArrowsSizeScale=1&amp;nodeSize=size-default&amp;nodeColor=color-outgoing">https://cosmograph.app/run/?data=https://raw.githubusercontent.com/opensource-observer/insights/refs/heads/main/community/deep_funder/data/unweighted_graph.csv&amp;source=seed_repo_name&amp;target=package_repo_name&amp;gravity=0.25&amp;repulsion=1&amp;repulsionTheta=1&amp;linkSpring=1&amp;linkDistance=10&amp;friction=0.1&amp;renderLabels=true&amp;renderHoveredLabel=true&amp;renderLinks=true&amp;linkArrows=true&amp;curvedLinks=true&amp;nodeSizeScale=0.5&amp;linkWidthScale=1&amp;linkArrowsSizeScale=1&amp;nodeSize=size-default&amp;nodeColor=color-outgoing</a> links&amp;linkWidth=width-number of data records&amp;linkColor=color-number of data records&amp;</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0ad76df414fee1609b2ace728a29bbec43fa4e66ff3eef60c8c7673bed250586.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>训练流程</strong></h1><p>两位参赛者均采用四步法：</p><ol><li><p>数据准备</p></li><li><p>特征工程</p></li><li><p>特征选择与参数优化</p></li><li><p>模型训练</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>数据预处理</strong></h2><p>虽然两位选手的训练流程基本一致，但是思路和方案还是有各自的特点，其中 davidgasquez 采用的是更多“传统”的数据特征，也就是一些项目的基础信息（stars, forks, watchers等）、和时间衰减、比率等特征；<strong>而 Allan Niemerg 在传统数据之余，还通过 GPT-4 分析项目文档来提取特征，所以抓取所有参与项目的代码文件</strong>。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/davidgasquez/predictive-funding-challenge/blob/main/src/data.py">https://github.com/davidgasquez/predictive-funding-challenge/blob/main/src/data.py</a></p><p>davidgasquez 在这一部份拉取了竞赛官方提供的一些基础特征之外，使用 Github 官方的 API 拉取了一些更多特征，如：</p><ul><li><p>时间维度特征</p><p>项目年龄：age_days</p><p>最近更新时间：days_since_update</p><p>时间衰减特征：使用多个衰减率(0.0001, 0.001, 0.01)计算</p></li></ul><pre data-type="codeBlock" text="时间衰减特征示例
stars_decay = stars * exp(-rate * days_since_update)
"><code>时间衰减特征示例
stars_decay <span class="hljs-operator">=</span> stars <span class="hljs-operator">*</span> exp(<span class="hljs-operator">-</span>rate <span class="hljs-operator">*</span> days_since_update)
</code></pre><ul><li><p>比率维度特征：</p><p>项目间的相对指标：stars_ratio、forks_ratio等</p><p>时间标准化指标：stars_per_day、forks_per_day</p><p>对数转换特征：log_stars、log_forks等</p></li><li><p>交互特征</p><p>项目活跃度：stars_issues_interaction</p><p>用户参与度：engagement_score</p></li></ul><p>以上特征会在不同的维度反映项目的一些实际情况。</p><p><strong>Allan Niemerg 通过获取所有仓库的代码，然后把代码提交给 GPT-4 进行评估，评估分为以下纬度</strong>：技术复杂性、Web3 关注度、开发者工具、项目成熟度、社区规模、企业级准备、社区参与度、文档质量、代码质量、项目声望、企业 vs 社区、安全性、创新性、性能、模块化及可访问性等。</p><p>这是 Allan 的 Prompt：</p><pre data-type="codeBlock" text="def analyze_project(readme_content, client): prompt = &apos;Rate this open source project on each dimension (1-5). Being decisive is better than being neutral. Use 3 only when there is no signal. Return JSON format: {&quot;technical_complexity&quot;: N, &quot;web3_focus&quot;: N, &quot;developer_tool&quot;: N, &quot;project_maturity&quot;: N, &quot;community_size&quot;: N, &quot;enterprise_ready&quot;: N, &quot;community_engagement&quot;: N, &quot;documentation&quot;: N, &quot;code_quality&quot;: N, &quot;status&quot;: N, &quot;corporate&quot;: N, &quot;security&quot;: N, &quot;innovation&quot;: N, &quot;performance&quot;: N, &quot;modularity&quot;: N, &quot;accessibility&quot;: N, &quot;key_features&quot;: [&quot;1-3 key features&quot;]} Scale: 1=Very Low, 2=Low, 3=Medium/No Signal, 4=High, 5=Very High. Project Documentation: &apos; + readme_content response = client.chat.completions.create(messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}], model=&quot;gpt-4&quot;, temperature=0.7) try: return response.choices[0].message.content except (AttributeError, IndexError) as e: return f&quot;Error analyzing project: {str(e)}&quot;
"><code>def analyze_project(readme_content, client): prompt <span class="hljs-operator">=</span> '<span class="hljs-type">Rate</span> this <span class="hljs-keyword">open</span> source project on <span class="hljs-keyword">each</span> dimension (<span class="hljs-number">1</span><span class="hljs-operator">-</span><span class="hljs-number">5</span>). <span class="hljs-type">Being</span> decisive <span class="hljs-keyword">is</span> better than being neutral. <span class="hljs-type">Use</span> <span class="hljs-number">3</span> only when there <span class="hljs-keyword">is</span> no signal. <span class="hljs-type">Return</span> <span class="hljs-type">JSON</span> format: {<span class="hljs-string">"technical_complexity"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"web3_focus"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"developer_tool"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"project_maturity"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"community_size"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"enterprise_ready"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"community_engagement"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"documentation"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"code_quality"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"status"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"corporate"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"security"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"innovation"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"performance"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"modularity"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"accessibility"</span>: <span class="hljs-type">N</span>, <span class="hljs-string">"key_features"</span>: [<span class="hljs-string">"1-3 key features"</span>]} <span class="hljs-type">Scale</span>: <span class="hljs-number">1</span><span class="hljs-operator">=</span><span class="hljs-type">Very</span> <span class="hljs-type">Low</span>, <span class="hljs-number">2</span><span class="hljs-operator">=</span><span class="hljs-type">Low</span>, <span class="hljs-number">3</span><span class="hljs-operator">=</span><span class="hljs-type">Medium</span><span class="hljs-operator">/</span><span class="hljs-type">No</span> <span class="hljs-type">Signal</span>, <span class="hljs-number">4</span><span class="hljs-operator">=</span><span class="hljs-type">High</span>, <span class="hljs-number">5</span><span class="hljs-operator">=</span><span class="hljs-type">Very</span> <span class="hljs-type">High</span>. <span class="hljs-type">Project</span> <span class="hljs-type">Documentation</span>: ' <span class="hljs-operator">+</span> readme_content response <span class="hljs-operator">=</span> client.chat.completions.create(messages<span class="hljs-operator">=</span>[{<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}], model<span class="hljs-operator">=</span><span class="hljs-string">"gpt-4"</span>, temperature<span class="hljs-operator">=</span><span class="hljs-number">0.7</span>) <span class="hljs-keyword">try</span>: <span class="hljs-keyword">return</span> response.choices[<span class="hljs-number">0</span>].message.content except (<span class="hljs-type">AttributeError</span>, <span class="hljs-type">IndexError</span>) <span class="hljs-keyword">as</span> e: <span class="hljs-keyword">return</span> f<span class="hljs-string">"Error analyzing project: {str(e)}"</span>
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">特征工程与训练</h3><p>davidgasquez <strong>通过</strong>通过交换 A/B 项目扩充训练集的方式，镜像扩大了训练样本空间，同时确保模型对项目顺序不敏感。</p><pre data-type="codeBlock" text="镜像训练数据
df_train = pl.concat([df_train, df_train.select(&quot;id&quot;, pl.col(&quot;project_b&quot;).alias(&quot;project_a&quot;), pl.col(&quot;project_a&quot;).alias(&quot;project_b&quot;), pl.col(&quot;weight_b&quot;).alias(&quot;weight_a&quot;), pl.col(&quot;weight_a&quot;).alias(&quot;weight_b&quot;))])
"><code>镜像训练数据
df_train <span class="hljs-operator">=</span> pl.concat([df_train, df_train.select(<span class="hljs-string">"id"</span>, pl.col(<span class="hljs-string">"project_b"</span>).alias(<span class="hljs-string">"project_a"</span>), pl.col(<span class="hljs-string">"project_a"</span>).alias(<span class="hljs-string">"project_b"</span>), pl.col(<span class="hljs-string">"weight_b"</span>).alias(<span class="hljs-string">"weight_a"</span>), pl.col(<span class="hljs-string">"weight_a"</span>).alias(<span class="hljs-string">"weight_b"</span>))])
</code></pre><p>准备好数据之后，开始特征工程，依次添加各类特征：</p><pre data-type="codeBlock" text="df_train_full = (df_train .pipe(add_github_projects_data)     # 基础GitHub特征 .pipe(extract_temporal_features)     # 时间相关特征 .pipe(extract_activity_features)     # 活动度特征 .pipe(add_target_encoding)          # 目标编码特征 .pipe(extract_ratio_features)       # 比率特征 )
"><code>df_train_full <span class="hljs-operator">=</span> (df_train .pipe(add_github_projects_data)     # 基础GitHub特征 .pipe(extract_temporal_features)     # 时间相关特征 .pipe(extract_activity_features)     # 活动度特征 .pipe(add_target_encoding)          # 目标编码特征 .pipe(extract_ratio_features)       # 比率特征 )
</code></pre><p><br>标记好之后<strong>通过 LightGBM 的特征重要性评估筛选最有价值的特征，降低模型复杂度。</strong></p><pre data-type="codeBlock" text="获取初始特征列表
features = get_features() X = df_train_full.select(features).to_numpy() y = df_train_full.get_column(&quot;weight_a&quot;).to_numpy()
特征筛选
selected_features = select_features(X, y, features) X = df_train_full.select(selected_features).to_numpy()
方法具体逻辑见源码
"><code>获取初始特征列表
features <span class="hljs-operator">=</span> get_features() X <span class="hljs-operator">=</span> df_train_full.select(features).to_numpy() y <span class="hljs-operator">=</span> df_train_full.get_column(<span class="hljs-string">"weight_a"</span>).to_numpy()
特征筛选
selected_features <span class="hljs-operator">=</span> select_features(X, y, features) X <span class="hljs-operator">=</span> df_train_full.select(selected_features).to_numpy()
方法具体逻辑见源码
</code></pre><p>然后使用 Optuna 进行贝叶斯优化，优化参数包括：</p><ul><li><p>树的结构参数（num_leaves）</p></li><li><p>学习率（learning_rate）</p></li><li><p>采样参数（feature_fraction, bagging_fraction）</p></li><li><p>正则化参数（reg_alpha, reg_lambda）</p></li></ul><p>优化之后进行模型验证，<strong>使用 5 折交叉验证评估模型性能，确保模型的稳定性和泛化能力。</strong></p><pre data-type="codeBlock" text="交叉验证评估
mean_mse, std_mse = train_and_evaluate(X, y, best_params)
"><code>交叉验证评估
mean_mse, <span class="hljs-attr">std_mse</span> = train_and_evaluate(X, y, best_params)
</code></pre><p>通过均值和标准差评估模型的稳定性和泛化能力。然后训练得到最后的模型，得出最终的训练结果</p><p><strong>Allan Niemerg 使用了 XGBoost 模型来预测项目偏好，这是他使用的一些关键参数：</strong></p><pre data-type="codeBlock" text="xgb_improved = XGBRegressor( max_depth=6,              # 树的最大深度，允许模型捕捉更复杂的模式 min_child_weight=1,       # 允许更小的叶节点 gamma=0,                  # 分裂所需的最小损失减少 subsample=0.8,            # 每棵树使用80%的数据，减少过拟合 colsample_bytree=0.8,     # 每棵树使用80%的特征 reg_alpha=0,              # L1 正则化 reg_lambda=1,             # L2 正则化 learning_rate=0.05,       # 较低的学习率可以使模型更稳定 n_estimators=200,         # 树的数量，更多的树可以提高模型的表现 random_state=42           # 随机种子，确保结果可重复 )
"><code>xgb_improved <span class="hljs-operator">=</span> XGBRegressor( max_depth<span class="hljs-operator">=</span><span class="hljs-number">6</span>,              # 树的最大深度，允许模型捕捉更复杂的模式 min_child_weight<span class="hljs-operator">=</span><span class="hljs-number">1</span>,       # 允许更小的叶节点 gamma<span class="hljs-operator">=</span><span class="hljs-number">0</span>,                  # 分裂所需的最小损失减少 subsample<span class="hljs-operator">=</span><span class="hljs-number">0</span><span class="hljs-number">.8</span>,            # 每棵树使用<span class="hljs-number">80</span><span class="hljs-operator">%</span>的数据，减少过拟合 colsample_bytree<span class="hljs-operator">=</span><span class="hljs-number">0</span><span class="hljs-number">.8</span>,     # 每棵树使用<span class="hljs-number">80</span><span class="hljs-operator">%</span>的特征 reg_alpha<span class="hljs-operator">=</span><span class="hljs-number">0</span>,              # L1 正则化 reg_lambda<span class="hljs-operator">=</span><span class="hljs-number">1</span>,             # L2 正则化 learning_rate<span class="hljs-operator">=</span><span class="hljs-number">0</span><span class="hljs-number">.05</span>,       # 较低的学习率可以使模型更稳定 n_estimators<span class="hljs-operator">=</span><span class="hljs-number">200</span>,         # 树的数量，更多的树可以提高模型的表现 random_state<span class="hljs-operator">=</span><span class="hljs-number">42</span>           # 随机种子，确保结果可重复 )
</code></pre><p><br>稳定性和泛化能力是评估模型性能的关键指标。<strong>Allan Niemerg</strong> 借助 <strong>XGBoost</strong> 模型完成了这一任务，并通过均值和标准差评估模型的稳定性后，训练得出了最终的结果。他使用了一组经过优化的关键参数，例如将 <strong>max_depth</strong> 设置为 6，以平衡模型的复杂性和过拟合风险；<strong>subsample</strong> 和 <strong>colsample_bytree</strong> 均设置为 0.8，分别控制数据和特征的采样比例，进一步提升模型的泛化能力；<strong>learning_rate</strong> 设为 0.05，确保模型更新稳定且准确。此外，他还通过 <strong>n_estimators=200</strong> 确定树的数量，从而在模型表现和计算成本之间取得平衡。这些参数的搭配，结合合理的正则化（<strong>reg_alpha=0</strong> 和 <strong>reg_lambda=1</strong>），使模型能够在复杂的预测任务中表现出色，同时保持较好的稳健性和可重复性。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">训练算法对比：</h2><p>Allan Niemerg 使用 XGBoost，而 davidgasquez 使用 LightGBM</p><p>XGBoost 和 LightGBM 都是基于梯度提升决策树（GBDT）的机器学习算法，广泛应用于分类和回归任务中。</p><p>XGBoost 由 Chen Tianqi 开发，以高效、准确和灵活著称，尤其在 Kaggle 社区中表现亮眼。它通过串行构建多棵树，不断优化预测误差，同时支持 L1 和 L2 正则化以防止过拟合，并具有较高的计算效率和灵活性。</p><p>LightGBM 则由微软推出，专注于大规模数据的高效处理。它采用叶子节点分裂（leaf-wise）策略和直方图优化技术，大幅提升了训练速度和内存利用率，同时对稀疏数据和类别特征的支持更优。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0efc3b748d88f62a9adda41026169eeacfe94a9727b1a44a960f26a22d2f5aa7.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>两者各有优势：XGBoost 更注重准确性和灵活性，LightGBM 在大数据场景下效率更高。选择算法时应根据数据规模和任务需求进行权衡。</p><p>其他推荐模型</p><p>对于类似的预测任务，可以考虑：</p><ul><li><p>CatBoost</p></li><li><p>矩阵分解（Matrix Factorization）</p></li><li><p>深度因子分解机（DeepFM）</p></li><li><p>神经协同过滤（NCF）</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结论</h2><p>两种方法都展示了全面特征工程和谨慎模型选择的重要性。davidgasquez 的方法在传统指标和数据增强方面表现出色，而 Allan Niemerg 通过集成 GPT-4 分析为项目评估带来了创新维度。看了两位选手的方案，如果你也有不错的 idea ，可以点击下方链接参加 DeepFunding 的 mini-context：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://huggingface.co/spaces/DeepFunding/PredictiveFundingChallengeforOpenSourceDependencies">https://huggingface.co/spaces/DeepFunding/PredictiveFundingChallengeforOpenSourceDependencies</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://cryptopond.xyz/modelfactory/detail/306250?tab=0">https://cryptopond.xyz/modelfactory/detail/306250?tab=0</a></p><p>同时欢迎加入由 LXDAO 和 ETHPanda 联合发起了 DeepFunding 中文力量一起交流一些 Deep Funding 的系列问题：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://t.me/deepfundingcn">https://t.me/deepfundingcn</a></p>]]></content:encoded>
            <author>lxdao@newsletter.paragraph.com (LXDAO)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b65f3129c35a8c60ed33be8c60dec767535c57042afff902c89bbfbd4bb1c925.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>