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

backing-availability-audit: Move ErasureChunk Proof to BoundedVec #3626

Merged

Conversation

Lldenaurois
Copy link
Contributor

Addresses: https://github.com/paritytech/srlabs_findings/issues/100

This PR moved the proof field in ErasureChunk to a nested set of BoundedVecs.

@Lldenaurois Lldenaurois force-pushed the erasure_chunk_proof_bounded branch 5 times, most recently from e660af4 to 2541526 Compare August 12, 2021 02:19
@@ -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(),
Copy link
Contributor Author

@Lldenaurois Lldenaurois Aug 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I am not sure what the best approach is here.

Since the obtain_chunks function (c.f. here) and branches function (c.f. here returns an AsRef<[u8]> and Vec<Vec> respectively, it may be prudent to modify those functions to return the actual Proof object. However, this is a failable conversion and would need proper error-handling, e.g. modifying the .map above to .filter_map

One option would be to have the erasure-coding return the Proof as oppose to the AsRef<[u8]> and the Vec<Vec<u8>>. But before I go ahead and do that it maybe be best to sync up regarding the proper approach.

@@ -16,11 +16,14 @@

use super::*;

use std::convert::TryFrom;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's no use of this trait anywhere (it seems), I believe it may be incorrect to implement the TryFrom trait as oppose to just making a Method on Proof.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would it be incorrect?

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];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the tersest way to store Merkle proofs, but it works. I wanted to have something working quickly in order to get some of the higher-level questions out of the way.

Strangely, the Merkle proofs generated in the polkadot tests seem to always an outer length 2, which I currently don't fully understand. The inner length varies from 3 to 347 and I also don't fully understand why.

Based on how we store these proofs as a Vec<Vec>, we can choose the best way to bound the inner and out vectors.

@@ -51,6 +52,9 @@ pub use disputes::{
SignedDisputeStatement, UncheckedDisputeMessage, ValidDisputeVote,
};

const MERKLE_NODE_MAX_SIZE: usize = 347;
const MERKLE_PROOF_MAX_DEPTH: usize = 3;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these constants would not work in practice. I've just set them to what I observed being crated in the tests. It is my belief that the depth is no more than 3 (in fact it's not more than 2) throughout all tests because we don't ever create a Merkle Tree with that many elements, i.e. intermediate nodes.

The correct values to use here are:

MERKLE_NODE_MAX_SIZE = 512;
MERKLE_PROOF_MAX_DEPTH = 8;

I just found it interesting that the node max size was uneven. I may be mistaken, but I'm quite certain the max proof node created is 347 bytes.

@eskimor
Copy link
Member

eskimor commented Aug 12, 2021

With the limits in place, how big can a proof become now?

@Lldenaurois
Copy link
Contributor Author

4096 (= 512 * 8) bytes is the maximum size of the Proof with this change, as oppose to the 500KB protocol-level limit.

@Lldenaurois Lldenaurois added A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes D5-nicetohaveaudit ⚠️ PR contains trivial changes to logic that should be properly reviewed. I2-security The node fails to follow expected, security-sensitive, behaviour. labels Aug 18, 2021
@Lldenaurois Lldenaurois marked this pull request as ready for review August 18, 2021 16:04
Copy link
Contributor

@drahnr drahnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the error handling to not be static strings, with the exception of CodecError unless there is a particular requirement to do so.

Besides that, 👍

Comment on lines 55 to 56
const MERKLE_NODE_MAX_SIZE: usize = 512;
const MERKLE_PROOF_MAX_DEPTH: usize = 8;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding comments as to why these were chosen as they are would be appreciated.

}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue for a

#[derive(thiserror::Error)]
enum Error {
    #[error("erkle max proof depth exceeded {0} > {} .", MERKLE_PROOF_MAX_DEPTH)]
    MerkleProofDepthExceeded(depth),
    // ..
}

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.")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC

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")))
Copy link
Contributor

@drahnr drahnr Aug 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Ok(Proof(BoundedVec::from_vec(out).expect("Buffer size is deterined above. QED")))
Ok(Proof(BoundedVec::from_vec(out).expect("Buffer size is already checked; qed")))


impl Proof {
/// This function allows to convert back to the standard nested Vec format
pub fn as_vec(&self) -> Vec<Vec<u8>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/future: Do we truely require a Vec<Vec<_>>? Or could we avoid all those allocations by employing a single BoundedVec<_> and providing a Vec<&[]> or even &[&[]]? Again, not for this PR, a potential future optimization.

@Lldenaurois Lldenaurois added the C1-low PR touches the given topic and has a low impact on builders. label Aug 20, 2021
node/primitives/src/lib.rs Show resolved Hide resolved
node/primitives/src/lib.rs Show resolved Hide resolved
node/primitives/src/lib.rs Show resolved Hide resolved
node/primitives/src/lib.rs Show resolved Hide resolved
self.current_pos += 1;
Some((nodes, chunk.as_ref()))
Proof::try_from(nodes).ok().map(|proof| (proof, chunk.as_ref()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is the right behavior to just let the iterator end if the Proof could not be constructed. Can this even happen, without the code having a logic error?

@@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we fix branch_hash in order to get rid of that conversion?

}
}

impl Decode for Proof {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am missing tests, to make sure Encode/Decode works and is compatible to what we had before.

@Lldenaurois Lldenaurois merged commit d4576bc into paritytech:master Aug 24, 2021
Lldenaurois added a commit that referenced this pull request Aug 24, 2021
ordian added a commit that referenced this pull request Aug 24, 2021
* master:
  backing-availability-audit: Move ErasureChunk Proof to BoundedVec (#3626)
  Substrate Companion #9575 (#3695)
  Fill up requests slots via `launch_parallel_requests` (#3681)
  Bump serde_json from 1.0.64 to 1.0.66 (#3669)
  substrate #9202 companion: Multiple vesting schedules (#3407)
  XCM: Introduce versioning to dispatchables' params (#3693)
  remove dead_code from chain selection test (#3685)
  Improve MultiLocation conversion functions in xcm-procedural (#3690)
@stze stze added D1-audited 👍 PR contains changes to critical logic that has been properly reviewed and externally audited. and removed D5-nicetohaveaudit ⚠️ PR contains trivial changes to logic that should be properly reviewed. labels Sep 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D1-audited 👍 PR contains changes to critical logic that has been properly reviewed and externally audited. I2-security The node fails to follow expected, security-sensitive, behaviour.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants