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 alloc::vec::Vec;
20use codec::{Decode, Encode};
21use sp_runtime::traits::Block as BlockT;
22use sp_trie::CompactProof;
23
24/// Special prefix used by [`ParachainBlockData`] from version 1 and upwards to distinguish from the
25/// unversioned legacy/v0 version.
26const VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX: &[u8] = b"VERSIONEDPBD";
27
28// Struct which allows prepending bytes after reading from an input.
29pub(crate) struct PrependBytesInput<'a, I> {
30	prepend: &'a [u8],
31	read: usize,
32	inner: &'a mut I,
33}
34
35impl<'a, I: codec::Input> codec::Input for PrependBytesInput<'a, I> {
36	fn remaining_len(&mut self) -> Result<Option<usize>, codec::Error> {
37		let remaining_compact = self.prepend.len().saturating_sub(self.read);
38		Ok(self.inner.remaining_len()?.map(|len| len.saturating_add(remaining_compact)))
39	}
40
41	fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> {
42		if into.is_empty() {
43			return Ok(());
44		}
45
46		let remaining_compact = self.prepend.len().saturating_sub(self.read);
47		if remaining_compact > 0 {
48			let to_read = into.len().min(remaining_compact);
49			into[..to_read].copy_from_slice(&self.prepend[self.read..][..to_read]);
50			self.read += to_read;
51
52			if to_read < into.len() {
53				// Buffer not full, keep reading the inner.
54				self.inner.read(&mut into[to_read..])
55			} else {
56				// Buffer was filled by the bytes.
57				Ok(())
58			}
59		} else {
60			// Prepended bytes has been read, just read from inner.
61			self.inner.read(into)
62		}
63	}
64}
65
66/// The parachain block that is created by a collator.
67///
68/// This is send as PoV (proof of validity block) to the relay-chain validators. There it will be
69/// passed to the parachain validation Wasm blob to be validated.
70#[derive(Clone)]
71pub enum ParachainBlockData<Block: BlockT> {
72	V0 { block: [Block; 1], proof: CompactProof },
73	V1 { blocks: Vec<Block>, proof: CompactProof },
74}
75
76impl<Block: BlockT> Encode for ParachainBlockData<Block> {
77	fn encode(&self) -> Vec<u8> {
78		match self {
79			Self::V0 { block, proof } =>
80				(block[0].header(), block[0].extrinsics(), &proof).encode(),
81			Self::V1 { blocks, proof } => {
82				let mut res = VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX.to_vec();
83				1u8.encode_to(&mut res);
84				blocks.encode_to(&mut res);
85				proof.encode_to(&mut res);
86				res
87			},
88		}
89	}
90}
91
92impl<Block: BlockT> Decode for ParachainBlockData<Block> {
93	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
94		let mut prefix = [0u8; VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX.len()];
95		input.read(&mut prefix)?;
96
97		if prefix == VERSIONED_PARACHAIN_BLOCK_DATA_PREFIX {
98			match input.read_byte()? {
99				1 => {
100					let blocks = Vec::<Block>::decode(input)?;
101					let proof = CompactProof::decode(input)?;
102
103					Ok(Self::V1 { blocks, proof })
104				},
105				_ => Err("Unknown `ParachainBlockData` version".into()),
106			}
107		} else {
108			let mut input = PrependBytesInput { prepend: &prefix, read: 0, inner: input };
109			let header = Block::Header::decode(&mut input)?;
110			let extrinsics = Vec::<Block::Extrinsic>::decode(&mut input)?;
111			let proof = CompactProof::decode(&mut input)?;
112
113			Ok(Self::V0 { block: [Block::new(header, extrinsics)], proof })
114		}
115	}
116}
117
118impl<Block: BlockT> ParachainBlockData<Block> {
119	/// Creates a new instance of `Self`.
120	pub fn new(blocks: Vec<Block>, proof: CompactProof) -> Self {
121		Self::V1 { blocks, proof }
122	}
123
124	/// Returns references to the stored blocks.
125	pub fn blocks(&self) -> &[Block] {
126		match self {
127			Self::V0 { block, .. } => &block[..],
128			Self::V1 { blocks, .. } => &blocks,
129		}
130	}
131
132	/// Returns mutable references to the stored blocks.
133	pub fn blocks_mut(&mut self) -> &mut [Block] {
134		match self {
135			Self::V0 { ref mut block, .. } => block,
136			Self::V1 { ref mut blocks, .. } => blocks,
137		}
138	}
139
140	/// Returns the stored blocks.
141	pub fn into_blocks(self) -> Vec<Block> {
142		match self {
143			Self::V0 { block, .. } => block.into_iter().collect(),
144			Self::V1 { blocks, .. } => blocks,
145		}
146	}
147
148	/// Returns a reference to the stored proof.
149	pub fn proof(&self) -> &CompactProof {
150		match self {
151			Self::V0 { proof, .. } => &proof,
152			Self::V1 { proof, .. } => proof,
153		}
154	}
155
156	/// Deconstruct into the inner parts.
157	pub fn into_inner(self) -> (Vec<Block>, CompactProof) {
158		match self {
159			Self::V0 { block, proof } => (block.into_iter().collect(), proof),
160			Self::V1 { blocks, proof } => (blocks, proof),
161		}
162	}
163
164	/// Log the size of the individual components (header, extrinsics, storage proof) as info.
165	pub fn log_size_info(&self) {
166		tracing::info!(
167			target: "cumulus",
168			header_kb = %self.blocks().iter().map(|b| b.header().encoded_size()).sum::<usize>() as f64 / 1024f64,
169			extrinsics_kb = %self.blocks().iter().map(|b| b.extrinsics().encoded_size()).sum::<usize>() as f64 / 1024f64,
170			storage_proof_kb = %self.proof().encoded_size() as f64 / 1024f64,
171			"PoV size",
172		);
173	}
174
175	/// Converts into [`ParachainBlockData::V0`].
176	///
177	/// Returns `None` if there is not exactly one block.
178	pub fn as_v0(&self) -> Option<Self> {
179		match self {
180			Self::V0 { .. } => Some(self.clone()),
181			Self::V1 { blocks, proof } => {
182				if blocks.len() != 1 {
183					return None
184				}
185
186				blocks
187					.first()
188					.map(|block| Self::V0 { block: [block.clone()], proof: proof.clone() })
189			},
190		}
191	}
192}
193
194#[cfg(test)]
195mod tests {
196	use super::*;
197	use sp_runtime::testing::*;
198
199	#[derive(codec::Encode, codec::Decode, Clone, PartialEq, Debug)]
200	struct ParachainBlockDataV0<B: BlockT> {
201		/// The header of the parachain block.
202		pub header: B::Header,
203		/// The extrinsics of the parachain block.
204		pub extrinsics: alloc::vec::Vec<B::Extrinsic>,
205		/// The data that is required to emulate the storage accesses executed by all extrinsics.
206		pub storage_proof: sp_trie::CompactProof,
207	}
208
209	type TestExtrinsic = TestXt<MockCallU64, ()>;
210	type TestBlock = Block<TestExtrinsic>;
211
212	#[test]
213	fn decoding_encoding_v0_works() {
214		let v0 = ParachainBlockDataV0::<TestBlock> {
215			header: Header::new_from_number(10),
216			extrinsics: vec![
217				TestExtrinsic::new_bare(MockCallU64(10)),
218				TestExtrinsic::new_bare(MockCallU64(100)),
219			],
220			storage_proof: CompactProof { encoded_nodes: vec![vec![10u8; 200], vec![20u8; 30]] },
221		};
222
223		let encoded = v0.encode();
224		let decoded = ParachainBlockData::<TestBlock>::decode(&mut &encoded[..]).unwrap();
225
226		match &decoded {
227			ParachainBlockData::V0 { block, proof } => {
228				assert_eq!(v0.header, block[0].header);
229				assert_eq!(v0.extrinsics, block[0].extrinsics);
230				assert_eq!(&v0.storage_proof, proof);
231			},
232			_ => panic!("Invalid decoding"),
233		}
234
235		let encoded = decoded.as_v0().unwrap().encode();
236
237		let decoded = ParachainBlockDataV0::<TestBlock>::decode(&mut &encoded[..]).unwrap();
238		assert_eq!(decoded, v0);
239	}
240
241	#[test]
242	fn decoding_encoding_v1_works() {
243		let v1 = ParachainBlockData::<TestBlock>::V1 {
244			blocks: vec![TestBlock::new(
245				Header::new_from_number(10),
246				vec![
247					TestExtrinsic::new_bare(MockCallU64(10)),
248					TestExtrinsic::new_bare(MockCallU64(100)),
249				],
250			)],
251			proof: CompactProof { encoded_nodes: vec![vec![10u8; 200], vec![20u8; 30]] },
252		};
253
254		let encoded = v1.encode();
255		let decoded = ParachainBlockData::<TestBlock>::decode(&mut &encoded[..]).unwrap();
256
257		assert_eq!(v1.blocks(), decoded.blocks());
258		assert_eq!(v1.proof(), decoded.proof());
259	}
260}