超硬核区块链共识算法仿真:教你C语言彻底学会PBFT算法仿真(第二部分)

前言:PBFT的“超能力”——视图切换的奥秘

老铁们,经过上两篇的洗礼,你已经用C语言亲手搭建了一个能跑通PBFT三阶段提交协议的仿真器。是不是感觉自己对“共识”这个词有了全新的理解?

但是,PBFT之所以能成为区块链和分布式系统中的“明星”,并不仅仅因为它能在正常情况下达成共识。它真正的“超能力”,是在面对“叛徒”(拜占庭节点)或者“掉线”(主节点故障)时,依然能够保证系统一致性活性

而实现这个“超能力”的核心,就是我们今天要深入剖析的——视图切换(View Change)机制!

想象一下,如果当前的主节点突然宕机了,或者它开始作恶,不按规矩发送消息,甚至发送了冲突的消息,整个系统是不是就停摆了?PBFT可不允许这种情况发生!它有一套完善的“自我修复”机制,能够在检测到主节点异常时,迅速“换届选举”,选出新的主节点,并让系统恢复正常运行。

今天,我们就来彻底揭开PBFT“容错心脏”的秘密!这部分内容会更加深入,代码量也会让你“吃到撑”,但每一行代码都将带着你理解PBFT在面对故障时的“力挽狂澜”!

我们将手把手带你:

  • 理解视图切换的“为什么”和“怎么触发”!

  • 深入剖析 VIEW-CHANGENEW-VIEW 这两种核心消息的“内涵”!

  • 用C语言实现视图切换的每一个逻辑节点,让你的仿真器真正具备“拜占庭容错”的能力!

  • 甚至,我们还会加入一个简单的“故障注入”机制,让你亲手模拟主节点“宕机”,然后看系统如何“自我修复”!

准备好了吗?系好安全带,咱们这就深入PBFT的“容错心脏”,让你的C语言PBFT仿真器,从“正常运行”模式切换到“故障自愈”模式!

第七章:视图切换机制——PBFT的“容错心脏”

视图切换是PBFT算法保证活性的关键。它确保了即使主节点出现故障(崩溃或作恶),系统也能继续处理客户端请求。

7.1 为什么需要视图切换?

在PBFT的三阶段提交过程中,主节点扮演着核心的协调者角色。它负责接收客户端请求、分配序列号、并广播 PRE-PREPARE 消息。如果主节点出现以下任何一种异常,都可能导致共识过程停滞:

  1. 主节点崩溃: 主节点直接宕机,不再发送任何消息。

  2. 主节点作恶(恶意行为):

    • 主节点故意不发送 PRE-PREPARE 消息。

    • 主节点发送了无效的 PRE-PREPARE 消息(例如,视图ID错误、序列号不合法、摘要不匹配)。

    • 主节点为同一个序列号发送了不同请求的 PRE-PREPARE 消息(双花攻击)。

    • 主节点不响应客户端的请求。

在这些情况下,如果系统没有一种机制来替换掉这个有问题的主节点,那么整个系统就会“卡死”,无法继续处理新的请求。视图切换就是为了解决这个问题而设计的。

7.2 视图切换的触发条件:计时器与消息异常

副本节点通过以下方式检测主节点是否异常,并触发视图切换:

  1. 超时机制 (Timeout):

    • 每个副本节点都会为每个客户端请求或每个 PRE-PREPARE 消息启动一个计时器。

    • 如果在一个预设的时间内(例如,收到客户端请求后,没有收到主节点的 PRE-PREPARE 消息;或者在 PRE-PREPARE 阶段后,没有收到足够的 PREPARE 消息),计时器超时,副本节点就认为主节点可能存在问题。

    • 这是最常见的触发视图切换的方式。

  2. 消息异常:

    • 副本节点收到来自主节点的无效消息(例如,签名验证失败、视图ID不匹配、序列号不合法、摘要错误)。

    • 副本节点收到来自主节点的冲突消息(例如,为同一个序列号发送了两个不同内容的 PRE-PREPARE 消息)。

一旦满足触发条件,副本节点就会启动视图切换流程。

7.3 VIEW-CHANGE 消息详解——副本节点的“弹劾书”

