Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ all = { level = "warn", priority = -1 }
missing-const-for-fn = "warn"
use-self = "warn"
redundant-clone = "warn"
result_large_err = "allow"
result_large_err = "warn"

# Use the `--profile profiling` flag to show symbols in release mode.
# e.g. `cargo build --profile profiling`
Expand Down
57 changes: 41 additions & 16 deletions src/proof/error.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
use alloc::boxed::Box;
use alloy_primitives::{B256, Bytes};
use nybbles::Nibbles;

/// Error during proof verification.
#[derive(PartialEq, Eq, Debug, thiserror::Error)]
pub enum ProofVerificationError {
/// State root does not match the expected.
#[error("root mismatch. got: {got}. expected: {expected}")]
RootMismatch {
/// Computed state root.
got: B256,
/// State root provided to verify function.
expected: B256,
},
#[error(transparent)]
RootMismatch(Box<RootMismatchError>),
/// The node value does not match at specified path.
#[error("value mismatch at path {path:?}. got: {got:?}. expected: {expected:?}")]
ValueMismatch {
/// Path at which error occurred.
path: Nibbles,
/// Value in the proof.
got: Option<Bytes>,
/// Expected value.
expected: Option<Bytes>,
},
#[error(transparent)]
ValueMismatch(Box<ValueMismatchError>),
/// Encountered unexpected empty root node.
#[error("unexpected empty root node")]
UnexpectedEmptyRoot,
/// Error during RLP decoding of trie node.
#[error(transparent)]
Rlp(#[from] alloy_rlp::Error),
}

/// State root does not match the expected.
#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
#[error("root mismatch. got: {got}. expected: {expected}")]
pub struct RootMismatchError {
/// Computed state root.
pub got: B256,
/// State root provided to verify function.
pub expected: B256,
}

/// The node value does not match at specified path.
#[derive(PartialEq, Eq, Debug, thiserror::Error)]
#[error("value mismatch at path {path:?}. got: {got:?}. expected: {expected:?}")]
pub struct ValueMismatchError {
/// Path at which error occurred.
pub path: Nibbles,
/// Value in the proof.
pub got: Option<Bytes>,
/// Expected value.
pub expected: Option<Bytes>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_error_size() {
let size = core::mem::size_of::<ProofVerificationError>();
eprintln!("ProofVerificationError size: {size} bytes");
// Down from 112 bytes to 24 after boxing both large variants. (The error was
// 144 bytes when #89 was filed; nybbles 0.4 had already shrunk `Nibbles` since.)
assert!(size <= 24, "ProofVerificationError is {size} bytes, should be <= 24");
}
}
2 changes: 1 addition & 1 deletion src/proof/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod verify;
pub use verify::verify_proof;

mod error;
pub use error::ProofVerificationError;
pub use error::{ProofVerificationError, RootMismatchError, ValueMismatchError};

mod decoded_proof_nodes;
pub use decoded_proof_nodes::DecodedProofNodes;
Expand Down
35 changes: 21 additions & 14 deletions src/proof/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
use crate::{
EMPTY_ROOT_HASH,
nodes::{BranchNode, CHILD_INDEX_RANGE, RlpNode, TrieNode},
proof::ProofVerificationError,
proof::{ProofVerificationError, RootMismatchError, ValueMismatchError},
};
use alloc::vec::Vec;
use alloc::{boxed::Box, vec::Vec};
use alloy_primitives::{B256, Bytes};
use alloy_rlp::{Decodable, EMPTY_STRING_CODE};
use core::ops::Deref;
Expand All @@ -32,14 +32,17 @@ where
if expected_value.is_none() {
Ok(())
} else {
Err(ProofVerificationError::ValueMismatch {
Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: key,
got: None,
expected: expected_value.map(Bytes::from),
})
})))
}
} else {
Err(ProofVerificationError::RootMismatch { got: EMPTY_ROOT_HASH, expected: root })
Err(ProofVerificationError::RootMismatch(Box::new(RootMismatchError {
got: EMPTY_ROOT_HASH,
expected: root,
})))
};
}

Expand All @@ -51,7 +54,11 @@ where
if Some(RlpNode::from_rlp(node).as_slice()) != last_decoded_node.as_deref() {
let got = Some(Bytes::copy_from_slice(node));
let expected = last_decoded_node.as_deref().map(Bytes::copy_from_slice);
return Err(ProofVerificationError::ValueMismatch { path: walked_path, got, expected });
return Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: walked_path,
got,
expected,
})));
}

// Decode the next node from the proof.
Expand All @@ -64,11 +71,11 @@ where
if last_decoded_node.as_deref() == expected_value.as_deref() {
Ok(())
} else {
Err(ProofVerificationError::ValueMismatch {
Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: key,
got: last_decoded_node.as_deref().map(Bytes::copy_from_slice),
expected: expected_value.map(Bytes::from),
})
})))
}
}

Expand Down Expand Up @@ -223,11 +230,11 @@ mod tests {
BranchNode::default().encode(&mut dummy_proof);
assert_eq!(
verify_proof(root, key, None, [&Bytes::from(dummy_proof.clone())]),
Err(ProofVerificationError::ValueMismatch {
Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: Nibbles::default(),
got: Some(Bytes::from(dummy_proof)),
expected: Some(Bytes::from(RlpNode::word_rlp(&EMPTY_ROOT_HASH)[..].to_vec()))
})
})))
);
}

Expand All @@ -254,21 +261,21 @@ mod tests {
assert_eq!(verify_proof(root, first_key, Some(first_value.clone()), &proof), Ok(()));
assert_eq!(
verify_proof(root, first_key, None, &proof),
Err(ProofVerificationError::ValueMismatch {
Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: first_key,
got: Some(first_value.into()),
expected: None,
})
})))
);

assert_eq!(verify_proof(root, second_key, Some(second_value.clone()), &proof), Ok(()));
assert_eq!(
verify_proof(root, second_key, None, &proof),
Err(ProofVerificationError::ValueMismatch {
Err(ProofVerificationError::ValueMismatch(Box::new(ValueMismatchError {
path: second_key,
got: Some(second_value.into()),
expected: None,
})
})))
);
}

Expand Down