• 以太坊源码笔记-blockchain


    本文章记录以太坊源码blockchain.go的阅读笔记。该文件包含了与区块链相关的各种功能和操作,用于实现区块链核心功能。

    源码开头部分定义了一个变量块,用于同时声明多个变量。

    var (
        // 表示头部区块数量的计量表,用于记录当前区块链的头部区块数量
        headBlockGauge          = metrics.NewRegisteredGauge("chain/head/block", nil)
        // 表示头部头信息数量的计量表,用于记录当前区块链的头部信息数量
        headHeaderGauge         = metrics.NewRegisteredGauge("chain/head/header", nil)
        // 表示头部快速块数量的计量表,用于记录当前区块链的头部快速块的数量
        headFastBlockGauge      = metrics.NewRegisteredGauge("chain/head/receipt", nil)
        // 表示已最终确定的块数量的计量表,用于记录当前区块链已经最终确定的块数量
        headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
        // 表示安全块数量的计量表,用于记录当前区块链的安全块数量
        headSafeBlockGauge      = metrics.NewRegisteredGauge("chain/head/safe", nil)
        // 表示区块链信息的计量表,用于记录与区块链相关的一般信息  
        chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
        // 表示账户读取时间的重置计时器,用于记录区块链中账户读取操作的时间
        accountReadTimer   = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
        // 表示账户哈希计算时间的重置计时器,用于记录区块链中账户哈希操作的时间
        accountHashTimer   = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
        // 表示账户更新时间的重置计时器,用于记录区块链中账户更新操作的时间
        accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil)
        // 表示账户提交时间的重置计时器,用于记录区块链中账户提交操作的时间
        accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil)
        // 读取操作时间计时器
        storageReadTimer   = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
        // 哈希计算操作时间计时器
        storageHashTimer   = metrics.NewRegisteredResettingTimer("chain/storage/hashes", nil)
        // 更新操作时间计时器
        storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
        // 提交操作时间计时器
        storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
        // 快照中账户读取操作时间计时器
        snapshotAccountReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/account/reads", nil)
        // 快照中存储读取操作时间计时器
        snapshotStorageReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/storage/reads", nil)
        // 快照提交操作时间计时器
        snapshotCommitTimer      = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
        // Trie数据库提交操作计时器
        triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
        // 区块插入操作计时器
        blockInsertTimer     = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
        // 区块验证操作计时器
        blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
        // 区块执行操作计时器
        blockExecutionTimer  = metrics.NewRegisteredResettingTimer("chain/execution", nil)
        // 区块写入操作计时器
        blockWriteTimer      = metrics.NewRegisteredResettingTimer("chain/write", nil)
        // 记录操作的频率
        blockReorgMeter     = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
        // 新增区块到重新组织的频率
        blockReorgAddMeter  = metrics.NewRegisteredMeter("chain/reorg/add", nil)
        // 丢弃的区块重新组织的频率
        blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
        // 区块预取执行操作的计时器 
        blockPrefetchExecuteTimer   = metrics.NewRegisteredTimer("chain/prefetch/executes", nil)
        // 区块预取中断的频率
        blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
        // 插入操作中断的错误信息
        errInsertionInterrupted = errors.New("insertion is interrupted")
        // 区块链停止的错误信息
        errChainStopped         = errors.New("blockchain is stopped")
        // 旧区块链无效的错误信息
        errInvalidOldChain      = errors.New("invalid old chain")
        // 新区块无效的错误信息
        errInvalidNewChain      = errors.New("invalid new chain")
    )

    上面提到了快速块和安全块,安全块和快速块是区块链中的两种不同类型的块,它们在区块链协议中具有不同的含义和作用。

    1. 安全块(Finalized Block)

      • 安全块是指已经被网络中大多数节点(或者特定的一组节点)确认的块,这些节点已经通过一定的共识机制达成一致,认可该块的有效性,并将其视为区块链的一部分。

      • 一般来说,安全块是区块链的最终确认版本,即它们被认为是永久的,不可逆转的。

      • 安全块的确认通常是通过一些共识算法(如工作量证明、权益证明等)来实现的,确保了块的有效性和整个网络的安全性。

    2. 快速块(Fast Block)

      • 快速块是指在某些区块链协议中,通过一定的机制使得特定的块可以更快地被确认和接受,从而提高了交易的处理速度和网络的吞吐量。

      • 快速块可能是临时性的,它们在一定条件下可以更快地被确认,但在后续的安全确认中可能会发生变化。

      • 快速块的引入可以带来更快的交易确认和更高的网络效率,但也可能会牺牲一定程度的安全性和去中心化特性。

    总的来说,快速块可以更快的被确认,从而提高交易处理的速度,而安全块则具备更高的安全性和可靠性。

    下面是常量块,用于同时声明多个常量:

    const (
        bodyCacheLimit     = 256        // 区块体缓存的大小限制,通常包含交易等数据         
        blockCacheLimit    = 256        // 区块缓存的大小限制,用于存储最近处理过的区块,提高性能
        receiptsCacheLimit = 32         // 收据缓存的大小限制,用于存储交易执行的结果
        txLookupCacheLimit = 1024       // 交易查询缓存的大小,用于存储交易哈希与区块的映射,加快查询
        TriesInMemory      = 128        // 内存中存储的Trie的数量
        BlockChainVersion uint64 = 8    // 区块链版本号
    )

    上文提到的Trie结构可以参考下面的文章:以太坊Geth Trie源码解析_以太坊设计与源码之美-CSDN博客

    下面是对Trie结构的配置:

    type CacheConfig struct {
        TrieCleanLimit      int                 // 用于缓存内存中trie节点的内存限制(以MB为单位)
        TrieCleanNoPrefetch bool                // 是否禁用启发式状态预取以供后续块使用
        TrieDirtyLimit      int                 // 当脏trie节点内存占用达到多少MB时开始将其刷新到磁盘
        TrieDirtyDisabled   bool                // 是否完全禁用trie写缓存和垃圾回收
        TrieTimeLimit       time.Duration       // 在何时将当前内存中的trie刷新到磁盘的时间限制
        SnapshotLimit       int                 // 用于在内存中缓存快照条目的内存限制
        Preimages           bool                // 是否将trie键的预映像存储到磁盘上
        StateHistory        uint64              // 从头开始保留状态历史记录的块数
        StateScheme         string              // 用于在顶部存储以太坊状态和默克尔树节点的方案
    ​
        SnapshotNoBuild bool                    // 是否允许后台生成快照
        SnapshotWait    bool                    // 是否在启动时等待快照构建
    }

    对于配置来说,以太坊提供了加载配置和默认配置:

    func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
        config := &triedb.Config{
            Preimages: c.Preimages,
            IsVerkle:  isVerkle,
        }
        // 根据不同的方案配置不同的存储引擎
        if c.StateScheme == rawdb.HashScheme {
            config.HashDB = &hashdb.Config{
                CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
            }
        }
        if c.StateScheme == rawdb.PathScheme {
            config.PathDB = &pathdb.Config{
                StateHistory:   c.StateHistory,
                CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
                DirtyCacheSize: c.TrieDirtyLimit * 1024 * 1024,
            }
        }
        return config
    }
    ​
    // 默认的缓存配置
    var defaultCacheConfig = &CacheConfig{  
        TrieCleanLimit: 256,                // Trie节点的内存限制为256MB
        TrieDirtyLimit: 256,                // 脏Trie节点达到256MB被刷新到磁盘
        TrieTimeLimit:  5 * time.Minute,    // 5分钟后将内存的Trie刷新到磁盘
        SnapshotLimit:  256,                // 缓存快照条目设置为256MB
        SnapshotWait:   true,               // 在启动时等待快照构建
        StateScheme:    rawdb.HashScheme,   // 使用哈希方案存储以太坊状态和merkle树
    }
    ​
    // 指定以哈希方式存储以太坊状态和 Merkle 树节点的方式
    func DefaultCacheConfigWithScheme(scheme string) *CacheConfig {
        config := *defaultCacheConfig
        config.StateScheme = scheme
        return &config
    }

    上面的代码涉及到Verkle Trie树,Verkle Trie 是一种改进的 Merkle Trie 数据结构,用于存储键值对。Merkle Trie节点存储的是哈希值,而Verkle Trie存储的是哈希值以及对这个哈希值存在的证明。它是以太坊 2.0 中引入的一项技术,旨在提供更高效的存储和验证方法。Verkle Trie 相比传统的 Merkle Trie 有几个优点:

    1. 更少的存储需求:Verkle Trie 使用路径压缩和共享子树的方式来减少存储需求,特别是对于包含大量相似键的情况,可以显著减少存储空间的占用。

    2. 更高的效率:由于路径压缩和共享子树的特性,Verkle Trie 在执行插入、删除和更新操作时,通常比传统的 Merkle Trie 更高效。

    3. 更好的隐私性:Verkle Trie 可以隐藏非叶子节点的具体键值对,从而提高了对存储数据的隐私性。

    下面是对交易查找的结构体封装,用于将交易对象和交易查找结果封装在一起,以便于在程序中处理和传递交易及其相关信息。通过将这两个信息结合在一起,可以更方便地在程序中使用交易对象,并且可以轻松地从交易查找结果中获取所需的信息。

    type txLookup struct {
        lookup      *rawdb.LegacyTxLookupEntry
        transaction *types.Transaction
    }

    下面是对区块链的结构体封装,它包含了各种与区块链操作相关的字段和方法。

    type BlockChain struct {
        // chainConfig 指向区块链中链和网络配置的指针。
        chainConfig *params.ChainConfig
    ​
        // cacheConfig 指向缓存配置的指针。
        cacheConfig *CacheConfig
    ​
        // db 是最终持久化数据的数据库。
        db ethdb.Database
    ​
        // snaps 是一个快速访问 Trie 的快照树。
        snaps *snapshot.Tree
    ​
        // triegc 是一个优先队列,将区块号映射到需要进行垃圾回收的 Trie。
        triegc *prque.Prque[int64, common.Hash]
    ​
        // gcproc 是用于累积规范块处理以进行 Trie 转储的时间间隔。
        gcproc time.Duration
    ​
        // lastWrite 是上一次状态被刷新时的区块号。
        lastWrite uint64
    ​
        // flushInterval 是用于定时刷新状态的时间间隔。
        flushInterval atomic.Int64
    ​
        // triedb 是用于维护 Trie 节点的数据库处理程序。
        triedb *triedb.Database
    ​
        // stateCache 是状态数据库,用于在导入之间重用状态。
        stateCache state.Database
    ​
        // txIndexer 是事务索引器,如果未启用,则可能为 nil。
        txIndexer *txIndexer
    ​
        // hc 是区块头链管理器。
        hc *HeaderChain
    ​
        // rmLogsFeed 是用于订阅链上移除日志的事件 Feed。
        rmLogsFeed event.Feed
    ​
        // chainFeed 是链事件的事件 Feed。
        chainFeed event.Feed
    ​
        // chainSideFeed 是侧链事件的事件 Feed。
        chainSideFeed event.Feed
    ​
        // chainHeadFeed 是链头事件的事件 Feed。
        chainHeadFeed event.Feed
    ​
        // logsFeed 是用于订阅链上新日志的事件 Feed。
        logsFeed event.Feed
    ​
        // blockProcFeed 是用于订阅块处理事件的事件 Feed。
        blockProcFeed event.Feed
    ​
        // scope 是事件订阅范围。
        scope event.SubscriptionScope
    ​
        // genesisBlock 是创世块。
        genesisBlock *types.Block
    ​
        // chainmu 是用于同步链写操作的互斥锁。
        chainmu *syncx.ClosableMutex
    ​
        // currentBlock 是当前链的头部,原子指针指向 types.Header 对象。
        currentBlock atomic.Pointer[types.Header]
    ​
        // currentSnapBlock 是当前快照同步的头部,原子指针指向 types.Header 对象。
        currentSnapBlock atomic.Pointer[types.Header]
    ​
        // currentFinalBlock 是最终确定的块,原子指针指向 types.Header 对象。
        currentFinalBlock atomic.Pointer[types.Header]
    ​
        // currentSafeBlock 是最近的安全块,原子指针指向 types.Header 对象。
        currentSafeBlock atomic.Pointer[types.Header]
    ​
        // bodyCache 是用于缓存区块体的 LRU 缓存。
        bodyCache *lru.Cache[common.Hash, *types.Body]
    ​
        // bodyRLPCache 是用于缓存区块体的 RLP 编码的 LRU 缓存。
        bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
    ​
        // receiptsCache 是用于缓存区块的交易收据的 LRU 缓存。
        receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
    ​
        // blockCache 是用于缓存区块的 LRU 缓存。
        blockCache *lru.Cache[common.Hash, *types.Block]
    ​
        // txLookupCache 是用于缓存事务查找结果的 LRU 缓存。
        txLookupCache *lru.Cache[common.Hash, txLookup]
    ​
        // wg 是一个 WaitGroup,用于等待所有区块链协程完成。
        wg sync.WaitGroup
    ​
        // quit 是一个用于发送关闭信号的通道。
        quit chan struct{}
    ​
        // stopping 是一个原子布尔类型,表示区块链是否正在停止。
        stopping atomic.Bool
    ​
        // procInterrupt 是一个原子布尔类型,用于中断块处理。
        procInterrupt atomic.Bool
    ​
        // engine 是共识引擎,用于验证和执行共识规则。
        engine consensus.Engine
    ​
        // validator 是验证器接口,用于验证区块和状态。
        validator Validator
    ​
        // prefetcher 是预取器接口,用于预取区块数据。
        prefetcher Prefetcher
    ​
        // processor 是块事务处理器接口,用于处理块中的事务。
        processor Processor
    ​
        // forker 是分叉选择对象,用于选择主链。
        forker *ForkChoice
    ​
        // vmConfig 是虚拟机配置,用于配置以太坊虚拟机。
        vmConfig vm.Config
    ​
        // logger 是日志记录钩子,用于记录区块链操作的日志。
        logger *tracing.Hooks
    }

    下面函数是用来创建一个全面初始化的区块链对象的。函数接受了一系列参数,包括数据库、缓存配置、创世块信息、链覆盖、共识引擎、虚拟机配置等。

    func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
        if cacheConfig == nil {
            // 使用默认的缓存配置
            cacheConfig = defaultCacheConfig
        }
        // 使用提供的数据库配置打开trie数据库
        triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle()))
        // 接收SetupGenesisBlockWithOverride的三个返回值
        chainConfig, genesisHash, genesisErr := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides)
        // 如果在创世块设置过程中发生了其他类型的错误会直接返回错误,否则如果genesisErr!=nil且是一个         // params.ConfigCompatError错误,说明创世块设置过程发生了配置不兼容的错误,但这种错误被允许
        if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
            return nil, genesisErr
        }
        // 打印空行-输出153个--分行打印配置的信息-答应153--打印空行
        log.Info("")
        log.Info(strings.Repeat("-", 153))
        for _, line := range strings.Split(chainConfig.Description(), "\n") {
            log.Info(line)
        }
        log.Info(strings.Repeat("-", 153))
        log.Info("")
        // 创建区块链
        bc := &BlockChain{
            chainConfig:   chainConfig,
            cacheConfig:   cacheConfig,
            db:            db,
            triedb:        triedb,
            triegc:        prque.New[int64, common.Hash](nil),
            quit:          make(chan struct{}),
            chainmu:       syncx.NewClosableMutex(),
            bodyCache:     lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
            bodyRLPCache:  lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
            receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
            blockCache:    lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
            txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
            engine:        engine,
            vmConfig:      vmConfig,
            logger:        vmConfig.Tracer,
        }
        // 将缓存配置中的 trie 时间限制存储到 BlockChain 对象的 flushInterval 字段中。
        bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
    ​
        // 使用 NewForkChoice 函数创建一个分叉选择对象,并赋值给 BlockChain 对象的 forker 字段,用于处    // 理分叉选择。
        bc.forker = NewForkChoice(bc, shouldPreserve)
    ​
        // 使用 state.NewDatabaseWithNodeDB 函数创建一个状态数据库,并赋值给 BlockChain 对象的           // stateCache 字段。
        bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
    ​
        // 使用 NewBlockValidator 函数创建一个区块验证器,并赋值给 BlockChain 对象的 validator 字段。
        bc.validator = NewBlockValidator(chainConfig, bc, engine)
    ​
        // 使用 newStatePrefetcher 函数创建一个状态预取器,并赋值给 BlockChain 对象的 prefetcher 字       // 段。
        bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
    ​
        // 使用 NewStateProcessor 函数创建一个状态处理器,并赋值给 BlockChain 对象的 processor 字段。
        bc.processor = NewStateProcessor(chainConfig, bc, engine)
    ​
        var err error
        bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
        if err != nil {
            return nil, err
        }
        // 获得创世区块
        bc.genesisBlock = bc.GetBlockByNumber(0)
        if bc.genesisBlock == nil {
            return nil, ErrNoGenesis
        }
        // 表示当前链上没有任何区块
        bc.currentBlock.Store(nil)
        bc.currentSnapBlock.Store(nil)
        bc.currentFinalBlock.Store(nil)
        bc.currentSafeBlock.Store(nil)
    ​
        // 将当前区块链的 ID 更新到度量指标中,以便监控该区块链的状态
        chainInfoGauge.Update(metrics.GaugeInfoValue{"chain_id": bc.chainConfig.ChainID.String()})
        // 如果区块链是否是空,为空则从快照加载数据来初始化
        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("Genesis state is missing, wait state sync")
            } else {
                var diskRoot common.Hash
                // 如果启用了快照,则从快照提取快照中的根哈希值
                if bc.cacheConfig.SnapshotLimit > 0 {
                    diskRoot = rawdb.ReadSnapshotRoot(bc.db)
                }
                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 {
                    // 快照根哈希值不存在,快照未启用或不存在,调用setHeadBeyondRoot函数设置链头
                    // 传入一个空的哈希值,尝试从头开始恢复状态
                    log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())
                    if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {
                        return nil, err
                    }
                }
            }
        }
        // 是否有祖先区块,声明是否需要回滚区块以及需要回滚区块的编号
        if frozen, err := bc.db.Ancients(); err == nil && frozen > 0 {
            var (
                needRewind bool
                low        uint64
            )
            // 获取最新区块
            fullBlock := bc.CurrentBlock()
            // 当前区块不为空,不是创世区块且区块变化小于祖先区块-1,需要回滚
            if fullBlock != nil && fullBlock.Hash() != bc.genesisBlock.Hash() && fullBlock.Number.Uint64() < frozen-1 {
                needRewind = true
                low = fullBlock.Number.Uint64()
            }
            // 获取当前区链的快照区块
            snapBlock := bc.CurrentSnapBlock()
            if snapBlock != nil && snapBlock.Number.Uint64() < frozen-1 {
                needRewind = true
                if snapBlock.Number.Uint64() < low || low == 0 {
                    low = snapBlock.Number.Uint64()
                }
            }
            // 回滚操作,需要指定回滚的起始区块编号和终止区块编号,设置新的区块链头部,回滚到low区块
            if needRewind {
                log.Error("Truncating ancient chain", "from", bc.CurrentHeader().Number.Uint64(), "to", low)
                if err := bc.SetHead(low); err != nil {
                    return nil, err
                }
            }
        }
        // 区块链引擎确定当前区块是否合法
        bc.engine.VerifyHeader(bc, bc.CurrentHeader())
        // 遍历有问题的哈希集合,如果区块头的哈希值和里面的某个值相等,表示该区块哈希值有问题需要回滚
        for hash := range BadHashes {
            if header := bc.GetHeaderByHash(hash); header != nil {
                headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64())
                if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
                    log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
                    if err := bc.SetHead(header.Number.Uint64() - 1); err != nil {
                        return nil, err
                    }
                    log.Error("Chain rewind was successful, resuming normal operation")
                }
            }
        }
        // 记录区块链初始化事件
        if bc.logger != nil && bc.logger.OnBlockchainInit != nil {
            bc.logger.OnBlockchainInit(chainConfig)
        }
        // 检查日志器是否有OnGenesisBlock函数,如果是创世区块则试图访问区块信息,然后执行回调函数
        if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
            if block := bc.CurrentBlock(); block.Number.Uint64() == 0 {
                alloc, err := getGenesisState(bc.db, block.Hash())
                if err != nil {
                    return nil, fmt.Errorf("failed to get genesis state: %w", err)
                }
    ​
                if alloc == nil {
                    return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set")
                }
    ​
                bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
            }
        }
        // 启用了快照
        if bc.cacheConfig.SnapshotLimit > 0 {
            // 是否启用了快照恢复
            var recover bool
            head := bc.CurrentBlock()
            if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *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)
        }
        // 检查是否存在配置兼容性问题并进行相应处理
        if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
            log.Warn("Rewinding chain to upgrade configuration", "err", compat)
            if compat.RewindToTime > 0 {
                // 将链回滚到指定时间
                bc.SetHeadWithTimestamp(compat.RewindToTime)
            } else {
                // 将链回滚到指定区块id
                bc.SetHead(compat.RewindToBlock)
            }
            // 将更新后的链配置写入数据库
            rawdb.WriteChainConfig(db, genesisHash, chainConfig)
        }
        // 检查是否传入了交易索引限制参数,并相应的配置区块链的交易索引器
        if txLookupLimit != nil {
            bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
        }
        return bc, nil
    }

    下面是从数据库中加载最新的链状态,

    // loadLastState 从数据库中加载最新的已知链状态。该方法假设链管理器的互斥锁已被持有。
    func (bc *BlockChain) loadLastState() error {
        // 恢复最新的已知头区块
        head := rawdb.ReadHeadBlockHash(bc.db) // 从数据库中读取最新的区块哈希
        if head == (common.Hash{}) { // 如果数据库中最新的区块哈希为空,表示数据库可能损坏或为空
            // 数据库损坏或为空,重新初始化区块链
            log.Warn("数据库为空,正在重置链") // 发出警告日志,数据库为空,重置区块链
            return bc.Reset() // 调用重置方法进行初始化
        }
        // 确保完整的头区块可用
        headBlock := bc.GetBlockByHash(head) // 获取最新的区块
        if headBlock == nil { // 如果获取的区块为空,表示数据库可能损坏或为空
            // 数据库损坏或为空,重新初始化区块链
            log.Warn("头区块丢失,正在重置链", "哈希", head) // 发出警告日志,头区块丢失,重置区块链
            return bc.Reset() // 调用重置方法进行初始化
        }
        // 一切正常,将其设置为头区块
        bc.currentBlock.Store(headBlock.Header()) // 存储头区块
        headBlockGauge.Update(int64(headBlock.NumberU64())) // 更新头区块计数器
    ​
        // 恢复最新已知的头标头
        headHeader := headBlock.Header() // 获取头标头
        if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { // 如果数据库中存在头标头哈希
            if header := bc.GetHeaderByHash(head); header != nil { // 获取头标头
                headHeader = header // 设置头标头
            }
        }
        bc.hc.SetCurrentHeader(headHeader) // 设置当前标头
    ​
        // 恢复最新已知的头快照块
        bc.currentSnapBlock.Store(headBlock.Header()) // 存储快照区块头
        headFastBlockGauge.Update(int64(headBlock.NumberU64())) // 更新快照区块计数器
    ​
        if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) { // 如果数据库中存在快照区块哈希
            if block := bc.GetBlockByHash(head); block != nil { // 获取快照区块
                bc.currentSnapBlock.Store(block.Header()) // 存储快照区块头
                headFastBlockGauge.Update(int64(block.NumberU64())) // 更新快照区块计数器
            }
        }
    ​
        // 恢复最新已知的最终化块和安全块
        // 注意:安全块未存储在磁盘上,并在启动时设置为最后已知的最终化块
        if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) { // 如果数据库中存在最终化块哈希
            if block := bc.GetBlockByHash(head); block != nil { // 获取最终化块
                bc.currentFinalBlock.Store(block.Header()) // 存储最终化块头
                headFinalizedBlockGauge.Update(int64(block.NumberU64())) // 更新最终化块计数器
                bc.currentSafeBlock.Store(block.Header()) // 存储安全块头
                headSafeBlockGauge.Update(int64(block.NumberU64())) // 更新安全块计数器
            }
        }
        // 向用户发出状态日志
        var (
            currentSnapBlock  = bc.CurrentSnapBlock() // 获取当前快照区块
            currentFinalBlock = bc.CurrentFinalBlock() // 获取当前最终化块
    ​
            headerTd = bc.GetTd(headHeader.Hash(), headHeader.Number.Uint64()) // 获取头标头的总难度
            blockTd  = bc.GetTd(headBlock.Hash(), headBlock.NumberU64()) // 获取头区块的总难度
        )
        if headHeader.Hash() != headBlock.Hash() { // 如果头标头哈希与头区块哈希不相等
            log.Info("加载最新的本地标头", "高度", headHeader.Number, "哈希", headHeader.Hash(), "总难度", headerTd, "时间", common.PrettyAge(time.Unix(int64(headHeader.Time), 0))) // 输出日志,加载最新的本地标头
        }
        log.Info("加载最新的本地区块", "高度", headBlock.Number(), "哈希", headBlock.Hash(), "总难度", blockTd, "时间", common.PrettyAge(time.Unix(int64(headBlock.Time()), 0))) // 输出日志,加载最新的本地区块
        if headBlock.Hash() != currentSnapBlock.Hash() { // 如果头区块哈希与当前快照区块哈希不相等
            snapTd := bc.GetTd(currentSnapBlock.Hash(), currentSnapBlock.Number.Uint64()) // 获取快照区块的总难度
            log.Info("加载最新的本地快照区块", "高度", currentSnapBlock.Number, "哈希", currentSnapBlock.Hash(), "总难度", snapTd, "时间", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) // 输出日志,加载最新的本地快照区块
        }
        if currentFinalBlock != nil { // 如果存在当前最终化块
            finalTd := bc.GetTd(currentFinalBlock.Hash(), currentFinalBlock.Number.Uint64()) // 获取最终化块的总难度
            log.Info("加载最新的本地最终化块", "高度", currentFinalBlock.Number, "哈希", currentFinalBlock.Hash(), "总难度", finalTd, "时间", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) // 输出日志,加载最新的本地最终化块
        }
        if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { // 如果存在最后的快照同步枢纽标记
            log.Info("加载最后的快照同步枢纽标记", "高度", *pivot) // 输出日志,加载最后的快照同步枢纽标记
        }
        return nil // 返回空错误
    }

    下面这个函数的主要功能是设置链的头部,负责回滚区块链到请求的头部,并根据需要进行相关的清理工作,包括:

    • 获取链的锁,确保只有一个线程正在进行链头设置的操作;

    • 根据修复标志和数据库状态,决定是否执行头部的更新操作;

    • 根据新的链头更新函数和删除函数对链更新;

    • 清理缓存中的陈旧内容;

    • 需要的情况下,清除已经失效的安全块和已经失效的最终化的块;

    • 加载最新的链状态;

    • 返回相关信息

    func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Hash, repair bool) (uint64, error) {
        // 如果无法获取锁,返回错误信息
        if !bc.chainmu.TryLock() {
            return 0, errChainStopped
        }
        defer bc.chainmu.Unlock()
    ​
        var (
            // 跟踪请求根哈希值的块编号
            rootNumber uint64 // (如果没有根,则始终为0)
    ​
            // 检索最后一个枢轴块以在其之前短路回滚,并检索当前冻结器限制以开始清理下溢的数据。
            pivot = rawdb.ReadLastPivotNumber(bc.db)
        )
        updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
            // 回滚区块链,确保我们不会得到一个无状态的链头块。注意,深度相等性被允许,以允许将 SetHead 用作链修复机制而不删除任何数据!
            if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() <= currentBlock.Number.Uint64() {
                var newHeadBlock *types.Header
                newHeadBlock, rootNumber = bc.rewindHead(header, root)
                rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
    ​
                // 如果显式地恢复了链标记,则降级链标记。在理论上,我们应该在最后一步更新所有内存中的标记,然而 SetHead 的方向是从高到低,所以可以直接更新内存中的标记。
                bc.currentBlock.Store(newHeadBlock)
                headBlockGauge.Update(int64(newHeadBlock.Number.Uint64()))
    ​
                // 头状态丢失,这只在基于路径的方案中可能发生。当链头被回滚到枢轴点以下时会出现这种情况。在这种情况下,除了重新运行快照同步器之外,没有可能的恢复方法。在此之前不执行任何操作。
                if !bc.HasState(newHeadBlock.Root) {
                    if newHeadBlock.Number.Uint64() != 0 {
                        log.Crit("链在非创世块处处于无状态")
                    }
                    log.Info("链处于无状态,请等待状态同步", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash())
                }
            }
            // 简单地回滚快照块到目标头
            if currentSnapBlock := bc.CurrentSnapBlock(); currentSnapBlock != nil && header.Number.Uint64() < currentSnapBlock.Number.Uint64() {
                newHeadSnapBlock := bc.GetBlock(header.Hash(), header.Number.Uint64())
                // 如果任一块达到 nil,则重置到创世状态
                if newHeadSnapBlock == nil {
                    newHeadSnapBlock = bc.genesisBlock
                }
                rawdb.WriteHeadFastBlockHash(db, newHeadSnapBlock.Hash())
    ​
                // 如果显式地恢复了链标记,则降级链标记。在理论上,我们应该在最后一步更新所有内存中的标记,然而 SetHead 的方向是从高到低,所以可以直接更新内存中的标记。
                bc.currentSnapBlock.Store(newHeadSnapBlock.Header())
                headFastBlockGauge.Update(int64(newHeadSnapBlock.NumberU64()))
            }
            var (
                headHeader = bc.CurrentBlock()
                headNumber = headHeader.Number.Uint64()
            )
            // 如果 SetHead 下溢冻结器阈值,并且之后的区块处理意图是完整的区块导入,则删除状态块之间的链段和设置头目标之间的链段。
            var wipe bool
            frozen, _ := bc.db.Ancients()
            if headNumber+1 < frozen {
                wipe = pivot == nil || headNumber >= *pivot
            }
            return headHeader, wipe // 如果是完整同步,只有在强制清理时才清除
        }
        // 回滚头链,删除直到所有区块体的块
        delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
            // 忽略此处的错误,因为轻客户端不会走到这条路径上
            frozen, _ := bc.db.Ancients()
            if num+1 <= frozen {
                // 从古代存储中截断所有相关数据(头部、总难度、主体、收据和规范哈希)。
                if _, err := bc.db.TruncateHead(num); err != nil {
                    log.Crit("截断古代数据失败", "number", num, "err", err)
                }
                // 从活动存储中删除哈希 <-> 编号映射。
                rawdb.DeleteHeaderNumber(db, hash)
            } else {
                // 从活动存储中删除相关体和收据。
                // 头、总难度和规范哈希将在 hc.SetHead 函数中删除。
                rawdb.DeleteBody(db, hash, num)
                rawdb.DeleteReceipts(db, hash, num)
            }
            // 待办事项(rjl493456442) 事务查找、布隆位图等
        }
        // 如果 SetHead 仅作为链修复方法调用,则尝试跳过头链
        // 如果修复冻结器已损坏,则强制进行清理。
        if repair {
            if target, force := updateFn(bc.db, bc.CurrentBlock()); force {
                bc.hc.SetHead(target.Number.Uint64(), nil, delFn)
            }
        } else {
            // 回滚链到请求的头部,然后向后继续,直到找到一个具有状态的块或通过快照同步枢轴。
            if time > 0 {
                log.Warn("回滚区块链到时间戳", "目标", time)
                bc.hc.SetHeadWithTimestamp(time, updateFn, delFn)
            } else {
                log.Warn("回滚区块链到区块", "目标", head)
                bc.hc.SetHead(head, updateFn, delFn)
            }
        }
        // 清除缓存中的任何陈旧内容
        bc.bodyCache.Purge()
        bc.bodyRLPCache.Purge()
        bc.receiptsCache.Purge()
        bc.blockCache.Purge()
        bc.txLookupCache.Purge()
    ​
        // 如果需要,清除安全块、已最终化的块
        if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
            log.Warn("SetHead 使安全块无效")
            bc.SetSafe(nil)
        }
        if finalized := bc.CurrentFinalBlock(); finalized != nil && head < finalized.Number.Uint64() {
            log.Error("SetHead 使已最终化的块无效")
            bc.SetFinalized(nil)
        }
        return rootNumber, bc.loadLastState()
    }

    在区块链停止前,需要执行一系列的操作来确保数据的一致性和持久性,主要逻辑如下:

    1. stopWithoutSaving 被调用,停止区块链服务但不保存数据。

    2. 如果存在快照管理器 snaps,则将当前区块的状态快照记录到磁盘日志中,并释放快照管理器。

    3. 如果区块链数据库的方案为路径方案(PathScheme),则确保将内存中的Trie节点记录到磁盘日志中。

    4. 否则,对于最近的一些区块(当前区块、当前区块的前一个区块、当前区块之前的一些区块),将其状态Trie写入磁盘以确保数据持久性。这是为了应对不同的重启场景,比如:

      • HEAD:为了避免在一般情况下不需要重新处理任何区块。

      • HEAD-1:为了避免如果HEAD成为叔块时出现大的重组。

      • HEAD-127:为了对重新执行的区块数量设置硬性限制。

    5. 如果存在快照基础 snapBase,则将其状态Trie写入磁盘。

    6. 对于缓存中的任何尚未写入磁盘的Trie节点,从缓存中删除。

    7. 检查数据库是否已经清空,如果没有,则记录错误。

    8. 关闭Trie数据库,释放所有资源。

    9. 打印区块链停止的信息。

    func (bc *BlockChain) Stop() {
        // 停止区块链服务
    ​
        // 停止区块链服务但不保存状态快照
        bc.stopWithoutSaving()
    ​
        // 确保将完整的状态快照记录到磁盘中
        var snapBase common.Hash
        if bc.snaps != nil {
            var err error
            // 如果存在快照,则记录快照
            if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root); err != nil {
                log.Error("记录状态快照失败", "err", err)
            }
            bc.snaps.Release() // 释放快照资源
        }
        // 如果 trie 数据库是基于路径的,则确保将内存中的 trie 节点正确记录到磁盘中
        if bc.triedb.Scheme() == rawdb.PathScheme {
            if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil {
                log.Info("记录内存中 trie 节点失败", "err", err)
            }
        } else {
            // 否则,确保最近一个区块的状态也被存储到磁盘中
            // 我们写入三种不同的状态以捕获不同的重启场景:
            //  - HEAD:     这样我们在一般情况下不需要重新处理任何区块
            //  - HEAD-1:   这样如果我们的 HEAD 成为叔块,我们不会做出大幅度的重组
            //  - HEAD-127: 这样我们对重新执行的区块数量有一个硬性限制
            if !bc.cacheConfig.TrieDirtyDisabled {
                triedb := bc.triedb
    ​
                for _, offset := range []uint64{0, 1, TriesInMemory - 1} {
                    if number := bc.CurrentBlock().Number.Uint64(); number > offset {
                        recent := bc.GetBlockByNumber(number - offset)
    ​
                        log.Info("将缓存的状态写入磁盘", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
                        if err := triedb.Commit(recent.Root(), true); err != nil {
                            log.Error("提交最近状态 trie 失败", "err", err)
                        }
                    }
                }
                if snapBase != (common.Hash{}) {
                    log.Info("将快照状态写入磁盘", "root", snapBase)
                    if err := triedb.Commit(snapBase, true); err != nil {
                        log.Error("提交快照状态 trie 失败", "err", err)
                    }
                }
                // 清理 triegc 中的节点
                for !bc.triegc.Empty() {
                    triedb.Dereference(bc.triegc.PopItem())
                }
                // 如果 trie 数据库中仍有节点,则输出错误信息
                if _, nodes, _ := triedb.Size(); nodes != 0 { // 所有内存都包含在节点返回的 hashdb 中
                    log.Error("清理后仍存在悬空 trie 节点")
                }
            }
        }
        // 关闭 trie 数据库,并在最后一步释放所有资源
        if err := bc.triedb.Close(); err != nil {
            log.Error("关闭 trie 数据库失败", "err", err)
        }
        log.Info("区块链服务已停止")
    }

    下面函数的作用是向区块链中插入收据链(Receipt Chain)。在以太坊区块链中,每个区块都包含一个交易列表,每个交易执行完成后都会生成一个收据(Receipt),收据中包含了交易执行的结果信息,比如合约调用的执行结果、日志等。而收据链则是指一系列区块中的收据的集合。

    具体来说,这个函数的主要作用包括:

    1. 将收据链插入到区块链中:

      • 该函数接受三个参数:blockChain(区块链中的区块列表)、receiptChain(收据链,与区块链中的区块一一对应)、ancientLimit(古老区块的限制值)。

      • 首先,函数会对提供的区块链进行一些合理性检查,确保区块的顺序和连续性。

      • 然后,根据古老区块的限制值,将区块链分为古老区块和活跃区块。

      • 接着,函数会将古老区块的数据写入古老存储(Ancient Store),活跃区块的数据写入活跃存储(Active Store)。

      • 在写入数据之前,还会进行一些额外的检查和处理,比如验证区块的连续性、更新头部快照等。

      • 最后,函数会输出一些统计信息,比如处理的区块数量、执行时间等。

    2. 在插入过程中,还会进行一些额外的处理:

      • 在写入古老存储之前,会检查古老存储中是否已包含 Genesis 区块,如果没有则将 Genesis 区块写入古老存储。

      • 在写入活跃存储之前,会检查活跃存储中是否已存在相同的区块,如果存在则会忽略该区块。

    func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) {
        // 这里我们不需要 chainMu,因为我们希望最大化头部插入和收据插入的并发性。
        bc.wg.Add(1) // 增加等待组计数器
        defer bc.wg.Done() // 在函数退出时减少等待组计数器
    ​
        var (
            ancientBlocks, liveBlocks     types.Blocks // 用于存放古老区块和活跃区块
            ancientReceipts, liveReceipts []types.Receipts // 用于存放古老区块和活跃区块对应的收据
        )
    ​
        // 执行一些合理性检查,确保提供的链是有序且连续链接的
        for i, block := range blockChain {
            if i != 0 {
                prev := blockChain[i-1]
                // 检查区块号和父哈希是否连续
                if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() {
                    log.Error("非连续的收据插入",
                        "number", block.Number(), "hash", block.Hash(), "parent", block.ParentHash(),
                        "prevnumber", prev.Number(), "prevhash", prev.Hash())
                    return 0, fmt.Errorf("非连续的插入:项目 %d 是 #%d [%x..],项目 %d 是 #%d [%x..](父级 [%x..])",
                        i-1, prev.NumberU64(), prev.Hash().Bytes()[:4],
                        i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4])
                }
            }
            // 将区块分为古老区块和活跃区块
            if block.NumberU64() <= ancientLimit {
                ancientBlocks, ancientReceipts = append(ancientBlocks, block), append(ancientReceipts, receiptChain[i])
            } else {
                liveBlocks, liveReceipts = append(liveBlocks, block), append(liveReceipts, receiptChain[i])
            }
    ​
            // 在这里还验证区块中的 blob 交易是否包含附属信息。
            // 虽然附属信息不影响区块哈希 / 交易哈希,但在一个区块内发送 blob 是不允许的。
            for txIndex, tx := range block.Transactions() {
                if tx.Type() == types.BlobTxType && tx.BlobTxSidecar() != nil {
                    return 0, fmt.Errorf("区块 #%d 中的交易索引 %d 包含意外的 blob 附属信息", block.NumberU64(), txIndex)
                }
            }
        }
    ​
        var (
            stats = struct{ processed, ignored int32 }{} // 统计处理的区块数和被忽略的区块数
            start = time.Now() // 记录插入开始时间
            size  = int64(0) // 记录数据大小
        )
    ​
        // 更新头部快照同步区块,如果插入的区块更好,并返回插入的区块是否是主链的指示器。
        updateHead := func(head *types.Block) bool {
            if !bc.chainmu.TryLock() {
                return false
            }
            defer bc.chainmu.Unlock()
    ​
            // 如果当前头部快照的区块号大于等于插入的区块号,则跳过
            if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 {
                // 如果发生重组,则跳过
                reorg, err := bc.forker.ReorgNeeded(bc.CurrentSnapBlock(), head.Header())
                if err != nil {
                    log.Warn("重组失败", "err", err)
                    return false
                } else if !reorg {
                    return false
                }
                rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
                bc.currentSnapBlock.Store(head.Header())
                headFastBlockGauge.Update(int64(head.NumberU64()))
                return true
            }
            return false
        }
    ​
        // 将区块链和对应的收据链写入古老存储
        writeAncient := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
            first := blockChain[0] // 古老区块链的第一个区块
            last := blockChain[len(blockChain)-1] // 古老区块链的最后一个区块
    ​
            // 确保 genesis 区块在古老存储中
            if first.NumberU64() == 1 {
                if frozen, _ := bc.db.Ancients(); frozen == 0 {
                    td := bc.genesisBlock.Difficulty()
                    writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}, td)
                    if err != nil {
                        log.Error("将 genesis 写入古老存储失败", "err", err)
                        return 0, err
                    }
                    size += writeSize
                    log.Info("已将 genesis 写入古老存储")
                }
            }
            // 在将区块写入古老存储之前,我们需要确保它们与头部链的预期一致。
            // 我们只检查最后一个区块/头部,因为它是一个连续的链。
            if !bc.HasHeader(last.Hash(), last.NumberU64()) {
                return 0, fmt.Errorf("包含头部 #%d [%x..] 未知", last.Number(), last.Hash().Bytes()[:4])
            }
    ​
            // 将所有链数据写入古老存储
            td := bc.GetTd(first.Hash(), first.NumberU64()) // 获取古老区块链的总难度
            writeSize, err := rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain, td)
            if err != nil {
                log.Error("将链数据导入古老存储失败", "err", err)
                return 0, err
            }
            size += writeSize
    ​
            // 显式同步古老存储,以确保所有数据已刷新到磁盘。
            if err := bc.db.Sync(); err != nil {
                return 0, err
            }
            // 更新当前快照区块,因为所有区块数据现在都存在于 DB 中。
            previousSnapBlock := bc.CurrentSnapBlock().Number.Uint64()
            if !updateHead(blockChain[len(blockChain)-1]) {
                // 如果头部链发生了重组,并且区块/收据与主链不匹配,则进入此处。
                if _, err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil {
                    log.Error("无法在插入失败后截断古老存储", "err", err)
                }
                return 0, errSideChainReceipts
            }
    ​
            // 从主数据库中删除区块数据
            var (
                batch       = bc.db.NewBatch()
                canonHashes = make(map[common.Hash]struct{}) // 存放主链区块哈希
            )
            for _, block := range blockChain {
                canonHashes[block.Hash()] = struct{}{}
                if block.NumberU64() == 0 {
                    continue
                }
                rawdb.DeleteCanonicalHash(batch, block.NumberU64())
                rawdb.DeleteBlockWithoutNumber(batch, block.Hash(), block.NumberU64())
            }
            // 删除侧链哈希到编号的映射
            for _, nh := range rawdb.ReadAllHashesInRange(bc.db, first.NumberU64(), last.NumberU64()) {
                if _, canon := canonHashes[nh.Hash]; !canon {
                    rawdb.DeleteHeader(batch, nh.Hash, nh.Number)
                }
            }
            if err := batch.Write(); err != nil {
                return 0, err
            }
            stats.processed += int32(len(blockChain))
            return 0, nil
        }
    ​
        // 将下载的链数据和对应的收据链数据写入古老存储和活跃存储
        if len(ancientBlocks) > 0 {
            if n, err := writeAncient(ancientBlocks, ancientReceipts); err != nil {
                if err == errInsertionInterrupted {
                    return 0, nil
                }
                return n, err
            }
        }
        if len(liveBlocks) > 0 {
            if n, err := writeLive(liveBlocks, liveReceipts); err != nil {
                if err == errInsertionInterrupted {
                    return 0, nil
                }
                return n, err
            }
        }
        var (
            head    = blockChain[len(blockChain)-1]
            context = []interface{}{
                "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)),
                "number", head.Number(), "hash", head.Hash(), "age", common.PrettyAge(time.Unix(int64(head.Time()), 0)),
                "size", common.StorageSize(size),
            }
        )
        if stats.ignored > 0 {
            context = append(context, []interface{}{"ignored", stats.ignored}...)
        }
        log.Debug("导入新区块收据", context...)
    ​
        return 0, nil
    }

    下面函数是将带有状态的区块写入区块链中,并将相关状态信息存储到数据库中:

    func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
        // 计算区块的总难度
        ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
        if ptd == nil {
            return consensus.ErrUnknownAncestor // 如果父区块的总难度为空,则返回错误
        }
        // 确保在插入期间不会泄露不一致的状态
        externTd := new(big.Int).Add(block.Difficulty(), ptd)
    ​
        // 不考虑规范状态,将区块本身写入数据库。
    ​
        // 注意:所有区块组件(总难度、哈希->编号映射、头部、主体、收据)都应原子化地写入。
        // BlockBatch 用于包含所有组件。
        blockBatch := bc.db.NewBatch()
        rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) // 写入总难度
        rawdb.WriteBlock(blockBatch, block) // 写入区块
        rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) // 写入收据
        rawdb.WritePreimages(blockBatch, state.Preimages()) // 写入预映像
        if err := blockBatch.Write(); err != nil {
            log.Crit("Failed to write block into disk", "err", err) // 如果写入失败,记录关键错误
        }
    ​
        // 将所有缓存的状态更改提交到底层内存数据库
        root, err := state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number())) // 提交状态更改
        if err != nil {
            return err // 如果提交失败,返回错误
        }
    ​
        // 如果节点运行在路径模式下,跳过显式的垃圾回收操作,因为在此模式下是不必要的
        if bc.triedb.Scheme() == rawdb.PathScheme {
            return nil
        }
    ​
        // 如果节点运行在存档模式下,始终执行刷新
        if bc.cacheConfig.TrieDirtyDisabled {
            return bc.triedb.Commit(root, false) // 将根哈希提交到数据库
        }
    ​
        // 如果节点不是运行在存档模式下,进行适当的垃圾回收操作
    ​
        // 引用根哈希以保持状态树存活
        bc.triedb.Reference(root, common.Hash{})
    ​
        // 将根哈希和区块号压入垃圾回收器
        bc.triegc.Push(root, -int64(block.NumberU64()))
    ​
        // 不考虑前TriesInMemory块的刷新限制
        current := block.NumberU64()
        if current <= TriesInMemory {
            return nil
        }
    ​
        // 如果超出了内存限制,将成熟的单例节点刷新到磁盘
        var (
            _, nodes, imgs = bc.triedb.Size() // 所有内存都包含在节点返回的大小中
            limit          = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024
        )
        if nodes > limit || imgs > 4*1024*1024 {
            bc.triedb.Cap(limit - ethdb.IdealBatchSize) // 限制内存使用
        }
    ​
        // 查找下一个需要提交的状态树
        chosen := current - TriesInMemory
        flushInterval := time.Duration(bc.flushInterval.Load())
    ​
        // 如果超过时间允许的范围,刷新整个状态树到磁盘
        if bc.gcproc > flushInterval {
            // 如果缺少头部(规范链后面的标头),则正在重新组织低难度侧链。暂停提交直到此操作完成。
            header := bc.GetHeaderByNumber(chosen)
            if header == nil {
                log.Warn("Reorg in progress, trie commit postponed", "number", chosen)
            } else {
                // 如果超过限制但尚未达到足够大的内存差距,警告用户系统正在变得不稳定
                if chosen < bc.lastWrite+TriesInMemory && bc.gcproc >= 2*flushInterval {
                    log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/TriesInMemory)
                }
                // 刷新整个状态树并重置计数器
                bc.triedb.Commit(header.Root, true)
                bc.lastWrite = chosen
                bc.gcproc = 0
            }
        }
    ​
        // 回收低于所需写入保留量的任何内容
        for !bc.triegc.Empty() {
            root, number := bc.triegc.Pop()
            if uint64(-number) > chosen {
                bc.triegc.Push(root, number)
                break
            }
            bc.triedb.Dereference(root)
        }
    ​
        return nil
    }

    下面是将一组区块插入到区块链中。它负责验证、处理和导入给定链的区块,并根据需要设置链的头部:

    func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) {
        // 如果链已经被终止,则不执行导入操作
        if bc.insertStopped() {
            return 0, nil
        }
    ​
        // 启动并行签名恢复(在分叉转换时签名者可能会失败,性能损失最小)
        SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
    ​
        var (
            stats     = insertStats{startTime: mclock.Now()} // 插入统计信息
            lastCanon *types.Block                          // 最后一个确定的区块
        )
        // 在返回之前,发送一个链头事件,如果我们推进了链
        defer func() {
            if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
                bc.chainHeadFeed.Send(ChainHeadEvent{lastCanon})
            }
        }()
    ​
        // 启动并行头部验证
        headers := make([]*types.Header, len(chain))
        for i, block := range chain {
            headers[i] = block.Header()
        }
        abort, results := bc.engine.VerifyHeaders(bc, headers)
        defer close(abort)
    ​
        // 获取第一个区块并检查错误,以决定导入逻辑的方向
        it := newInsertIterator(chain, results, bc.validator)
        block, err := it.next()
    ​
        // 如果导入链不需要构建快照,则跳过已知区块
        if bc.skipBlock(err, it) {
            // 第一个区块(和状态)已知
            // 1. 我们进行了回滚,并且现在应该重新导入
            // 2. 区块被存储为侧链,并且它在其状态根上说谎,并且通过了状态根
            //    来自尚未验证的规范链,这是不一致的。
            // 跳过我们后面已知的区块。
            var (
                reorg   bool
                current = bc.CurrentBlock()
            )
            for block != nil && bc.skipBlock(err, it) {
                reorg, err = bc.forker.ReorgNeeded(current, block.Header())
                if err != nil {
                    return it.index, err
                }
                if reorg {
                    // 如果分叉器表示重新组织是必要的,则切换到导入模式
                    // 并且区块不在规范链上。
                    // 在 eth2 中,分叉器总是返回 true 以进行重新组织决策
                    // (盲目信任外部共识引擎),但为了避免导入已知块时不必要的重新组织,
                    // 这里处理特殊情况。
                    if block.NumberU64() > current.Number.Uint64() || bc.GetCanonicalHash(block.NumberU64()) != block.Hash() {
                        break
                    }
                }
                log.Debug("忽略已知区块", "number", block.Number(), "hash", block.Hash())
                stats.ignored++
    ​
                block, err = it.next()
            }
            // 跳转到区块导入
        }
    ​
        switch {
        case errors.Is(err, consensus.ErrPrunedAncestor):
            // 第一个块已被修剪
            if setHead {
                // 第一个块已修剪,将其插入为侧链,只有在 TD 增长足够时才重新组织
                log.Debug("修剪的祖先,插入为侧链", "number", block.Number(), "hash", block.Hash())
                return bc.insertSideChain(block, it)
            } else {
                // 我们已经合并并且父块已被修剪,尝试恢复父状态
                log.Debug("修剪的祖先", "number", block.Number(), "hash", block.Hash())
                _, err := bc.recoverAncestors(block)
                return it.index, err
            }
        case err != nil && !errors.Is(err, ErrKnownBlock):
            // 发生了其他错误(除了 ErrKnownBlock),中止
            stats.ignored += len(it.chain)
            bc.reportBlock(block, nil, err)
            return it.index, err
        }
    ​
        // 没有验证错误的第一个块(或跳过了链前缀)
        var activeState *state.StateDB
        defer func() {
            // 区块导入过程中,该函数启动和停止了预取器。
            // 如果发生了坏块或其他错误,可能会导致早期返回不正确地终止后台线程。
            // 此延迟确保我们清理悬挂的预取器,而不需要每次延迟一个都持有活动引用。
            if activeState != nil {
                activeState.StopPrefetcher()
            }
        }()
    ​
        for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
            // 如果链已终止,则停止处理区块
            if bc.insertStopped() {
                log.Debug("区块处理过程中中止")
                break
            }
            // 如果头部是被禁止的,则直接中止
            if BadHashes[block.Hash()] {
                bc.reportBlock(block, nil, ErrBannedHash)
                return it.index, ErrBannedHash
            }
            // 如果区块是已知的(在链中间),这是 Clique 区块的特殊情况,
            // 它们可以共享彼此之间的状态,因此导入旧区块可能完成后续区块的状态。
            // 在这种情况下,只需跳过区块(我们已经完全验证过了它们一次,因为它们的头部和主体已经在数据库中)。
            // 但是,如果缺少对应的快照层,可能需要强制重新执行以构建它。
            if bc.skipBlock(err, it) {
                logger := log.Debug
                if bc.chainConfig.Clique == nil {
                    logger = log.Warn
                }
                logger("插入已知区块", "number", block.Number(), "hash", block.Hash(),
                    "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
                    "root", block.Root())
    ​
                // 特殊情况。如果我们在链中间遇到已知块,那么就提交空的 receipt 切片。
                // 它只能在 Clique 链中发生。每当我们通过 `insertSideChain` 插入块时,
                // 我们只提交 `td`、`header` 和 `body`(如果不存在的话)。由于我们没有
                // 事务而没有收据,所以没有什么可提交的。但是如果侧链最终会被采纳为规范链,
                // 那么它需要重新执行以获得丢失的状态,但如果是这种特殊情况(跳过重新执行),
                // 我们将丢失空收据条目。
                if len(block.Transactions()) == 0 {
                    rawdb.WriteReceipts(bc.db, block.Hash(), block.NumberU64(), nil)
                } else {
                    log.Error("请提交问题,跳过已知块执行而没有收据",
                        "hash", block.Hash(), "number", block.NumberU64())
                }
                if err := bc.writeKnownBlock(block); err != nil {
                    return it.index, err
                }
                stats.processed++
                if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
                    bc.logger.OnSkippedBlock(tracing.BlockEvent{
                        Block:     block,
                        TD:        bc.GetTd(block.ParentHash(), block.NumberU64()-1),
                        Finalized: bc.CurrentFinalBlock(),
                        Safe:      bc.CurrentSafeBlock(),
                    })
                }
    ​
                // 在这里我们可以假定日志为空,因为连续的 Clique 区块共享状态的唯一方式是没有交易。
                lastCanon = block
                continue
            }
    ​
            // 获取父区块及其状态以在其上执行
            start := time.Now()
            parent := it.previous()
            if parent == nil {
                parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
            }
            statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
            if err != nil {
                return it.index, err
            }
            statedb.SetLogger(bc.logger)
    ​
            // 启用预取以在处理事务时拉取 Trie 节点路径
            statedb.StartPrefetcher("chain")
            activeState = statedb
    ​
            // 如果有后续区块,则根据当前状态运行以预缓存事务和概率性地一些账户/存储 Trie 节点。
            var followupInterrupt atomic.Bool
            if !bc.cacheConfig.TrieCleanNoPrefetch {
                if followup, err := it.peek(); followup != nil && err == nil {
                    throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)
    ​
                    go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
                        // 对预取执行禁用跟踪。
                        vmCfg := bc.vmConfig
                        vmCfg.Tracer = nil
                        bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
    ​
                        blockPrefetchExecuteTimer.Update(time.Since(start))
                        if followupInterrupt.Load() {
                            blockPrefetchInterruptMeter.Mark(1)
                        }
                    }(time.Now(), followup, throwaway)
                }
            }
    ​
            // 区块导入的跟踪部分
            res, err := bc.processBlock(block, statedb, start, setHead)
            followupInterrupt.Store(true)
            if err != nil {
                return it.index, err
            }
            // 在返回各种结果之前报告导入统计信息
            stats.processed++
            stats.usedGas += res.usedGas
    ​
            var snapDiffItems, snapBufItems common.StorageSize
            if bc.snaps != nil {
                snapDiffItems, snapBufItems = bc.snaps.Size()
            }
            trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
            stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
    ​
            if !setHead {
                // 在合并后,我们预期很少的侧链。只计数
                // CL 提供给我们的所有块,以用于 GC 处理时间
                bc.gcproc += res.procTime
                return it.index, nil // 直接插入单个块
            }
            switch res.status {
            case CanonStatTy:
                log.Debug("插入新块", "number", block.Number(), "hash", block.Hash(),
                    "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
                    "elapsed", common.PrettyDuration(time.Since(start)),
                    "root", block.Root())
    ​
                lastCanon = block
    ​
                // 仅对规范块计数以进行 GC 处理时间
                bc.gcproc += res.procTime
    ​
            case SideStatTy:
                log.Debug("插入分叉块", "number", block.Number(), "hash", block.Hash(),
                    "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
                    "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
                    "root", block.Root())
    ​
            default:
                // 理论上不可能,但是为了我们将来的自己,还是留下一条日志,
                // 而不是尝试追踪不发出日志的块导入。
                log.Warn("插入具有未知状态的块", "number", block.Number(), "hash", block.Hash(),
                    "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
                    "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
                    "root", block.Root())
            }
        }
        stats.ignored += it.remaining()
        return it.index, err
    }

    结构体 blockProcessingResult 用于表示处理区块的结果,它包含了以下字段:

    1. usedGas uint64:表示处理该区块时消耗的燃气数量(Gas)。在以太坊区块链中,Gas 是执行智能合约和交易时所需支付的手续费。这个字段记录了执行区块中的交易所消耗的 Gas 数量。

    2. procTime time.Duration:表示处理该区块所花费的时间。这个字段记录了处理区块的时间长度,以 time.Duration 类型表示。

    3. status WriteStatus:表示处理该区块的结果状态。WriteStatus 是一个枚举类型,可能的取值有 CanonStatTy(表示这个区块是规范的,是区块链上的主要分支)、SideStatTy(表示这个区块是分叉的,不是区块链的主要分支)、以及其他可能的状态。

    得到区块后通过下面的函数来处理,包括下面的操作:

    1. 调用处理器(bc.processor)处理区块,执行区块中的交易,并返回区块的收据(receipts)、日志(logs)、消耗的 Gas 数量(usedGas)以及可能的错误(err)。

    2. 调用验证器(bc.validator)验证区块的状态,确保区块中的交易执行正确,并根据验证结果更新相应的指标。

    3. 将处理后的区块写入到区块链中,并设置新的区块头(如果 setHead 参数为 true)。

    4. 更新与区块处理相关的度量指标,如账户读取、存储读取、账户更新、存储更新、区块执行时间、区块验证时间等。

    5. 返回处理结果,包括使用的 Gas 数量、处理时间以及区块写入状态。

    func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) {
        // 如果日志记录器存在且存在块启动事件回调函数
        if bc.logger != nil && bc.logger.OnBlockStart != nil {
            // 获取区块的父区块的难度
            td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
            // 触发块启动事件回调
            bc.logger.OnBlockStart(tracing.BlockEvent{
                Block:     block,                     // 当前区块
                TD:        td,                        // 父区块的难度
                Finalized: bc.CurrentFinalBlock(),    // 当前已经最终化的区块
                Safe:      bc.CurrentSafeBlock(),     // 当前已经安全的区块
            })
        }
        // 如果日志记录器存在且存在块结束事件回调函数
        if bc.logger != nil && bc.logger.OnBlockEnd != nil {
            // 在函数返回时触发块结束事件回调
            defer func() {
                bc.logger.OnBlockEnd(blockEndErr)
            }()
        }
    ​
        // 使用父状态为参考点处理区块
        pstart := time.Now()  // 记录处理开始时间
        // 调用处理器处理区块,执行区块中的交易,获取收据、日志、使用的 Gas 数量和可能的错误
        receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
        if err != nil {
            // 如果处理过程中发生错误,报告区块处理错误并返回错误
            bc.reportBlock(block, receipts, err)
            return nil, err
        }
        ptime := time.Since(pstart)  // 记录处理时间
    ​
        vstart := time.Now()  // 记录验证开始时间
        // 验证区块状态,确保区块中的交易执行正确
        if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
            // 如果验证过程中发生错误,报告区块验证错误并返回错误
            bc.reportBlock(block, receipts, err)
            return nil, err
        }
        vtime := time.Since(vstart)  // 记录验证时间
        proctime := time.Since(start) // 计算总的处理时间(处理时间 + 验证时间)
    ​
        // 更新区块处理和验证期间涉及的度量指标
        accountReadTimer.Update(statedb.AccountReads)                   // 账户读取已完成(在处理中)
        storageReadTimer.Update(statedb.StorageReads)                   // 存储读取已完成(在处理中)
        snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads)   // 账户读取已完成(在处理中)
        snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads)   // 存储读取已完成(在处理中)
        accountUpdateTimer.Update(statedb.AccountUpdates)               // 账户更新已完成(在验证中)
        storageUpdateTimer.Update(statedb.StorageUpdates)               // 存储更新已完成(在验证中)
        accountHashTimer.Update(statedb.AccountHashes)                  // 账户哈希已完成(在验证中)
        storageHashTimer.Update(statedb.StorageHashes)                  // 存储哈希已完成(在验证中)
        triehash := statedb.AccountHashes + statedb.StorageHashes       // 用于哈希的 tries 的时间
        trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates   // 用于更新的 tries 的时间
        trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // 用于账户读取的 tries 的时间
        trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // 用于存储读取的 tries 的时间
        blockExecutionTimer.Update(ptime - trieRead)                    // 区块执行的时间(除去 trie 读取时间)
        blockValidationTimer.Update(vtime - (triehash + trieUpdate))    // 区块验证的时间(除去哈希和更新时间)
    ​
        // 将区块写入链中并获取状态
        var (
            wstart = time.Now()  // 记录写入开始时间
            status WriteStatus   // 写入状态
        )
        if !setHead {
            // 不设置头,仅插入区块
            err = bc.writeBlockWithState(block, receipts, statedb)
        } else {
            // 设置头并插入区块
            status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
        }
        if err != nil {
            return nil, err
        }
        // 更新写入期间涉及的度量指标
        accountCommitTimer.Update(statedb.AccountCommits)   // 账户提交已完成,可以标记
        storageCommitTimer.Update(statedb.StorageCommits)   // 存储提交已完成,可以标记
        snapshotCommitTimer.Update(statedb.SnapshotCommits) // 快照提交已完成,可以标记
        triedbCommitTimer.Update(statedb.TrieDBCommits)     // tries 数据库提交已完成,可以标记
    ​
        // 更新区块写入时间指标
        blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
        // 更新区块插入时间指标
        blockInsertTimer.UpdateSince(start)
    ​
        // 返回区块处理结果,包括使用的 Gas 数量、处理时间和写入状态
        return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
    }

    侧链区块是指与主链区块无关的区块,可能因为某些原因被裁减或分叉,以太坊需要对这些区块进行处理,

    1. 验证侧链区块的状态和难度,并根据情况更新外部总难度。

    2. 将侧链区块写入磁盘,同时记录区块相关信息,如交易数量、Gas消耗等。

    3. 如果外部总难度大于本地总难度,可能需要重新导入之前被裁剪的区块以重新生成所需的状态。

    4. 最后,该函数将导入侧链区块的信息记录下来,以便后续参考。

    func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) {
        var (
            externTd  *big.Int   // 外部总难度,用于存储外部链上当前区块的总难度
            lastBlock = block    // 最后一个处理的区块,默认为当前区块
            current   = bc.CurrentBlock() // 当前主链上的最新区块
        )
        // 第一个侧链区块的错误已经被验证为 ErrPrunedAncestor。
        // 由于我们在这里不导入它们,因此我们期望对于剩余的侧链区块会出现 ErrUnknownAncestor 错误。
        // 任何其他错误意味着该区块无效,不应写入磁盘。
        err := consensus.ErrPrunedAncestor
        for ; block != nil && errors.Is(err, consensus.ErrPrunedAncestor); block, err = it.next() {
            // 检查该编号的规范状态根
            if number := block.NumberU64(); current.Number.Uint64() >= number {
                canonical := bc.GetBlockByNumber(number)
                if canonical != nil && canonical.Hash() == block.Hash() {
                    // 不是侧链区块,这是一个重新导入的正常区块,其状态已被裁剪
    ​
                    // 收集该区块的难度。由于我们知道它是一个正常区块,
                    // 我们可以直接获取它,而不是像下面的方式那样使用父区块,然后在其上添加该区块
                    externTd = bc.GetTd(block.Hash(), block.NumberU64())
                    continue
                }
                if canonical != nil && canonical.Root() == block.Root() {
                    // 这很可能是一个影子状态攻击。当一个分叉被导入到数据库中,并且最终达到一个未裁剪的区块高度时,
                    // 我们刚刚发现状态已经存在!这意味着侧链区块引用的状态已经存在于我们的主链中。
                    //
                    // 如果不检查,我们现在将继续导入区块,而没有实际验证以前区块的状态。
                    log.Warn("侧链影子状态攻击检测到", "编号", block.NumberU64(), "侧链根", block.Root(), "主链根", canonical.Root())
    ​
                    // 如果有人合法地侧矿块,它们仍然会像往常一样被导入。然而,
                    // 当我们明显的目标是裁剪机制时,我们不能冒险写入未经验证的区块到磁盘上。
                    return it.index, errors.New("侧链影子状态攻击")
                }
            }
            if externTd == nil {
                externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1)
            }
            externTd = new(big.Int).Add(externTd, block.Difficulty())
    ​
            if !bc.HasBlock(block.Hash(), block.NumberU64()) {
                start := time.Now()
                if err := bc.writeBlockWithoutState(block, externTd); err != nil {
                    return it.index, err
                }
                log.Debug("注入侧链区块", "编号", block.Number(), "哈希", block.Hash(),
                    "难度", block.Difficulty(), "经过时间", common.PrettyDuration(time.Since(start)),
                    "交易数", len(block.Transactions()), "gas消耗", block.GasUsed(), "叔块数", len(block.Uncles()),
                    "根", block.Root())
            }
            lastBlock = block
        }
        // 此时,我们已将所有侧链区块写入数据库。循环要么在其他错误上结束,要么全部被处理。
        // 如果出现其他错误,我们可以忽略剩余的区块。
        //
        // 如果 externTd 大于我们的本地总难度,则需要重新导入之前的区块以重新生成所需的状态
        reorg, err := bc.forker.ReorgNeeded(current, lastBlock.Header())
        if err != nil {
            return it.index, err
        }
        if !reorg {
            localTd := bc.GetTd(current.Hash(), current.Number.Uint64())
            log.Info("侧链已写入磁盘", "开始", it.first().NumberU64(), "结束", it.previous().Number, "侧链总难度", externTd, "本地总难度", localTd)
            return it.index, err
        }
        // 收集所有侧链哈希(完整区块可能会占用大量内存)
        var (
            hashes  []common.Hash
            numbers []uint64
        )
        parent := it.previous()
        for parent != nil && !bc.HasState(parent.Root) {
            if bc.stateRecoverable(parent.Root) {
                if err := bc.triedb.Recover(parent.Root); err != nil {
                    return 0, err
                }
                break
            }
            hashes = append(hashes, parent.Hash())
            numbers = append(numbers, parent.Number.Uint64())
    ​
            parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
        }
        if parent == nil {
            return it.index, errors.New("缺少父区块")
        }
        // 导入所有被裁剪的区块以使状态可用
        var (
            blocks []*types.Block
            memory uint64
        )
        for i := len(hashes) - 1; i >= 0; i-- {
            // 将下一个区块追加到我们的批处理中
            block := bc.GetBlock(hashes[i], numbers[i])
    ​
            blocks = append(blocks, block)
            memory += block.Size()
    ​
            // 如果内存使用量增长太大,则导入并继续。遗憾的是,我们需要丢弃
            // 由于内存使用过重,此处的所有事件和日志通知。
            if len(blocks) >= 2048 || memory > 64*1024*1024 {
                log.Info("导入重量级侧链片段", "区块数", len(blocks), "开始", blocks[0].NumberU64(), "结束", block.NumberU64())
                if _, err := bc.insertChain(blocks, true); err != nil {
                    return 0, err
                }
                blocks, memory = blocks[:0], 0
    ​
                // 如果链正在终止,停止处理区块
                if bc.insertStopped() {
                    log.Debug("在处理区块时中止")
                    return 0, nil
                }
            }
        }
        if len(blocks) > 0 {
            log.Info("导入侧链片段", "开始", blocks[0].NumberU64(), "结束", blocks[len(blocks)-1].NumberU64())
            return bc.insertChain(blocks, true)
        }
        return 0, nil
    }

    当发生分叉时,需要重新组织区块链,需要执行的操作如下:

    1. 确定两条链的共同祖先块(commonBlock)。

    2. 根据共同祖先块,将两条链分为旧链(oldChain)和新链(newChain)。

    3. 将旧链上的所有块标记为已删除,并收集旧链上的所有交易。

    4. 将新链上的所有块标记为待添加,并收集新链上的所有交易。

    5. 发送链事件,通知有关旧链和新链的变化。

    6. 更新交易查找缓存,以清除过时的交易查找。

    7. 将新链(除了头块)按正确的增量顺序插入到链中。

    8. 删除无用的索引,包括非规范交易索引和超过头部的规范链索引。

    9. 发送事件以通知删除的日志和新增的日志。

    func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
        var (
            newChain    types.Blocks   // 新链
            oldChain    types.Blocks   // 旧链
            commonBlock *types.Block   // 共同祖先块
    ​
            deletedTxs []common.Hash  // 已删除的交易哈希列表
            addedTxs   []common.Hash  // 待添加的交易哈希列表
        )
    ​
        // 获取旧链和新链的头块
        oldBlock := bc.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
        if oldBlock == nil {
            return errors.New("current head block missing")  // 当前头块缺失
        }
        newBlock := newHead
    ​
        // 将较长的链缩短为与较短链相同数量的块
        if oldBlock.NumberU64() > newBlock.NumberU64() {
            // 旧链更长,收集所有交易和日志作为已删除项
            for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) {
                oldChain = append(oldChain, oldBlock)
                for _, tx := range oldBlock.Transactions() {
                    deletedTxs = append(deletedTxs, tx.Hash())
                }
            }
        } else {
            // 新链更长,保存所有块以供后续插入
            for ; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) {
                newChain = append(newChain, newBlock)
            }
        }
    ​
        // 确保旧链块不为空
        if oldBlock == nil {
            return errInvalidOldChain  // 无效的旧链
        }
        if newBlock == nil {
            return errInvalidNewChain  // 无效的新链
        }
    ​
        // 两边的链达到相同数量后,继续缩短直到找到共同祖先块
        for {
            // 如果找到了共同祖先块,则退出循环
            if oldBlock.Hash() == newBlock.Hash() {
                commonBlock = oldBlock
                break
            }
    ​
            // 将旧块移除,并将新块存储起来
            oldChain = append(oldChain, oldBlock)
            for _, tx := range oldBlock.Transactions() {
                deletedTxs = append(deletedTxs, tx.Hash())
            }
            newChain = append(newChain, newBlock)
    ​
            // 同时将两边的链向前移动一步
            oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1)
            if oldBlock == nil {
                return errInvalidOldChain  // 无效的旧链
            }
            newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1)
            if newBlock == nil {
                return errInvalidNewChain  // 无效的新链
            }
        }
    ​
        // 确保用户看到大的链重组
        if len(oldChain) > 0 && len(newChain) > 0 {
            logFn := log.Info
            msg := "Chain reorg detected"  // 检测到链重组
            if len(oldChain) > 63 {
                msg = "Large chain reorg detected"  // 检测到大型链重组
                logFn = log.Warn
            }
            logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(),
                "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash())
            blockReorgAddMeter.Mark(int64(len(newChain)))
            blockReorgDropMeter.Mark(int64(len(oldChain)))
            blockReorgMeter.Mark(1)
        } else if len(newChain) > 0 {
            // 特殊情况发生在后续合并阶段,当前头块是新头块的祖先,但这两个块不是连续的
            log.Info("Extend chain", "add", len(newChain), "number", newChain[0].Number(), "hash", newChain[0].Hash())
            blockReorgAddMeter.Mark(int64(len(newChain)))
        } else {
            // len(newChain) == 0 && len(oldChain) > 0
            // 将规范链倒回到较低的位置。
            log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
        }
    ​
        // 在写入新链数据之前,重置交易查找缓存,以清除过时的交易查找。
        bc.txLookupCache.Purge()
    ​
        // 将新链(除了头块)按适当的增量顺序插入链中。
        for i := len(newChain) - 1; i >= 1; i-- {
            // 按照规范的方式插入块,重写历史记录
            bc.writeHeadBlock(newChain[i])
    ​
            // 收集新添加的交易
            for _, tx := range newChain[i].Transactions() {
                addedTxs = append(addedTxs, tx.Hash())
            }
        }
    ​
        // 删除无用的索引,包括非规范交易索引和超过头部的规范链索引。
        var (
            indexesBatch = bc.db.NewBatch()
            diffs        = types.HashDifference(deletedTxs, addedTxs)
        )
        for _, tx := range diffs {
            rawdb.DeleteTxLookupEntry(indexesBatch, tx)
        }
    ​
        // 删除所有不属于新规范链的哈希标记。
        number := commonBlock.NumberU64()
        if len(newChain) > 1 {
            number = newChain[1].NumberU64()
        }
        for i := number + 1; ; i++ {
            hash := rawdb.ReadCanonicalHash(bc.db, i)
            if hash == (common.Hash{}) {
                break
            }
            rawdb.DeleteCanonicalHash(indexesBatch, i)
        }
        if err := indexesBatch.Write(); err != nil {
            log.Crit("Failed to delete useless indexes", "err", err)
        }
    ​
        // 发送事件以通知删除的日志和新增的日志。
    ​
        // 已删除的日志 + 块:
        var deletedLogs []*types.Log
        for i := len(oldChain) - 1; i >= 0; i-- {
            // 也为从规范链中删除的块发送事件。
            bc.chainSideFeed.Send(ChainSideEvent{Block: oldChain[i]})
    ​
            // 收集已删除日志以进行通知
            if logs := bc.collectLogs(oldChain[i], true); len(logs) > 0 {
                deletedLogs = append(deletedLogs, logs...)
            }
            if len(deletedLogs) > 512 {
                bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
                deletedLogs = nil
            }
        }
        if len(deletedLogs) > 0 {
            bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
        }
    ​
        // 新日志:
        var rebirthLogs []*types.Log
        for i := len(newChain) - 1; i >= 1; i-- {
            if logs := bc.collectLogs(newChain[i], false); len(logs) > 0 {
                rebirthLogs = append(rebirthLogs, logs...)
            }
            if len(rebirthLogs) > 512 {
                bc.logsFeed.Send(rebirthLogs)
                rebirthLogs = nil
            }
        }
        if len(rebirthLogs) > 0 {
            bc.logsFeed.Send(rebirthLogs)
        }
        return nil
    }

    最后给出其他函数的功能说明:

    函数签名说明
    func (bc *BlockChain) empty() bool区块链是不是空的
    func (bc *BlockChain) SetHead(head uint64) error修改区块的id
    func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error根据时间戳设置区块头
    func (bc *BlockChain) SetFinalized(header *types.Header)更新区块链的最终化区块,并将相关信息写入数据库
    func (bc *BlockChain) SetSafe(header *types.Header)设置安全区块(经过指定数目区块确认)
    func (bc *BlockChain) rewindHashHead(head types.Header, root common.Hash) (types.Header, uint64)在区块链中找到一个新的链头,以便在区块链的状态发生变化或重新组织时进行更新(哈希方案)
    func (bc *BlockChain) rewindPathHead(head types.Header, root common.Hash) (types.Header, uint64)在区块链中找到一个新的链头,以便在区块链的状态发生变化或重新组织时进行更新(路径方案)
    func (bc *BlockChain) rewindHead(head types.Header, root common.Hash) (types.Header, uint64)根据方案(路径方案或哈希方案)调用相应的链头回溯函数
    func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error在进行快照同步时提交链的头部
    func (bc *BlockChain) Reset() error函数的作用是清除整个区块链,将其恢复到创世状态
    func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error将活动链导出到给定的写入器
    func (bc *BlockChain) Export(w io.Writer) error将活动链导出到给定的写入器
    func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error将活动链的子集导出到给定的写入器,指定区间
    func (bc *BlockChain) writeHeadBlock(block *types.Block)更新链的索引和标记,将指定的区块设置为链的头部块
    func (bc *BlockChain) stopWithoutSaving()停止区块链服务,但不执行保存数据的后续工作
    func (bc *BlockChain) StopInsert()停止区块链的插入过程
    func (bc *BlockChain) insertStopped() bool检查插入过程是否已经停止
    func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (err error)将一个区块写入到数据库中,但不包含状态数据(即账户状态)的写入
    func (bc *BlockChain) writeKnownBlock(block *types.Block) error写入已知的区块到区块链中
    func (bc *BlockChain) writeBlockAndSetHead(block types.Block, receipts []types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error)将区块写入区块链数据库,并根据区块的规范性与否,更新区块链的头区块,在需要情况下发送事件通知
    func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error)将一组区块导入到区块链
    func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)从给定的区块开始,向上追溯并恢复所有被裁剪或丢失的祖先区块的状态
    func (bc *BlockChain) collectLogs(b types.Block, removed bool) []types.Log收集指定区块中的日志信息,并返回一个包含所有日志的切片
    func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error向区块链中插入新的区块,但不设置成头块
    func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error)将指定区块设置为区块链头块,并在需要时运行重新组织过程
    func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool是否可以跳过处理导入的区块
    func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error)记录和报告区块处理过程中遇到的错误
    func summarizeBadBlock(block types.Block, receipts []types.Receipt, config *params.ChainConfig, err error) string生成一个包含了坏区块相关信息的字符串
    func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Processor)设置用于验证和处理区块的验证器(Validator)和处理器(Processor)
    func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration)配置内存中 trie 刷新到磁盘的间隔时间
    func (bc *BlockChain) GetTrieFlushInterval() time.Duration获取内存中 trie 刷新到磁盘的间隔时间
  • 相关阅读:
    R语言使用data.table包的fread函数读取(加载)csv数据为data.table格式、将数据中所有缺失值替换为0(或者其它固定值)
    计算机毕业设计springboot+vue基本微信小程序的水库巡检系统
    [C++][数据结构][图][中][图的遍历][最小生成树]详细讲解
    局域网下共享文件夹全流程
    Java开发学习(三十三)----Maven私服(一)私服简介安装与私服分类
    ArduinoUNO实战-第十一章-4位共阴数码管实验
    MogaFX外汇储备以一年多来的最快速度增长
    机械设计基础试题3
    谈一谈在两个商业项目中使用MVI架构后的感悟
    五大经典智能算法实现机器人路径规划,包含简单路径与复杂路径,详细对比实验...
  • 原文地址:https://blog.csdn.net/pan_1214_/article/details/138122549