以太坊区块设计分析(上)
字数 1614 2025-08-22 12:23:12

以太坊区块设计分析教学文档

1. 区块链基础概念

区块链是由包含交易的区块按照时间先后顺序依次连接起来的数据结构,这种数据结构是一个形象的链表结构:

  • 所有数据有序地链接在同一条区块链上
  • 每个区块通过一个hash指针指向前一个区块
  • hash指针是前一个区块头进行SHA256哈希计算得到的
  • 通过这个哈希值可以唯一识别一个区块

2. 区块结构

区块是区块链中数据存储的最小单元,由两部分组成:

2.1 区块头(Header)

包含以下关键字段:

  • ParentHash:父区块Hash值
  • Coinbase:矿工账户地址
  • UncleHash:Block结构体的成员Uncles的RLP哈希值
  • Root:Merkle Tree Root
  • TxHash:区块中所有交易验证结果组成的交易结果的默克尔树
  • ReceiptHash:Block中的"Receipt Trie"的根节点的RLP哈希值
  • Bloom:Bloom过滤器,用于快速判断Log对象是否存在
  • Difficulty:区块的难度
  • Number:区块序号(父区块Number+1)
  • Time:区块创建时间戳
  • GasLimit:区块内所有Gas消耗的理论上限
  • GasUsed:区块内所有Transaction实际消耗的Gas总和
  • Nonce:64bit哈希数,用于区块"挖掘"阶段

2.2 区块主体(Body)

用于存储交易信息

3. 以太坊区块数据结构

3.1 Block结构定义

type Block struct {
    header       *Header
    uncles       []*Header
    transactions Transactions
    
    // 缓存字段
    hash   atomic.Value
    size   atomic.Value
    td     *big.Int
    
    // 区块中继相关字段
    ReceivedAt   time.Time
    ReceivedFrom interface{}
}

3.2 Header结构定义

type Header struct {
    ParentHash  common.Hash    `json:"parentHash" gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles" gencodec:"required"`
    Coinbase    common.Address `json:"miner" gencodec:"required"`
    Root        common.Hash    `json:"stateRoot" gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot" gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom" gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty" gencodec:"required"`
    Number      *big.Int       `json:"number" gencodec:"required"`
    GasLimit    uint64         `json:"gasLimit" gencodec:"required"`
    GasUsed     uint64         `json:"gasUsed" gencodec:"required"`
    Time        uint64         `json:"timestamp" gencodec:"required"`
    Extra       []byte         `json:"extraData" gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"`
    Nonce       BlockNonce     `json:"nonce"`
}

4. 默克尔树结构

以太坊采用Merkle-PatriciaTrie(MPT)结构,包含三棵树:

  1. StateTrie

    • 在StateDB中每个账户以stateObject对象表示
    • 所有账户对象逐个插入MPT结构,形成"state Trie"
  2. Tx Trie

    • Block的transactions中所有tx对象逐个插入MPT结构
    • 形成"tx Trie"
  3. Receipt Trie

    • Transaction执行后生成Receipt数组
    • 所有Receipt被逐个插入MPT结构
    • 形成"Receipt Trie"

5. 创世区块

区块链中第一个区块称为"创世区块",是所有区块的共同祖先。

5.1 创世区块初始化

当启动节点不指定创世区块文件时:

  1. 尝试从本地LevelDB加载区块信息
  2. 如果未获取到区块,则根据硬编码的创世区块信息初始化本地链

5.2 主要网络创世哈希

var (
    MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
    RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
    RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
    GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
    YoloV3GenesisHash = common.HexToHash("0x374f07cc7fa7c251fc5f36849f574b43db43600526410349efdca2bcea14101a")
)

5.3 初始化创世区块流程

  1. 执行命令:./geth init <genesispath>
  2. 检查创世文件格式和权限
  3. 调用makeConfigNode加载全局配置
  4. 调用SetupGenesisBlock创建创世区块

5.4 默认创世区块

func DefaultGenesisBlock() *Genesis {
    return &Genesis{
        Config:     params.MainnetChainConfig,
        Nonce:      66,
        ExtraData:  hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"),
        GasLimit:   5000,
        Difficulty: big.NewInt(17179869184),
        Alloc:      decodePrealloc(mainnetAllocData),
    }
}

6. 新区块创建

新区块由矿工打包交易信息生成,主要流程:

  1. mainLoop goroutine打包新交易到区块
  2. 调用updateSnapshot更新快照
  3. 调用NewBlock更新区块信息

6.1 NewBlock函数