当一个副本节点决定触发视图切换时,它会构建并广播一个 VIEW-CHANGE 消息。这个消息是副本节点向其他节点发出的“弹劾书”,表明它认为当前主节点有问题,需要更换。

VIEW-CHANGE 消息结构:

// common.h 中添加 VIEW_CHANGE 消息类型
// 视图切换消息 (由副本节点发送给所有节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_VIEW_CHANGE)
    uint32_t new_view_id; // 提议的新视图编号
    uint32_t last_stable_checkpoint_seq_num; // 节点已确认的最新检查点序列号 (低水位线)
    // P 集合: 包含所有在旧视图中已Prepared但未Committed的请求的PRE-PREPARE消息和PREPARE消息集合
    // Q 集合: 包含所有在旧视图中已Prepared的请求的PRE-PREPARE消息和PREPARE消息集合
    // 为了简化仿真,这里不直接传输P和Q集合的完整消息,而是传输其摘要和相关信息
    // 实际PBFT中,P和Q集合是视图切换的关键,用于新主节点恢复状态
    // 我们将简化为只传输 Prepared 状态的请求摘要和序列号
    // 实际PBFT中,P和Q集合是视图切换的关键,用于新主节点恢复状态
    // 这里用一个数组来模拟P集合,存储Prepared状态的请求的摘要和序列号
    // 简化:只存储最近的几个Prepared请求,实际应存储所有未完成的Prepared请求
    uint32_t prepared_requests_count;
    struct {
        uint32_t seq_num;
        uint8_t digest[DIGEST_SIZE];
        // 实际P集合还包含对应的pre-prepare和prepare消息集合的证明
    } prepared_requests[10]; // 简化:最多记录10个Prepared请求
} ViewChangeMsg;

VIEW-CHANGE 消息中的关键信息:

  • new_view_id 提议的新视图编号。通常是当前视图编号加1。

  • last_stable_checkpoint_seq_num 发送此 VIEW-CHANGE 消息的节点所知道的最新稳定检查点的序列号。检查点是PBFT用于垃圾回收和状态恢复的关键。它表示系统在该序列号之前的状态已经稳定,并且所有正常节点都已执行。

  • P 集合: (Prepared messages) 一个集合,包含所有发送者节点在旧视图中已经达到 Prepared 状态尚未 Committed 的请求的证明。每个证明包括:

    • 对应的 PRE-PREPARE 消息。

    • 2f 个匹配的 PREPARE 消息。

    • P 集合是新主节点用来恢复未完成请求的关键。

  • Q 集合: (Quorum messages) 一个集合,包含所有发送者节点在旧视图中已经达到 Prepared 状态的所有请求的证明(包括已Committed和未Committed的)。Q 集合用于确保新主节点不会跳过任何已达成Prepared状态的请求。

ER图:VIEW-CHANGE消息结构(简化)

erDiagram
    VIEW_CHANGE_MSG {
        MessageType header_type
        uint32_t header_view_id
        uint8_t header_sender_id
        uint32_t new_view_id
        uint32_t last_stable_checkpoint_seq_num
        uint32_t prepared_requests_count
    }
    VIEW_CHANGE_MSG ||--o{ PREPARED_REQUEST_ENTRY : "contains"
    PREPARED_REQUEST_ENTRY {
        uint32_t seq_num
        uint8_t digest[32]
    }

7.4 NEW-VIEW 消息详解——新主节点的“就职宣言”

当新的主节点被选举出来后,它会收集足够多的 VIEW-CHANGE 消息,并构建一个 NEW-VIEW 消息广播给所有节点。这个消息是新主节点的“就职宣言”,通知所有节点进入新的视图,并告知它们需要处理哪些未完成的请求。

NEW-VIEW 消息结构:

// common.h 中添加 NEW_VIEW 消息类型
// 新视图消息 (由新主节点发送给所有节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_NEW_VIEW)
    uint32_t new_view_id; // 新视图编号
    // V 集合: 收集到的 2f+1 个 VIEW-CHANGE 消息的集合
    // 为了简化,这里不直接传输V集合的完整消息
    // O 集合: 新主节点重新PRE-PREPARE的请求的PRE-PREPARE消息集合
    // 实际O集合是PRE-PREPARE消息的集合
    // 这里用一个数组来模拟O集合,存储PRE-PREPARE消息的摘要和序列号
    uint32_t pre_prepared_requests_count;
    PrePrepareMsg pre_prepared_requests[10]; // 简化:最多记录10个重新PRE-PREPARE的请求
} NewViewMsg;

