use std::sync::Arc;
use freenet_stdlib::prelude::RelatedContracts;
use super::property::ConformanceProperty;
use super::verifier::{Bytes, ConformanceCase};
#[derive(Debug, Default, Clone)]
pub struct Corpus {
pub states: Vec<Bytes>,
pub deltas: Vec<Bytes>,
pub delta_bases: Vec<Option<Bytes>>,
pub summaries: Vec<Bytes>,
pub transitions: Vec<(Bytes, Bytes)>,
pub related: RelatedContracts<'static>,
}
impl Corpus {
pub fn from_states(states: Vec<Vec<u8>>) -> Self {
Self {
states: states
.into_iter()
.map(|s| Arc::from(s.as_slice()))
.collect(),
..Default::default()
}
}
pub fn deduplicated(mut self) -> Self {
self.states = dedup(self.states);
let (deltas, bases) = dedup_with_bases(self.deltas, self.delta_bases);
self.deltas = deltas;
self.delta_bases = bases;
self.summaries = dedup(self.summaries);
let mut seen = std::collections::HashSet::new();
self.transitions.retain(|(base, result)| {
seen.insert((
*blake3::hash(base).as_bytes(),
*blake3::hash(result).as_bytes(),
))
});
self
}
pub fn delta_base(&self, i: usize) -> Option<&Bytes> {
self.delta_bases.get(i).and_then(Option::as_ref)
}
pub fn is_empty(&self) -> bool {
self.states.is_empty() && self.transitions.is_empty()
}
}
fn dedup_with_bases(
deltas: Vec<Bytes>,
bases: Vec<Option<Bytes>>,
) -> (Vec<Bytes>, Vec<Option<Bytes>>) {
let mut seen: std::collections::HashMap<[u8; 32], usize> = std::collections::HashMap::new();
let mut kept_deltas: Vec<Bytes> = Vec::with_capacity(deltas.len());
let mut kept_bases: Vec<Option<Bytes>> = Vec::with_capacity(deltas.len());
for (i, delta) in deltas.into_iter().enumerate() {
let base = bases.get(i).cloned().flatten();
match seen.entry(*blake3::hash(&delta).as_bytes()) {
std::collections::hash_map::Entry::Vacant(slot) => {
slot.insert(kept_deltas.len());
kept_bases.push(base);
kept_deltas.push(delta);
}
std::collections::hash_map::Entry::Occupied(slot) => {
let at = *slot.get();
if kept_bases[at].is_none() {
kept_bases[at] = base;
}
}
}
}
(kept_deltas, kept_bases)
}
fn dedup(items: Vec<Bytes>) -> Vec<Bytes> {
let mut seen = std::collections::HashSet::new();
items
.into_iter()
.filter(|item| seen.insert(*blake3::hash(item).as_bytes()))
.collect()
}
#[derive(Debug, Clone)]
pub struct GeneratorConfig {
pub max_cases: usize,
pub properties: Vec<ConformanceProperty>,
pub max_case_bytes: usize,
pub max_states_paired: usize,
pub max_transitions: usize,
pub seed: u64,
}
impl Default for GeneratorConfig {
fn default() -> Self {
Self {
max_cases: 512,
properties: ConformanceProperty::ALL.to_vec(),
max_case_bytes: 4 * 1024 * 1024,
max_states_paired: 24,
max_transitions: 24,
seed: 0,
}
}
}
pub fn generate_cases(corpus: &Corpus, config: &GeneratorConfig) -> Vec<ConformanceCase> {
if corpus.is_empty() {
return Vec::new();
}
let mut queues: Vec<Vec<ConformanceCase>> = Vec::new();
for property in &config.properties {
queues.push(cases_for(*property, corpus, config));
}
let mut out = Vec::new();
let mut round = 0usize;
loop {
let mut produced = false;
for queue in &mut queues {
if round >= queue.len() {
continue;
}
produced = true;
out.push(queue[round].clone());
if out.len() >= config.max_cases {
return out;
}
}
if !produced {
return out;
}
round += 1;
}
}
fn cases_for(
property: ConformanceProperty,
corpus: &Corpus,
config: &GeneratorConfig,
) -> Vec<ConformanceCase> {
let states = paired_states(corpus, config);
let mut cases = Vec::new();
let mut push = |case: ConformanceCase| {
if case.input_bytes() <= config.max_case_bytes {
cases.push(case);
}
};
let build = |states: Vec<Bytes>| {
ConformanceCase::new(property, states).with_related(corpus.related.clone())
};
if property == ConformanceProperty::DeltaDeterminism {
for state in &states {
push(build(vec![state.clone()]));
for summary in &corpus.summaries {
push(build(vec![state.clone()]).with_summary(summary.clone()));
}
}
return cases;
}
if property == ConformanceProperty::TransitionPathAgreement {
for (base, result) in sampled_transitions(corpus, config) {
push(build(vec![base, result]));
}
return cases;
}
match property.state_arity() {
1 if property.delta_arity() == 0 => {
for state in &states {
push(build(vec![state.clone()]));
}
}
1 => {
for state in &states {
for pair in delta_pairs(corpus, property.delta_arity()) {
push(build(vec![state.clone()]).with_deltas(pair).clone());
}
}
}
2 => {
for (i, a) in states.iter().enumerate() {
for b in states.iter().skip(i + 1) {
push(build(vec![a.clone(), b.clone()]));
}
}
}
3 => {
let n = states.len();
if n >= 3 {
let target = config.max_cases.min(n.saturating_mul(2));
let mut emitted = 0;
'outer: for i in 0..n {
for j in 0..n {
if emitted >= target {
break 'outer;
}
if i == j {
continue;
}
let k = (i + j + 1 + config.seed as usize) % n;
if k == i || k == j {
continue;
}
push(build(vec![
states[i].clone(),
states[j].clone(),
states[k].clone(),
]));
emitted += 1;
}
}
}
}
_ => {}
}
cases
}
fn paired_states(corpus: &Corpus, config: &GeneratorConfig) -> Vec<Bytes> {
let n = corpus.states.len();
if n <= config.max_states_paired {
return corpus.states.clone();
}
let stride = n as f64 / config.max_states_paired as f64;
(0..config.max_states_paired)
.map(|i| corpus.states[((i as f64) * stride) as usize % n].clone())
.collect()
}
fn sampled_transitions(corpus: &Corpus, config: &GeneratorConfig) -> Vec<(Bytes, Bytes)> {
let n = corpus.transitions.len();
if n <= config.max_transitions {
return corpus.transitions.clone();
}
let stride = n as f64 / config.max_transitions as f64;
(0..config.max_transitions)
.map(|i| corpus.transitions[((i as f64) * stride) as usize % n].clone())
.collect()
}
fn delta_pairs(corpus: &Corpus, arity: usize) -> Vec<Vec<Bytes>> {
if corpus.deltas.len() < arity {
return Vec::new();
}
match arity {
1 => corpus.deltas.iter().map(|d| vec![d.clone()]).collect(),
2 => {
let mut pairs = Vec::new();
for (i, a) in corpus.deltas.iter().enumerate() {
let Some(base_a) = corpus.delta_base(i) else {
continue;
};
for (j, b) in corpus.deltas.iter().enumerate().skip(i + 1) {
let Some(base_b) = corpus.delta_base(j) else {
continue;
};
if base_a == base_b {
pairs.push(vec![a.clone(), b.clone()]);
}
}
}
pairs
}
_ => Vec::new(),
}
}