BSC区块生产流程深度解析

一、区块准备阶段

1. 准备工作

区块准备工作在miner/worker.goprepareWork函数中完成:
 

2. 出块机制

验证者按照固定顺序轮流出块:
 
代码解析:
  1. s.Number + 1: 下一个区块高度
  1. s.TurnLength: 每个验证者的出块轮次长度
  1. len(validators): 验证者总数
  1. 通过取模运算%确保验证者循环轮询
举例说明:
  • 假设有3个验证者: A、B、C
  • TurnLength = 1
  • 则出块顺序为:
  • 区块高度1: A ((1)/1) % 3 = 0
  • 区块高度2: B ((2)/1) % 3 = 1
  • 区块高度3: C ((3)/1) % 3 = 2
  • 区块高度4: A ((4)/1) % 3 = 0
  • 以此类推...
这种确定性的轮询机制确保了:
  1. 验证者按照固定顺序出块
  1. 出块机会均等
  1. 网络参与者可以预测下一个出块者

 

3. 时间窗口

 BSC的区块时间窗口管理机制如下:

  1. 基本参数:
  • 目标出块时间:3秒
  • 最大出块时间:6秒
  • 每个验证者有固定的出块时间窗口
  1. 时间窗口控制:
     
  1. 超时处理:
  • 如果验证者未在指定时间窗口内出块
  • 触发超时惩罚机制
  • 允许下一个验证者提前出块

4. 交易填充

当轮到验证者出块时,会从交易池中选择交易进行打包:
 

二、区块密封(Seal)流程

1. 任务提交

区块密封任务通过worker.go中的commit函数提交:

2. 签名流程

使用ECDSA(secp256k1)进行区块签名:

3. 投票收集

收集父区块的验证者投票:
收集投票的机制(core/vote/vote_pool.go): 异步收集
专有的P2P 协议接收:基于以太坊的devp2p
只打包当前收集到的投票,打包进入区块
如果满足验证者的2/3,触发快速确定性机制(父块可以确认)
 

三、区块广播流程

区块密封完成后通过resultLoop进行广播:
 

四、区块接收流程

1. 消息处理链路

eth/protocols/eth/handler.go:Handle              // 处理P2P消息
    -> handlers.go:handleNewBlock               // 解码区块
        -> handler_eth.go:handleBlockBroadcast  // 加入处理队列
            -> block_fetcher.go:importBlocks    // 导入区块
                -> blockchain.go:InsertChain    // 插入链上
                    -> blockchain.go:processBlock // 处理区块

2. 区块验证

主要验证项包括:
// 基本字段验证
func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
    // 验证时间戳
    if header.Time > uint64(time.Now().Unix()+maxTimeFutureBlocks) {
        return consensus.ErrFutureBlock
    }
    
    // 验证难度值
    if header.Difficulty == nil || header.Difficulty.Cmp(diffInTurn) != 0 {
        return errInvalidDifficulty
    }
    
    // 验证投票证明
    if err := p.verifyVoteAttestation(chain, header); err != nil {
        return err
    }
}

3. 区块持久化

执行区块中的交易:

