cumulus_primitives_spec_messaging/flow_control.rs
1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Channel flow-control types: the sender's channel-stream leaf payload
18//! ([`SpecMsgKind`] / [`SpecMsgSignal`]) and the receiver's out-of-band confirmation [`Register`]
19//! with its advisory [`WindowGrant`]. The pallet owns the logic: windowing, version enforcement,
20//! channel storage, the accept extrinsic.
21//!
22//! # Consensus surface
23//!
24//! - [`SpecMsgKind`] is the SCALE payload of every channel-stream MMR leaf, inside
25//! [`crate::message::leaf_hash`]'s `LEAF_TAG ++ LEAF_VERSION ++ payload` framing. It can evolve
26//! without a `LEAF_VERSION` bump.
27//! - [`Register`] is the SCALE payload of every ack-stream leaf: the receiver's whole channel
28//! state, lossy and latest-wins, read out-of-band by the sender.
29//!
30//! # Frozen core
31//!
32//! These must parse at every protocol version: `OpenChannel` (variant 0, so it decodes before any
33//! version is announced), `CloseChannel`, `Upgrade`, and the `Register` format. The tests pin their
34//! bytes. Versioning is by monotonic per-side announcements (sender in-band, receiver via
35//! [`Register::version`]); the effective version is the lower of the two.
36
37use alloc::vec::Vec;
38use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
39use scale_info::TypeInfo;
40
41use crate::mmr::MessagePosition;
42
43/// The payload of every channel-stream MMR leaf: protocol signalling or userspace data. The
44/// transport acts only on `Signal` vs `Data` (window accounting, pallet-internal consumption);
45/// demultiplexing within `Data` is an upper-layer convention.
46#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo)]
47pub enum SpecMsgKind {
48 /// Channel lifecycle signalling. Emitted and consumed by the pallet, never by applications;
49 /// window-counted and ordered like any other leaf.
50 Signal(SpecMsgSignal),
51 /// Userspace payload bytes, delivered in order. The sender STF bounds the encoded leaf, not
52 /// these bytes, by [`crate::message::MAX_SPECULATIVE_MESSAGE_LEN`]. The variant byte and the
53 /// compact length take up to 5 of it, so `data` holds 5 bytes less.
54 Data(Vec<u8>),
55}
56
57/// Sender-side channel lifecycle signal. Frozen core: `OpenChannel` must stay variant 0, since it
58/// parses before any version announcement exists. Only variants beyond these three could ever be
59/// version-gated.
60#[derive(
61 Clone,
62 Copy,
63 Encode,
64 Decode,
65 DecodeWithMemTracking,
66 MaxEncodedLen,
67 PartialEq,
68 Eq,
69 Debug,
70 TypeInfo,
71)]
72pub enum SpecMsgSignal {
73 /// Open the channel and announce the sender's initial protocol version.
74 OpenChannel { version: u8 },
75 /// Sender-side half-close.
76 CloseChannel,
77 /// Raise the sender's version announcement mid-channel (announcements never decrease).
78 Upgrade { version: u8 },
79}
80
81/// Advisory send-window credit a receiver grants a sender beyond the confirmed watermark. Not
82/// enforced: registers are lossy and read late, and on an ordered stream the receiver cannot reject
83/// without stalling. The hard bound is the sender STF's message-size constant. A grant may shrink
84/// between publishes.
85#[derive(
86 Clone,
87 Copy,
88 Encode,
89 Decode,
90 DecodeWithMemTracking,
91 MaxEncodedLen,
92 PartialEq,
93 Eq,
94 Debug,
95 Default,
96 TypeInfo,
97)]
98pub struct WindowGrant {
99 /// Max messages the sender may have in flight beyond the watermark.
100 pub max_messages: u32,
101 /// Max cumulative bytes in flight beyond the watermark.
102 pub max_bytes: u64,
103 /// Max size of one encoded [`SpecMsgKind`] leaf the sender may send.
104 pub max_message_size: u32,
105}
106
107/// The receiver's whole channel state as the sender sees it. Frozen core. Published as the
108/// ack-stream's lossy, latest-wins leaf payload and read out-of-band by the sender via an inclusion
109/// proof. The first publish is the channel acceptance.
110#[derive(
111 Clone,
112 Copy,
113 Encode,
114 Decode,
115 DecodeWithMemTracking,
116 MaxEncodedLen,
117 PartialEq,
118 Eq,
119 Debug,
120 Default,
121 TypeInfo,
122)]
123pub struct Register {
124 /// The receiver's monotonic protocol-version announcement.
125 pub version: u8,
126 /// Cumulative confirmation watermark: consumed up to this position. Monotonic and idempotent,
127 /// so lost or delayed confirmations are harmless; the latest supersedes.
128 pub up_to: MessagePosition,
129 /// The advisory send window beyond `up_to`.
130 pub grant: WindowGrant,
131 /// Receiver-side close / rejection.
132 pub closed: bool,
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use codec::Decode;
139
140 #[test]
141 fn signal_frozen_core_variant_indices() {
142 // Frozen core: `OpenChannel` is variant 0. Renumbering is a consensus break.
143 assert_eq!(SpecMsgSignal::OpenChannel { version: 7 }.encode(), vec![0x00, 0x07]);
144 assert_eq!(SpecMsgSignal::CloseChannel.encode(), vec![0x01]);
145 assert_eq!(SpecMsgSignal::Upgrade { version: 9 }.encode(), vec![0x02, 0x09]);
146 }
147
148 #[test]
149 fn spec_msg_kind_variant_layout() {
150 // The transport distinguishes only Signal (variant 0) vs Data (variant 1).
151 assert_eq!(
152 SpecMsgKind::Signal(SpecMsgSignal::OpenChannel { version: 1 }).encode(),
153 vec![0x00, 0x00, 0x01],
154 );
155 // Data: variant 1 ++ compact(len) ++ bytes.
156 assert_eq!(SpecMsgKind::Data(vec![0xAA]).encode(), vec![0x01, 0x04, 0xAA]);
157 }
158
159 #[test]
160 fn data_at_the_protocol_bound_takes_five_bytes_of_framing() {
161 // The bound is on the encoded leaf. At this size the compact length takes 4 bytes, so 5
162 // bytes less data fill it exactly and one more overflows it.
163 let max = crate::message::MAX_SPECULATIVE_MESSAGE_LEN as usize;
164 assert_eq!(SpecMsgKind::Data(vec![0; max - 5]).encoded_size(), max);
165 assert_eq!(SpecMsgKind::Data(vec![0; max - 4]).encoded_size(), max + 1);
166 }
167
168 #[test]
169 fn register_frozen_layout() {
170 // Frozen core: `Register` is read cross-chain. Pin its byte layout; any reorder or width
171 // change is a consensus break.
172 let r = Register {
173 version: 2,
174 up_to: MessagePosition(5),
175 grant: WindowGrant { max_messages: 10, max_bytes: 1000, max_message_size: 100 },
176 closed: true,
177 };
178 assert_eq!(
179 r.encode(),
180 vec![
181 0x02, // version: u8
182 0x05, 0, 0, 0, 0, 0, 0, 0, // up_to: u64 LE = 5
183 0x0a, 0, 0, 0, // grant.max_messages: u32 LE = 10
184 0xe8, 0x03, 0, 0, 0, 0, 0, 0, // grant.max_bytes: u64 LE = 1000
185 0x64, 0, 0, 0, // grant.max_message_size: u32 LE = 100
186 0x01, // closed: bool = true
187 ],
188 );
189 assert_eq!(Register::decode(&mut &r.encode()[..]).unwrap(), r);
190 }
191}