referrerpolicy=no-referrer-when-downgrade

cumulus_primitives_core/
parachain_block_data.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//! Provides [`ParachainBlockData`] and its historical versions.
18
19use crate::SchedulingProof;
20use alloc::vec::Vec;
21use codec::{Decode, Encode};
22use sp_runtime::traits::Block as BlockT;
23use sp_trie::CompactProof;
24
25/// Special prefix used by [`ParachainBlockData`] from version 1 and upwards to distinguish from the
26/// unversioned legacy/v0 version.
27const VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX: &[u8] = b"VERSIONEDPBD";
28
29// Struct which allows prepending bytes after reading from an input.
30pub(crate) struct PrependBytesInput<'a, I> {
31	prepend: &'a [u8],
32	read: usize,
33	inner: &'a mut I,
34}
35
36impl<'a, I: codec::Input> codec::Input for PrependBytesInput<'a, I> {
37	fn remaining_len(&mut self) -> Result<Option<usize>, codec::Error> {
38		let remaining_compact = self.prepend.len().saturating_sub(self.read);
39		Ok(self.inner.remaining_len()?.map(|len| len.saturating_add(remaining_compact)))
40	}
41
42	fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> {
43		if into.is_empty() {
44			return Ok(());
45		}
46
47		let remaining_compact = self.prepend.len().saturating_sub(self.read);
48		if remaining_compact > 0 {
49			let to_read = into.len().min(remaining_compact);
50			into[..to_read].copy_from_slice(&self.prepend[self.read..][..to_read]);
51			self.read += to_read;
52
53			if to_read < into.len() {
54				// Buffer not full, keep reading the inner.
55				self.inner.read(&mut into[to_read..])
56			} else {
57				// Buffer was filled by the bytes.
58				Ok(())
59			}
60		} else {
61			// Prepended bytes has been read, just read from inner.
62			self.inner.read(into)
63		}
64	}
65}
66
67/// The parachain block that is created by a collator.
68///
69/// This is send as PoV (proof of validity block) to the relay-chain validators. There it will be
70/// passed to the parachain validation Wasm blob to be validated.
71#[derive(Clone)]
72pub enum ParachainBlockData<Block> {
73	V0 {
74		block: [Block; 1],
75		proof: CompactProof,
76	},
77	V1 {
78		blocks: Vec<Block>,
79		proof: CompactProof,
80	},
81	/// V2 adds scheduling proof for V3 candidates.
82	V2 {
83		blocks: Vec<Block>,
84		proof: CompactProof,
85		scheduling_proof: SchedulingProof,
86	},
87}
88
89impl<Block: Encode> Encode for ParachainBlockData<Block> {
90	fn encode(&self) -> Vec<u8> {
91		match self {
92			Self::V0 { block, proof } => (&block[0], &proof).encode(),
93			Self::V1 { blocks, proof } => {
94				let mut res = VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX.to_vec();
95				1u8.encode_to(&mut res);
96				blocks.encode_to(&mut res);
97				proof.encode_to(&mut res);
98				res
99			},
100			Self::V2 { blocks, proof, scheduling_proof } => {
101				let mut res = VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX.to_vec();
102				2u8.encode_to(&mut res);
103				blocks.encode_to(&mut res);
104				proof.encode_to(&mut res);
105				scheduling_proof.encode_to(&mut res);
106				res
107			},
108		}
109	}
110}
111
112impl<Block: Decode> Decode for ParachainBlockData<Block> {
113	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
114		let mut prefix = [0u8; VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX.len()];
115		input.read(&mut prefix)?;
116
117		if prefix == VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX {
118			match input.read_byte()? {
119				1 => {
120					let blocks = Vec::<Block>::decode(input)?;
121					let proof = CompactProof::decode(input)?;
122
123					Ok(Self::V1 { blocks, proof })
124				},
125				2 => {
126					let blocks = Vec::<Block>::decode(input)?;
127					let proof = CompactProof::decode(input)?;
128					let scheduling_proof = crate::SchedulingProof::decode(input)?;
129
130					Ok(Self::V2 { blocks, proof, scheduling_proof })
131				},
132				_ => Err("Unknown `ParachainBlockData` version".into()),
133			}
134		} else {
135			let mut input = PrependBytesInput { prepend: &prefix, read: 0, inner: input };
136			let block = Block::decode(&mut input)?;
137			let proof = CompactProof::decode(&mut input)?;
138
139			Ok(Self::V0 { block: [block], proof })
140		}
141	}
142}
143
144impl<Block> ParachainBlockData<Block> {
145	/// Creates a new instance of `Self`.
146	pub fn new(
147		blocks: Vec<Block>,
148		proof: CompactProof,
149		scheduling_proof: Option<SchedulingProof>,
150	) -> Self {
151		match scheduling_proof {
152			Some(sp) => Self::V2 { blocks, proof, scheduling_proof: sp },
153			None => Self::V1 { blocks, proof },
154		}
155	}
156
157	/// Returns references to the stored blocks.
158	pub fn blocks(&self) -> &[Block] {
159		match self {
160			Self::V0 { block, .. } => &block[..],
161			Self::V1 { blocks, .. } => &blocks,
162			Self::V2 { blocks, .. } => &blocks,
163		}
164	}
165
166	/// Returns mutable references to the stored blocks.
167	pub fn blocks_mut(&mut self) -> &mut [Block] {
168		match self {
169			Self::V0 { ref mut block, .. } => block,
170			Self::V1 { ref mut blocks, .. } => blocks,
171			Self::V2 { ref mut blocks, .. } => blocks,
172		}
173	}
174
175	/// Returns the stored blocks.
176	pub fn into_blocks(self) -> Vec<Block> {
177		match self {
178			Self::V0 { block, .. } => block.into_iter().collect(),
179			Self::V1 { blocks, .. } => blocks,
180			Self::V2 { blocks, .. } => blocks,
181		}
182	}
183
184	/// Returns a reference to the stored proof.
185	pub fn proof(&self) -> &CompactProof {
186		match self {
187			Self::V0 { proof, .. } => &proof,
188			Self::V1 { proof, .. } => proof,
189			Self::V2 { proof, .. } => proof,
190		}
191	}
192
193	/// Deconstruct into the inner parts.
194	pub fn into_inner(self) -> (Vec<Block>, CompactProof) {
195		match self {
196			Self::V0 { block, proof } => (block.into_iter().collect(), proof),
197			Self::V1 { blocks, proof } => (blocks, proof),
198			Self::V2 { blocks, proof, .. } => (blocks, proof),
199		}
200	}
201
202	/// Returns the scheduling proof if this is a V2 POV.
203	pub fn scheduling_proof(&self) -> Option<&crate::SchedulingProof> {
204		match self {
205			Self::V2 { scheduling_proof, .. } => Some(scheduling_proof),
206			_ => None,
207		}
208	}
209}
210
211impl<Block: BlockT> ParachainBlockData<Block> {
212	/// Log the size of the individual components (header, extrinsics, storage proof) as info.
213	pub fn log_size_info(&self) {
214		tracing::info!(
215			target: "cumulus",
216			header_kb = %self.blocks().iter().map(|b| b.header().encoded_size()).sum::<usize>() as f64 / 1024f64,
217			extrinsics_kb = %self.blocks().iter().map(|b| b.extrinsics().encoded_size()).sum::<usize>() as f64 / 1024f64,
218			storage_proof_kb = %self.proof().encoded_size() as f64 / 1024f64,
219			"PoV size",
220		);
221	}
222
223	/// Converts into [`ParachainBlockData::V0`].
224	///
225	/// Returns `None` if there is not exactly one block.
226	pub fn as_v0(&self) -> Option<Self> {
227		match self {
228			Self::V0 { .. } => Some(self.clone()),
229			Self::V1 { blocks, proof } => {
230				if blocks.len() != 1 {
231					return None;
232				}
233
234				blocks
235					.first()
236					.map(|block| Self::V0 { block: [block.clone()], proof: proof.clone() })
237			},
238			Self::V2 { blocks, proof, .. } => {
239				if blocks.len() != 1 {
240					return None;
241				}
242
243				blocks
244					.first()
245					.map(|block| Self::V0 { block: [block.clone()], proof: proof.clone() })
246			},
247		}
248	}
249}
250
251#[cfg(test)]
252mod tests {
253	use super::*;
254	use sp_runtime::testing::*;
255
256	#[derive(codec::Encode, codec::Decode, Clone, PartialEq, Debug)]
257	struct ParachainBlockDataV0<B: BlockT> {
258		/// The header of the parachain block.
259		pub header: B::Header,
260		/// The extrinsics of the parachain block.
261		pub extrinsics: alloc::vec::Vec<B::Extrinsic>,
262		/// The data that is required to emulate the storage accesses executed by all extrinsics.
263		pub storage_proof: sp_trie::CompactProof,
264	}
265
266	type TestExtrinsic = TestXt<MockCallU64, ()>;
267	type TestBlock = Block<TestExtrinsic>;
268
269	#[test]
270	fn decoding_encoding_v0_works() {
271		let v0 = ParachainBlockDataV0::<TestBlock> {
272			header: Header::new_from_number(10),
273			extrinsics: vec![
274				TestExtrinsic::new_bare(MockCallU64(10)),
275				TestExtrinsic::new_bare(MockCallU64(100)),
276			],
277			storage_proof: CompactProof { encoded_nodes: vec![vec![10u8; 200], vec![20u8; 30]] },
278		};
279
280		let encoded = v0.encode();
281		let decoded = ParachainBlockData::<TestBlock>::decode(&mut &encoded[..]).unwrap();
282
283		match &decoded {
284			ParachainBlockData::V0 { block, proof } => {
285				assert_eq!(v0.header, block[0].header);
286				assert_eq!(v0.extrinsics, block[0].extrinsics);
287				assert_eq!(&v0.storage_proof, proof);
288			},
289			_ => panic!("Invalid decoding"),
290		}
291
292		let encoded = decoded.as_v0().unwrap().encode();
293
294		let decoded = ParachainBlockDataV0::<TestBlock>::decode(&mut &encoded[..]).unwrap();
295		assert_eq!(decoded, v0);
296	}
297
298	#[test]
299	fn decoding_encoding_v1_works() {
300		let v1 = ParachainBlockData::<TestBlock>::V1 {
301			blocks: vec![TestBlock::new(
302				Header::new_from_number(10),
303				vec![
304					TestExtrinsic::new_bare(MockCallU64(10)),
305					TestExtrinsic::new_bare(MockCallU64(100)),
306				],
307			)],
308			proof: CompactProof { encoded_nodes: vec![vec![10u8; 200], vec![20u8; 30]] },
309		};
310
311		let encoded = v1.encode();
312		let decoded = ParachainBlockData::<TestBlock>::decode(&mut &encoded[..]).unwrap();
313
314		assert_eq!(v1.blocks(), decoded.blocks());
315		assert_eq!(v1.proof(), decoded.proof());
316	}
317
318	// Helper to create a relay chain header for tests
319	fn make_relay_header(number: u32) -> polkadot_primitives::Header {
320		use sp_runtime::traits::Header as _;
321		polkadot_primitives::Header::new(
322			number,
323			polkadot_core_primitives::Hash::repeat_byte(1),
324			polkadot_core_primitives::Hash::repeat_byte(2),
325			polkadot_core_primitives::Hash::repeat_byte(3),
326			Default::default(),
327		)
328	}
329
330	#[test]
331	fn decoding_encoding_v2_works() {
332		let scheduling_proof = crate::SchedulingProof {
333			header_chain: vec![make_relay_header(5)],
334			internal_scheduling_parent_header: make_relay_header(4),
335			signed_scheduling_info: None,
336		};
337
338		let v2 = ParachainBlockData::<TestBlock>::V2 {
339			blocks: vec![TestBlock::new(
340				Header::new_from_number(10),
341				vec![
342					TestExtrinsic::new_bare(MockCallU64(10)),
343					TestExtrinsic::new_bare(MockCallU64(100)),
344				],
345			)],
346			proof: CompactProof { encoded_nodes: vec![vec![10u8; 200], vec![20u8; 30]] },
347			scheduling_proof: scheduling_proof.clone(),
348		};
349
350		let encoded = v2.encode();
351		let decoded = ParachainBlockData::<TestBlock>::decode(&mut &encoded[..]).unwrap();
352
353		assert_eq!(v2.blocks(), decoded.blocks());
354		assert_eq!(v2.proof(), decoded.proof());
355		assert_eq!(v2.scheduling_proof(), decoded.scheduling_proof());
356
357		// Verify scheduling_proof accessor works
358		assert!(decoded.scheduling_proof().is_some());
359		assert_eq!(decoded.scheduling_proof().unwrap().header_chain.len(), 1);
360	}
361
362	#[test]
363	fn v2_scheduling_proof_accessor_returns_none_for_v0_v1() {
364		// V0 should return None for scheduling_proof
365		let v0 = ParachainBlockData::<TestBlock>::V0 {
366			block: [TestBlock::new(Header::new_from_number(1), vec![])],
367			proof: CompactProof { encoded_nodes: vec![] },
368		};
369		assert!(v0.scheduling_proof().is_none());
370
371		// V1 should return None for scheduling_proof
372		let v1 = ParachainBlockData::<TestBlock>::V1 {
373			blocks: vec![TestBlock::new(Header::new_from_number(1), vec![])],
374			proof: CompactProof { encoded_nodes: vec![] },
375		};
376		assert!(v1.scheduling_proof().is_none());
377	}
378
379	#[test]
380	fn v2_into_inner_drops_scheduling_proof() {
381		let scheduling_proof = crate::SchedulingProof {
382			header_chain: vec![make_relay_header(5)],
383			internal_scheduling_parent_header: make_relay_header(4),
384			signed_scheduling_info: None,
385		};
386
387		let v2 = ParachainBlockData::<TestBlock>::V2 {
388			blocks: vec![TestBlock::new(Header::new_from_number(10), vec![])],
389			proof: CompactProof { encoded_nodes: vec![vec![1u8; 10]] },
390			scheduling_proof,
391		};
392
393		let (blocks, proof) = v2.into_inner();
394		assert_eq!(blocks.len(), 1);
395		assert_eq!(proof.encoded_nodes.len(), 1);
396	}
397
398	#[test]
399	fn v2_as_v0_works_with_single_block() {
400		let scheduling_proof = crate::SchedulingProof {
401			header_chain: vec![make_relay_header(5)],
402			internal_scheduling_parent_header: make_relay_header(4),
403			signed_scheduling_info: None,
404		};
405
406		// V2 with single block can be converted to V0
407		let v2_single = ParachainBlockData::<TestBlock>::V2 {
408			blocks: vec![TestBlock::new(Header::new_from_number(10), vec![])],
409			proof: CompactProof { encoded_nodes: vec![] },
410			scheduling_proof: scheduling_proof.clone(),
411		};
412		assert!(v2_single.as_v0().is_some());
413
414		// V2 with multiple blocks cannot be converted to V0
415		let v2_multi = ParachainBlockData::<TestBlock>::V2 {
416			blocks: vec![
417				TestBlock::new(Header::new_from_number(10), vec![]),
418				TestBlock::new(Header::new_from_number(11), vec![]),
419			],
420			proof: CompactProof { encoded_nodes: vec![] },
421			scheduling_proof,
422		};
423		assert!(v2_multi.as_v0().is_none());
424	}
425}