-
Notifications
You must be signed in to change notification settings - Fork 1
New Target for BeaconVote Decode Encode #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
576bf60
f941a12
9bb6759
42ba64a
c4003cf
96e3c2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||
| use std::path::Path; | ||||
| use rand::{Rng, rngs::StdRng}; | ||||
| use ssz::{Encode, Decode}; | ||||
| use types::{Hash256, Checkpoint}; | ||||
| use ssv_types::consensus::BeaconVote; | ||||
| use crate::{CorpusGenerator, ValidationResult, CorpusUtils}; | ||||
|
|
||||
| pub struct BeaconVoteGenerator; | ||||
|
|
||||
| impl CorpusGenerator for BeaconVoteGenerator { | ||||
| fn name(&self) -> &'static str { | ||||
| "diff_fuzz_beacon_vote_decode_encode" | ||||
| } | ||||
|
|
||||
| fn description(&self) -> &'static str { | ||||
| "BeaconVote - Beacon vote data encoding/decoding" | ||||
| } | ||||
|
|
||||
| fn validate_corpus_entry(&self, data: &[u8]) -> Result<ValidationResult, Box<dyn std::error::Error>> { | ||||
| let critical_sizes: Vec<usize> = crate::common::SizeBoundaryUtils::get_critical_size_boundaries() | ||||
| .into_iter() | ||||
| .map(|(size, _)| size) | ||||
| .collect(); | ||||
|
|
||||
| if critical_sizes.contains(&data.len()) { | ||||
| return Ok(ValidationResult::Boundary); | ||||
| } | ||||
|
Comment on lines
+25
to
+27
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we return this here and stop validation?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My idea was, when the input length matches a critical boundary size, we don’t mark it as valid or invalid right away. Instead, we label it as
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean by critical? Why
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By critical, I mean sizes that are interesting for testing, not normal
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked this one
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I'm not sure we can generalize boundaries for different types. Wouldn't they be like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, the |
||||
|
|
||||
| if data.len() != 112 { | ||||
| return Ok(ValidationResult::Invalid); | ||||
| } | ||||
|
|
||||
| match BeaconVote::from_ssz_bytes(data) { | ||||
| Ok(_) => Ok(ValidationResult::Valid), | ||||
| Err(_) => Ok(ValidationResult::Invalid), | ||||
| } | ||||
| } | ||||
|
|
||||
| fn generate(&self, output_dir: &Path, rng: &mut StdRng) -> Result<usize, Box<dyn std::error::Error>> { | ||||
| let mut count = 0; | ||||
|
|
||||
| count += self.generate_valid_beacon_votes(output_dir, rng)?; | ||||
| count += self.generate_malformed_beacon_votes(output_dir, rng)?; | ||||
| count += self.generate_size_boundary_cases(output_dir, rng)?; | ||||
| count += self.generate_ssz_structure_edges(output_dir, rng)?; | ||||
|
|
||||
| Ok(count) | ||||
| } | ||||
| } | ||||
|
|
||||
| impl BeaconVoteGenerator { | ||||
| fn generate_valid_beacon_votes(&self, output_dir: &Path, rng: &mut StdRng) -> Result<usize, Box<dyn std::error::Error>> { | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The usize in the return type is the count of files generated (how many valid beacon votes were written to the corpus). I just follow the previous generator implementations because all of them return the same output (
|
||||
| let mut count = 0; | ||||
| let vote_cases = vec![ | ||||
| ("standard_vote", (10, 11)), | ||||
| ("near_epoch_vote", (15, 16)), | ||||
| ("far_epoch_vote", (5, 20)), | ||||
| ("zero_epoch_vote", (0, 1)), | ||||
| ("large_epoch_vote", (1000, 1001)), | ||||
| ]; | ||||
|
|
||||
| for (name, (source_epoch, target_epoch)) in vote_cases { | ||||
| let vote = self.create_valid_beacon_vote(rng, source_epoch, target_epoch)?; | ||||
| let bytes = vote.as_ssz_bytes(); | ||||
| CorpusUtils::save_corpus_file(output_dir, &format!("beacon_vote_valid_{}", name), &bytes)?; | ||||
| count += 1; | ||||
| } | ||||
|
|
||||
| Ok(count) | ||||
| } | ||||
|
|
||||
| fn create_valid_beacon_vote(&self, rng: &mut StdRng, source_epoch: u64, target_epoch: u64) -> Result<BeaconVote, Box<dyn std::error::Error>> { | ||||
| Ok(BeaconVote { | ||||
| block_root: Hash256::new(rng.gen::<[u8; 32]>()), | ||||
| source: Checkpoint { | ||||
| epoch: types::Epoch::new(source_epoch), | ||||
| root: Hash256::new(rng.gen::<[u8; 32]>()), | ||||
| }, | ||||
| target: Checkpoint { | ||||
| epoch: types::Epoch::new(target_epoch), | ||||
| root: Hash256::new(rng.gen::<[u8; 32]>()), | ||||
| }, | ||||
| }) | ||||
| } | ||||
|
|
||||
| fn generate_malformed_beacon_votes( | ||||
| &self, | ||||
| output_dir: &Path, | ||||
| rng: &mut StdRng, | ||||
| ) -> Result<usize, Box<dyn std::error::Error>> { | ||||
| let mut count = 0; | ||||
| let base_vote = self.create_valid_beacon_vote(rng, 10, 11)?; | ||||
|
|
||||
| // Precompute all malformed votes | ||||
| let invalid_epoch_order = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.source.epoch = types::Epoch::new(11); | ||||
| vote.target.epoch = types::Epoch::new(10); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let zero_block_root = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.block_root = Hash256::repeat_byte(0); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let zero_source_root = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.source.root = Hash256::repeat_byte(0); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let zero_target_root = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.target.root = Hash256::repeat_byte(0); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let all_ones_block_root = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.block_root = Hash256::repeat_byte(0xFF); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let extreme_epoch = { | ||||
| let mut vote = base_vote.clone(); | ||||
| vote.source.epoch = types::Epoch::new(u64::MAX); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| let random_corruption = { | ||||
| let mut vote = base_vote.clone(); | ||||
| let mut random_bytes = rng.gen::<[u8; 32]>(); | ||||
| random_bytes[0] = 0xFF; | ||||
| vote.block_root = Hash256::new(random_bytes); | ||||
| vote.as_ssz_bytes() | ||||
| }; | ||||
|
|
||||
| // Vector of (name, data) | ||||
| let malformed_cases = vec![ | ||||
| ("invalid_epoch_order", invalid_epoch_order), | ||||
| ("zero_block_root", zero_block_root), | ||||
| ("zero_source_root", zero_source_root), | ||||
| ("zero_target_root", zero_target_root), | ||||
| ("all_ones_block_root", all_ones_block_root), | ||||
| ("extreme_epoch", extreme_epoch), | ||||
| ("random_corruption", random_corruption), | ||||
| ]; | ||||
|
|
||||
| for (name, bytes) in malformed_cases { | ||||
| CorpusUtils::save_corpus_file(output_dir, &format!("beacon_vote_malformed_{}", name), &bytes)?; | ||||
| count += 1; | ||||
| } | ||||
|
|
||||
| Ok(count) | ||||
| } | ||||
|
|
||||
|
|
||||
| fn generate_size_boundary_cases(&self, output_dir: &Path, rng: &mut StdRng) -> Result<usize, Box<dyn std::error::Error>> { | ||||
| let mut count = 0; | ||||
| let boundaries = crate::common::SizeBoundaryUtils::get_critical_size_boundaries(); | ||||
|
|
||||
| for (size, name) in boundaries { | ||||
| let data = if size < 32 { | ||||
| CorpusUtils::generate_random_data(size, rng) | ||||
| } else { | ||||
| let vote = self.create_valid_beacon_vote(rng, 10, 11)?; | ||||
| let mut bytes = vote.as_ssz_bytes(); | ||||
| while bytes.len() < size { | ||||
| bytes.push(rng.gen()); | ||||
| } | ||||
| bytes.truncate(size); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| CorpusUtils::save_corpus_file(output_dir, &format!("beacon_vote_boundary_{}_{}", name, size), &data)?; | ||||
| count += 1; | ||||
| } | ||||
|
|
||||
| Ok(count) | ||||
| } | ||||
|
|
||||
| fn generate_ssz_structure_edges(&self, output_dir: &Path, rng: &mut StdRng) -> Result<usize, Box<dyn std::error::Error>> { | ||||
| let mut count = 0; | ||||
| let base_vote = self.create_valid_beacon_vote(rng, 10, 11)?; | ||||
| let base_bytes = base_vote.as_ssz_bytes(); | ||||
|
|
||||
| // Precompute all edge cases to avoid macro errors | ||||
| let invalid_offsets = { | ||||
| let mut bytes = base_bytes.clone(); | ||||
| if bytes.len() < 4 { bytes.resize(4, 0); } | ||||
| bytes[0..4].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| let truncated_data = { | ||||
| let len = base_bytes.len().min(100); | ||||
| base_bytes[..len].to_vec() | ||||
| }; | ||||
|
|
||||
| let wrong_length_indicator = { | ||||
| let mut bytes = base_bytes.clone(); | ||||
| if bytes.len() < 4 { bytes.resize(4, 0); } | ||||
| bytes[0..4].copy_from_slice(&0x0000_0000u32.to_le_bytes()); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| let partial_field_corruption = { | ||||
| let mut bytes = base_bytes.clone(); | ||||
| if bytes.len() < 32 { bytes.resize(32, 0); } | ||||
| bytes[16..32].fill(0xAA); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| let oversized_data = { | ||||
| let mut bytes = base_bytes.clone(); | ||||
| bytes.extend_from_slice(&[0u8; 1024]); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| let randomized_field = { | ||||
| let mut bytes = base_bytes.clone(); | ||||
| if bytes.len() < 32 { bytes.resize(32, 0); } | ||||
| bytes[0..32].copy_from_slice(&rng.gen::<[u8; 32]>()); | ||||
| bytes | ||||
| }; | ||||
|
|
||||
| let edge_cases = vec![ | ||||
| ("invalid_offsets", invalid_offsets), | ||||
| ("truncated_data", truncated_data), | ||||
| ("wrong_length_indicator", wrong_length_indicator), | ||||
| ("partial_field_corruption", partial_field_corruption), | ||||
| ("oversized_data", oversized_data), | ||||
| ("randomized_field", randomized_field), | ||||
| ]; | ||||
|
|
||||
| for (name, data) in edge_cases { | ||||
| CorpusUtils::save_corpus_file(output_dir, &format!("beacon_vote_ssz_{}", name), &data)?; | ||||
| count += 1; | ||||
| } | ||||
|
|
||||
| Ok(count) | ||||
| } | ||||
| } | ||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to this PR, but not sure
get_critical_size_boundariesis a descriptive name