NEW-VIEW 消息中的关键信息:

  • new_view_id 新的视图编号。

  • V 集合: 新主节点收集到的 2f+1 个有效的 VIEW-CHANGE 消息的集合。这个集合是新主节点合法性的证明。

  • O 集合: 新主节点为所有在视图切换过程中被确定为未完成的请求,重新生成的 PRE-PREPARE 消息的集合。这些请求是新主节点从收集到的 VIEW-CHANGE 消息的 P 集合中恢复出来的。

7.5 视图切换核心流程——PBFT的“自我修复”

思维导图:PBFT视图切换流程

graph TD
    A[副本节点计时器超时 或 收到无效消息] --> B[副本节点: 广播 VIEW-CHANGE]
    B --(VIEW-CHANGE, new_view_id, P, Q)--> C{所有节点收集VIEW-CHANGE}
    C --新主节点收集到2f+1个VIEW-CHANGE--> D[新主节点: 确定新视图状态]
    D --> D1[确定新低水位线h']
    D --> D2[从P集合恢复未完成请求]
    D --> D3[为未完成请求重新PRE-PREPARE (生成O集合)]
    D --> E[新主节点: 广播 NEW-VIEW]
    E --(NEW-VIEW, new_view_id, V, O)--> F{所有节点接收NEW-VIEW}
    F --> F1[验证NEW-VIEW合法性]
    F --> F2[更新本地视图ID]
    F --> F3[处理O集合中的PRE-PREPARE消息]
    F --> G[系统在新视图中恢复正常共识]

详细流程:

  1. 触发视图切换:

    • 每个副本节点维护一个计时器,用于检测主节点是否在预定时间内响应。

    • 如果计时器超时,或者副本节点检测到主节点发送了无效或冲突的消息,该副本节点就会启动视图切换。

    • 它会递增自己的视图编号(例如,从 vv+1),并构建一个 VIEW-CHANGE 消息。

    • VIEW-CHANGE 消息包含:提议的新视图编号 v+1,以及该节点在当前视图 v 中所有已 Prepared 但尚未 Committed 的请求的证明(P 集合),以及所有已 Prepared 的请求的证明(Q 集合)。

    • 该副本节点将 VIEW-CHANGE 消息广播给所有其他节点(包括它自己)。

  2. 收集 VIEW-CHANGE 消息:

    • 所有节点都会接收来自其他节点的 VIEW-CHANGE 消息。

    • 每个节点会验证收到的 VIEW-CHANGE 消息的合法性(例如,签名、视图编号、PQ 集合的有效性)。

    • 当一个节点(无论是旧主节点、副本节点,还是未来的新主节点)收集到 2f+1 个来自不同节点的有效 VIEW-CHANGE 消息时,它就认为视图切换可以开始了。

  3. 新主节点确定新视图状态:

    • 在新的视图 v+1 中,新的主节点是 (v+1) % N

    • 新的主节点在收集到 2f+1 个有效的 VIEW-CHANGE 消息后,会执行以下操作:

      • 确定新的低水位线 h' 从所有收集到的 VIEW-CHANGE 消息中,找到所有节点报告的 last_stable_checkpoint_seq_num 中的最大值。这个值将成为新视图的低水位线。

      • P 集合中恢复未完成请求: 新主节点会检查所有收到的 VIEW-CHANGE 消息中的 P 集合。它会找出所有在旧视图中已经达到 Prepared 状态但尚未 Committed 的请求。

      • 为未完成请求重新 PRE-PREPARE (生成 O 集合): 对于这些未完成的请求,新主节点会为它们重新生成 PRE-PREPARE 消息,使用新的视图编号 v+1 和它们原有的序列号。这些重新生成的 PRE-PREPARE 消息构成了 O 集合。

  4. 新主节点广播 NEW-VIEW 消息:

    • 新主节点构建一个 NEW-VIEW 消息,其中包含:新的视图编号 v+1,它收集到的 2f+1VIEW-CHANGE 消息的集合(V 集合),以及它重新 PRE-PREPARE 的请求集合(O 集合)。

    • 新主节点将 NEW-VIEW 消息广播给所有其他节点。

  5. 所有节点处理 NEW-VIEW 消息:

    • 所有节点收到 NEW-VIEW 消息后,会验证其合法性(例如,发送者是否是新主节点、V 集合是否包含 2f+1 个有效 VIEW-CHANGE 消息,并且这些 VIEW-CHANGE 消息是否与 NEW-VIEW 中的 O 集合一致)。

    • 如果验证通过,节点会更新自己的 current_viewnew_view_id

    • 节点会处理 O 集合中的所有 PRE-PREPARE 消息,就像它们是正常阶段收到的 PRE-PREPARE 消息一样,从而继续推进这些请求的共识过程。

    • 对于那些在旧视图中已经达到 Prepared 状态但在新视图中没有收到 PRE-PREPARE 消息的请求,节点会将其标记为“中止”,并等待新的请求。

  6. 系统在新视图中恢复正常共识:

    • 一旦所有正常节点都处理了 NEW-VIEW 消息并更新了视图,系统就在新的视图中恢复了正常运行。

    • 新的客户端请求将发送给新的主节点,并开始新的三阶段提交过程。

面试考点:

  • “详细描述PBFT的视图切换机制。它解决了什么问题?包括哪些消息?” 这是PBFT的难点,也是区分高手的关键。

  • VIEW-CHANGE 消息中的 PQ 集合有什么作用?” 它们是新主节点恢复状态的关键。

  • “为什么新主节点需要收集 2f+1VIEW-CHANGE 消息?” 确保至少有一个正常节点在其中,并且能够从正常节点那里获取到正确的状态信息。

7.6 C语言实现:视图切换的“代码之魂”

现在,我们将把上述复杂的视图切换逻辑,用C语言一步步实现。这会涉及到对 common.hnode.cclient.c 的大量修改和新增代码。

首先,更新 common.h,添加视图切换相关的消息结构体和宏定义:

#ifndef COMMON_H
#define COMMON_H

#include <stdint.h> // For uint32_t, uint8_t
#include <stdbool.h> // For bool
#include <pthread.h> // For pthread_mutex_t

// 定义PBFT系统中的节点总数 (N) 和可容忍的拜占庭节点数 (f)
// N = 3f + 1
#define NUM_NODES 4 // 例如,4个节点,f = (4-1)/3 = 1,可容忍1个拜占庭节点
#define FAULTY_NODES ((NUM_NODES - 1) / 3) // f

// 定义消息摘要长度 (例如,SHA256摘要是32字节)
#define DIGEST_SIZE 32
// 定义最大消息内容长度
#define MAX_MSG_CONTENT_SIZE 256
// 定义每个节点的监听端口
#define BASE_NODE_PORT 8000 // Node 0 监听 8000, Node 1 监听 8001, ...
// 定义客户端监听端口 (用于接收Reply)
#define CLIENT_PORT 9000

// 定义视图切换超时时间 (毫秒)
#define VIEW_CHANGE_TIMEOUT_MS 5000 // 5秒

// 定义PBFT消息类型
typedef enum {
    MSG_TYPE_CLIENT_REQUEST, // 客户端请求
    MSG_TYPE_PRE_PREPARE,    // 预准备消息
    MSG_TYPE_PREPARE,        // 准备消息
    MSG_TYPE_COMMIT,         // 提交消息
    MSG_TYPE_REPLY,          // 回复消息
    MSG_TYPE_VIEW_CHANGE,    // 视图切换消息
    MSG_TYPE_NEW_VIEW,       // 新视图消息
    MSG_TYPE_HEARTBEAT       // 心跳消息 (用于检测节点存活)
} MessageType;

// 消息公共头部
typedef struct {
    MessageType type;      // 消息类型
    uint32_t view_id;      // 视图编号
    uint32_t sequence_num; // 序列号
    uint8_t sender_id;     // 发送者节点ID
} MessageHeader;

// 客户端请求消息 (由客户端发送给主节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_CLIENT_REQUEST)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (用于防止重放攻击)
    char operation[MAX_MSG_CONTENT_SIZE]; // 客户端请求的操作内容 (例如 "transfer 100 to Bob")
    uint8_t digest[DIGEST_SIZE]; // 请求内容的摘要 (SHA256(operation))
} ClientRequest;

