ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DiemNet 消息协议(Messaging Protocol v1)深度解析:网络消息类型、RPC/DirectSend 语义与 8 MiB 帧定界

DiemNet 消息协议(Messaging Protocol v1)深度解析:网络消息类型、RPC/DirectSend 语义与 8 MiB 帧定界 区块链金融科技【免费下载链接】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点击查看免费下载DiemNet 是 Diem 生态中任意两个节点之间通信的主网络协议而消息协议Messaging Protocol v1定义了连接建立后所有应用数据在网线上的字节形态与交互语义NetworkMessage消息类型、ProtocolId应用协议标识、RPC 与 DirectSend 两种消息模式、优先级字段、错误码以及 4 字节长度前缀 8 MiB 上限的帧定界规则。本文以 messaging-v1.md 规格文档为骨架对照仓库中 wire/messaging/v1/mod.rs 的真实实现与测试用例逐层拆解这套协议的版本协商、消息编码、发送接收链路与边界约束帮助读者既掌握协议本身的实战细节也能在源码中准确找到对应落点。DiemNet 与消息协议的整体定位在 DiemNet README 中DiemNet 被定义为主要承载 Diem 生态内任意两个节点间通信的网络协议它只描述网线上消息的结构与顺序实际投递依赖底层 TCP 传输并且所有通信必须经过 Noise 协议 的加密与认证。DiemNet 支持在单条连接上多路复用多个应用协议并为每个应用协议提供两种消息语义DirectSend单向、fire-and-forget发出即忘的消息投递RPC一元unary请求-响应调用。这两者正是本文主角NetworkMessage枚举所承载的两类核心消息。一条 DiemNet 连接从建立到可收发消息需要依次经过TCP 握手 → Noise IK 安全握手 → 版本握手handshake-v1.md→ 消息协议。也就是说消息协议是连接建立与升级完成后、节点间交换共识、mempool、状态同步等业务数据的最终载体。从仓库源码看消息协议的完整落点在 network/src/protocols/wire/messaging/v1/mod.rs其模块注释明确写道该模块定义了 DiemNet v1 消息类型、序列化/反序列化方式并为在一个抽象 IO 对象通常是一个 socket上发送NetworkMessage提供了Sink与Stream实现且直接引用了本文所依据的这份规格文档。版本化机制MessagingProtocolVersion 与握手协商消息协议通过MessagingProtocolVersion进行版本化该版本在连接建立与升级过程中由 DiemNet 握手协议 协商确定。/// Enum representing different versions of the Diem network protocol. These /// should be listed from old to new, old having the smallest value. We derive /// [PartialOrd] since nodes need to find highest intersecting protocol version. pub enum MessagingProtocolVersion { V1 0, }从源码注释可以确认两个关键设计点版本枚举从旧到新排列旧版本值最小V1 0并且派生PartialOrd因为节点之间需要找出“双方共同支持的最高版本”。协商逻辑位于HandshakeMsg::perform_handshakenetwork/src/protocols/wire/handshake/v1/mod.rs#L229-L276先校验双方chain_id与network_id一致再对supported_protocols映射按版本从大到小迭代寻找两个节点都能支持的最高MessagingProtocolVersion找不到交集则返回HandshakeError::NoCommonProtocols。握手消息本身HandshakeMsg是“从MessagingProtocolVersion到SupportedProtocols的映射”其中SupportedProtocols是一个 bit-vector第i位为 1 当且仅当该节点支持第i个ProtocolId变体。握手完成后双方必须只使用接收方声明支持的ProtocolId否则接收方可以返回ErrorCode::NotSupported错误消息详见下文“错误码”一节。NetworkMessage最原始的网线消息类型规格文档开篇给出了消息协议的核心数据结构——NetworkMessage枚举所有 DiemNet 端点都必须能够处理接收到的全部这些消息/// Most primitive message type set on the network. Note this can only support up to 127 message /// types. The first byte in any message is the message type itself starting from 0. enum NetworkMessage { Error(ErrorCode), RpcRequest(RpcRequest), RpcResponse(RpcResponse), DirectSendMsg(DirectSendMsg), }对应的真实实现位于 network/src/protocols/wire/messaging/v1/mod.rs#L38-L46与规格文档完全一致并派生Clone, Debug, PartialEq, Eq, Deserialize, Serialize以支持 BCS 序列化。这里有两个值得注意的底层约束最多 127 种消息类型NetworkMessage枚举变体本身编码在消息的第一个字节中从 0 开始编号由于 Rust 枚举判别式在 BCS 中通常占用一个字节理论上限被限制在 127 个变体以内。线编码为 BCS规格文档明确所有 DiemNet 消息在网线上使用 [bcs] 编码。仓库测试 test.rs 中的network_message_canonical_serialization用 proptest 断言任意NetworkMessage的规范化编码-解码往返一致保证编码的确定性。消息在网线上的字节布局示例测试文件 libranet_wire_test_vectors 给出了一个非常直观的线上字节示例发送一个DirectSendMsgprotocol_id MempoolDirectSend、priority 0、raw_msg hello world实际产生的完整字节流为[0, 0, 0, 15] // 帧长度前缀4 字节大端 u32 15 [3] // NetworkMessage 类型DirectSendMsg 的判别值 [2] // protocol_idMempoolDirectSend [0] // priority [11] // raw_msg 长度 [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] // hello world 的 ASCII 字节这个测试同时验证了NetworkMessageStream能正确反序列化这些字节、NetworkMessageSink能把这些消息序列化成完全相同的字节是理解整个帧定界与编码规则的绝佳起点。ProtocolId应用协议标识符每个应用协议都有一个唯一的ProtocolId标识。规格文档给出的枚举包含 8 个变体ConsensusRpc、ConsensusDirectSend、MempoolDirectSend、StateSyncDirectSend、DiscoveryDirectSend、HealthCheckerRpc、IdentityDirectSend、OnchainDiscoveryRpc。仓库当前版本的实际实现network/src/protocols/wire/handshake/v1/mod.rs#L36-L45略有演进#[repr(u8)] pub enum ProtocolId { ConsensusRpc 0, ConsensusDirectSend 1, MempoolDirectSend 2, StateSyncDirectSend 3, DiscoveryDirectSend 4, HealthCheckerRpc 5, // json provides flexibility for backwards compatible upgrade ConsensusDirectSendJSON 6, }与规格文档相比仓库实现将IdentityDirectSend与OnchainDiscoveryRpc移除/替换新增了ConsensusDirectSendJSON 6并在注释中说明这是为了向后兼容升级而提供的 JSON 序列化变体。从ProtocolId::to_bytes/from_bytesnetwork/src/protocols/wire/handshake/v1/mod.rs#L73-L89可以看到除ConsensusDirectSendJSON使用serde_json外其余协议统一使用 BCS 序列化。由于#[repr(u8)]ProtocolId在 BCS 编码中恰好占用 1 字节测试protocol_id_serializationtest.rs#L12-L17专门断言了ProtocolId::ConsensusRpc编码为单个字节0x00。ProtocolId的值同时也被用于握手阶段SupportedProtocolsbit-vector 的位索引因此协议 ID 的数值一旦分配就应保持稳定。错误码ParsingError 与 NotSupportedErrorCode枚举定义了两类协议级错误enum ErrorCode { /// Failed to parse NetworkMessage, the entries are the first two bytes of the message: /// NetworkMessage type and possibly the ProtocolId ParsingError(u8, u8), /// A message was received for a message / protocol that is not supported over this connection: /// The NetworkMessage type is encoded as a u8. NotSupported(u8, ProtocolId), }仓库实现mod.rs#L48-L79对这两个变体做了结构化细化语义保持一致ParsingError(ParsingErrorType { message: u8, protocol: u8 })无法解析对方消息的头信息携带尽可能多的头信息NetworkMessage 类型字节与可能的 ProtocolId 字节以便对方诊断NotSupported(NotSupportedType::RpcRequest(ProtocolId) | NotSupportedType::DirectSendMsg(ProtocolId))消息可以被解析出头信息但当前连接上不支持该协议例如收到了未在握手中向对方通告的ProtocolId。规格文档给出了一个典型场景如果收到了针对某个未向对方通告的ProtocolId的RpcRequest则发送错误消息ErrorCode::NotSupported(1, ProtocolId)其中1是RpcRequest在NetworkMessage枚举中的索引。测试error_codetest.rs#L19-L27验证了ParsingError编码为[0, 9, 5]错误码类型 message protocol 两个字节。RPC 协议请求-响应语义RPC 协议的流程非常直接请求方向响应方发送NetworkMessage::RpcRequest携带一个request_id响应方发送NetworkMessage::RpcResponserequest_id原样复制自对应的请求。struct RpcRequest { protocol_id: ProtocolId, // 应用协议标识符 request_id: RequestId, // RequestId u32 priority: Priority, // 0..255 raw_request: Vecu8, // 请求负载由应用层 handler 解析 } struct RpcResponse { request_id: RequestId, // 与请求中的 request_id 完全一致 priority: Priority, raw_response: Vecu8, // 响应负载 }两个要点protocol_id只出现在请求中响应对象不包含该字段——request_id是唯一将响应关联回请求的纽带任何应用层处理错误都应包装在RpcResponse消息内部即“应用错误不占用协议级错误码”ErrorCode仅用于传输/解析层的错误。RpcRequest的 BCS 编码顺序由测试 rpc_request 完整展示protocol_id1 字节→request_id4 字节小端 u32例如 25 编码为[25, 0, 0, 0]→priority1 字节→raw_request长度4 字节→raw_request字节。这个字节序与RequestId u32的类型别名、Priority u8的类型别名mod.rs#L81-L85一一对应。DirectSend 协议单向即发即忘DirectSend 提供单向、fire-and-forget 式的消息投递struct DirectSendMsg { protocol_id: ProtocolId, // 应用协议标识符 priority: Priority, // 0..255 raw_msg: Vecu8, // 消息负载 }发送方将消息负载装入NetworkMessage::DirectSendMsg发出接收方根据protocol_id将负载交给对应的应用层 handler协议本身不保证送达、不提供确认与重传。这也是共识Consensus、mempool、状态同步等模块中广播类业务所依赖的基础通道。消息优先级best-effort 的调度提示RpcRequest、RpcResponse与DirectSendMsg都带有priority字段类型Priority u8取值范围0..255其语义是优先级是一个best-effort信号数值越高表示越紧急发送端与接收端都可以据此进行消息调度对于 RPC接收方可以尊重请求的优先级并为出站响应附加相同的优先级值待处理的入站/出站消息 MAY 根据优先级被重排或丢弃MAY是规格中的可选能力不是强制要求。规格文档特别指出虽然协议允许按优先级重排或丢弃消息但DiemNet 参考实现目前并不执行prioritythe DiemNet reference implementation does not currently respectpriority。这一点在源码中同样可以看到——messaging/v1/mod.rs 中的NetworkMessageStream/NetworkMessageSink只是按序读取、序列化并发送消息帧并没有针对priority做任何排队或抢占逻辑优先级字段目前仅作为协议预留的、面向未来的调度扩展点。错误处理与流控关于协议级错误与背压规格文档明确了三条规则不强制响应错误收到错误消息的一方不要求必须回复最小触发长度一条消息至少要有 2 字节长度才会触发错误响应否则错误信息本身会因数据不足而失去意义例如ParsingError至少要能带上消息类型字节无内置流控DiemNet 不定义任何背压/流控back-pressure / flow-control机制或策略每个端点可以自由实现本地策略来防御“话痨邻居”chatty neighbors例如通过不发放 TCP window update 来限制对方的发送速率。最后一点在实际实现中有一个有趣的对偶虽然协议层不做流控但 NetworkMessageStream/Sink 在构造时会包一层AsyncRateLimiter来自diem_rate_limiter接收OptionSharedBucket参数——也就是说速率限制rate limiting作为一个可选能力被注入到消息收发路径中而不是协议本身的强制机制这与规格“每个端-point 自行决定本地策略”的表述一致。帧定界u32 长度前缀 BCS 消息每条序列化后的 DiemNet 消息以4 字节大端big-endianu32长度前缀进行帧定界然后这些消息帧被送入 Noise 加密 socketNoise 层有自己内部的成帧、加密与解密。因此单个消息帧可能横跨多个 Noise 帧单个 Noise 帧也可能包含多个消息帧。忽略底层加密与成帧后网线上序列化的NetworkMsg序列看起来就是“长度前缀 消息字节”的重复[u32-length-prefix] || [serialized-message-bytes] || ..源码中的对应实现是network_message_frame_codecmod.rs#L148-L154它基于tokio_util::codec::LengthDelimitedCodec构建pub fn network_message_frame_codec(max_frame_size: usize) - LengthDelimitedCodec { LengthDelimitedCodec::builder() .max_frame_length(max_frame_size) .length_field_length(4) .big_endian() .new_codec() }length_field_length(4)与.big_endian()正是规格文档中“big-endian encoded u32 (4-bytes) length prefix”的代码实现。关于长度前缀与噪声层的协作DiemNet README 还补充了一个重要细节Noise 层将单帧大小限制在至多65535字节其中16字节始终保留给 AES-GCM 认证标签因此发送一条序列化NetworkMsg含 4 字节长度前缀时需要先将其切成不超过65535 - 16 65519字节的块逐块加密后再发送。最大帧大小8 MiB 的硬性边界规格文档对帧大小给出了强制约束每条serialized-message-bytes必须小于或等于 8 MiB8388608 字节。注意该长度不包含u32长度前缀。DiemNet 服务器必须拒绝超过 8 MiB 上限的入站消息DiemNet 客户端不得发送超过 8 MiB 上限的出站消息。源码中该常量定义在 network/src/constants.rs#L20pub const MAX_FRAME_SIZE: usize 8 * 1024 * 1024; /* 8 MiB */规格文档同时给出读取单条 DiemNet 消息的参考伪代码const MAX_DIEMNET_FRAME_LEN: u32 8388608; // 8 MiB // read the 4-byte length prefix first let length_prefix: u32 noise_socket.read(4).to_host_endian(); // reject messages that are too large if length_prefix MAX_DIEMNET_FRAME_LEN { reject; } // read the actual bcs-serialized message let message_bytes noise_socket.read(length_prefix); // deserialize the message let message bcs::from_bytes(message_bytes);这一约束在测试中有两处直接验证test.rssend_fails_when_larger_than_frame_limittest.rs#L90-L103用 64 字节的帧上限构造NetworkMessageSink发送 123 字节负载的消息send返回Err——即发送端拒绝超大出站消息recv_fails_when_larger_than_frame_limittest.rs#L105-L123发送端帧上限 128 字节、接收端帧上限 64 字节发送 80 字节负载的消息后接收端读取返回Err——即接收端拒绝超大入站消息。这两条测试共同印证了规格文档“客户端不得发送 / 服务器必须拒绝”的双向约束而LengthDelimitedCodec的max_frame_length正是这一上限在实际读写路径上的强制者。发送与接收的实现NetworkMessageSink 与 NetworkMessageStream在 wire/messaging/v1/mod.rs 中消息协议收发被封装为两个异步组件NetworkMessageSinkTWriteSocket实现SinkNetworkMessage负责把NetworkMessage用bcs::to_bytes序列化、交给LengthDelimitedCodec成帧后写到底层 socket。序列化失败产生WriteError::SerializeErrorIO 失败产生WriteError::IoErrormod.rs#L136-L144NetworkMessageStreamTReadSocket实现StreamItem ResultNetworkMessage, ReadError从底层 socket 读出帧后用bcs::from_bytes反序列化反序列化失败时保留帧长度与前 8 字节便于调试产生ReadError::DeserializeErrormod.rs#L126-L134。两者都接受max_frame_size与可选的速率限制桶OptionSharedBucket作为构造参数。proptest 用例network_message_socket_roundtriptest.rs#L190-L221在读写两端同时开启/关闭碎片化fragmented read/write的情况下验证Sink与Stream能够互相理解并完整保留所有NetworkMessage——这也回应了规格文档中“一个消息帧可能横跨多个 Noise 帧”的描述即使底层字节被切碎长度前缀定界依然能正确还原出完整消息。总结一份协议、一套实现、一组测试通过对照 messaging-v1.md 规格与仓库实现可以总结出消息协议 v1 的全貌消息模型NetworkMessage四种变体Error / RpcRequest / RpcResponse / DirectSendMsgBCS 编码首字节为消息类型上限 127 种协议标识ProtocolId以#[repr(u8)]单字节编码在握手中通过SupportedProtocolsbit-vector 通告消息协议版本由HandshakeMsg::perform_handshake协商出最高交集两种语义RPC 靠request_id关联请求与响应DirectSend 即发即忘protocol_id决定负载去向质量信号priority0..255是 best-effort 调度提示参考实现暂未执行错误处理ParsingError/NotSupported覆盖解析与协议不支持两类场景应用错误一律封装在RpcResponse内错误响应非强制消息长度至少 2 字节才触发错误帧定界4 字节大端u32长度前缀 BCS 消息Noise 层分包上限 65519 字节消息帧上限 8 MiBMAX_FRAME_SIZE发送与接收两端分别由NetworkMessageSink/NetworkMessageStream强制。无论读者是要实现一个兼容的 DiemNet 对端、排查线上消息解析问题还是为应用接入某个ProtocolId通道都可以以本文的协议语义为纲、以 wire/messaging/v1/mod.rs 与 test.rs 为实现的参照与验证基准。赞分享区块链金融科技【免费下载链接】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点击查看免费下载相关推荐TorchSharp神经网络模块实战搭建ResNet与MobileNet模型TorchSharp神经网络模块实战搭建ResNet与MobileNet模型 TorchSharp是一个强大的.NET库它提供了对PyTorch核心功能的访人工智能深度学习机器学习Temporal 消息协议Message Protocol深度解析Workflow Update 的可插拔消息机制Temporal 消息协议Message Protocol深度解析Workflow Update 的可插拔消息机制 导读 本文围绕 Temporal 服务后端工作流自动化任务调度Celery 消息协议深度解析Task 消息 V1/V2 与 Event 事件的传输规范Celery 消息协议深度解析Task 消息 V1/V2 与 Event 事件的传输规范 本文基于 Celery 官方内部协议文档 docs/interna任务调度后端消息队列创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表