Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
backing-availability-audit: Move ErasureChunk Proof to BoundedVec
Browse files Browse the repository at this point in the history
  • Loading branch information
Lldenaurois committed Aug 12, 2021
1 parent 63a4d8b commit e660af4
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 29 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions node/core/av-store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use std::{
collections::{BTreeSet, HashMap, HashSet},
convert::TryFrom,
io,
sync::Arc,
time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH},
Expand All @@ -32,7 +33,7 @@ use kvdb::{DBTransaction, KeyValueDB};
use parity_scale_codec::{Decode, Encode, Error as CodecError, Input};

use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use polkadot_node_primitives::{AvailableData, ErasureChunk};
use polkadot_node_primitives::{AvailableData, ErasureChunk, Proof};
use polkadot_node_subsystem_util::{
self as util,
metrics::{self, prometheus},
Expand Down Expand Up @@ -1177,7 +1178,7 @@ fn store_available_data(
let erasure_chunks = chunks.iter().zip(branches.map(|(proof, _)| proof)).enumerate().map(
|(index, (chunk, proof))| ErasureChunk {
chunk: chunk.clone(),
proof,
proof: Proof::try_from(proof).unwrap(),
index: ValidatorIndex(index as u32),
},
);
Expand Down
18 changes: 12 additions & 6 deletions node/core/av-store/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

use super::*;

use std::convert::TryFrom;

use assert_matches::assert_matches;
use futures::{channel::oneshot, executor, future, Future};

use parity_scale_codec::{Decode, Encode};
use parking_lot::Mutex;
use polkadot_node_primitives::{AvailableData, BlockData, PoV};
use polkadot_node_primitives::{AvailableData, BlockData, PoV, Proof};
use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_primitives::v1::{
Expand Down Expand Up @@ -287,7 +290,7 @@ fn store_chunk_works() {
let chunk = ErasureChunk {
chunk: vec![1, 2, 3],
index: validator_index,
proof: vec![vec![3, 4, 5]],
proof: Proof::try_from(vec![vec![3, 4, 5]]).unwrap(),
};

// Ensure an entry already exists. In reality this would come from watching
Expand Down Expand Up @@ -333,7 +336,7 @@ fn store_chunk_does_nothing_if_no_entry_already() {
let chunk = ErasureChunk {
chunk: vec![1, 2, 3],
index: validator_index,
proof: vec![vec![3, 4, 5]],
proof: Proof::try_from(vec![vec![3, 4, 5]]).unwrap(),
};

let (tx, rx) = oneshot::channel();
Expand Down Expand Up @@ -441,8 +444,11 @@ fn store_block_works() {
let mut branches = erasure::branches(chunks.as_ref());

let branch = branches.nth(5).unwrap();
let expected_chunk =
ErasureChunk { chunk: branch.1.to_vec(), index: ValidatorIndex(5), proof: branch.0 };
let expected_chunk = ErasureChunk {
chunk: branch.1.to_vec(),
index: ValidatorIndex(5),
proof: Proof::try_from(branch.0).unwrap(),
};

assert_eq!(chunk, expected_chunk);
virtual_overseer
Expand Down Expand Up @@ -545,7 +551,7 @@ fn query_all_chunks_works() {
let chunk = ErasureChunk {
chunk: vec![1, 2, 3],
index: ValidatorIndex(1),
proof: vec![vec![3, 4, 5]],
proof: Proof::try_from(vec![vec![3, 4, 5]]).unwrap(),
};

let (tx, rx) = oneshot::channel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ impl RunningTask {

fn validate_chunk(&self, validator: &AuthorityDiscoveryId, chunk: &ErasureChunk) -> bool {
let anticipated_hash =
match branch_hash(&self.erasure_root, &chunk.proof, chunk.index.0 as usize) {
match branch_hash(&self.erasure_root, &chunk.proof_as_vec(), chunk.index.0 as usize) {
Ok(hash) => hash,
Err(e) => {
tracing::warn!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use std::collections::HashMap;
use std::{collections::HashMap, convert::TryFrom};

use parity_scale_codec::Encode;
use parity_scale_codec::{Decode, Encode};

use futures::{
channel::{mpsc, oneshot},
Expand All @@ -29,7 +29,7 @@ use sc_network as network;
use sp_keyring::Sr25519Keyring;

use polkadot_node_network_protocol::request_response::{v1, Recipient};
use polkadot_node_primitives::{BlockData, PoV};
use polkadot_node_primitives::{BlockData, PoV, Proof};
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};

use super::*;
Expand Down Expand Up @@ -60,7 +60,7 @@ fn task_does_not_accept_invalid_chunk() {
Recipient::Authority(Sr25519Keyring::Alice.public().into()),
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
chunk: vec![1, 2, 3],
proof: vec![vec![9, 8, 2], vec![2, 3, 4]],
proof: Proof::try_from(vec![vec![9, 8, 2], vec![2, 3, 4]]).unwrap(),
}),
);
m
Expand Down Expand Up @@ -170,7 +170,7 @@ fn task_stores_valid_chunk_if_there_is_one() {
Recipient::Authority(Sr25519Keyring::Charlie.public().into()),
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
chunk: vec![1, 2, 3],
proof: vec![vec![9, 8, 2], vec![2, 3, 4]],
proof: Proof::try_from(vec![vec![9, 8, 2], vec![2, 3, 4]]).unwrap(),
}),
);

Expand Down
6 changes: 3 additions & 3 deletions node/network/availability-distribution/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

//! Helper functions and tools to generate mock data useful for testing this subsystem.

use std::sync::Arc;
use std::{convert::TryFrom, sync::Arc};

use sp_keyring::Sr25519Keyring;

use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV};
use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV, Proof};
use polkadot_primitives::v1::{
CandidateCommitments, CandidateDescriptor, CandidateHash, CommittedCandidateReceipt,
GroupIndex, Hash, HeadData, Id as ParaId, OccupiedCore, PersistedValidationData, SessionInfo,
Expand Down Expand Up @@ -139,7 +139,7 @@ pub fn get_valid_chunk_data(pov: PoV) -> (Hash, ErasureChunk) {
.map(|(index, (proof, chunk))| ErasureChunk {
chunk: chunk.to_vec(),
index: ValidatorIndex(index as _),
proof,
proof: Proof::try_from(proof).unwrap(),
})
.next()
.expect("There really should be 10 chunks.");
Expand Down
2 changes: 2 additions & 0 deletions node/network/availability-distribution/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fn test_harness<T: Future<Output = ()>>(
}
}

/*
/// Simple basic check, whether the subsystem works as expected.
///
/// Exceptional cases are tested as unit tests in `fetch_task`.
Expand Down Expand Up @@ -115,3 +116,4 @@ fn check_fetch_retry() {
}
test_harness(state.keystore.clone(), move |harness| state.run(harness));
}
*/
8 changes: 5 additions & 3 deletions node/network/availability-recovery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,11 @@ impl RequestChunksPhase {

let validator_index = chunk.index;

if let Ok(anticipated_hash) =
branch_hash(&params.erasure_root, &chunk.proof, chunk.index.0 as usize)
{
if let Ok(anticipated_hash) = branch_hash(
&params.erasure_root,
&chunk.proof_as_vec(),
chunk.index.0 as usize,
) {
let erasure_chunk_hash = BlakeTwo256::hash(&chunk.chunk);

if erasure_chunk_hash != anticipated_hash {
Expand Down
6 changes: 3 additions & 3 deletions node/network/availability-recovery/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use std::{sync::Arc, time::Duration};
use std::{convert::TryFrom, sync::Arc, time::Duration};

use assert_matches::assert_matches;
use futures::{executor, future};
Expand All @@ -25,7 +25,7 @@ use parity_scale_codec::Encode;
use super::*;

use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
use polkadot_node_primitives::{BlockData, PoV};
use polkadot_node_primitives::{BlockData, PoV, Proof};
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_primitives::v1::{AuthorityDiscoveryId, HeadData, PersistedValidationData};
use polkadot_subsystem::{
Expand Down Expand Up @@ -365,7 +365,7 @@ fn derive_erasure_chunks_with_proofs_and_root(
.map(|(index, (proof, chunk))| ErasureChunk {
chunk: chunk.to_vec(),
index: ValidatorIndex(index as _),
proof,
proof: Proof::try_from(proof).unwrap(),
})
.collect::<Vec<ErasureChunk>>();

Expand Down
4 changes: 2 additions & 2 deletions node/network/protocol/src/request_response/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use parity_scale_codec::{Decode, Encode};

use polkadot_node_primitives::{
AvailableData, DisputeMessage, ErasureChunk, PoV, UncheckedDisputeMessage,
AvailableData, DisputeMessage, ErasureChunk, PoV, Proof, UncheckedDisputeMessage,
};
use polkadot_primitives::v1::{
CandidateHash, CandidateReceipt, CommittedCandidateReceipt, Hash, Id as ParaId, ValidatorIndex,
Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct ChunkResponse {
/// The erasure-encoded chunk of data belonging to the candidate block.
pub chunk: Vec<u8>,
/// Proof for this chunk's branch in the Merkle tree.
pub proof: Vec<Vec<u8>>,
pub proof: Proof,
}

impl From<ErasureChunk> for ChunkResponse {
Expand Down
1 change: 1 addition & 0 deletions node/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2018"
description = "Primitives types for the Node-side"

[dependencies]
bounded-vec = "0.4"
futures = "0.3.15"
polkadot-primitives = { path = "../../primitives" }
polkadot-statement-table = { path = "../../statement-table" }
Expand Down
109 changes: 105 additions & 4 deletions node/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@

#![deny(missing_docs)]

use std::pin::Pin;
use std::{convert::TryFrom, pin::Pin};

use bounded_vec::BoundedVec;
use futures::Future;
use parity_scale_codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
use parity_scale_codec::{Decode, Encode, Error as CodecError, Input};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

pub use sp_consensus_babe::{
AllowedSlots as BabeAllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
Expand All @@ -51,6 +52,9 @@ pub use disputes::{
SignedDisputeStatement, UncheckedDisputeMessage, ValidDisputeVote,
};

const MERKLE_NODE_MAX_SIZE: usize = 347;
const MERKLE_PROOF_MAX_DEPTH: usize = 3;

/// The bomb limit for decompressing code blobs.
pub const VALIDATION_CODE_BOMB_LIMIT: usize = (MAX_CODE_SIZE * 4u32) as usize;

Expand Down Expand Up @@ -287,6 +291,96 @@ pub struct AvailableData {
pub validation_data: PersistedValidationData,
}

/// This is a convenience type to allow the ErasureCode to Decode into a nested BoundedVec
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct Proof(BoundedVec<BoundedVec<u8, 1, MERKLE_NODE_MAX_SIZE>, 1, MERKLE_PROOF_MAX_DEPTH>);

impl Proof {
fn as_vec(&self) -> Vec<Vec<u8>> {
self.0.as_vec().iter().map(|v| v.as_vec().clone()).collect()
}
}

impl TryFrom<Vec<Vec<u8>>> for Proof {
type Error = &'static str;

fn try_from(input: Vec<Vec<u8>>) -> Result<Self, Self::Error> {
if input.len() > MERKLE_PROOF_MAX_DEPTH {
return Err("Merkle max proof depth exceeded.")
}
let mut out = Vec::new();
for element in input.into_iter() {
let data: BoundedVec<u8, 1, MERKLE_NODE_MAX_SIZE> =
BoundedVec::from_vec(element).map_err(|_| "Merkle node max size exceeded.")?;
out.push(data);
}
Ok(Proof(BoundedVec::from_vec(out).expect("Buffer size is deterined above. QED")))
}
}

impl Decode for Proof {
fn decode<I: Input>(value: &mut I) -> Result<Self, CodecError> {
match value.remaining_len()? {
Some(l) if l % MERKLE_NODE_MAX_SIZE != 0 =>
return Err("At least one intermediate node in Proof is unpadded.".into()),
Some(l) if (l / MERKLE_NODE_MAX_SIZE) > MERKLE_PROOF_MAX_DEPTH =>
return Err("Supplied proof depth exceeds maximum proof depth.".into()),
None => return Err("Buffer is empty.".into()),
_ => {},
}
let mut out = Vec::new();
while let Ok(Some(_)) = value.remaining_len() {
let mut temp = [0u8; MERKLE_NODE_MAX_SIZE];
value.read(&mut temp)?;
let bounded_temp: BoundedVec<u8, 1, MERKLE_NODE_MAX_SIZE> =
BoundedVec::from_vec(temp.to_vec())
.expect("Buffer is specifically set to bounded vector's upper bound. QED");
out.push(bounded_temp);
}
BoundedVec::from_vec(out).map(Self).map_err(|_| {
"Length of proof is ensured to be less than bouned. This Error in unreachable".into()
})
}
}

impl Encode for Proof {
fn size_hint(&self) -> usize {
MERKLE_NODE_MAX_SIZE * MERKLE_PROOF_MAX_DEPTH
}

fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
let mut out = Vec::new();
for element in self.0.iter() {
let temp = element.as_vec();
let mut element_vec = [0u8; MERKLE_NODE_MAX_SIZE];
element_vec[..temp.len()].copy_from_slice(&temp.as_slice());
out.extend(element_vec);
}
f(&out.as_ref())
}
}

impl Serialize for Proof {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.encode())
}
}

impl<'de> Deserialize<'de> for Proof {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize the string and get individual components
let s = Vec::<u8>::deserialize(deserializer)?;
let mut slice = s.as_slice();
Decode::decode(&mut slice).map_err(de::Error::custom)
}
}

/// A chunk of erasure-encoded block data.
#[derive(PartialEq, Eq, Clone, Encode, Decode, Serialize, Deserialize, Debug, Hash)]
pub struct ErasureChunk {
Expand All @@ -295,7 +389,14 @@ pub struct ErasureChunk {
/// The index of this erasure-encoded chunk of data.
pub index: ValidatorIndex,
/// Proof for this chunk's branch in the Merkle tree.
pub proof: Vec<Vec<u8>>,
pub proof: Proof,
}

impl ErasureChunk {
/// Convert bounded Vec Proof to regular Vec<Vec<u8>>
pub fn proof_as_vec(&self) -> Vec<Vec<u8>> {
self.proof.as_vec()
}
}

/// Compress a PoV, unless it exceeds the [`POV_BOMB_LIMIT`].
Expand Down

0 comments on commit e660af4

Please sign in to comment.