func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
    // 1. 初始化所需变量
    var (
        receipts    = make([]*types.Receipt, 0)    // 存储交易收据
        usedGas     = new(uint64)                  // 记录使用的gas总量
        header      = block.Header()               // 区块头
        blockHash   = block.Hash()                 // 区块哈希
        blockNumber = block.Number()               // 区块号
        allLogs     []*types.Log                   // 所有日志
        gp          = new(GasPool).AddGas(block.GasLimit()) // gas池
    )

    // 2. 处理硬分叉相关的状态变更
    if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
        misc.ApplyDAOHardFork(statedb)
    }

    // 3. 获取父区块并更新系统合约
    lastBlock := p.chain.GetHeaderByHash(block.ParentHash())
    if lastBlock == nil {
        return nil, errors.New("could not get parent block")
    }
    systemcontracts.TryUpdateBuildInSystemContract(p.config, blockNumber, lastBlock.Time, block.Time(), statedb, true)

    // 4. 创建EVM执行环境
    context = NewEVMBlockContext(header, p.chain, nil)
    evm := vm.NewEVM(context, tracingStateDB, p.config, cfg)

    // 5. 处理信标链根和父区块哈希(如果需要)
    if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
        ProcessBeaconBlockRoot(*beaconRoot, evm)
    }
    if p.config.IsPrague(block.Number(), block.Time()) {
        ProcessParentBlockHash(block.ParentHash(), evm)
    }

    // 6. 初始化交易处理相关变量
    posa, isPoSA := p.chain.engine.(consensus.PoSA)
    commonTxs := make([]*types.Transaction, 0, txNum)
    systemTxs := make([]*types.Transaction, 0, 2)

    // 7. 遍历处理每个交易
    for i, tx := range block.Transactions() {
        // 7.1 处理系统交易
        if isPoSA {
            if isSystemTx, err := posa.IsSystemTransaction(tx, block.Header()); err != nil {
                return nil, err
            } else if isSystemTx {
                systemTxs = append(systemTxs, tx)
                continue
            }
        }

        // 7.2 将交易转换为消息
        msg, err := TransactionToMessage(tx, signer, header.BaseFee)
        if err != nil {
            return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
        }

        // 7.3 设置交易上下文并执行交易
        statedb.SetTxContext(tx.Hash(), i)
        receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, tx, usedGas, evm, bloomProcessors)
        if err != nil {
            return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
        }

        // 7.4 收集交易结果
        commonTxs = append(commonTxs, tx)
        receipts = append(receipts, receipt)
    }

    // 8. 处理共识引擎相关的奖励
    err = p.chain.engine.Finalize(p.chain, header, tracingStateDB, &commonTxs, block.Uncles(), block.Withdrawals(), &receipts, &systemTxs, usedGas, cfg.Tracer)
    if err != nil {
        return nil, err
    }

    // 9. 返回处理结果
    return &ProcessResult{
        Receipts: receipts,
        Requests: requests,
        Logs:     allLogs,
        GasUsed:  *usedGas,
    }, nil
}
持久化:
// 源码:bsc/core/blockchain.go:processBlock 
// 将区块和状态写入数据库
if err := bc.writeBlockWithState(block, receipts, statedb); err != nil {
    return nil, err
}

4. 奖励

Finalize阶段完成:

五、区块投票流程

投票广播

 
 

六、区块分叉处理

尽管BSC采用确定性出块顺序,但仍需要处理可能的分叉情况:
  1. 分叉原因:
  • 网络延迟导致区块传播不同步
  • 验证者节点时间不同步
  • 验证者可能出现双签行为
  1. 分叉选择规则:
     
     
  1. 分叉处理流程:
  • 检测到分叉时暂存所有分叉链
  • 应用最长链原则选择主链
  • 将非主链上的交易重新放入交易池
  • 回滚状态到分叉点并重新应用主链交易
// core/blockchain.go
func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error {
    var (
        newChain    []*types.Header
        oldChain    []*types.Header
        commonBlock *types.Header
    )
    
    // 1. 找到共同祖先
    if oldHead.Number.Uint64() > newHead.Number.Uint64() {
        // 旧链更长,收集需要移除的区块
        for ; oldHead != nil && oldHead.Number.Uint64() != newHead.Number.Uint64(); oldHead = bc.GetHeader(oldHead.ParentHash, oldHead.Number.Uint64()-1) {
            oldChain = append(oldChain, oldHead)
        }
    } else {
        // 新链更长,收集需要添加的区块
        for ; newHead != nil && newHead.Number.Uint64() != oldHead.Number.Uint64(); newHead = bc.GetHeader(newHead.ParentHash, newHead.Number.Uint64()-1) {
            newChain = append(newChain, newHead)
        }
    }
    
    // 2. 处理交易
    // 收集需要重新执行的交易
    for i := len(oldChain) - 1; i >= 0; i-- {
        // 将旧链上的交易放回交易池
        block := bc.GetBlock(oldChain[i].Hash(), oldChain[i].Number.Uint64())
        for _, tx := range block.Transactions() {
            deletedTxs = append(deletedTxs, tx.Hash())
        }
    }
    
    // 3. 应用新链
    for i := len(newChain) - 1; i >= 0; i-- {
        // 将新链上的交易标记为已确认
        block := bc.GetBlock(newChain[i].Hash(), newChain[i].Number.Uint64())
        for _, tx := range block.Transactions() {
            rebirthTxs = append(rebirthTxs, tx.Hash())
        }
        // 更新区块头
        bc.writeHeadBlock(block)
    }
}
  1. 重组影响:
  • 交易可能需要重新打包
  • 状态需要回滚和重新计算
  • 可能影响交易确认时间
 
 
这就是BSC区块从生产到确认的完整流程。整个过程保证了:
  1. 有序的区块生产
  1. 安全的区块传播
  1. 可靠的状态更新
  1. 快速的共识达成
posted @ 2025-06-12 15:33  若-飞  阅读(82)  评论(0)    收藏  举报