1use codec::{Decode, Encode};
28use polkadot_node_primitives::{AvailableData, Proof};
29use polkadot_primitives::{BlakeTwo256, Hash as H256, HashT};
30use sp_core::Blake2Hasher;
31use sp_trie::{
32 trie_types::{TrieDBBuilder, TrieDBMutBuilderV0 as TrieDBMutBuilder},
33 LayoutV0, MemoryDB, Trie, TrieMut, EMPTY_PREFIX,
34};
35use thiserror::Error;
36
37use novelpoly::{CodeParams, WrappedShard};
38
39const MAX_VALIDATORS: usize = novelpoly::f2e16::FIELD_SIZE;
41
42#[derive(Debug, Clone, PartialEq, Error)]
44pub enum Error {
45 #[error("There are too many validators")]
47 TooManyValidators,
48 #[error("Expected at least 2 validators")]
50 NotEnoughValidators,
51 #[error("Validator count mismatches between encoding and decoding")]
53 WrongValidatorCount,
54 #[error("Not enough chunks to reconstruct message")]
56 NotEnoughChunks,
57 #[error("Too many chunks present")]
59 TooManyChunks,
60 #[error("Chunks are not uniform, mismatch in length or are zero sized")]
62 NonUniformChunks,
63 #[error("Uneven length is not valid for field GF(2^16)")]
65 UnevenLength,
66 #[error("Chunk is out of bounds: {chunk_index} not included in 0..{n_validators}")]
68 ChunkIndexOutOfBounds { chunk_index: usize, n_validators: usize },
69 #[error("Reconstructed payload invalid")]
71 BadPayload,
72 #[error("Unable to decode reconstructed payload: {0}")]
74 Decode(#[source] codec::Error),
75 #[error("Invalid branch proof")]
77 InvalidBranchProof,
78 #[error("Branch is out of bounds")]
80 BranchOutOfBounds,
81 #[error("An unknown error has appeared when reconstructing erasure code chunks")]
83 UnknownReconstruction,
84 #[error("An unknown error has appeared when deriving code parameters from validator count")]
86 UnknownCodeParam,
87}
88
89impl From<novelpoly::Error> for Error {
90 fn from(error: novelpoly::Error) -> Self {
91 match error {
92 novelpoly::Error::NeedMoreShards { .. } => Self::NotEnoughChunks,
93 novelpoly::Error::ParamterMustBePowerOf2 { .. } => Self::UnevenLength,
94 novelpoly::Error::WantedShardCountTooHigh(_) => Self::TooManyValidators,
95 novelpoly::Error::WantedShardCountTooLow(_) => Self::NotEnoughValidators,
96 novelpoly::Error::PayloadSizeIsZero { .. } => Self::BadPayload,
97 novelpoly::Error::InconsistentShardLengths { .. } => Self::NonUniformChunks,
98 _ => Self::UnknownReconstruction,
99 }
100 }
101}
102
103pub const fn recovery_threshold(n_validators: usize) -> Result<usize, Error> {
105 if n_validators > MAX_VALIDATORS {
106 return Err(Error::TooManyValidators);
107 }
108 if n_validators <= 1 {
109 return Err(Error::NotEnoughValidators);
110 }
111
112 let needed = n_validators.saturating_sub(1) / 3;
113 Ok(needed + 1)
114}
115
116pub fn systematic_recovery_threshold(n_validators: usize) -> Result<usize, Error> {
121 code_params(n_validators).map(|params| params.k())
122}
123
124fn code_params(n_validators: usize) -> Result<CodeParams, Error> {
125 let n_wanted = n_validators;
128 let k_wanted = recovery_threshold(n_wanted)?;
129
130 if n_wanted > MAX_VALIDATORS as usize {
131 return Err(Error::TooManyValidators);
132 }
133
134 CodeParams::derive_parameters(n_wanted, k_wanted).map_err(|e| match e {
135 novelpoly::Error::WantedShardCountTooHigh(_) => Error::TooManyValidators,
136 novelpoly::Error::WantedShardCountTooLow(_) => Error::NotEnoughValidators,
137 _ => Error::UnknownCodeParam,
138 })
139}
140
141pub fn reconstruct_from_systematic_v1(
146 n_validators: usize,
147 chunks: Vec<Vec<u8>>,
148) -> Result<AvailableData, Error> {
149 reconstruct_from_systematic(n_validators, chunks)
150}
151
152pub fn reconstruct_from_systematic<T: Decode>(
157 n_validators: usize,
158 chunks: Vec<Vec<u8>>,
159) -> Result<T, Error> {
160 let code_params = code_params(n_validators)?;
161 let k = code_params.k();
162
163 for chunk_data in chunks.iter().take(k) {
164 if chunk_data.len() % 2 != 0 {
165 return Err(Error::UnevenLength);
166 }
167 }
168
169 let bytes = code_params.make_encoder().reconstruct_from_systematic(
170 chunks.into_iter().take(k).map(|data| WrappedShard::new(data)).collect(),
171 )?;
172
173 Decode::decode(&mut &bytes[..]).map_err(|err| Error::Decode(err))
174}
175
176pub fn obtain_chunks_v1(n_validators: usize, data: &AvailableData) -> Result<Vec<Vec<u8>>, Error> {
180 obtain_chunks(n_validators, data)
181}
182
183pub fn obtain_chunks<T: Encode>(n_validators: usize, data: &T) -> Result<Vec<Vec<u8>>, Error> {
187 let params = code_params(n_validators)?;
188 let encoded = data.encode();
189
190 if encoded.is_empty() {
191 return Err(Error::BadPayload);
192 }
193
194 let shards = params
195 .make_encoder()
196 .encode::<WrappedShard>(&encoded[..])
197 .expect("Payload non-empty, shard sizes are uniform, and validator numbers checked; qed");
198
199 Ok(shards.into_iter().map(|w: WrappedShard| w.into_inner()).collect())
200}
201
202pub fn reconstruct_v1<'a, I: 'a>(n_validators: usize, chunks: I) -> Result<AvailableData, Error>
210where
211 I: IntoIterator<Item = (&'a [u8], usize)>,
212{
213 reconstruct(n_validators, chunks)
214}
215
216pub fn reconstruct<'a, I: 'a, T: Decode>(n_validators: usize, chunks: I) -> Result<T, Error>
224where
225 I: IntoIterator<Item = (&'a [u8], usize)>,
226{
227 let params = code_params(n_validators)?;
228 let mut received_shards: Vec<Option<WrappedShard>> = vec![None; n_validators];
229 for (chunk_data, chunk_idx) in chunks.into_iter().take(n_validators) {
230 if chunk_data.len() % 2 != 0 {
231 return Err(Error::UnevenLength);
232 }
233
234 if chunk_idx >= n_validators {
235 return Err(Error::ChunkIndexOutOfBounds { chunk_index: chunk_idx, n_validators });
236 }
237
238 received_shards[chunk_idx] = Some(WrappedShard::new(chunk_data.to_vec()));
239 }
240
241 let payload_bytes = params.make_encoder().reconstruct(received_shards)?;
242
243 Decode::decode(&mut &payload_bytes[..]).map_err(|_| Error::BadPayload)
244}
245
246pub struct Branches<'a, I> {
249 trie_storage: MemoryDB<Blake2Hasher>,
250 root: H256,
251 chunks: &'a [I],
252 current_pos: usize,
253}
254
255impl<'a, I: AsRef<[u8]>> Branches<'a, I> {
256 pub fn root(&self) -> H256 {
258 self.root
259 }
260}
261
262impl<'a, I: AsRef<[u8]>> Iterator for Branches<'a, I> {
263 type Item = (Proof, &'a [u8]);
264
265 fn next(&mut self) -> Option<Self::Item> {
266 use sp_trie::Recorder;
267
268 let mut recorder = Recorder::<LayoutV0<Blake2Hasher>>::new();
269 let res = {
270 let trie = TrieDBBuilder::new(&self.trie_storage, &self.root)
271 .with_recorder(&mut recorder)
272 .build();
273
274 (self.current_pos as u32).using_encoded(|s| trie.get(s))
275 };
276
277 match res.expect("all nodes in trie present; qed") {
278 Some(_) => {
279 let nodes: Vec<Vec<u8>> = recorder.drain().into_iter().map(|r| r.data).collect();
280 let chunk = self.chunks.get(self.current_pos).expect(
281 "there is a one-to-one mapping of chunks to valid merkle branches; qed",
282 );
283 self.current_pos += 1;
284 Proof::try_from(nodes).ok().map(|proof| (proof, chunk.as_ref()))
285 },
286 None => None,
287 }
288 }
289}
290
291pub fn branches<'a, I: 'a>(chunks: &'a [I]) -> Branches<'a, I>
294where
295 I: AsRef<[u8]>,
296{
297 let mut trie_storage: MemoryDB<Blake2Hasher> = MemoryDB::default();
298 let mut root = H256::default();
299
300 {
302 let mut trie = TrieDBMutBuilder::new(&mut trie_storage, &mut root).build();
303 for (i, chunk) in chunks.as_ref().iter().enumerate() {
304 (i as u32).using_encoded(|encoded_index| {
305 let chunk_hash = BlakeTwo256::hash(chunk.as_ref());
306 trie.insert(encoded_index, chunk_hash.as_ref())
307 .expect("a fresh trie stored in memory cannot have errors loading nodes; qed");
308 })
309 }
310 }
311
312 Branches { trie_storage, root, chunks, current_pos: 0 }
313}
314
315pub fn branch_hash(root: &H256, branch_nodes: &Proof, index: usize) -> Result<H256, Error> {
318 let mut trie_storage: MemoryDB<Blake2Hasher> = MemoryDB::default();
319 for node in branch_nodes.iter() {
320 (&mut trie_storage as &mut sp_trie::HashDB<_>).insert(EMPTY_PREFIX, node);
321 }
322
323 let trie = TrieDBBuilder::new(&trie_storage, &root).build();
324 let res = (index as u32).using_encoded(|key| {
325 trie.get_with(key, |raw_hash: &[u8]| H256::decode(&mut &raw_hash[..]))
326 });
327
328 match res {
329 Ok(Some(Ok(hash))) => Ok(hash),
330 Ok(Some(Err(_))) => Err(Error::InvalidBranchProof), Ok(None) => Err(Error::BranchOutOfBounds),
332 Err(_) => Err(Error::InvalidBranchProof),
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use std::sync::Arc;
339
340 use super::*;
341 use polkadot_node_primitives::{AvailableData, BlockData, PoV};
342 use polkadot_primitives::{HeadData, PersistedValidationData};
343 use quickcheck::{Arbitrary, Gen, QuickCheck};
344
345 const KEY_INDEX_NIBBLE_SIZE: usize = 4;
348
349 #[derive(Clone, Debug)]
350 struct ArbitraryAvailableData(AvailableData);
351
352 impl Arbitrary for ArbitraryAvailableData {
353 fn arbitrary(g: &mut Gen) -> Self {
354 let pov_len = (u32::arbitrary(g) % (1024 * 1024)).max(2);
356
357 let pov = (0..pov_len).map(|_| u8::arbitrary(g)).collect();
358
359 let pvd = PersistedValidationData {
360 parent_head: HeadData((0..u16::arbitrary(g)).map(|_| u8::arbitrary(g)).collect()),
361 relay_parent_number: u32::arbitrary(g),
362 relay_parent_storage_root: [u8::arbitrary(g); 32].into(),
363 max_pov_size: u32::arbitrary(g),
364 };
365
366 ArbitraryAvailableData(AvailableData {
367 pov: Arc::new(PoV { block_data: BlockData(pov) }),
368 validation_data: pvd,
369 })
370 }
371 }
372
373 #[test]
374 fn field_order_is_right_size() {
375 assert_eq!(MAX_VALIDATORS, 65536);
376 }
377
378 #[test]
379 fn round_trip_works() {
380 let pov = PoV { block_data: BlockData((0..255).collect()) };
381
382 let available_data = AvailableData { pov: pov.into(), validation_data: Default::default() };
383 let chunks = obtain_chunks(10, &available_data).unwrap();
384
385 assert_eq!(chunks.len(), 10);
386
387 let reconstructed: AvailableData = reconstruct(
389 10,
390 [(&*chunks[1], 1), (&*chunks[4], 4), (&*chunks[6], 6), (&*chunks[9], 9)]
391 .iter()
392 .cloned(),
393 )
394 .unwrap();
395
396 assert_eq!(reconstructed, available_data);
397 }
398
399 #[test]
400 fn round_trip_systematic_works() {
401 fn property(available_data: ArbitraryAvailableData, n_validators: u16) {
402 let n_validators = n_validators.max(2);
403 let kpow2 = systematic_recovery_threshold(n_validators as usize).unwrap();
404 let chunks = obtain_chunks(n_validators as usize, &available_data.0).unwrap();
405 assert_eq!(
406 reconstruct_from_systematic_v1(
407 n_validators as usize,
408 chunks.into_iter().take(kpow2).collect()
409 )
410 .unwrap(),
411 available_data.0
412 );
413 }
414
415 QuickCheck::new().quickcheck(property as fn(ArbitraryAvailableData, u16))
416 }
417
418 #[test]
419 fn reconstruct_does_not_panic_on_low_validator_count() {
420 let reconstructed = reconstruct_v1(1, [].iter().cloned());
421 assert_eq!(reconstructed, Err(Error::NotEnoughValidators));
422 }
423
424 #[test]
425 fn reconstruct_returns_error_on_out_of_bounds_chunk_index() {
426 let n_validators = 10;
427 let pov = PoV { block_data: BlockData((0..255).collect()) };
428 let available_data = AvailableData { pov: pov.into(), validation_data: Default::default() };
429 let chunks = obtain_chunks(n_validators, &available_data).unwrap();
430
431 let reconstructed: Result<AvailableData, _> = reconstruct(
432 n_validators,
433 [(&*chunks[0], 0), (&*chunks[1], n_validators)].iter().cloned(),
434 );
435 assert_eq!(
436 reconstructed,
437 Err(Error::ChunkIndexOutOfBounds { chunk_index: n_validators, n_validators })
438 );
439 }
440
441 fn generate_trie_and_generate_proofs(magnitude: u32) {
442 let n_validators = 2_u32.pow(magnitude) as usize;
443 let pov = PoV { block_data: BlockData(vec![2; n_validators / KEY_INDEX_NIBBLE_SIZE]) };
444
445 let available_data = AvailableData { pov: pov.into(), validation_data: Default::default() };
446
447 let chunks = obtain_chunks(magnitude as usize, &available_data).unwrap();
448
449 assert_eq!(chunks.len() as u32, magnitude);
450
451 let branches = branches(chunks.as_ref());
452 let root = branches.root();
453
454 let proofs: Vec<_> = branches.map(|(proof, _)| proof).collect();
455 assert_eq!(proofs.len() as u32, magnitude);
456 for (i, proof) in proofs.into_iter().enumerate() {
457 let encode = Encode::encode(&proof);
458 let decode = Decode::decode(&mut &encode[..]).unwrap();
459 assert_eq!(proof, decode);
460 assert_eq!(encode, Encode::encode(&decode));
461
462 assert_eq!(branch_hash(&root, &proof, i).unwrap(), BlakeTwo256::hash(&chunks[i]));
463 }
464 }
465
466 #[test]
467 fn roundtrip_proof_encoding() {
468 for i in 2..16 {
469 generate_trie_and_generate_proofs(i);
470 }
471 }
472}