// 预准备消息 (由主节点广播给副本节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PRE_PREPARE)
    uint8_t client_id;    // 原始客户端ID
    uint32_t timestamp;   // 原始客户端请求时间戳
    char operation[MAX_MSG_CONTENT_SIZE]; // 原始客户端请求的操作内容
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} PrePrepareMsg;

// 准备消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PREPARE)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} PrepareMsg;

// 提交消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_COMMIT)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} CommitMsg;

// 回复消息 (由节点发送给客户端)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_REPLY)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (来自原始请求)
    char result[MAX_MSG_CONTENT_SIZE]; // 执行请求后的结果 (例如 "OK" 或 "Error")
} ReplyMsg;

// --- 视图切换相关消息 ---
// Prepared请求的简要信息 (用于VIEW-CHANGE的P和Q集合)
typedef struct {
    uint32_t seq_num;
    uint8_t digest[DIGEST_SIZE];
    // 实际PBFT中,这里还包含对应的PRE-PREPARE和PREPARE消息的证明
    // 为了简化,我们只传输序列号和摘要
} PreparedRequestInfo;

// 视图切换消息 (由副本节点发送给所有节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_VIEW_CHANGE)
    uint32_t new_view_id; // 提议的新视图编号
    uint32_t last_stable_checkpoint_seq_num; // 节点已确认的最新检查点序列号 (低水位线)

    // P 集合: 包含所有在旧视图中已Prepared但未Committed的请求的简要信息
    uint32_t p_set_count;
    PreparedRequestInfo p_set[NUM_NODES * 2]; // 简化:假设最多有2N个未完成请求
    // 实际PBFT中,P集合是PRE-PREPARE消息和2f个PREPARE消息的集合

    // Q 集合: 包含所有在旧视图中已Prepared的请求的简要信息
    // 为了简化,我们不显式区分P和Q,只传输P集合,并在新主节点处根据P集合恢复
    // 实际Q集合是所有Prepared请求的集合,用于确保新主节点不会跳过任何已Prepared的请求
} ViewChangeMsg;

