referrerpolicy=no-referrer-when-downgrade

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}