本文作者:咔咔

区块链共识机制源码中,节点如何通过算法达成数据一致?

区块链共识机制源码中,节点如何通过算法达成数据一致?摘要: 由于区块链项目众多,我们选择最经典、最广为人知的 比特币 和 以太坊 作为分析对象,比特币是工作量证明的鼻祖,而以太坊则从 PoW 演进到了权益证明,代表了两种主流的共识思想,我们...

由于区块链项目众多,我们选择最经典、最广为人知的 比特币以太坊 作为分析对象,比特币是工作量证明的鼻祖,而以太坊则从 PoW 演进到了权益证明,代表了两种主流的共识思想。

我们将从以下几个方面展开:

区块链共识机制源码中,节点如何通过算法达成数据一致?
(图片来源网络,侵删)
  1. 共识机制概览:理解不同共识的哲学和目标。
  2. 比特币 PoW 共识源码分析:深入 bitcoind 的核心,看一个新区块是如何被“创造”出来的。
  3. 以太坊 PoS 共识源码分析:以最新的 Cancun 升级为例,分析如何从 PoW 过渡到 PoS,以及 PoS 是如何运作的。
  4. 总结与对比:比较两种共识的优缺点和实现上的核心差异。

共识机制概览

共识机制的目标是确保网络的安全性和一致性,主要分为以下几类:

共识类型 核心思想 优点 缺点 代表项目
工作量证明 通过解决复杂的数学难题(哈希碰撞)来争夺记账权,算力越高,赢得记账权的概率越大。 去中心化程度高,安全性经过实战检验(比特币已运行十余年)。 能源消耗巨大,交易确认慢(可扩展性差),存在“51%攻击”理论风险 比特币, 莱特币
权益证明 根据节点质押的代币数量和时间(“权益”)来分配记账权,质押越多,获得奖励的概率越高。 能耗极低,交易速度快,理论上可扩展性更好。 “无利害关系”问题,初始分配不均可能导致中心化,安全模型相对较新。 以太坊 (2.0), 卡尔达诺, Solana
委托权益证明 DPoS 是 PoS 的变种,代币持有者投票选举少量(如 101 个)节点(见证人/超级节点)来负责出块和验证。 效率极高,交易秒级确认。 去中心化程度相对较低,容易出现“贿选”和节点作恶问题。 EOS, TRON, BitShares
实用拜占庭容错 通过多轮投票和消息传递,在节点间达成共识,要求至少 2/3 的节点是诚实的。 不依赖代币或算力,理论性能高。 在大规模节点网络中通信开销巨大,扩展性差,不适合公链。 Hyperledger Fabric (联盟链), EOS 早期共识

比特币 PoW 共识源码分析

比特币的共识是“计算+经济”的结合,其核心是“最长有效链”原则,而“有效”的标志就是包含了满足特定难度条件的 PoW。

核心概念与源码模块

比特币的核心代码主要在 src/ 目录下,与共识最相关的模块是:

  • validation.cpp/h: 包含了验证区块和交易是否有效的核心逻辑。CheckBlock()CheckBlockHeader() 是入口函数。
  • pow.cpp/h: 专门负责工作量证明的计算和验证。
  • chain.cpp/h: 管理区块链的数据结构,如 CChainCBlockIndex,用于维护“最长链”。
  • consensus/consensus.h: 定义了共识相关的常量和规则,如 MAX_BLOCK_SIZE, nSubsidyHalvingInterval 等。

共识流程源码剖析

我们以一个节点收到一个新区块后的处理流程为例,来分析 PoW 共识的实现。

区块链共识机制源码中,节点如何通过算法达成数据一致?
(图片来源网络,侵删)

Step 1: 接收新区块

当一个节点从网络接收到一个新区块(通过 inv 消息获取,然后通过 getdata 请求完整数据),它会首先调用 ProcessNewBlock() 函数(在 validation.cpp 中)。

// validation.cpp
bool ProcessNewBlock(const CChainParams& chainparams, const CNode* pfrom, CBlock* pblock, bool* fNewBlock) {
    // ... 省略部分代码 ...
    // 1. 验证区块头的基本结构
    if (!CheckBlockHeader(block, chainparams.GetConsensus(), GetSpendHeight(pindex))) {
        return false;
    }
    // 2. 验证整个区块(包括交易列表、默克尔树根等)
    if (!CheckBlock(block, state, chainparams.GetConsensus(), fScriptChecks, GetSpendHeight(pindex))) {
        return error("%s: CheckBlock failed", __func__);
        return false;
    }
    // ...
}

