Diem 状态同步器(State Synchronizer)技术规范详解:三种同步协议、网络消息格式与核心模块协作
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
导读:本文以 Diem 开源仓库中的 State Synchronizer 规范文档 为主体,系统讲解 Diem 节点如何通过状态同步组件将本地账本(ledger)同步到 Diem 支付网络的最新状态。文章将覆盖状态同步的整体流程、验证器同步(Validator sync)、全节点同步(Full Node sync)与 Waypoint 同步三种协议的技术要求、GetChunkRequest/GetChunkResponse等网络消息格式,并结合 state-sync-v1 的实际 Rust 源码,说明这些协议在实现层面的落地方式与关键配置参数。读完本文,你将掌握 Diem 状态同步的协议设计、消息结构、验证规则与模块边界,能够读懂并定位相关源码。
一、组件定位与核心职责
State Synchronizer(状态同步器)是 Diem 节点中的一个系统组件,其职责是在 Diem 支付网络中的各个节点之间同步账本状态(ledger state)。借助状态同步,节点可以发现并更新到更新的账本状态,其一般流程如下:
- 节点向远端对等节点(peer)发送
GetChunkRequest; - 如果远端对等节点存在更新的账本状态,它会返回对应的
GetChunkResponse,其中携带一批交易以及"这些交易已由共识处理"的对应证明(proof); - 请求方利用响应数据,通过重新执行(re-executing)交易的方式推进本地账本状态。
该组件解决的问题与常见分布式系统中的数据追赶场景一致:节点掉线一段时间、网络分区恢复、新节点启动等情形都会导致节点账本落后于网络最新状态,此时必须依靠状态同步完成追赶。
从源码结构看,该组件在当前仓库中的实现位于 state-sync/state-sync-v1(含coordinator.rs、chunk_request.rs、chunk_response.rs、request_manager.rs、bootstrapper.rs等模块),其库入口 lib.rs 中的注释点明了组件用途:"Used to perform catching up between nodes for committed states. Used for node restarts, network partitions, full node syncs"(用于在节点间对已提交状态进行追赶,适用于节点重启、网络分区、全节点同步等场景)。
二、三类受支持的同步协议
Diem 状态同步支持三种同步协议,每种协议都要求组件同时具备**请求方(requester)与响应方(responder)**两种角色能力:
| 协议 | 请求方(Requester) | 响应方(Responder) | 触发场景 |
|---|---|---|---|
| Validator sync | 验证器(validator) | 验证器 | 验证器落后于共识状态时追赶 |
| Full Node sync | 全节点(full node) | 验证器或全节点 | 全节点发现并同步到最新账本状态 |
| Waypoint sync | 验证器或全节点 | 验证器或全节点 | 节点初始化时按 waypoint 同步到指定状态 |
验证器同步(Validator sync)
参与者:请求方为验证器,响应方为验证器。
一般情况下,验证器通过参与共识协议获知最新账本状态,但仍有可能落后——例如节点离线一段时间,或发生网络分区。此时落后的验证器可以依赖状态同步追赶至最新状态。
发送请求:请求方发送GetChunkRequest,其中target设置为TargetType::TargetLedgerInfo;目标LedgerInfoWithSignatures由共识模块的sync_to请求提供(即共识主动要求状态同步到某一目标账本信息)。
处理请求 / 发送响应:响应方依据请求参数,从存储模块获取一段TransactionListWithProof(带证明的交易列表),满足以下约束:
- 首笔交易的版本为
known_version + 1; - 交易数量上限为
limit; - 这些交易属于同一个 epoch。
随后响应方发送GetChunkResponse,其中txn_list_with_proof设为上述交易列表,response_li设为ResponseLedgerInfo::VerifiableLedgerInfo,包含:
- 对应请求
target中的账本信息;或 - 若这些交易属于更早的 epoch,则设为该 epoch 结束时对应的带签名账本信息。
接收 / 验证响应:请求方执行以下验证:
txn_list_with_proof的起始版本 == 本地状态版本 + 1;- 依据本地状态的可信 epoch,验证
ResponseLedgerInfo::VerifiableLedgerInfo中的账本信息; - 通过执行模块的
ChunkExecutor::execute_and_commit_chunk调用执行并存储这些交易。
若已同步到目标状态,则通知共识模块其sync_to请求已完成;若尚未完成,则基于新的本地状态再次按本协议发送下一个 chunk 请求,如此循环直到达标。
全节点同步(Full Node sync)
参与者:请求方为全节点,响应方为验证器或全节点。
全节点不参与共识,因此只能依赖状态同步器来发现并同步到更新的账本状态。
发送请求:请求方发送GetChunkRequest,target设置为TargetType::HighestAvailable。
处理请求 / 发送响应:响应方同样从存储模块获取满足三条约束(起始版本known_version + 1、上限limit笔、同属一个 epoch)的TransactionListWithProof,并发送GetChunkResponse,其中response_li设置为ResponseLedgerInfo::ProgressiveLedgerInfo。
全节点长轮询协议(long-poll):如果请求的目标状态在响应方处尚不可用,响应方可将该请求缓存timeout_ms毫秒。若在此时间窗口内目标账本状态变为可用(即响应方自身推进了账本状态),则立即为该请求发送对应的 chunk 响应。
接收响应:请求方执行以下验证:
txn_list_with_proof的起始版本 == 本地状态版本 + 1;- 依据本地状态的可信 epoch,验证
ResponseLedgerInfo::ProgressiveLedgerInfo中的所有账本信息; - 通过
ChunkExecutor::execute_and_commit_chunk执行并存储交易。
随后基于新的本地状态继续发送下一个 chunk 请求,直至追上最新状态。
Waypoint 同步(Waypoint sync)
参与者:请求方与响应方均为验证器或全节点。
Waypoint 同步发生在节点初始化阶段,且必须在节点参与其他状态同步协议之前完成。当节点启动时,可以指定一个 waypoint,将本地状态同步到该 waypoint 对应的版本。
发送请求:请求方发送GetChunkRequest,target设置为TargetType::Waypoint,其中Waypoint携带的版本与节点初始化时提供的 waypoint 版本一致。
处理请求 / 发送响应:响应方依据请求参数,从存储模块获取满足约束(起始版本known_version + 1、上限limit笔、同属一个 epoch)的TransactionListWithProof,并发送GetChunkResponse,其中response_li设置为ResponseLedgerInfo::LedgerInfoForWaypoint。
接收响应:请求方执行以下验证:
- 依据节点初始化时提供的 waypoint,验证
waypoint_li; txn_list_with_proof的起始版本 == 本地状态版本 + 1;- 通过
ChunkExecutor::execute_and_commit_chunk执行并存储交易。
若处理完该响应后尚未同步到 waypoint 对应版本,则基于更新后的账本状态按本协议继续发送下一个 chunk 请求。
三、网络协议:线上消息格式
本节说明状态同步器在网络上传输的消息的技术细节。实际实现中,所有消息被封装为StateSyncMessage枚举(仅含GetChunkRequest与GetChunkResponse两个变体),定义见 network.rs,并通过 DiemNet 网络栈传输(StateSyncEvents/StateSyncSender分别是网络到状态同步、状态同步到网络的封装接口)。
GetChunkRequest 请求结构
struct GetChunkRequest { known_version: u64, current_epoch: u64, limit: u64, target: TargetType }字段说明:
known_version——请求方最新账本状态的版本号。current_epoch——请求方最新账本状态所处的 epoch。若请求方的状态已推进到 epochx的末尾,则下一次请求的current_epoch应为x + 1。limit——请求的交易数量(chunk 大小)。target——本次请求的同步目标,见下节TargetType。
仓库中的实际实现与规范一致,见 chunk_request.rs,并额外提供了new构造方法与Display实现(便于日志输出请求的 known version、epoch、limit 与 target)。
TargetType 同步目标类型
TargetType表示一次GetChunkRequest的同步目标,其中携带的所有LedgerInfoWithSignatures都用于证明请求方想要同步到的账本状态:
enum TargetType { TargetLedgerInfo(LedgerInfoWithSignatures), HighestAvailable { target_li: Option<LedgerInfoWithSignatures>, timeout_ms: u64, }, Waypoint(Version), }子类型说明:
TargetLedgerInfo——LedgerInfoWithSignatures表示请求方请求同步到的目标账本状态(用于验证器同步场景)。HighestAvailabletarget_li——若无已知的带签名账本信息可作为目标,则设为None(通常在全节点首次发送 chunk 请求时设为None);否则携带一个目标账本信息。timeout_ms——请求方发送携带该目标类型的 chunk 请求后、在发送下一个 chunk 请求之前等待的时间间隔。
Waypoint(Version)——对指定版本的同步请求,该版本与节点初始化时提供的 waypoint 版本一致。
从源码注释(chunk_request.rs)可以补充更深入的设计动机:
TargetLedgerInfo已标记为 DEPRECATED:源码注释指出该类型仅为了向后兼容而保留,状态同步已避免发送此类请求而改用HighestAvailable,并计划在下一个破坏性版本中移除(对应 issue 编号为 8013)。因此读者阅读旧文档或旧版本代码时可能看到该类型,但新实现已迁移。HighestAvailable的target_li语义:它支持"同步请求方落后于响应方太多"的场景。如果响应方的最新账本信息版本持续前进,即便同步请求方持续收到并同步交易,这些交易也可能永远得不到账本信息(LedgerInfo)背书——因为账本信息只有在覆盖到其版本的全部交易收到之后才能提交,而交易必须得到账本信息背书才能在存储查询时显示为已提交。为避免同步追赶期间交易永远得不到账本信息背书(或同步请求方已同步版本与被提交账本信息版本的差距不断增大),该目标类型可以同时做到:(1) 询问最高可用账本信息;(2) 指定一个目标账本信息用于构建请求的交易证明。通过 (1),同步请求方可以在同步到较早的目标账本信息之后,存储该账本信息以便后续做目标同步。- 若
target_li未指定,响应方将基于其最高账本信息构建响应。
TargetType还提供了epoch()与version()辅助方法(chunk_request.rs):前者提取目标账本信息所属 epoch(Waypoint返回None),后者提取目标版本。
GetChunkResponse 响应结构
GetChunkResponse是对应GetChunkRequest的网络响应消息。响应中包含的交易 chunk 全部属于对应请求known_epoch(即请求中的current_epoch)指定的 epoch,永远不会跨越 epoch 边界:
struct GetChunkResponse { response_li: ResponseLedgerInfo, txn_list_with_proof: TransactionListWithProof, }字段说明:
response_li——响应中的所有证明都相对于该账本信息构建;账本信息验证的具体方式取决于其类型,见下节ResponseLedgerInfo。txn_list_with_proof——与响应携带的账本信息对应的交易 chunk 及其证明。
实际实现见 chunk_response.rs,源码注释同样强调:"The returned chunk is bounded by the end of the known_epoch of the requester (i.e., a chunk never crosses epoch boundaries)."(返回的 chunk 以请求方 known_epoch 的末尾为界,即 chunk 绝不跨越 epoch 边界)。Display实现会输出响应账本信息类型以及交易覆盖的版本区间(如versions [first - last])。
ResponseLedgerInfo 响应账本信息
enum ResponseLedgerInfo { VerifiableLedgerInfo(LedgerInfoWithSignatures), ProgressiveLedgerInfo { target_li: LedgerInfoWithSignatures, highest_li: Option<LedgerInfoWithSignatures>, }, LedgerInfoForWaypoint { waypoint_li: LedgerInfoWithSignatures, end_of_epoch_li: Option<LedgerInfoWithSignatures>, } }子类型说明:
VerifiableLedgerInfo——包含可用于验证响应中txn_list_with_proof的带签名账本信息。与TargetLedgerInfo类似,源码注释也标记其为 DEPRECATED(仅为向后兼容保留,由ProgressiveLedgerInfo取代)。ProgressiveLedgerInfotarget_li——用于验证响应中txn_list_with_proof的带签名账本信息;highest_li——若响应方本地状态中存在不同于target_li的更高账本信息,则设为此最高账本信息,否则为None。
LedgerInfoForWaypointwaypoint_li——对应请求TargetType::Waypoint指定版本处账本状态的带签名账本信息;end_of_epoch_li——可选:若txn_list_with_proof中的交易 chunk 终止于某个 epoch 边界,则为该 epoch 结束对应的带签名账本信息,否则为None。
ResponseLedgerInfo提供version()方法(chunk_response.rs),返回证明所相对构建的账本信息版本(三种类型分别取li.ledger_info().version()、target_li的版本、waypoint_li的版本)。
四、源码级实现:协调器与长轮询机制
规范描述的是"应当如何",而 state-sync-v1 给出了"实际如何"的实现。核心是StateSyncCoordinator(见 coordinator.rs),其start()函数运行一个无限事件循环,根据外部与内部(本地)请求触发动作。源码注释明确描述了两种工作模式:
- 全节点模式(FullNode):向预定义的静态对等节点持续发送无限流式 chunk 请求(若父节点在其已提交版本于超时窗口内变高,则会返回 chunk 响应);
- 验证器模式(Validator):按需为特定目标账本信息生成 chunk 请求,以同步到该目标。
事件循环(coordinator.rs)通过futures::select!同时监听四类事件源:
- 共识通知(
consensus_listener):处理ConsensusNotification::SyncToTarget(即共识发起的sync_to请求,对应验证器同步协议)与ConsensusNotification::NotifyCommit(共识提交通知,状态同步需要把提交结果转发给 mempool 并维护自身状态); - 客户端消息(
client_events):处理GetSyncState(查询同步状态)与WaitForInitialization(等待初始化完成,即 waypoint 同步完成); - 网络事件(
network_events):处理NewPeer/LostPeer(对等节点上线/下线,动态启用或禁用请求管理器中的对等节点)以及Event::Message(收到远端发来的GetChunkRequest/GetChunkResponse,交由process_chunk_message处理); - 定时器(
interval):以tick_interval_ms为周期调用check_progress()检查同步进度,超时未进展则触发重试或广播。
长轮询(long-poll)在实现层面体现为协调器维护的一张订阅表subscriptions: HashMap<PeerNetworkId, PendingRequestInfo>(coordinator.rs),PendingRequestInfo记录了每个挂起请求的过期时间、已知版本、请求 epoch、目标账本信息与 chunk 上限。当响应方收到一个HighestAvailable请求而目标状态尚未可用时,会把该请求登记为订阅,并在到期前一旦有新信息可用就主动通知对方——这与规范中"缓存响应timeout_ms"的描述完全对应。
请求重试与多播策略由RequestManager(request_manager.rs)负责:协调器构造时依据节点角色计算重试超时(coordinator.rs)——全节点为tick_interval_ms + long_poll_timeout_ms,验证器为tick_interval_ms * 2——并传入多播超时multicast_timeout_ms;若在限定时间内向一定数量的网络发送 chunk 请求仍无进展,下一次同步请求将多播(发送到更多网络)。
五、配置参数:StateSyncConfig
状态同步行为可通过节点配置中的state_sync节调整,配置结构体StateSyncConfig定义于 state_sync_config.rs,各项默认值如下:
| 参数 | 默认值 | 含义 |
|---|---|---|
chunk_limit | 1000 | 每次状态同步请求的 chunk 大小(请求的交易笔数上限) |
client_commit_timeout_ms | 5_000 | 状态同步客户端处理一次提交通知的超时(毫秒) |
long_poll_timeout_ms | 10_000 | 远端对等节点长轮询的默认超时(毫秒) |
max_chunk_limit | 1000 | 合法 chunk 上限(用于健全性检查,拒绝异常请求) |
max_timeout_ms | 120_000 | 合法超时上限(用于健全性检查) |
mempool_commit_timeout_ms | 5_000 | 协调器等待 mempool 提交确认(ack)的超时(毫秒) |
multicast_timeout_ms | 30_000 | 状态同步默认多播超时:若向一定数量网络发送请求仍无进展,下一次请求将多播到更多网络 |
sync_request_timeout_ms | 60_000 | 处理同步请求时确保其有进展的超时(即两次提交之间的最大间隔) |
tick_interval_ms | 100 | 检查状态同步进度的周期(毫秒),即协调器主循环的 tick 间隔 |
这些参数直接驱动上一节的协调器行为:tick_interval_ms控制interval定时器、long_poll_timeout_ms控制长轮询等待窗口、chunk_limit对应GetChunkRequest的limit字段、multicast_timeout_ms控制请求的多播策略。理解这些默认值有助于在部署验证器或全节点时合理调优同步行为。
六、抽象模块接口:状态同步器的协作边界
状态同步器不是孤岛,它需要与多个外部模块交互。规范将交互关系抽象为如下模块,其中标注"示例接口(非严格必需)"的部分仅用于说明协作方式,实现细节由各实现方自定。
Network 网络
状态同步器与网络模块交互,向网络中的对等节点发送网络协议中定义的消息。规范给出的示例接口:
/// Sends `message` to `recipient` fn send_to(recipient: PeerId, message: StateSyncMessage)实际实现中,网络侧通过StateSyncEvents(网络事件流)与StateSyncSender(发送封装)与状态同步层对接,二者均为NetworkEvents/NetworkSender的薄封装(见 network.rs),StateSyncSender可克隆、可发送到独立异步任务。
Consensus 共识
状态同步器处理共识的StateComputer::sync_to调用,通过执行验证器同步协议完成,并在账本状态达到共识请求中账本信息指定的版本后通知共识。该交互的协调与实现属于实现细节(仓库中的对应物是consensus_notificationscrate 提供的ConsensusSyncNotification与ConsensusNotificationListener,见 coordinator.rs)。
Mempool 交易池
状态同步器在处理 chunk 响应时,会通知 mempool 哪些交易已成功执行并提交,使 mempool 能清除这些交易。仓库中对应mempool_notifications::MempoolNotificationSender接口(见 coordinator.rs),协调器结构体持有mempool_notifier字段,并配置了mempool_commit_timeout_ms超时。
Execution 执行
状态同步器使用执行模块的ChunkExecutor接口来执行并存储交易。该接口在 executor-types/src/lib.rs 中定义,实际实现在 executor/src/lib.rs 的Executor<V>::execute_and_commit_chunk:其流程为
- 重置执行器缓存以与最新已同步状态保持一致;
- 验证输入交易列表(
verify_chunk,依据已独立验证的目标账本信息验证证明); - 执行交易并提交。
源码签名(executor/src/lib.rs)为:
fn execute_and_commit_chunk( &self, txn_list_with_proof: TransactionListWithProof, // 已独立验证的目标 LI:证明相对于该版本构建 verified_target_li: LedgerInfoWithSignatures, // 可选的 epoch 结束账本信息;不允许携带 epoch 变更 LI 的 chunk 直接结束 epoch epoch_change_li: Option<LedgerInfoWithSignatures>, ) -> Result<Vec<ContractEvent>>Storage 存储
状态同步器与存储模块交互,以TransactionListWithProof形式获取一段交易及其对应证明。规范给出的示例接口:
/// Returns a batch of transactions starting at `known_version` with /// max size `limit` with proofs built against `target_version` fn get_chunk( &self, known_version: u64, limit: u64, target_version: u64, ) -> Result<TransactionListWithProof>七、总结
Diem 状态同步器是连接共识、网络、执行与存储的关键枢纽:它让落后的验证器能够追赶共识进度(Validator sync)、让全节点能够持续跟踪网络最新状态(Full Node sync)、让新启动的节点能够按 waypoint 完成初始化(Waypoint sync)。三套协议的骨架高度一致——请求方发送携带目标类型的GetChunkRequest,响应方返回不跨 epoch 边界的交易 chunk 与证明,请求方验证并重新执行交易——区别主要在于目标类型(TargetLedgerInfo/HighestAvailable/Waypoint)与响应账本信息类型(VerifiableLedgerInfo/ProgressiveLedgerInfo/LedgerInfoForWaypoint)的选择,以及长轮询等配套机制。
若需继续深入,建议按以下路径阅读仓库源码:
- 协议消息定义:chunk_request.rs、chunk_response.rs;
- 同步主流程:coordinator.rs、request_manager.rs;
- 网络封装:network.rs;
- 执行接口:executor-types/src/lib.rs、executor/src/lib.rs;
- 配置项:state_sync_config.rs。
此外,仓库中state-sync/state-sync-v1/tests目录下包含状态同步的测试用例,可用于观察各协议在具体场景下的预期行为。
【免费下载链接】diemDiem’s mission is to build a trusted and innovative financial network that empowers people and businesses around the world.项目地址: https://gitcode.com/gh_mirrors/di/diem
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考