// 新视图消息 (由新主节点发送给所有节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_NEW_VIEW)
    uint32_t new_view_id; // 新视图编号
    // V 集合: 收集到的 2f+1 个 VIEW-CHANGE 消息的集合
    // 为了简化,这里不直接传输V集合的完整消息,而是传递其发送者ID和提议的新视图ID
    uint32_t view_change_proof_count;
    uint8_t view_change_senders[NUM_NODES]; // 存储发送VIEW-CHANGE的节点ID

    // O 集合: 新主节点重新PRE-PREPARE的请求的PRE-PREPARE消息集合
    uint32_t pre_prepared_requests_count;
    PrePrepareMsg pre_prepared_requests[NUM_NODES * 2]; // 简化:最多记录2N个重新PRE-PREPARE的请求
} NewViewMsg;

// 统一消息结构体 (使用union节省内存,一次只存储一种消息)
// 方便网络传输和处理
typedef struct {
    MessageHeader common_header; // 所有消息共有的头部信息
    union {
        ClientRequest client_request;
        PrePrepareMsg pre_prepare;
        PrepareMsg prepare;
        CommitMsg commit;
        ReplyMsg reply;
        ViewChangeMsg view_change; // 新增视图切换消息
        NewViewMsg new_view;       // 新增新视图消息
        // 其他消息类型待添加
    } payload; // 消息的具体内容
} PBFTMessage;

// 定义PBFT处理阶段
typedef enum {
    PHASE_IDLE,
    PHASE_PRE_PREPARE, // 收到PRE-PREPARE (副本节点) 或发送PRE-PREPARE (主节点)
    PHASE_PREPARE,     // 收到或发送PREPARE
    PHASE_COMMIT,      // 收到或发送COMMIT
    PHASE_REPLY,       // 收到或发送REPLY
    PHASE_VIEW_CHANGE, // 正在进行视图切换
    PHASE_NEW_VIEW     // 正在处理新视图
} PBFTPhase;