func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
    b := &Block{header: CopyHeader(header), td: new(big.Int)}
    
    // 处理交易
    if len(txs) == 0 {
        b.header.TxHash = EmptyRootHash
    } else {
        b.header.TxHash = DeriveSha(Transactions(txs), hasher)
        b.transactions = make(Transactions, len(txs))
        copy(b.transactions, txs)
    }
    
    // 处理收据
    if len(receipts) == 0 {
        b.header.ReceiptHash = EmptyRootHash
    } else {
        b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher)
        b.header.Bloom = CreateBloom(receipts)
    }
    
    // 处理叔区块
    if len(uncles) == 0 {
        b.header.UncleHash = EmptyUncleHash
    } else {
        b.header.UncleHash = CalcUncleHash(uncles)
        b.uncles = make([]*Header, len(uncles))
        for i := range uncles {
            b.uncles[i] = CopyHeader(uncles[i])
        }
    }
    
    return b
}

7. 区块验证

区块验证是防止区块链分叉的重要手段,在以下情况会进行验证:

  1. 挖矿节点成功挖掘区块并提交时
  2. 用户通过API接口提交区块时
  3. 同步区块时
  4. 矿池节点向矿池提交工作时

7.1 区块体验证

func (v *BlockValidator) ValidateBody(block *types.Block) error {
    // 检查区块是否已知
    if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
        return ErrKnownBlock
    }
    
    // 验证叔区块
    if err := v.engine.VerifyUncles(v.bc, block); err != nil {
        return err
    }
    
    // 验证叔区块哈希
    if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
        return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash)
    }
    
    // 验证交易哈希
    if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
        return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
    }
    
    // 验证父区块是否存在
    if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
        if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
            return consensus.ErrUnknownAncestor
        }
        return consensus.ErrPrunedAncestor
    }
    
    return nil
}

7.2 叔区块验证

func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
    // 验证叔区块数量(最多2个)
    if len(block.Uncles()) > maxUncles {
        return errTooManyUncles
    }
    
    // 收集过去的叔区块和祖先
    uncles, ancestors := mapset.NewSet(), make(map[common.Hash]*types.Header)
    
    // 验证每个叔区块
    for _, uncle := range block.Uncles() {
        // 确保每个叔区块只奖励一次
        hash := uncle.Hash()
        if uncles.Contains(hash) {
            return errDuplicateUncle
        }
        uncles.Add(hash)
        
        // 验证叔区块有有效的祖先
        if ancestors[hash] != nil {
            return errUncleIsAncestor
        }
        if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
            return errDanglingUncle
        }
        
        // 验证叔区块头
        if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true, time.Now().Unix()); err != nil {
            return err
        }
    }
    return nil
}

7.3 区块头验证

func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error {
    // 验证额外数据大小
    if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
        return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
    }
    
    // 验证时间戳
    if !uncle {
        if header.Time > uint64(unixNow + allowedFutureBlockTimeSeconds) {
            return consensus.ErrFutureBlock
        }
    }
    if header.Time <= parent.Time {
        return errOlderBlockTime
    }
    
    // 验证难度
    expected := ethash.CalcDifficulty(chain, header.Time, parent)
    if expected.Cmp(header.Difficulty) !=  {
        return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
    }
    
    // 验证Gas限制
    if header.GasLimit > uint64(0x7fffffffffffffff) {
        return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
    }
    if header.GasUsed > header.GasLimit {
        return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
    }
    
    // 验证区块号
    if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
        return consensus.ErrInvalidNumber
    }
    
    // 验证Seal
    if seal {
        if err := ethash.verifySeal(chain, header, false); err != nil {
            return err
        }
    }
    
    // 验证硬分叉特殊字段
    if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
        return err
    }
    if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
        return err
    }
    
    return nil
}

8. 难度目标计算

难度调整算法根据不同的网络阶段使用不同版本:

func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
    next := new(big.Int).Add(parent.Number, big1)
    switch {
    case config.IsMuirGlacier(next):
        return calcDifficultyEip2384(time, parent)
    case config.IsConstantinople(next):
        return calcDifficultyConstantinople(time, parent)
    case config.IsByzantium(next):
        return calcDifficultyByzantium(time, parent)
    case config.IsHomestead(next):
        return calcDifficultyHomestead(time, parent)
    default:
        return calcDifficultyFrontier(time, parent)
    }
}

8.1 Homestead难度计算

func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int {
    // 算法公式:
    // diff = (parent_diff + 
    //        (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) / 10, -99))
    //       ) + 2^(periodCount - 2)
    
    bigTime := new(big.Int).SetUint64(time)
    bigParentTime := new(big.Int).SetUint64(parent.Time)
    
    // 计算时间差因子
    x := new(big.Int)
    y := new(big.Int)
    x.Sub(bigTime, bigParentTime)
    x.Div(x, big10)
    x.Sub(big1, x)
    
    // 限制最小值为-99
    if x.Cmp(bigMinus99) < 0 {
        x.Set(bigMinus99)
    }
    
    // 计算难度调整
    y.Div(parent.Difficulty, params.DifficultyBoundDivisor)
    x.Mul(y, x)
    x.Add(parent.Difficulty, x)
    
    // 确保不低于最小难度
    if x.Cmp(params.MinimumDifficulty) < 0 {
        x.Set(params.MinimumDifficulty)
    }
    
    // 计算指数因子(炸弹)
    periodCount := new(big.Int).Add(parent.Number, big1)
    periodCount.Div(periodCount, expDiffPeriod)
    if periodCount.Cmp(big1) > 0 {
        y.Sub(periodCount, big2)
        y.Exp(big2, y, nil)
        x.Add(x, y)
    }
    
    return x
}