Step 2: 验证 PoW (The Core of Consensus)

CheckBlockHeader() 是验证 PoW 的关键,它检查两件事:

  1. 区块头的 nBits 字段是否在有效范围内。
  2. 区块头的哈希值是否小于当前网络目标值。
// pow.cpp
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params) {
    // 1. 检查 nBits 是否代表一个有效的难度目标
    // nBits 是一个紧凑格式,需要转换为一个大整数
    bool fNegative;
    bool fOverflow;
    arith_uint256 bnTarget;
    bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
    // 检查是否超出最大/最小限制
    if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit)) {
        return false;
    }
    // 2. 检查哈希值是否满足难度要求
    // 这就是 PoW 的核心:区块头的哈希必须 <= 目标值
    if (UintToArith256(hash) > bnTarget) {
        return false;
    }
    return true;
}

代码解读:

  • nBits: 这是矿工在挖矿时不断调整的值,它编码了当前网络的目标难度,一个更小的 nBits 代表更高的难度。
  • bnTarget.SetCompact(nBits): 将紧凑格式的 nBits 转换为一个 256 位的整数 bnTarget,这个 bnTarget 目标值”。
  • UintToArith256(hash) > bnTarget: 这是 PoW 的“题解”验证,矿工需要找到一个随机数(Nonce),使得 SHA256(SHA256(区块头 + Nonce)) 的结果小于等于 bnTarget,节点收到区块后,只需用区块头里的 Nonce 重新计算一次哈希,然后和 bnTarget 比较即可验证,这个过程非常快。

Step 3: 链的选择与重组

如果新区块有效,节点会尝试将其连接到自己的主链上,这涉及到 ActivateBestChain() 函数(在 validation.cpp 中)。

// validation.cpp
bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const CBlockHeader* pblock, bool fJustCheck) {
    // ...
    // 1. 检查新接收的链是否比当前主链“更长”(总工作量更大)
    if (!pindexMostWork || chainActive.Tip()->nChainWork < pindexMostWork->nChainWork) {
        // ...
        // 2. 如果是,则进行“重新组织”(Reorg)
        // 将旧主链回滚,然后将新链连接上
        if (!Reorg(state, chainparams, pindexMostWork, nConnectSize, fScriptChecks)) {
            // ...
        }
    }
    // ...
}

代码解读:

  • chainActive.Tip()->nChainWork < pindexMostWork->nChainWork: 这是“最长链”原则的体现,这里的“长”不是指区块数量,而是指总工作量,每个区块头里都存储了从创世块到该区块的累计工作量 nChainWork,节点选择累计工作量最大的链作为主链。
  • Reorg(): 当一条新的、工作量更大的链出现时,节点需要执行“重组”,它会断开当前主链的末端,然后沿着新链的分支重新连接区块,这确保了网络最终会收敛到唯一的一条“工作量最大链”上。

挖矿过程(PoW 的生成)

虽然分析的是“验证”端,但了解“生成”端有助于理解共识的全貌,挖矿的核心在 miner.cpp 中的 BitcoinsMiner() 函数,它会不断构建一个候选区块头,然后暴力尝试不同的 Nonce 值,直到找到一个满足 CheckProofOfWork() 的解。

// miner.cpp (简化逻辑)
void BitcoinsMiner(const CChainParams& chainparams) {
    // ...
    while (true) {
        // 1. 构建候选区块
        CBlock block;
        // ... (填充交易、默克尔根等)
        // 2. 循环尝试不同的 Nonce
        block.nNonce = 0;
        while (true) {
            // 3. 计算哈希
            uint256 hash = block.GetHash();
            // 4. 检查是否满足难度要求
            if (CheckProofOfWork(hash, block.nBits, chainparams.GetConsensus())) {
                // 找到解!广播出去
                ProcessNewBlock(chainparams, nullptr, &block, nullptr);
                break;
            }
            // 5. Nonce 自增,继续尝试
            ++block.nNonce;
            if ((block.nNonce & 0xffff) == 0) {
                // ... (检查是否被停止或新交易加入)
            }
        }
    }
}