// PBFT消息日志条目
typedef struct MessageLogEntry {
    uint32_t view_id;
    uint32_t sequence_num;
    uint8_t request_digest[DIGEST_SIZE];
    PBFTMessage original_client_request; // 原始客户端请求的副本 (用于执行)
    PBFTMessage pre_prepare_msg;         // 存储收到的PRE-PREPARE消息
    // 存储收到的PREPARE和COMMIT消息的集合 (这里简化为计数,实际应存储消息本身)
    bool received_prepare[NUM_NODES]; // 标记是否收到某个节点的PREPARE消息
    bool received_commit[NUM_NODES];  // 标记是否收到某个节点的COMMIT消息
    int prepare_count;                // 收集到的PREPARE消息数量
    int commit_count;                 // 收集到的COMMIT消息数量
    bool is_prepared;                 // 是否已达到Prepared状态
    bool is_committed;                // 是否已达到Committed状态
    bool is_executed;                 // 是否已执行
    struct MessageLogEntry *next;     // 链表指针
} MessageLogEntry;

// --- 节点状态信息 ---
typedef struct {
    uint8_t node_id;         // 当前节点的ID
    uint32_t current_view;   // 当前视图编号
    uint32_t last_executed_seq_num; // 上一个已执行的序列号 (低水位线)
    uint32_t next_seq_num;   // 下一个可用的序列号 (主节点分配)
    bool is_primary;         // 是否是主节点
    PBFTPhase current_phase; // 当前节点所处的PBFT阶段

    MessageLogEntry *message_log_head; // 消息日志链表头
    pthread_mutex_t log_mutex;         // 保护消息日志的互斥锁

    // 视图切换相关状态
    uint32_t view_change_timer_start_time_ms; // 视图切换计时器开始时间
    bool view_change_triggered;               // 是否已触发视图切换
    uint32_t proposed_new_view_id;            // 提议的新视图ID
    int view_change_count[NUM_NODES];         // 记录每个节点发送的VIEW-CHANGE次数 (简化)
    bool received_view_change[NUM_NODES];     // 标记是否收到某个节点的VIEW-CHANGE消息
    int total_view_change_count;              // 收集到的VIEW-CHANGE消息总数
    // 存储收集到的VIEW-CHANGE消息 (实际应存储完整消息,这里简化为计数)
    // 存储收集到的NEW-VIEW消息 (实际应存储完整消息,这里简化为计数)
    bool received_new_view[NUM_NODES]; // 标记是否收到某个节点的NEW-VIEW消息
    int total_new_view_count; // 收集到的NEW-VIEW消息总数

    // 故障注入标志 (用于模拟主节点不响应)
    bool simulate_primary_failure; // 如果为true,主节点不发送PRE-PREPARE
    int failure_node_id; // 要模拟故障的节点ID

} NodeState;


// --- 辅助函数声明 ---
// 计算消息摘要 (这里简化为简单的哈希,实际应使用SHA256等)
void calculate_digest(const char* data, size_t len, uint8_t* digest_out);

// 打印消息内容 (用于调试)
void print_message(const PBFTMessage* msg);

// 查找或创建消息日志条目
MessageLogEntry* get_or_create_log_entry(uint32_t view_id, uint32_t seq_num, const uint8_t* digest);
// 标记日志条目为Prepared状态
void mark_as_prepared(MessageLogEntry* entry);
// 标记日志条目为Committed状态
void mark_as_committed(MessageLogEntry* entry);
// 执行操作并标记为Executed状态
void execute_operation(MessageLogEntry* entry);
// 清理旧的日志条目 (基于水位线,简化)
void clean_old_log_entries();

// 获取当前时间 (毫秒)
uint32_t get_current_time_ms();

#endif // COMMON_H

然后,更新 utils.c,添加新的辅助函数:

#include "common.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // For exit
#include <time.h>   // For clock_gettime, CLOCK_MONOTONIC

// 全局节点状态 (在node.c中定义,这里只是声明 extern)
extern NodeState g_node_state;