9. 关键验证点总结

  1. 区块头验证

    • 额外数据大小
    • 时间戳合理性
    • 难度计算正确性
    • Gas限制有效性
    • 区块号连续性
    • Seal验证
  2. 区块体验证

    • 叔区块数量限制
    • 交易哈希正确性
    • 叔区块哈希正确性
    • 父区块存在性
  3. 特殊验证

    • DAO硬分叉额外数据
    • 分叉哈希验证
    • 难度炸弹计算

通过以上全面的验证机制,以太坊确保了区块链的安全性和一致性,有效防止了分叉和无效区块的产生。

以太坊区块设计分析教学文档 1. 区块链基础概念 区块链是由包含交易的区块按照时间先后顺序依次连接起来的数据结构,这种数据结构是一个形象的链表结构: 所有数据有序地链接在同一条区块链上 每个区块通过一个hash指针指向前一个区块 hash指针是前一个区块头进行SHA256哈希计算得到的 通过这个哈希值可以唯一识别一个区块 2. 区块结构 区块是区块链中数据存储的最小单元,由两部分组成: 2.1 区块头(Header) 包含以下关键字段: ParentHash :父区块Hash值 Coinbase :矿工账户地址 UncleHash :Block结构体的成员Uncles的RLP哈希值 Root :Merkle Tree Root TxHash :区块中所有交易验证结果组成的交易结果的默克尔树 ReceiptHash :Block中的"Receipt Trie"的根节点的RLP哈希值 Bloom :Bloom过滤器,用于快速判断Log对象是否存在 Difficulty :区块的难度 Number :区块序号(父区块Number+1) Time :区块创建时间戳 GasLimit :区块内所有Gas消耗的理论上限 GasUsed :区块内所有Transaction实际消耗的Gas总和 Nonce :64bit哈希数,用于区块"挖掘"阶段 2.2 区块主体(Body) 用于存储交易信息 3. 以太坊区块数据结构 3.1 Block结构定义 3.2 Header结构定义 4. 默克尔树结构 以太坊采用Merkle-PatriciaTrie(MPT)结构,包含三棵树: StateTrie : 在StateDB中每个账户以stateObject对象表示 所有账户对象逐个插入MPT结构,形成"state Trie" Tx Trie : Block的transactions中所有tx对象逐个插入MPT结构 形成"tx Trie" Receipt Trie : Transaction执行后生成Receipt数组 所有Receipt被逐个插入MPT结构 形成"Receipt Trie" 5. 创世区块 区块链中第一个区块称为"创世区块",是所有区块的共同祖先。 5.1 创世区块初始化 当启动节点不指定创世区块文件时: 尝试从本地LevelDB加载区块信息 如果未获取到区块,则根据硬编码的创世区块信息初始化本地链 5.2 主要网络创世哈希 5.3 初始化创世区块流程 执行命令: ./geth init <genesispath> 检查创世文件格式和权限 调用 makeConfigNode 加载全局配置 调用 SetupGenesisBlock 创建创世区块 5.4 默认创世区块 6. 新区块创建 新区块由矿工打包交易信息生成,主要流程: mainLoop goroutine打包新交易到区块 调用 updateSnapshot 更新快照 调用 NewBlock 更新区块信息 6.1 NewBlock函数 7. 区块验证 区块验证是防止区块链分叉的重要手段,在以下情况会进行验证: 挖矿节点成功挖掘区块并提交时 用户通过API接口提交区块时 同步区块时 矿池节点向矿池提交工作时 7.1 区块体验证 7.2 叔区块验证 7.3 区块头验证 8. 难度目标计算 难度调整算法根据不同的网络阶段使用不同版本: 8.1 Homestead难度计算 9. 关键验证点总结 区块头验证 : 额外数据大小 时间戳合理性 难度计算正确性 Gas限制有效性 区块号连续性 Seal验证 区块体验证 : 叔区块数量限制 交易哈希正确性 叔区块哈希正确性 父区块存在性 特殊验证 : DAO硬分叉额外数据 分叉哈希验证 难度炸弹计算 通过以上全面的验证机制,以太坊确保了区块链的安全性和一致性,有效防止了分叉和无效区块的产生。