以太坊 PoS 共识源码分析

以太坊的“合并”(The Merge)后,共识机制从 PoW 变为 PoS,其实现核心是 Casper FFG (Liveness + Finality) + LMD GHOST (Chain Re-org),它由一个名为 beacon chain 的独立链来协调所有分片链(包括主网)。

核心概念与源码模块

以太坊的 Go 客户端 Prysm 和 Rust 客户端 Lodestar 是分析 PoS 共识的绝佳选择,我们以 Prysm 为例,其代码结构清晰。

  • beacon-chain/: beacon chain 的核心实现。
    • core/: 包含核心数据结构,如 BeaconState, BeaconBlock
    • consensus/: 共识算法的具体实现。
      • fork_choice/: 实现了 LMD GHOST 算法,用于选择哪条链是“canonical chain”。
      • state/: 实现了 Casper FFG 的状态转换函数,如 ProcessAttestation, ProcessBlock
    • operations/: 处理各种类型的“操作”,如 Deposit (存入 ETH 成为验证者), Attestation ( attestations, 即投票), VoluntaryExit (自愿退出)。

共识流程源码剖析

PoS 的共识流程与 PoW 完全不同,它不再依赖“挖矿”,而是依赖验证者的“投票”。

Step 1: 成为验证者

用户需要将至少 32 ETH 发送到一个指定的合约地址,这个过程称为“存入”(Deposit),一旦存入并被 beacon chain 确认,用户就成为了一个验证者。

Step 2: 分配职责

以太坊的共识时间被划分为时隙纪元

  • Slot (时隙): 12 秒一个。
  • Epoch (纪元): 32 个时隙一个(约 6.4 分钟)。

在每个纪元开始时,beacon chain 会根据 RANDAO 算法随机地将验证者分配到不同的委员会 中。

// beacon-chain/core/assignments.go (简化)
func (s *BeaconState) CommitteeAtSlot(slot types.Slot) [][]ValidatorIndex {
    // ...
    // 根据验证者余额和 RANDAO 值,为每个 slot 生成一个委员会
    // 每个委员会负责验证一个特定的 slot 产生的区块
    // ...
}

Step 3: 提议与投票

在一个时隙内,只有一个验证者被选为提议者,负责创建并广播一个新区块,被分配到该时隙的委员会中的其他验证者,负责对该区块及其父区块进行“投票”(称为 Attestation)。

A. 提议者创建区块

BeaconBlock 结构体中包含了大量的 Attestations,这些就是其他验证者提前提交的投票。

// beacon-chain/types/block.go
type BeaconBlock struct {
    Slot          Slot
    ProposerIndex ValidatorIndex
    ParentRoot    Root
    StateRoot     Root
    Body          BeaconBlockBody
    // ...
}
type BeaconBlockBody struct {
    RANDAOReveal       []byte
    Eth1Data          *Eth1Data
    Graffiti          []byte
    ProposerSlashings []*ProposerSlashing
    AttesterSlashings []*AttesterSlashing
    Attestations      []*Attestation // 关键:包含来自委员会的投票
    Deposits          []*Deposit
    VoluntaryExits    []*VoluntaryExit
    // ...
}

B. 验证者投票

一个验证者的投票 (Attestation) 包含两部分信息:

  1. 对哪个检查点 进行投票(Casper FFG 的 Liveness 部分)。
  2. 认为哪个区块 是当前链的头部(LMD GHOST 的链选择部分)。
// beacon-chain/types/attestation.go
type Attestation struct {
    AggregationBits Bitlist // 委员员中哪些验证者参与了本次投票
    Data            *AttestationData
    Signature       []byte
}
type AttestationData struct {
    Slot             Slot
    Index            CommitteeIndex
    BeaconBlockRoot  Root // 投票的区块哈希
    Source           Checkpoint // 投票的源检查点
    Target           Checkpoint // 投票的目标检查点
}

Step 4: 共识达成 (Fork Choice + Finality)

当节点收到一个新区块时,它会运行两个核心算法:

LMD GHOST (链选择算法)

这个算法决定哪条链是“最重的”,它的核心思想是:一个区块的权重,等于所有投给它(及其祖先)的有效投票的总和。