// 简化版摘要计算 (实际应使用SHA256等加密哈希函数)
void calculate_digest(const char* data, size_t len, uint8_t* digest_out) {
    // 这里只是一个非常简单的模拟摘要,实际应用中绝对不能这样用!
    // 实际应使用如 OpenSSL 库中的 SHA256 函数
    uint32_t hash = 0;
    for (size_t i = 0; i < len; ++i) {
        hash = (hash * 31) + data[i]; // 简单的乘法哈希
    }
    // 将32位哈希值填充到DIGEST_SIZE字节的数组中
    memset(digest_out, 0, DIGEST_SIZE); // 先清零
    memcpy(digest_out, &hash, sizeof(uint32_t)); // 复制哈希值
}

// 打印消息内容 (用于调试)
void print_message(const PBFTMessage* msg) {
    if (!msg) {
        printf("空消息指针。\n");
        return;
    }

    printf("--- 消息详情 ---\n");
    printf("  类型: ");
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST: printf("CLIENT_REQUEST\n"); break;
        case MSG_TYPE_PRE_PREPARE:    printf("PRE_PREPARE\n"); break;
        case MSG_TYPE_PREPARE:        printf("PREPARE\n"); break;
        case MSG_TYPE_COMMIT:         printf("COMMIT\n"); break;
        case MSG_TYPE_REPLY:          printf("REPLY\n"); break;
        case MSG_TYPE_VIEW_CHANGE:    printf("VIEW_CHANGE\n"); break;
        case MSG_TYPE_NEW_VIEW:       printf("NEW_VIEW\n"); break;
        case MSG_TYPE_HEARTBEAT:      printf("HEARTBEAT\n"); break;
        default:                      printf("未知类型\n"); break;
    }
    printf("  视图ID: %u\n", msg->common_header.view_id);
    printf("  序列号: %u\n", msg->common_header.sequence_num);
    printf("  发送者ID: %u\n", msg->common_header.sender_id);

    // 根据消息类型打印具体内容
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST:
            printf("  客户端ID: %u\n", msg->payload.client_request.client_id);
            printf("  时间戳: %u\n", msg->payload.client_request.timestamp);
            printf("  操作: %s\n", msg->payload.client_request.operation);
            printf("  摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.client_request.digest[i]);
            }
            printf("\n");
            break;
        case MSG_TYPE_PRE_PREPARE:
            printf("  原始客户端ID: %u\n", msg->payload.pre_prepare.client_id);
            printf("  原始时间戳: %u\n", msg->payload.pre_prepare.timestamp);
            printf("  操作: %s\n", msg->payload.pre_prepare.operation);
            printf("  请求摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.pre_prepare.request_digest[i]);
            }
            printf("\n");
            break;
        case MSG_TYPE_PREPARE:
        case MSG_TYPE_COMMIT:
            printf("  请求摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.prepare.request_digest[i]); // prepare和commit结构体中摘要字段相同
            }
            printf("\n");
            break;
        case MSG_TYPE_REPLY:
            printf("  客户端ID: %u\n", msg->payload.reply.client_id);
            printf("  时间戳: %u\n", msg->payload.reply.timestamp);
            printf("  结果: %s\n", msg->payload.reply.result);
            break;
        case MSG_TYPE_VIEW_CHANGE:
            printf("  提议新视图ID: %u\n", msg->payload.view_change.new_view_id);
            printf("  最新检查点序列号: %u\n", msg->payload.view_change.last_stable_checkpoint_seq_num);
            printf("  Prepared请求数量 (P集合简化): %u\n", msg->payload.view_change.p_set_count);
            for (uint32_t i = 0; i < msg->payload.view_change.p_set_count; ++i) {
                printf("    SeqNum: %u, Digest: ", msg->payload.view_change.p_set[i].seq_num);
                for (int j = 0; j < DIGEST_SIZE; ++j) {
                    printf("%02x", msg->payload.view_change.p_set[i].digest[j]);
                }
                printf("\n");
            }
            break;
        case MSG_TYPE_NEW_VIEW:
            printf("  新视图ID: %u\n", msg->payload.new_view.new_view_id);
            printf("  VIEW-CHANGE证明数量: %u\n", msg->
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值