Skip to content

Commit

Permalink
fuzz: test vectors tests
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Dec 9, 2024
1 parent 21c7cc5 commit 458b2db
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 6 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

.DS_Store
.DS_Store

harness/tests/test-vectors/
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ num-format = "0.4.4"
prost = "0.10"
prost-build = "0.10"
prost-types = "0.10"
rayon = "1.10.0"
serde = "1.0.203"
serde_json = "1.0.117"
serial_test = "2.0"
Expand Down
1 change: 1 addition & 0 deletions harness/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ solana-timings = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }
rayon = { workspace = true }
serial_test = { workspace = true }

[[bench]]
Expand Down
9 changes: 4 additions & 5 deletions harness/src/fuzz/firedancer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn build_fixture_context(
let Mollusk {
compute_budget,
feature_set,
sysvars,
slot, // FD-Fuzz feature only.
..
} = mollusk;

Expand All @@ -100,9 +100,7 @@ fn build_fixture_context(
instruction_accounts,
instruction_data: instruction.data.clone(),
compute_units_available: compute_budget.compute_unit_limit,
slot_context: FuzzSlotContext {
slot: sysvars.clock.slot,
},
slot_context: FuzzSlotContext { slot: *slot },
epoch_context: FuzzEpochContext {
feature_set: feature_set.clone(),
},
Expand All @@ -118,8 +116,8 @@ fn parse_fixture_context(
instruction_accounts,
instruction_data,
compute_units_available,
slot_context,
epoch_context,
..
} = context;

let compute_budget = ComputeBudget {
Expand All @@ -135,6 +133,7 @@ fn parse_fixture_context(
let mollusk = Mollusk {
compute_budget,
feature_set: epoch_context.feature_set.clone(),
slot: slot_context.slot,
..Default::default()
};

Expand Down
4 changes: 4 additions & 0 deletions harness/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub struct Mollusk {
pub fee_structure: FeeStructure,
pub program_cache: ProgramCache,
pub sysvars: Sysvars,
#[cfg(feature = "fuzz-fd")]
pub slot: u64,
}

impl Default for Mollusk {
Expand Down Expand Up @@ -94,6 +96,8 @@ impl Default for Mollusk {
fee_structure: FeeStructure::default(),
program_cache: ProgramCache::default(),
sysvars: Sysvars::default(),
#[cfg(feature = "fuzz-fd")]
slot: 0,
}
}
}
Expand Down
159 changes: 159 additions & 0 deletions harness/tests/fd_test_vectors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#![cfg(feature = "fuzz-fd")]

use {
mollusk_svm::fuzz::firedancer::{build_fixture_from_mollusk_test, load_firedancer_fixture},
mollusk_svm_fuzz_fixture_firedancer::{account::SeedAddress, Fixture},
rayon::prelude::*,
solana_sdk::{
account::AccountSharedData, feature_set::FeatureSet, pubkey::Pubkey,
transaction_context::InstructionAccount,
},
std::{assert_eq, fs, path::Path, process::Command},
};

const TEST_VECTORS_PATH: &str = "tests/test-vectors";
const TEST_VECTORS_REPOSITORY: &str = "https://github.com/firedancer-io/test-vectors.git";
const TEST_VECTORS_TO_TEST: &[&str] = &[
"instr/fixtures/address-lookup-table",
"instr/fixtures/config",
"instr/fixtures/stake",
// Add more here!
];

#[test]
fn test_load_firedancer_fixtures() {
let test_vectors_out_dir = Path::new(TEST_VECTORS_PATH);

// Fetch the test vectors.
if test_vectors_out_dir.exists() {
Command::new("git")
.arg("-C")
.arg(test_vectors_out_dir)
.arg("fetch")
.status()
.expect("Failed to execute git pull");
} else {
Command::new("git")
.arg("clone")
.arg("--depth=1")
.arg(TEST_VECTORS_REPOSITORY)
.arg(test_vectors_out_dir)
.status()
.expect("Failed to execute git clone");
}

// Attempt to go fixture -> Mollusk -> fixture and compare.
TEST_VECTORS_TO_TEST.par_iter().for_each(|directory| {
fs::read_dir(test_vectors_out_dir.join(directory))
.unwrap()
.par_bridge()
.for_each(|entry| {
let path = entry.unwrap().path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "fix") {
let loaded_fixture = Fixture::load_from_blob_file(path.to_str().unwrap());
let (mollusk, instruction, accounts, result) =
load_firedancer_fixture(&loaded_fixture);
let generated_fixture = build_fixture_from_mollusk_test(
&mollusk,
&instruction,
&accounts,
&result,
/* checks */ &[],
);

assert_eq!(loaded_fixture.metadata, generated_fixture.metadata);
assert_eq!(
loaded_fixture.input.program_id,
generated_fixture.input.program_id,
);
// Sometimes ordering is not the same because of the `KeyMap`.
// Contents should match though.
compare_accounts(
&loaded_fixture.input.accounts,
&generated_fixture.input.accounts,
);
compare_instruction_accounts(
&loaded_fixture.input.instruction_accounts,
&generated_fixture.input.instruction_accounts,
);
assert_eq!(
loaded_fixture.input.compute_units_available,
generated_fixture.input.compute_units_available,
);
assert_eq!(
loaded_fixture.input.slot_context,
generated_fixture.input.slot_context,
);
// Feature set is not always ordered the same as a side effect
// of `HashMap`.
compare_feature_sets(
&loaded_fixture.input.epoch_context.feature_set,
&generated_fixture.input.epoch_context.feature_set,
);
assert_eq!(
loaded_fixture.output.program_result,
generated_fixture.output.program_result,
);
assert_eq!(
loaded_fixture.output.program_custom_code,
generated_fixture.output.program_custom_code,
);
compare_accounts(
&loaded_fixture.output.modified_accounts,
&generated_fixture.output.modified_accounts,
);
assert_eq!(
loaded_fixture.output.compute_units_available,
generated_fixture.output.compute_units_available,
);
// assert_eq!(
// loaded_fixture.output.return_data,
// generated_fixture.output.return_data,
// );
}
});
});
}

fn compare_accounts(
a: &[(Pubkey, AccountSharedData, Option<SeedAddress>)],
b: &[(Pubkey, AccountSharedData, Option<SeedAddress>)],
) -> bool {
if a.len() != b.len() {
return false;
}

let mut a_sorted = a.to_vec();
let mut b_sorted = b.to_vec();

// Sort by Pubkey
a_sorted.sort_by(|(pubkey_a, _, _), (pubkey_b, _, _)| pubkey_a.cmp(pubkey_b));
b_sorted.sort_by(|(pubkey_a, _, _), (pubkey_b, _, _)| pubkey_a.cmp(pubkey_b));

// Compare sorted lists
a_sorted == b_sorted
}

fn compare_instruction_accounts(a: &[InstructionAccount], b: &[InstructionAccount]) -> bool {
if a.len() != b.len() {
return false;
}

let mut a_sorted = a.to_vec();
let mut b_sorted = b.to_vec();

// Sort by Pubkey
a_sorted.sort_by(|ia_a, ia_b| ia_a.index_in_transaction.cmp(&ia_b.index_in_transaction));
b_sorted.sort_by(|ia_a, ia_b| ia_a.index_in_transaction.cmp(&ia_b.index_in_transaction));

// Compare sorted lists
a_sorted == b_sorted
}

fn compare_feature_sets(from_fixture: &FeatureSet, from_mollusk: &FeatureSet) {
assert_eq!(from_fixture.active.len(), from_mollusk.active.len());
assert_eq!(from_fixture.inactive.len(), from_mollusk.inactive.len());
for f in from_fixture.active.keys() {
assert!(from_mollusk.active.contains_key(f));
}
}

0 comments on commit 458b2db

Please sign in to comment.