// beacon-chain/consensus/fork_choice/lmd_ghost.go (简化逻辑)
func (f *ForkChoice) onBlock(block *pb.BeaconBlock, atts []*pb.Attestation) {
    // ...
    // 1. 将新区块添加到 DAG (Directed Acyclic Graph) 中
    // 2. 遍历新区块收到的所有投票 (atts)
    for _, att := range atts {
        // 3. 投票指向的区块权重 +1
        // 4. 该区块所有祖先的权重也 +1
        f.updateVoteWeights(att.Data.BeaconBlockRoot, att)
    }
    // 5. 从当前头部开始,沿着权重最高的子区块向上追溯,找到新的头部
    f.findHead()
}

Casper FFG (最终确定性算法)

FFG 保证了区块一旦被“最终确定”,就不可逆转,它通过两个超时阈值来实现:

  • Justification (合理性): 当一个检查点收到了超过 2/3 的验证者投票时,它就被认为是“合理的”。
  • Finalization (最终确定性): 当一个检查点 C 被合理化,并且下一个检查点 C+1 也被合理化时,检查点 C 就被“最终确定”了。
// beacon-chain/consensus/state/process.go (简化逻辑)
func (p *StateProcessor) ProcessAttestation(state *pb.BeaconState, attestation *pb.Attestation) error {
    // ...
    // 1. 验证投票的有效性
    // 2. 更新验证者对检查点的投票记录
    // 3. 检查是否有新的检查点可以被合理化
    if p.hasSupermajority(state, attestation.Data.Target) {
        // 标记检查点为合理
        state.CurrentJustifiedCheckpoint = attestation.Data.Target
    }
    // 4. 检查是否有新的检查点可以被最终确定
    if p.isFinalizable(state) {
        state.FinalizedCheckpoint = state.CurrentJustifiedCheckpoint
    }
    // ...
}

总结与对比

特性 比特币 PoW 以太坊 PoS
核心目标 防止女巫攻击,保证安全 能效,可扩展性,最终确定性
参与者 矿工 (拥有算力) 验证者 (质押 ETH)
记账权竞争 通过哈希算力竞争 通过随机分配的职责(提议/投票)
安全性来源 算力成本高昂,攻击者需掌握 >51% 算力才能作恶。 质押金被罚没(Slashing),作弊成本高昂。
出块时间 ~10 分钟 ~12 秒
能源消耗 极高 (堪比一些国家) 极低 (仅运行服务器的能耗)
最终确定性 概率性,区块越深,越安全,但理论上仍可能被更长的链替代。 确定性,一旦被 Finalized,绝对不可逆。
源码核心 validation.cpp, pow.cpp beacon-chain/consensus/
关键函数 CheckBlockHeader(), ActivateBestChain() onBlock(), ProcessAttestation()

源码层面的核心差异

  1. 验证逻辑:

    • PoW: 验证是计算密集型的,但验证本身非常快(一次哈希计算),核心是验证一个数学难题的解。
    • PoS: 验证是状态和逻辑密集型的,需要验证大量的签名、检查投票有效性、更新状态、运行复杂的 FFG 和 GHOST 算法。
  2. 链选择:

    • PoW: 链选择是简单的“最长链”,比较 nChainWork 即可。
    • PoS: 链选择是复杂的“最重链”,需要聚合所有投票来计算每个区块的权重,实现更灵活的链重组。
  3. 状态管理:

    • PoW: 比特币的共识状态相对简单,主要是 UTXO 集合和区块链本身。
    • PoS: 以太坊的 beacon chain 维护了一个极其复杂的状态,包括每个验证者的余额、退出状态、投票历史等,共识算法直接作用于这个巨大的状态机上。

通过以上分析,我们可以清晰地看到,从比特币到以太坊,共识机制的设计哲学发生了根本性的转变,源码的实现也因此呈现出截然不同的面貌,理解这些源码,是深入掌握区块链底层原理的关键一步。

文章版权及转载声明

作者:咔咔本文地址:https://www.jits.cn/content/23151.html发布于 01-04
文章转载或复制请以超链接形式并注明出处杰思科技・AI 股讯

阅读
分享

发表评论

快捷回复:

评论列表 (暂无评论,1人围观)参与讨论

还没有评论,来说两句吧...