use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use freenet_stdlib::prelude::{ContractInstanceId, ContractKey};
use tokio::sync::mpsc;
use super::bundle::ReplayBundle;
use super::sampler::{Admission, ContractSampler, SamplerConfig};
pub const CAPTURE_DIR_ENV: &str = "FREENET_CONFORMANCE_CAPTURE_DIR";
pub const CAPTURE_MAX_BYTES_ENV: &str = "FREENET_CONFORMANCE_CAPTURE_MAX_BYTES";
fn sampler_config() -> SamplerConfig {
sampler_config_from(std::env::var(CAPTURE_MAX_BYTES_ENV).ok().as_deref())
}
fn sampler_config_from(raw: Option<&str>) -> SamplerConfig {
let mut config = SamplerConfig::default();
if let Some(bytes) = raw
.and_then(|raw| raw.trim().parse::<usize>().ok())
.filter(|bytes| *bytes > 0)
{
config.max_bytes = bytes;
config.max_state_bytes = (bytes / 4).max(1);
}
config
}
const OBSERVATION_QUEUE: usize = 256;
const MAX_QUEUED_BYTES: usize = 8 * 1024 * 1024;
fn related_budgets(config: &SamplerConfig) -> (usize, usize) {
(config.max_state_bytes, config.max_bytes)
}
const MAX_RELATED_CONTRACTS: usize = 8;
const MAX_TRACKED_CONTRACTS: usize = 64;
const HOSTED_SOURCE_WARMUP: std::time::Duration = std::time::Duration::from_secs(5);
const HOSTED_SOURCE_WARMUP_ATTEMPTS: u32 = 12;
const FLUSH_EVERY_OBSERVATIONS: usize = 32;
const FLUSH_EVERY: std::time::Duration = std::time::Duration::from_secs(60);
#[derive(Debug)]
pub struct Observation {
pub contract: ContractInstanceId,
pub code_hash: [u8; 32],
pub parameters: Vec<u8>,
pub base_state: Vec<u8>,
pub incoming_state: Option<Vec<u8>>,
pub delta: Option<Vec<u8>>,
pub result_state: Vec<u8>,
pub related: Vec<(ContractInstanceId, Vec<u8>)>,
}
impl Observation {
fn queued_bytes(&self) -> usize {
self.parameters.len()
+ self.base_state.len()
+ self.incoming_state.as_ref().map_or(0, Vec::len)
+ self.delta.as_ref().map_or(0, Vec::len)
+ self.result_state.len()
+ self
.related
.iter()
.map(|(_, state)| state.len())
.sum::<usize>()
}
}
#[derive(Debug)]
pub(crate) enum CaptureMsg {
Transition(Box<Observation>),
Related {
contract: ContractInstanceId,
related: Vec<(ContractInstanceId, Vec<u8>)>,
},
}
impl CaptureMsg {
fn queued_bytes(&self) -> usize {
match self {
CaptureMsg::Transition(observation) => observation.queued_bytes(),
CaptureMsg::Related { related, .. } => {
related.iter().map(|(_, state)| state.len()).sum()
}
}
}
}
#[derive(Clone)]
pub struct CaptureHandle {
tx: mpsc::Sender<CaptureMsg>,
dropped: Arc<AtomicU64>,
queued_bytes: Arc<AtomicUsize>,
}
impl CaptureHandle {
pub fn observe_with(&self, size_hint: usize, build: impl FnOnce() -> Observation) {
let queued = self.queued_bytes.load(Ordering::Relaxed);
if size_hint > MAX_QUEUED_BYTES || queued.saturating_add(size_hint) > MAX_QUEUED_BYTES {
self.dropped.fetch_add(1, Ordering::Relaxed);
return;
}
match self.tx.try_reserve() {
Ok(permit) => {
let msg = CaptureMsg::Transition(Box::new(build()));
self.queued_bytes
.fetch_add(msg.queued_bytes(), Ordering::Relaxed);
permit.send(msg);
}
Err(_) => {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn observe_related_with(
&self,
contract: ContractInstanceId,
size_hint: usize,
build: impl FnOnce() -> Vec<(ContractInstanceId, Vec<u8>)>,
) {
let queued = self.queued_bytes.load(Ordering::Relaxed);
if size_hint > MAX_QUEUED_BYTES || queued.saturating_add(size_hint) > MAX_QUEUED_BYTES {
self.dropped.fetch_add(1, Ordering::Relaxed);
return;
}
match self.tx.try_reserve() {
Ok(permit) => {
let related = build();
let msg = CaptureMsg::Related { contract, related };
self.queued_bytes
.fetch_add(msg.queued_bytes(), Ordering::Relaxed);
permit.send(msg);
}
Err(_) => {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn observe(&self, observation: Observation) {
let msg = CaptureMsg::Transition(Box::new(observation));
let bytes = msg.queued_bytes();
match self.tx.try_send(msg) {
Ok(()) => {
self.queued_bytes.fetch_add(bytes, Ordering::Relaxed);
}
Err(_) => {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
}
}
pub fn dropped(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
}
static CONTRACT_STORE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
pub fn set_contract_store(path: PathBuf) {
if global().is_none() {
return;
}
if let Err(rejected) = CONTRACT_STORE.set(path) {
if CONTRACT_STORE.get() != Some(&rejected) {
tracing::warn!(
registered = %CONTRACT_STORE.get().map(|p| p.display().to_string()).unwrap_or_default(),
rejected = %rejected.display(),
"conflicting contract-store paths registered for conformance; probes \
will look in the first one"
);
}
}
}
pub(crate) fn contract_store() -> Option<&'static PathBuf> {
CONTRACT_STORE.get()
}
type HostedContractsFn = Box<dyn Fn() -> Vec<ContractInstanceId> + Send + Sync>;
static HOSTED_CONTRACTS: std::sync::OnceLock<HostedContractsFn> = std::sync::OnceLock::new();
pub fn set_hosted_contracts_source(source: HostedContractsFn) {
if HOSTED_CONTRACTS.set(source).is_err() {
tracing::debug!("hosted-contract source already registered for conformance");
}
}
pub(crate) fn hosted_contracts() -> Option<Vec<ContractInstanceId>> {
HOSTED_CONTRACTS.get().map(|source| source())
}
static CAPTURE: std::sync::OnceLock<Option<CaptureHandle>> = std::sync::OnceLock::new();
pub fn global() -> Option<&'static CaptureHandle> {
CAPTURE.get_or_init(start_from_env).as_ref()
}
pub fn start_from_env() -> Option<CaptureHandle> {
let dir = capture_dir_from(std::env::var(CAPTURE_DIR_ENV).ok().as_deref())?;
match start(dir) {
Ok(handle) => Some(handle),
Err(err) => {
tracing::warn!(
error = %err,
"conformance capture requested but could not start; continuing without it"
);
None
}
}
}
fn capture_dir_from(raw: Option<&str>) -> Option<PathBuf> {
let raw = raw?;
if raw.trim().is_empty() {
return None;
}
Some(PathBuf::from(raw))
}
pub fn start(dir: PathBuf) -> std::io::Result<CaptureHandle> {
if tokio::runtime::Handle::try_current().is_err() {
return Err(std::io::Error::other(
"conformance capture must be started from within a tokio runtime",
));
}
std::fs::create_dir_all(&dir)?;
let (tx, rx) = mpsc::channel(OBSERVATION_QUEUE);
let dropped = Arc::new(AtomicU64::new(0));
let queued_bytes = Arc::new(AtomicUsize::new(0));
let handle = CaptureHandle {
tx,
dropped: dropped.clone(),
queued_bytes: queued_bytes.clone(),
};
tracing::info!(
directory = %dir.display(),
"conformance capture enabled: recording contract merges for offline replay"
);
crate::conformance::status::mark_enabled();
tokio::spawn(run_writer(dir, rx, dropped, queued_bytes));
Ok(handle)
}
async fn run_writer(
dir: PathBuf,
mut rx: mpsc::Receiver<CaptureMsg>,
dropped: Arc<AtomicU64>,
queued_bytes: Arc<AtomicUsize>,
) {
let mut samplers = reload(&dir);
if !samplers.is_empty() {
tracing::info!(
contracts = samplers.len(),
"conformance capture resumed from existing bundles"
);
}
let mut since_flush = 0usize;
let mut shadow = crate::conformance::shadow::ShadowRunner::new(
&dir,
crate::conformance::policy::EnforcementMode::default(),
);
let mut probe = tokio::time::interval(crate::conformance::shadow::PROBE_INTERVAL);
let mut in_flight: Option<
tokio::task::JoinHandle<(
crate::conformance::shadow::ShadowReport,
Vec<crate::conformance::shadow::Finding>,
)>,
> = None;
probe.reset();
probe.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
let mut flush = tokio::time::interval(FLUSH_EVERY);
flush.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
let mut warmup = tokio::time::interval(HOSTED_SOURCE_WARMUP);
warmup.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
let mut last_focus;
let mut related_untracked = 0u64;
let mut warmup_attempts = 0u32;
let mut awaiting_samples = 0usize;
let wide = wide_capture_requested();
let startup_focus = shadow.focus(
hosted_contracts(),
&samplers.keys().copied().collect::<Vec<_>>(),
);
let mut scope = if wide {
SamplingScope::Wide
} else {
SamplingScope::Focused(startup_focus.selected.iter().copied().collect())
};
last_focus = startup_focus;
loop {
tokio::select! {
received = rx.recv() => {
let Some(msg) = received else { break };
queued_bytes.fetch_sub(msg.queued_bytes(), Ordering::Relaxed);
let observation = match msg {
CaptureMsg::Transition(observation) => *observation,
CaptureMsg::Related { contract, related } => {
match record_related(&mut samplers, &scope, contract, &related) {
RelatedOutcome::Recorded => {}
RelatedOutcome::OutOfFocus => continue,
RelatedOutcome::Untracked => {
related_untracked += 1;
continue;
}
}
since_flush += 1;
if since_flush >= FLUSH_EVERY_OBSERVATIONS {
write_all(
&dir,
&samplers,
dropped.load(Ordering::Relaxed),
related_untracked,
)
.await;
since_flush = 0;
}
continue;
}
};
if let Some(evicted) = record(&mut samplers, &scope, observation) {
let path = bundle_path(&dir, &evicted);
if let Err(err) = tokio::fs::remove_file(&path).await {
if err.kind() != std::io::ErrorKind::NotFound {
tracing::debug!(
path = %path.display(),
error = %err,
"could not remove an evicted conformance bundle"
);
}
}
}
since_flush += 1;
if since_flush >= FLUSH_EVERY_OBSERVATIONS {
write_all(
&dir,
&samplers,
dropped.load(Ordering::Relaxed),
related_untracked,
)
.await;
since_flush = 0;
}
}
_ = warmup.tick(),
if crate::conformance::shadow::needs_hosted_warmup(
wide, &last_focus, warmup_attempts, HOSTED_SOURCE_WARMUP_ATTEMPTS) => {
warmup_attempts += 1;
let focus = shadow.focus(
hosted_contracts(),
&samplers.keys().copied().collect::<Vec<_>>(),
);
if focus.source == crate::conformance::shadow::CandidateSource::Hosted {
tracing::debug!(
candidates = focus.candidates,
focused = focus.selected.len(),
"conformance capture picked up the hosted-contract source"
);
}
scope = SamplingScope::Focused(focus.selected.iter().copied().collect());
last_focus = focus;
}
_ = flush.tick() => {
write_all(
&dir,
&samplers,
dropped.load(Ordering::Relaxed),
related_untracked,
)
.await;
since_flush = 0;
}
_ = probe.tick(), if in_flight.is_none() => {
shadow.advance();
let focus = shadow.focus(
hosted_contracts(),
&samplers.keys().copied().collect::<Vec<_>>(),
);
if !matches!(scope, SamplingScope::Wide) {
scope = SamplingScope::Focused(focus.selected.iter().copied().collect());
}
last_focus = focus.clone();
let (work, awaiting) = shadow.select(&focus, &samplers);
awaiting_samples = awaiting;
if work.is_empty() {
tracing::info!(
epoch = shadow.epoch(),
tracked = samplers.len(),
candidates = focus.candidates,
candidate_source = focus.source.as_str(),
focused = focus.selected.len(),
scope = scope.as_str(),
judged = 0,
without_verdict = awaiting_samples,
"conformance shadow tick selected nothing to probe"
);
crate::conformance::status::publish(
Vec::new(),
0,
awaiting_samples,
tokio::time::Instant::now(),
);
} else {
let store = contract_store().cloned();
let mode = shadow.mode();
in_flight = Some(tokio::spawn(crate::conformance::shadow::probe(
work, store, mode,
)));
}
}
Some(finished) = async {
match in_flight.as_mut() {
Some(handle) => Some(handle.await),
None => None,
}
}, if in_flight.is_some() => {
in_flight = None;
let (mut report, findings) = match finished {
Ok(result) => result,
Err(err) => {
tracing::warn!(error = %err, "conformance shadow probe failed");
continue;
}
};
crate::conformance::shadow::count_awaiting_samples(
&mut report,
awaiting_samples,
);
shadow.record(&findings);
tracing::info!(
epoch = shadow.epoch(),
tracked = samplers.len(),
tracking_at_cap = samplers.len() >= MAX_TRACKED_CONTRACTS,
candidates = last_focus.candidates,
candidate_source = last_focus.source.as_str(),
scope = scope.as_str(),
focused = report.focused,
probed = report.probed,
cases = report.cases,
inconclusive = report.inconclusive,
would_remove = report.would_remove,
reported = report.reported,
skipped_no_code = report.skipped_no_code,
skipped_no_samples = report.skipped_no_samples,
timed_out = report.timed_out,
judged = report.judged.len(),
without_verdict = report.without_verdict,
"conformance shadow tick"
);
crate::conformance::status::publish(
crate::conformance::status::checked_contracts(&report.judged, &findings),
report.judged.len(),
report.without_verdict,
tokio::time::Instant::now(),
);
}
}
}
write_all(
&dir,
&samplers,
dropped.load(Ordering::Relaxed),
related_untracked,
)
.await;
}
pub(crate) struct TrackedContract {
sampler: ContractSampler,
code_hash: [u8; 32],
parameters: Vec<u8>,
pub(crate) refused_related: RelatedRefusals,
refused_too_large: u64,
related: HashMap<ContractInstanceId, Vec<u8>>,
}
fn reload(dir: &Path) -> HashMap<ContractInstanceId, TrackedContract> {
let mut samplers = HashMap::new();
let Ok(entries) = std::fs::read_dir(dir) else {
return samplers;
};
let mut entries: Vec<_> = entries.flatten().collect();
entries.sort_by_key(|entry| entry.file_name());
for entry in entries {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("bundle") {
continue;
}
let bundle = match ReplayBundle::read_from(&path) {
Ok(bundle) => bundle,
Err(err) => {
tracing::warn!(error = %err, path = %path.display(), "skipping unreadable capture bundle");
continue;
}
};
let (Some(instance), Some(code_hash)) = (bundle.instance, bundle.code_hash) else {
continue;
};
if samplers.len() >= MAX_TRACKED_CONTRACTS {
break;
}
let mut sampler = ContractSampler::new(sampler_config());
for state in &bundle.states {
sampler.observe_state(state);
}
for transition in &bundle.transitions {
sampler.observe_transition(
&transition.base_state,
transition.incoming_state.as_deref(),
transition.delta.as_deref(),
transition.summary.as_deref(),
&transition.result_state,
);
}
let (max_related_state, max_related_total) = related_budgets(sampler.config());
let mut reload_refusals = RelatedRefusals::default();
samplers.insert(
instance,
TrackedContract {
sampler,
code_hash,
related: {
let mut restored = HashMap::new();
admit_related(
&mut restored,
&bundle.related,
&mut reload_refusals,
max_related_state,
max_related_total,
);
restored
},
parameters: bundle.parameters,
refused_too_large: 0,
refused_related: reload_refusals,
},
);
}
samplers
}
#[derive(Debug, Clone)]
pub(crate) enum SamplingScope {
Focused(std::collections::HashSet<ContractInstanceId>),
Wide,
}
impl SamplingScope {
fn admits(&self, contract: &ContractInstanceId) -> bool {
match self {
SamplingScope::Focused(focus) => focus.contains(contract),
SamplingScope::Wide => true,
}
}
pub(crate) fn as_str(&self) -> &'static str {
match self {
SamplingScope::Focused(_) => "focused",
SamplingScope::Wide => "wide",
}
}
}
pub const CAPTURE_WIDE_ENV: &str = "FREENET_CONFORMANCE_CAPTURE_WIDE";
fn wide_capture_requested() -> bool {
matches!(
std::env::var(CAPTURE_WIDE_ENV).ok().as_deref(),
Some("1") | Some("true")
)
}
pub(crate) fn bundle_path(dir: &Path, instance: &ContractInstanceId) -> PathBuf {
dir.join(format!("{instance}.bundle"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RelatedOutcome {
Recorded,
OutOfFocus,
Untracked,
}
#[must_use]
pub(crate) fn record_related(
samplers: &mut HashMap<ContractInstanceId, TrackedContract>,
scope: &SamplingScope,
contract: ContractInstanceId,
related: &[(ContractInstanceId, Vec<u8>)],
) -> RelatedOutcome {
if !scope.admits(&contract) {
return RelatedOutcome::OutOfFocus;
}
let Some(tracked) = samplers.get_mut(&contract) else {
return RelatedOutcome::Untracked;
};
let (max_state, max_total) = related_budgets(tracked.sampler.config());
admit_related(
&mut tracked.related,
related,
&mut tracked.refused_related,
max_state,
max_total,
);
RelatedOutcome::Recorded
}
#[must_use = "the evicted contract's bundle must be deleted, or it is orphaned on disk"]
pub(crate) fn record(
samplers: &mut HashMap<ContractInstanceId, TrackedContract>,
scope: &SamplingScope,
observation: Observation,
) -> Option<ContractInstanceId> {
let known = samplers.contains_key(&observation.contract);
if !scope.admits(&observation.contract) {
return None;
}
let mut evicted = None;
if !known && samplers.len() >= MAX_TRACKED_CONTRACTS {
let evictable = match scope {
SamplingScope::Focused(focus) => samplers
.keys()
.filter(|id| !focus.contains(id))
.min_by(|a, b| a.as_bytes().cmp(b.as_bytes()))
.copied(),
SamplingScope::Wide => None,
};
match evictable {
Some(victim) => {
samplers.remove(&victim);
evicted = Some(victim);
}
None => {
if matches!(scope, SamplingScope::Focused(_)) {
tracing::warn!(
contract = %observation.contract,
tracked = samplers.len(),
"conformance capture could not make room for a focused \
contract: every tracked contract is in focus"
);
}
return None;
}
}
}
let tracked = samplers
.entry(observation.contract)
.or_insert_with(|| TrackedContract {
sampler: ContractSampler::new(sampler_config()),
code_hash: observation.code_hash,
parameters: observation.parameters.clone(),
refused_too_large: 0,
refused_related: RelatedRefusals::default(),
related: HashMap::new(),
});
let (max_related_state, max_related_total) = related_budgets(tracked.sampler.config());
admit_related(
&mut tracked.related,
&observation.related,
&mut tracked.refused_related,
max_related_state,
max_related_total,
);
let admission = tracked.sampler.observe_transition(
&observation.base_state,
observation.incoming_state.as_deref(),
observation.delta.as_deref(),
None,
&observation.result_state,
);
if matches!(admission, Admission::TooLarge) {
tracked.refused_too_large += 1;
}
evicted
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RelatedRefusals {
pub too_large: u64,
pub over_budget: u64,
pub no_slot: u64,
}
impl RelatedRefusals {
pub(crate) fn total(&self) -> u64 {
self.too_large + self.over_budget + self.no_slot
}
}
fn admit_related(
held: &mut HashMap<ContractInstanceId, Vec<u8>>,
offered: &[(ContractInstanceId, Vec<u8>)],
refused: &mut RelatedRefusals,
max_state_bytes: usize,
max_total_bytes: usize,
) {
for (instance, state) in offered {
if state.len() > max_state_bytes {
refused.too_large += 1;
continue;
}
if !held.contains_key(instance) && held.len() >= MAX_RELATED_CONTRACTS {
refused.no_slot += 1;
continue;
}
let total: usize = held.values().map(Vec::len).sum();
let replacing = held.get(instance).map_or(0, Vec::len);
if total - replacing + state.len() > max_total_bytes {
refused.over_budget += 1;
continue;
}
held.insert(*instance, state.clone());
}
}
pub(crate) fn bundle_for(
instance: ContractInstanceId,
tracked: &TrackedContract,
) -> crate::conformance::bundle::ReplayBundle {
let mut bundle =
tracked
.sampler
.to_bundle(None, Some(tracked.code_hash), tracked.parameters.clone());
bundle.instance = Some(instance);
let mut related: Vec<(ContractInstanceId, Vec<u8>)> = tracked
.related
.iter()
.map(|(id, state)| (*id, state.clone()))
.collect();
related.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
bundle.related = related;
bundle
}
async fn write_all(
dir: &Path,
samplers: &HashMap<ContractInstanceId, TrackedContract>,
dropped: u64,
related_untracked: u64,
) {
for (instance, tracked) in samplers {
let mut bundle = bundle_for(*instance, tracked);
let refused = tracked.refused_related;
bundle.note = Some(format!(
"captured by freenet {} ({} observation(s) dropped node-wide{}){}",
env!("CARGO_PKG_VERSION"),
dropped,
if related_untracked == 0 {
String::new()
} else {
format!(
", {related_untracked} validation-related message(s) discarded for \
untracked contracts"
)
},
if refused.total() == 0 {
String::new()
} else {
let mut extra = format!(
" (related state refused: {} too large, {} over budget, {} no slot.",
refused.too_large, refused.over_budget, refused.no_slot,
);
extra.push_str(" This corpus may be unable to reach a verdict for this contract.");
if refused.too_large > 0 || refused.over_budget > 0 {
extra.push_str(&format!(" Raise {CAPTURE_MAX_BYTES_ENV} to capture it."));
}
if refused.no_slot > 0 {
extra.push_str(&format!(
" {} related contract(s) exceeded the {}-contract limit, which no configuration can raise.",
refused.no_slot, MAX_RELATED_CONTRACTS,
));
}
extra.push(')');
extra
},
));
if refused.total() > 0 {
tracing::warn!(
contract = %instance,
too_large = refused.too_large,
over_budget = refused.over_budget,
no_slot = refused.no_slot,
related_held = tracked.related.len(),
remedy = match (
refused.too_large > 0 || refused.over_budget > 0,
refused.no_slot > 0,
) {
(true, true) => "raise the byte budget; some refusals are also \
capped by the related-contract COUNT limit, which \
is not configurable",
(true, false) => "raise the byte budget",
(false, true) => "none: the related-contract COUNT limit bound, \
which is not configurable",
(false, false) => "none",
},
byte_budget_env = CAPTURE_MAX_BYTES_ENV,
"conformance capture refused related-contract state; replays of this \
contract may reach no verdict"
);
}
if bundle.states.is_empty() {
tracing::warn!(
contract = %instance,
refused_too_large = tracked.refused_too_large,
"conformance capture retained nothing for this contract: its states \
exceed the per-state ceiling. Raise \
FREENET_CONFORMANCE_CAPTURE_MAX_BYTES to sample it."
);
continue;
}
let path = bundle_path(dir, instance);
match bundle.encode() {
Ok(bytes) => {
let temporary = path.with_extension("bundle.tmp");
let write_then_rename = async {
tokio::fs::write(&temporary, bytes).await?;
tokio::fs::rename(&temporary, &path).await
};
if let Err(err) = write_then_rename.await {
drop(tokio::fs::remove_file(&temporary).await);
tracing::warn!(
error = %err,
path = %path.display(),
"could not write capture bundle"
);
}
}
Err(err) => {
tracing::warn!(
error = %err,
path = %path.display(),
"could not encode capture bundle"
);
}
}
}
if dropped > 0 {
tracing::info!(
dropped,
contracts = samplers.len(),
"conformance capture flushed (dropped count is observations the writer could not keep up with)"
);
}
}
pub fn code_hash_of(key: &ContractKey) -> [u8; 32] {
let mut out = [0u8; 32];
let bytes: &[u8] = key.code_hash().as_ref();
let len = out.len().min(bytes.len());
out[..len].copy_from_slice(&bytes[..len]);
out
}
#[cfg(test)]
mod validation_related_capture_pin {
fn blank_string_literals(src: &str) -> String {
fn blank(out: &mut String, ch: char) {
for _ in 0..ch.len_utf8() {
out.push(' ');
}
}
let mut out = String::with_capacity(src.len());
let mut chars = src.chars();
let mut in_string = false;
while let Some(ch) = chars.next() {
match ch {
'\\' if in_string => {
blank(&mut out, ch);
if let Some(escaped) = chars.next() {
blank(&mut out, escaped);
}
}
'"' => {
in_string = !in_string;
blank(&mut out, ch);
}
_ if in_string => blank(&mut out, ch),
_ => out.push(ch),
}
}
out
}
fn fetch_related_body() -> &'static str {
let src = include_str!("../contract/executor/runtime/contract_ops.rs");
let start = src
.find("async fn fetch_related_for_validation_network(")
.expect("fetch_related_for_validation_network not found in contract_ops.rs");
let after = &src[start..];
let open = after.find('{').expect("function has no body");
let scan = blank_string_literals(after);
let mut depth = 0usize;
for (offset, ch) in scan[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &after[..open + offset + 1];
}
}
_ => {}
}
}
panic!("fetch_related_for_validation_network's body is not brace-balanced");
}
fn code_only() -> String {
fetch_related_body()
.lines()
.map(|line| match line.find("//") {
Some(at) => &line[..at],
None => line,
})
.collect::<Vec<_>>()
.join("\n")
}
fn production_fetch_related_body() -> &'static str {
let src = include_str!("../contract/executor/runtime/executor_impl.rs");
let start = src
.find(" async fn fetch_related_for_validation(")
.expect("fetch_related_for_validation not found in executor_impl.rs");
let after = &src[start..];
let open = after.find('{').expect("function has no body");
let scan = blank_string_literals(after);
let mut depth = 0usize;
for (offset, ch) in scan[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &after[..open + offset + 1];
}
}
_ => {}
}
}
panic!("fetch_related_for_validation's body is not brace-balanced");
}
fn production_code_only() -> String {
production_fetch_related_body()
.lines()
.map(|line| match line.find("//") {
Some(at) => &line[..at],
None => line,
})
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn the_production_executor_captures_validation_resolved_related_state() {
let body = production_code_only();
assert!(
body.contains("observe_related_with("),
"`fetch_related_for_validation` in executor_impl.rs no longer hands \
validation-resolved related state to capture. This is the implementation a \
NETWORK peer runs, so without it contracts whose validity depends on \
another contract are unjudgeable on every real capture — and the \
local-mode pin below would still pass, which is how this shipped wrong \
the first time"
);
}
#[test]
fn the_executor_captures_validation_resolved_related_state() {
let body = code_only();
assert!(
body.contains("observe_related_with("),
"the executor no longer hands validation-resolved related state to \
capture, so contracts whose VALIDITY depends on another contract go back \
to being unjudgeable — and an unjudgeable contract reads as a clean one"
);
}
}
#[cfg(test)]
mod doc_attachment_pin {
const PAIRINGS: &[(&str, &str)] = &[
(
"/// Fold validation-resolved related state into a contract already being tracked.",
"pub(crate) fn record_related",
),
(
"/// What became of a validation-resolved related-state message.",
"pub(crate) enum RelatedOutcome",
),
(
"/// Fold one observation into the sampler map.",
"pub(crate) fn record",
),
(
"/// Related state a contract offered and capture would not keep.",
"pub(crate) struct RelatedRefusals",
),
(
"/// Offer an observation, building it only if there is somewhere to put it.",
"pub fn observe_with",
),
(
"/// Record related-contract state resolved during validation.",
"pub fn observe_related_with",
),
];
#[test]
fn every_doc_block_is_attached_to_the_item_it_describes() {
let src = include_str!("capture.rs");
for (doc, item) in PAIRINGS {
let at = src.find(doc).unwrap_or_else(|| {
panic!("doc line not found, so this pin no longer checks anything: {doc}")
});
let mut rest = src[at..].lines();
rest.next();
let landed = rest
.find(|line| {
let t = line.trim_start();
!t.starts_with("///") && !t.starts_with("#[") && !t.is_empty()
})
.unwrap_or("<end of file>");
assert!(
landed.trim_start().starts_with(item),
"doc block {doc:?} is attached to {landed:?}, not to {item:?}. An item \
was inserted between the doc and what it describes, so the doc now \
documents the wrong thing and the original item has none."
);
}
}
}
#[cfg(test)]
mod contract_store_registration_pin {
fn code_only(body: &str) -> String {
body.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n")
}
fn get_runtime_stores_body() -> &'static str {
let src = include_str!("../contract/executor.rs");
let start = src
.find(" pub(crate) fn get_runtime_stores(")
.expect("get_runtime_stores not found in executor.rs");
let after = &src[start..];
let open = after.find('{').expect("get_runtime_stores has no body");
let mut depth = 0usize;
for (offset, ch) in after[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &after[..open + offset + 1];
}
}
_ => {}
}
}
panic!("get_runtime_stores' body is not brace-balanced");
}
fn ring_new_body() -> &'static str {
let src = include_str!("../ring.rs");
let start = src
.find(" pub fn new<ER: NetEventRegister>(")
.expect("Ring::new not found in ring.rs");
let after = &src[start..];
let open = after.find('{').expect("Ring::new has no body");
let mut depth = 0usize;
for (offset, ch) in after[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &after[..open + offset + 1];
}
}
_ => {}
}
}
panic!("Ring::new's body is not brace-balanced");
}
#[test]
fn the_ring_registers_its_hosted_contracts_for_conformance() {
let body = code_only(ring_new_body());
assert!(
body.contains("set_hosted_contracts_source("),
"the ring no longer tells conformance how to list hosted contracts, so \
focus selection falls back to whatever the sampler already held (#5366)"
);
}
#[test]
fn the_hosted_contracts_source_holds_only_a_weak_reference() {
let body = code_only(ring_new_body());
let start = body
.find("set_hosted_contracts_source(")
.expect("registration missing; the sibling pin covers that");
let before = &body[..start];
assert!(
before.contains("Arc::downgrade(&ring)"),
"the hosted-contract source does not downgrade the ring first, so the \
process-global closure may be keeping the ring alive after teardown"
);
}
#[test]
fn the_executor_registers_the_contract_store_for_conformance() {
let body = code_only(get_runtime_stores_body());
assert!(
body.contains("set_contract_store("),
"the executor no longer registers its contract store with conformance, so \
every shadow probe will fail to resolve code and the mechanism reports \
nothing while looking healthy"
);
}
}
#[cfg(test)]
mod probe_wiring_pins {
fn run_writer_body() -> &'static str {
let src = include_str!("capture.rs");
let start = src
.find("async fn run_writer(")
.expect("run_writer not found");
let after = &src[start..];
let open = after.find('{').expect("run_writer has no body");
let mut depth = 0usize;
for (offset, ch) in after[open..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return &after[..open + offset + 1];
}
}
_ => {}
}
}
panic!("run_writer's body is not brace-balanced");
}
fn run_writer_code_only() -> String {
run_writer_body()
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn the_probe_runs_off_the_async_runtime() {
let src = include_str!("shadow.rs");
let body = src
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n");
assert!(
body.contains("spawn_blocking("),
"the conformance probe no longer runs on a blocking thread. It executes \
contract WASM synchronously, so on the async runtime it competes with \
the event loop — and a node sized by available_parallelism() has one \
worker thread on a single-vCPU host. See .claude/rules/contracts.md"
);
}
#[test]
fn an_evicted_contract_has_its_bundle_removed() {
let body = run_writer_code_only();
assert!(
body.contains("bundle_path(&dir, &evicted)"),
"the writer no longer derives the deleted path from the evicted contract, \
so eviction either orphans a bundle or deletes the wrong one"
);
assert!(
body.contains("remove_file(&path)"),
"the writer no longer deletes evicted bundles, so every rotation past the \
tracking cap orphans a file that nothing will ever clean up"
);
}
#[test]
fn focus_is_drawn_at_startup_and_retried_until_the_hosted_source_arrives() {
let body = run_writer_code_only();
let loop_start = body.find("loop {").expect("run_writer has no select loop");
assert!(
body[..loop_start].contains(".focus("),
"focus is no longer computed before the select loop, so a focus-scoped \
peer records nothing for the first probe interval after every restart"
);
assert!(
body.contains("warmup.tick()"),
"the hosted-source warm-up retry arm is gone; the startup draw alone runs \
before the ring registers its hosted contracts and therefore misses it"
);
}
#[test]
fn contracts_awaiting_samples_are_counted_in_the_tick() {
let body = run_writer_code_only();
let args = count_awaiting_samples_args();
assert_eq!(
args[0], "&mut report",
"the warming-up counters are folded into something other than THIS tick's \
report, so the numbers the dashboard is fed below describe a different \
object than the one the log line reports"
);
assert_eq!(
args[1], "awaiting_samples",
"the warming-up count passed to `count_awaiting_samples` is no longer \
`awaiting_samples`, so the focus contracts that had nothing to check yet \
are dropped from the tick's counts and a warming-up focus set reports as \
a smaller, fully-checked one"
);
for open_coded in [
"report.focused +=",
"report.skipped_no_samples +=",
"report.without_verdict +=",
] {
assert!(
!body.contains(open_coded),
"`{open_coded}` is open-coded in run_writer again alongside \
`count_awaiting_samples`, so the warm-up contracts are counted twice \
— or, worse, the helper's own version has drifted from it"
);
}
}
#[test]
fn at_most_one_probe_runs_at_a_time() {
let body = run_writer_code_only();
assert!(
body.contains("in_flight.is_none()"),
"the probe tick arm is no longer guarded on there being no probe in \
flight, so a probe that overran its interval would have another stacked \
on top of it — unbounded concurrent WASM execution from a job whose \
entire justification is that it is bounded"
);
}
const PUBLISH_CALL_SITES: usize = 2;
const PROBE_PUBLISH: usize = 1;
const BARREN_PUBLISH: usize = 0;
fn publish_call_sites() -> Vec<usize> {
let body = run_writer_code_only();
let anchor = "status::publish(";
let mut at = Vec::new();
let mut from = 0usize;
while let Some(found) = body[from..].find(anchor) {
at.push(from + found + anchor.len());
from += found + anchor.len();
}
let any_spelling = body.matches("publish(").count();
assert_eq!(
any_spelling,
at.len(),
"run_writer calls `publish(` {any_spelling} times but only {} of them are \
spelled `status::publish(`. A call reached through a `use` import is \
invisible to this anchor AND to PUBLISH_CALL_SITES, so the positional \
pins below would keep passing while asserting about the wrong set. \
Spell it `status::publish(` at every call site",
at.len()
);
assert_eq!(
at.len(),
PUBLISH_CALL_SITES,
"run_writer calls status::publish {} times, not {PUBLISH_CALL_SITES}. \
`publish_call_args` selects positionally, so the pins below are now \
asserting about a different call than the one they name — decide which \
index each pin means and update PUBLISH_CALL_SITES deliberately",
at.len()
);
at
}
fn publish_call_args(nth: usize) -> Vec<String> {
let body = run_writer_code_only();
let start = publish_call_sites()[nth];
let args = call_args_at(&body, start, &format!("status::publish call {nth}"));
assert_eq!(
args.len(),
4,
"status::publish's argument list changed shape, so the positional pins \
below are asserting about the wrong arguments: {args:?}"
);
args
}
fn call_args_at(body: &str, start: usize, label: &str) -> Vec<String> {
let mut depth = 0usize;
let mut args: Vec<String> = Vec::new();
let mut current = String::new();
for ch in body[start..].chars() {
match ch {
'(' | '[' => {
depth += 1;
current.push(ch);
}
')' if depth == 0 => break,
']' if depth == 0 => panic!(
"unbalanced `]` while slicing {label}'s arguments: the anchor no \
longer lands on that call's opening paren, so nothing below is \
asserting about it. parsed so far: {args:?} + {current:?}"
),
')' | ']' => {
depth -= 1;
current.push(ch);
}
',' if depth == 0 => {
args.push(current.trim().to_string());
current.clear();
}
_ => current.push(ch),
}
}
if !current.trim().is_empty() {
args.push(current.trim().to_string());
}
args
}
fn count_awaiting_samples_args() -> Vec<String> {
let body = run_writer_code_only();
let anchor = "count_awaiting_samples(";
let calls = body.matches(anchor).count();
assert_eq!(
calls, 1,
"run_writer calls `count_awaiting_samples` {calls} times, not once. Zero \
drops the warming-up focus contracts from the tick's counts; more than \
one folds them in twice"
);
let start = body.find(anchor).expect("checked just above") + anchor.len();
let args = call_args_at(&body, start, "count_awaiting_samples");
assert_eq!(
args.len(),
2,
"count_awaiting_samples's argument list changed shape, so the positional \
pins below are asserting about the wrong arguments: {args:?}"
);
args
}
#[test]
fn dashboard_checked_window_is_fed_judged_contracts_with_their_findings() {
let body = run_writer_code_only();
let args = publish_call_args(PROBE_PUBLISH);
assert!(
args[0].contains("checked_contracts(&report.judged, &findings)"),
"the first argument no longer builds per-contract records from the judged \
contracts AND their findings, so either an unjudged contract renders as \
checked, or findings live in a window that can evict independently of \
the contract they belong to (#5403 H1). got: {}",
args[0]
);
let whitespace_free: String = body.chars().filter(|c| !c.is_whitespace()).collect();
assert!(
!whitespace_free.contains("last_focus.selected"),
"run_writer reads focus SELECTION out of `last_focus`. Wherever the read \
happens, that is the one thing the checked window must not be built \
from: a selected contract can be skipped before probing (no code, no \
samples) or probed and never reach a verdict, and it would render on the \
per-contract page as 'checked, no violation found'"
);
for nth in 0..PUBLISH_CALL_SITES {
let first = &publish_call_args(nth)[0];
assert!(
!first.contains("selected"),
"status::publish call {nth} is fed focus SELECTION (`{first}`). A \
selected contract can be skipped before probing (no code, no \
samples) or probed and never reach a verdict, and it would render on \
the per-contract page as 'checked, no violation found'"
);
}
}
#[test]
fn dashboard_tick_counts_are_the_report_fields_positionally() {
let args = publish_call_args(PROBE_PUBLISH);
assert_eq!(
args[1], "report.judged.len()",
"the judged-this-tick count is no longer `report.judged.len()`; \
`report.probed` counts contracts that ran cases without forming an \
opinion and would overstate what was established"
);
assert_eq!(
args[2], "report.without_verdict",
"the unjudged count is no longer `report.without_verdict`, so a probed \
contract whose every case was inconclusive stops counting toward the \
fleet-wide unjudged total and renders as a clean result"
);
}
fn work_is_empty_block() -> String {
let body = run_writer_code_only();
let anchor = "if work.is_empty() {";
let start = body
.find(anchor)
.expect("run_writer no longer branches on an empty work set")
+ anchor.len()
- 1;
let mut depth = 0usize;
for (offset, ch) in body[start..].char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
return body[start..start + offset + 1].to_string();
}
}
_ => {}
}
}
panic!("the empty-work branch is not brace-balanced");
}
#[test]
fn a_tick_that_selects_nothing_still_publishes() {
let block = work_is_empty_block();
assert!(
block.contains("status::publish("),
"the empty-work branch returns without publishing, so a peer whose ticks \
have gone barren keeps serving its last healthy tick as a current \
result, with the snapshot's age frozen so it never reads as stale. \
got:\n{block}"
);
let args = publish_call_args(BARREN_PUBLISH);
assert_eq!(
args[0], "Vec::new()",
"a tick that probed nothing must publish an EMPTY record set. Anything \
else — focus selection most plausibly, since `focus` is in scope right \
here — puts contracts the tick formed no opinion about into the checked \
window, where the per-contract page renders them as 'checked, no \
violation found'. Un-asserted, this argument was the one position of the \
four that a wrong value could occupy silently"
);
assert_eq!(
args[1], "0",
"a tick that probed nothing must publish zero contracts judged; anything \
else reports established results from a tick that established none"
);
assert_eq!(
args[2], "awaiting_samples",
"the barren tick's unjudged count must be `awaiting_samples`, which is \
`focus.selected.len()` when the work set is empty — every contract this \
tick formed no opinion about. A zero here renders a barren tick as one \
with nothing left unjudged"
);
assert!(
args[3].contains("Instant::now()"),
"the barren tick's snapshot carries no publish time, so it cannot age and \
the frozen-checker note never fires. got: {}",
args[3]
);
}
#[test]
fn the_published_snapshot_carries_its_publish_time() {
let args = publish_call_args(PROBE_PUBLISH);
assert!(
args[3].contains("Instant::now()"),
"the published snapshot no longer carries a publish time, so a frozen \
checker renders identically to a live clean one. got: {}",
args[3]
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_related_budgets() -> (usize, usize) {
related_budgets(&sampler_config())
}
fn instance(n: u8) -> ContractInstanceId {
ContractInstanceId::new([n; 32])
}
fn observation_for(contract: ContractInstanceId) -> Observation {
Observation {
contract,
code_hash: [2; 32],
parameters: vec![3],
base_state: vec![1, 2],
incoming_state: Some(vec![2, 3]),
delta: None,
result_state: vec![1, 2, 3],
related: Vec::new(),
}
}
fn focused_on(ids: &[u8]) -> SamplingScope {
SamplingScope::Focused(ids.iter().copied().map(instance).collect())
}
#[test]
fn focused_sampling_records_only_the_focus_set() {
let mut samplers = HashMap::new();
let scope = focused_on(&[1]);
let _evicted = record(&mut samplers, &scope, observation_for(instance(1)));
let _evicted = record(&mut samplers, &scope, observation_for(instance(2)));
assert!(
samplers.contains_key(&instance(1)),
"the focused contract was not sampled"
);
assert!(
!samplers.contains_key(&instance(2)),
"an unfocused contract was sampled: sampling is not following focus"
);
}
#[test]
fn a_restart_reloads_a_deterministic_set_when_there_are_more_bundles_than_slots() {
let dir = tempfile::TempDir::new().expect("tempdir");
let extra = 12usize;
let mut written = Vec::new();
for i in 0..(MAX_TRACKED_CONTRACTS + extra) {
let instance = ContractInstanceId::new([(i % 251) as u8; 32]);
written.push(instance);
let mut bundle = super::super::bundle::ReplayBundle::new(vec![9, 9], vec![3]);
bundle.instance = Some(instance);
bundle.states = vec![vec![1, 2], vec![2, 3]];
bundle
.write_to(&bundle_path(dir.path(), &instance))
.expect("write bundle");
}
let mut expected: Vec<String> = written.iter().map(|id| format!("{id}.bundle")).collect();
expected.sort();
expected.truncate(MAX_TRACKED_CONTRACTS);
let mut kept: Vec<String> = reload(dir.path())
.into_keys()
.map(|id| format!("{id}.bundle"))
.collect();
kept.sort();
assert_eq!(
kept.len(),
MAX_TRACKED_CONTRACTS,
"fixture did not exceed the cap, so truncation never ran"
);
assert_eq!(
kept, expected,
"reload kept a different set than the ordering promises, so which samples \
survive a restart depends on filesystem enumeration order"
);
}
#[test]
fn reload_reports_related_state_it_had_to_refuse() {
let dir = tempfile::TempDir::new().expect("tempdir");
let instance = ContractInstanceId::new([3; 32]);
let (max_state, _) = related_budgets(&sampler_config());
let mut bundle = super::super::bundle::ReplayBundle::new(vec![9, 9], vec![3]);
bundle.instance = Some(instance);
bundle.states = vec![vec![1, 2], vec![2, 3]];
bundle.related = (0..(MAX_RELATED_CONTRACTS + 4))
.map(|i| (ContractInstanceId::new([i as u8; 32]), vec![i as u8; 16]))
.collect();
assert!(
max_state > 16,
"fixture states must be individually admissible"
);
bundle
.write_to(&bundle_path(dir.path(), &instance))
.expect("write bundle");
let samplers = reload(dir.path());
let tracked = samplers
.get(&instance)
.expect("bundle should have reloaded");
assert_eq!(
tracked.related.len(),
MAX_RELATED_CONTRACTS,
"reload did not trim to the slot limit, so nothing was refused"
);
assert_eq!(
tracked.refused_related.no_slot, 4,
"reload trimmed related state without counting it, so the next flush will \
record a corpus as complete when it is not"
);
}
#[test]
fn each_reloaded_bundle_counts_only_its_own_refusals() {
let dir = tempfile::TempDir::new().expect("tempdir");
let over_by = [3usize, 5usize];
let instances = [
ContractInstanceId::new([200; 32]),
ContractInstanceId::new([201; 32]),
];
for (instance, extra) in instances.iter().zip(over_by) {
let mut bundle = super::super::bundle::ReplayBundle::new(vec![9, 9], vec![3]);
bundle.instance = Some(*instance);
bundle.states = vec![vec![1, 2], vec![2, 3]];
bundle.related = (0..(MAX_RELATED_CONTRACTS + extra))
.map(|i| (ContractInstanceId::new([i as u8; 32]), vec![i as u8; 16]))
.collect();
bundle
.write_to(&bundle_path(dir.path(), instance))
.expect("write bundle");
}
let samplers = reload(dir.path());
for (instance, extra) in instances.iter().zip(over_by) {
let tracked = samplers.get(instance).expect("bundle should have reloaded");
assert_eq!(
tracked.refused_related.no_slot, extra as u64,
"{instance} reported a refusal count that is not its own; a shared \
counter across bundles makes every contract after the first overstate"
);
}
}
#[test]
fn the_related_budget_follows_the_sampler_budget() {
let small = sampler_config_from(Some("1048576"));
let large = sampler_config_from(Some("16777216"));
let (_, small_total) = related_budgets(&small);
let (_, large_total) = related_budgets(&large);
assert!(
large_total > small_total,
"raising the capture budget did not raise the related-state budget, so a \
contract that needs large related state stays unjudgeable however the \
operator configures the run ({small_total} vs {large_total})"
);
assert_eq!(large_total, large.max_bytes);
}
#[test]
fn refused_related_state_is_counted_rather_than_dropped_silently() {
let config = sampler_config();
let (max_state, _) = related_budgets(&config);
let mut held = HashMap::new();
let mut refused = RelatedRefusals::default();
admit_related(
&mut held,
&[(instance(9), vec![0u8; max_state + 1])],
&mut refused,
max_state,
config.max_bytes,
);
assert!(held.is_empty(), "an oversized related state was admitted");
assert_eq!(
refused.too_large, 1,
"an oversized related state was dropped without being counted"
);
assert_eq!(refused.total(), 1);
}
#[test]
fn related_state_over_the_total_budget_is_counted_separately() {
let mut held = HashMap::new();
let mut refused = RelatedRefusals::default();
let (max_state, max_total) = (1024, 1024);
admit_related(
&mut held,
&[(instance(1), vec![0u8; 800]), (instance(2), vec![0u8; 800])],
&mut refused,
max_state,
max_total,
);
assert_eq!(held.len(), 1, "both states fit, so the budget never bound");
assert_eq!(
refused.over_budget, 1,
"a related state refused for budget was not counted"
);
assert_eq!(refused.too_large, 0, "counted under the wrong reason");
}
#[test]
fn related_state_beyond_the_slot_limit_is_counted_separately() {
let mut held = HashMap::new();
let mut refused = RelatedRefusals::default();
let offered: Vec<_> = (0..(MAX_RELATED_CONTRACTS + 3))
.map(|i| (instance(i as u8), vec![0u8; 8]))
.collect();
admit_related(&mut held, &offered, &mut refused, 4096, 1 << 20);
assert_eq!(held.len(), MAX_RELATED_CONTRACTS);
assert_eq!(
refused.no_slot, 3,
"related contracts beyond the slot limit were dropped uncounted"
);
}
#[tokio::test]
async fn a_bundle_records_that_related_state_was_refused() {
let dir = tempfile::TempDir::new().expect("tempdir");
let mut samplers = HashMap::new();
let watched = instance(1);
let mut obs = observation_for(watched);
let (max_state, _) = related_budgets(&sampler_config());
obs.related = vec![(instance(2), vec![0u8; max_state + 1])];
let _evicted = record(&mut samplers, &SamplingScope::Wide, obs);
assert_eq!(
samplers[&watched].refused_related.too_large, 1,
"fixture did not trigger a refusal, so this proves nothing"
);
write_all(dir.path(), &samplers, 0, 0).await;
let bundle =
super::super::bundle::ReplayBundle::read_from(&bundle_path(dir.path(), &watched))
.expect("bundle should have been written");
let note = bundle.note.unwrap_or_default();
assert!(
note.contains("related state refused"),
"the bundle does not record that related state was refused, so a replay \
cannot tell 'needed none' from 'could not keep it': {note}"
);
}
#[test]
fn the_related_handle_refuses_oversized_input_without_building_it() {
let (tx, _rx) = mpsc::channel(64);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
let built = Arc::new(AtomicU64::new(0));
let counter = built.clone();
handle.observe_related_with(instance(1), MAX_QUEUED_BYTES + 1, move || {
counter.fetch_add(1, Ordering::Relaxed);
Vec::new()
});
assert_eq!(
built.load(Ordering::Relaxed),
0,
"the closure ran, so the copy was paid for before the refusal — which is \
the ordering this path exists to avoid"
);
assert_eq!(handle.dropped(), 1, "the refusal was not counted");
}
#[test]
fn the_related_handle_drops_when_the_queue_is_full() {
let (tx, _rx) = mpsc::channel(1);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
handle.observe_related_with(instance(1), 8, || vec![(instance(2), vec![0u8; 8])]);
assert_eq!(handle.dropped(), 0, "the first offer should have fit");
let built = Arc::new(AtomicU64::new(0));
let counter = built.clone();
handle.observe_related_with(instance(1), 8, move || {
counter.fetch_add(1, Ordering::Relaxed);
vec![(instance(2), vec![0u8; 8])]
});
assert_eq!(handle.dropped(), 1, "a full queue did not count the drop");
assert_eq!(
built.load(Ordering::Relaxed),
0,
"the closure ran on the drop path, paying for copies that were discarded"
);
}
#[tokio::test]
async fn related_state_discarded_for_an_untracked_contract_is_recorded_in_the_note() {
let dir = tempfile::TempDir::new().expect("tempdir");
let mut samplers = HashMap::new();
let tracked = instance(1);
let _evicted = record(
&mut samplers,
&SamplingScope::Wide,
observation_for(tracked),
);
assert_eq!(
record_related(&mut samplers, &SamplingScope::Wide, instance(8), &[]),
RelatedOutcome::Untracked
);
assert_eq!(
record_related(&mut samplers, &SamplingScope::Wide, instance(9), &[]),
RelatedOutcome::Untracked
);
write_all(dir.path(), &samplers, 0, 2).await;
let bundle =
super::super::bundle::ReplayBundle::read_from(&bundle_path(dir.path(), &tracked))
.expect("bundle should have been written");
let note = bundle.note.unwrap_or_default();
assert!(
note.contains("2 validation-related message(s) discarded"),
"the corpus does not record that validation-resolved related state was \
discarded, so a reader cannot tell an untaken dependency from an absent \
one: {note}"
);
}
#[tokio::test]
async fn the_note_says_nothing_about_discards_when_there_were_none() {
let dir = tempfile::TempDir::new().expect("tempdir");
let mut samplers = HashMap::new();
let tracked = instance(1);
let _evicted = record(
&mut samplers,
&SamplingScope::Wide,
observation_for(tracked),
);
write_all(dir.path(), &samplers, 0, 0).await;
let bundle =
super::super::bundle::ReplayBundle::read_from(&bundle_path(dir.path(), &tracked))
.expect("bundle should have been written");
let note = bundle.note.unwrap_or_default();
assert!(
!note.contains("discarded"),
"a clean run still claims discards, so the signal means nothing: {note}"
);
}
#[test]
fn validation_resolved_related_state_is_merged_into_the_tracked_contract() {
let mut samplers = HashMap::new();
let watched = instance(1);
let scope = focused_on(&[1]);
let mut obs = observation_for(watched);
obs.related = Vec::new();
let _evicted = record(&mut samplers, &scope, obs);
assert!(
samplers[&watched].related.is_empty(),
"fixture started with related state, so this proves nothing"
);
let recorded = record_related(
&mut samplers,
&scope,
watched,
&[(instance(2), vec![7u8; 32])],
);
assert_eq!(
recorded,
RelatedOutcome::Recorded,
"a real merge did not report itself as recorded, so the writer will skip \
the flush that persists it"
);
assert_eq!(
samplers[&watched].related.len(),
1,
"validation-resolved related state never reached the tracked contract, so \
a replay of it cannot reach a verdict"
);
}
#[test]
fn validation_related_state_does_not_create_an_untracked_contract() {
let mut samplers = HashMap::new();
let stranger = instance(9);
let recorded = record_related(
&mut samplers,
&focused_on(&[9]),
stranger,
&[(instance(2), vec![7u8; 32])],
);
assert_eq!(
recorded,
RelatedOutcome::Untracked,
"a message for an untracked contract must report Untracked specifically — \
it is the case where related state is DISCARDED, and reporting it as a \
plain no-op is how it goes uncounted"
);
assert!(
samplers.is_empty(),
"related state alone created a tracked entry, spending a slot on a \
contract no case can be built from"
);
}
#[test]
fn validation_related_state_is_not_collected_out_of_focus() {
let mut samplers = HashMap::new();
let watched = instance(1);
let _evicted = record(&mut samplers, &focused_on(&[1]), observation_for(watched));
let recorded = record_related(
&mut samplers,
&focused_on(&[2]),
watched,
&[(instance(3), vec![7u8; 32])],
);
assert_eq!(
recorded,
RelatedOutcome::OutOfFocus,
"an out-of-focus message must report OutOfFocus, not Untracked — the first \
is benign and the second means data was lost, and conflating them is what \
the enum exists to prevent"
);
assert!(
samplers[&watched].related.is_empty(),
"an out-of-focus contract kept collecting related state"
);
}
#[test]
fn a_contract_that_leaves_focus_stops_collecting() {
let mut samplers = HashMap::new();
let watched = instance(1);
let _evicted = record(&mut samplers, &focused_on(&[1]), observation_for(watched));
let before = samplers
.get(&watched)
.expect("focused contract was not sampled")
.sampler
.total_seen();
assert!(
before > 0,
"fixture recorded nothing, so this proves nothing"
);
let elsewhere = focused_on(&[2]);
for n in 0..8u8 {
let mut obs = observation_for(watched);
obs.result_state = vec![n, 9, 9];
let _evicted = record(&mut samplers, &elsewhere, obs);
}
let after = samplers
.get(&watched)
.expect("an out-of-focus contract must be RETAINED, only not collected")
.sampler
.total_seen();
assert_eq!(
before, after,
"a contract kept collecting after it left the focus set"
);
}
#[test]
fn a_contract_that_leaves_focus_keeps_what_it_already_collected() {
let mut samplers = HashMap::new();
let watched = instance(1);
let _evicted = record(&mut samplers, &focused_on(&[1]), observation_for(watched));
let elsewhere = focused_on(&[2]);
for n in 0..4u8 {
let mut obs = observation_for(watched);
obs.result_state = vec![n, 4, 4];
let _evicted = record(&mut samplers, &elsewhere, obs);
}
assert!(
samplers.contains_key(&watched),
"defocusing a contract discarded its accumulated sample"
);
}
#[test]
fn eviction_reports_the_victim_so_its_bundle_can_be_deleted() {
let mut samplers = HashMap::new();
for i in 0..MAX_TRACKED_CONTRACTS {
let id = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation_for(id));
}
assert_eq!(samplers.len(), MAX_TRACKED_CONTRACTS);
let newcomer = ContractInstanceId::new([255; 32]);
let scope = SamplingScope::Focused([newcomer].into_iter().collect());
let evicted = record(&mut samplers, &scope, observation_for(newcomer));
let victim = evicted.expect("eviction happened but reported no victim to clean up");
assert!(
!samplers.contains_key(&victim),
"the reported victim is still tracked"
);
assert_ne!(
victim, newcomer,
"eviction reported the newcomer as its own victim"
);
}
#[test]
fn eviction_takes_the_lowest_id_contract_not_in_focus() {
let mut samplers = HashMap::new();
for i in 0..MAX_TRACKED_CONTRACTS {
let id = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation_for(id));
}
assert_eq!(samplers.len(), MAX_TRACKED_CONTRACTS);
let mut ids: Vec<_> = samplers.keys().copied().collect();
ids.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
let protected = ids[0];
let expected_victim = ids[1];
let newcomer = ContractInstanceId::new([255; 32]);
let scope = SamplingScope::Focused([protected, newcomer].into_iter().collect());
let evicted = record(&mut samplers, &scope, observation_for(newcomer));
assert_eq!(
evicted,
Some(expected_victim),
"eviction did not take the lowest-id non-focused contract"
);
assert!(
samplers.contains_key(&protected),
"a focused contract was evicted"
);
}
#[test]
fn wide_sampling_records_contracts_outside_the_focus_set() {
let mut samplers = HashMap::new();
let _evicted = record(
&mut samplers,
&SamplingScope::Wide,
observation_for(instance(2)),
);
assert!(
samplers.contains_key(&instance(2)),
"wide capture refused a contract, so no corpus can be built"
);
}
#[test]
fn a_full_map_still_admits_the_contract_focus_selected() {
let mut samplers = HashMap::new();
for i in 0..MAX_TRACKED_CONTRACTS {
let id = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation_for(id));
}
assert_eq!(
samplers.len(),
MAX_TRACKED_CONTRACTS,
"fixture did not reach the cap, so the admission path under test never ran"
);
let newcomer = ContractInstanceId::new([255; 32]);
assert!(
!samplers.contains_key(&newcomer),
"fixture already tracked the newcomer"
);
let scope = SamplingScope::Focused([newcomer].into_iter().collect());
let _evicted = record(&mut samplers, &scope, observation_for(newcomer));
assert!(
samplers.contains_key(&newcomer),
"a full map locked out the contract focus actually selected (#5366)"
);
assert!(
samplers.len() <= MAX_TRACKED_CONTRACTS,
"admission grew the map past its cap: {}",
samplers.len()
);
}
#[test]
fn making_room_never_evicts_a_focused_contract() {
let mut samplers = HashMap::new();
for i in 0..MAX_TRACKED_CONTRACTS {
let id = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation_for(id));
}
assert_eq!(samplers.len(), MAX_TRACKED_CONTRACTS);
let held = *samplers
.keys()
.min_by(|a, b| a.as_bytes().cmp(b.as_bytes()))
.expect("fixture is empty");
let newcomer = ContractInstanceId::new([255; 32]);
let scope = SamplingScope::Focused([held, newcomer].into_iter().collect());
let _evicted = record(&mut samplers, &scope, observation_for(newcomer));
assert!(
samplers.contains_key(&held),
"eviction took a focused contract - the lowest id is exactly what the \
deterministic victim rule would pick if it did not skip focus"
);
assert!(
samplers.contains_key(&newcomer),
"newcomer was not admitted"
);
}
#[test]
fn wide_sampling_stops_at_the_cap_rather_than_rolling_over() {
let mut samplers = HashMap::new();
let first = ContractInstanceId::new([0; 32]);
for i in 0..(MAX_TRACKED_CONTRACTS + 20) {
let id = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation_for(id));
}
assert_eq!(
samplers.len(),
MAX_TRACKED_CONTRACTS,
"wide capture did not stop at its cap"
);
assert!(
samplers.contains_key(&first),
"wide capture dropped the earliest contract it saw, so the corpus now \
depends on eviction order rather than on what the peer observed"
);
let last = ContractInstanceId::new([(MAX_TRACKED_CONTRACTS + 19) as u8; 32]);
assert!(
!samplers.contains_key(&last),
"wide capture admitted a contract past its cap"
);
}
fn observation() -> Observation {
Observation {
contract: ContractInstanceId::new([1; 32]),
code_hash: [2; 32],
parameters: vec![3],
base_state: vec![1, 2],
incoming_state: Some(vec![2, 3]),
delta: None,
result_state: vec![1, 2, 3],
related: Vec::new(),
}
}
#[tokio::test]
async fn a_full_queue_drops_rather_than_blocking() {
let (tx, _rx) = mpsc::channel(1);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
for _ in 0..64 {
handle.observe(observation());
}
assert!(
handle.dropped() >= 60,
"expected the overflow to be dropped and counted, saw {}",
handle.dropped()
);
}
#[tokio::test]
async fn a_closed_receiver_does_not_panic_the_caller() {
let (tx, rx) = mpsc::channel(4);
drop(rx);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
handle.observe(observation());
assert_eq!(handle.dropped(), 1);
}
#[test]
fn capture_is_off_when_the_environment_does_not_ask_for_it() {
assert!(
capture_dir_from(None).is_none(),
"an unset environment must leave capture off; otherwise every node in \
the network starts recording user state"
);
for blank in ["", " ", "\t\n"] {
assert!(
capture_dir_from(Some(blank)).is_none(),
"a blank setting ({blank:?}) must not enable capture"
);
}
assert_eq!(
capture_dir_from(Some("/tmp/somewhere")),
Some(PathBuf::from("/tmp/somewhere")),
"an explicit directory must be honoured, or the knob does nothing"
);
}
#[tokio::test]
async fn the_queue_refuses_more_bytes_than_its_budget() {
let (tx, _rx) = mpsc::channel(64);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
let builds = std::cell::Cell::new(0usize);
let huge = || {
builds.set(builds.get() + 1);
let mut obs = observation();
obs.base_state = vec![0u8; MAX_QUEUED_BYTES + 1];
obs
};
handle.observe_with(MAX_QUEUED_BYTES + 1, huge);
assert_eq!(
builds.get(),
0,
"an observation bigger than the whole budget must be refused without \
being built; otherwise one huge contract pays for itself in full"
);
assert_eq!(handle.dropped(), 1);
let each = MAX_QUEUED_BYTES / 4;
let admitted = std::cell::Cell::new(0usize);
for _ in 0..8 {
handle.observe_with(each, || {
admitted.set(admitted.get() + 1);
let mut obs = observation();
obs.base_state = vec![0u8; each];
obs
});
}
assert!(
admitted.get() <= 4,
"the byte budget should have stopped admissions at about four of these, \
built {} instead",
admitted.get()
);
assert!(
handle.dropped() >= 4,
"the refusals should be counted, saw {}",
handle.dropped()
);
}
#[tokio::test]
async fn related_contract_state_is_captured_and_replayable() {
let related_id = ContractInstanceId::new([9; 32]);
let mut observed = observation();
observed.related = vec![(related_id, vec![42, 43])];
let mut samplers = HashMap::new();
let _evicted = record(&mut samplers, &SamplingScope::Wide, observed);
let dir = tempfile::TempDir::new().expect("tempdir");
write_all(dir.path(), &samplers, 0, 0).await;
let path = dir
.path()
.join(format!("{}.bundle", ContractInstanceId::new([1; 32])));
let bundle = super::super::bundle::ReplayBundle::read_from(&path).expect("read back");
assert_eq!(
bundle.related,
vec![(related_id, vec![42, 43])],
"the bundle must carry the related state the merge referenced"
);
let corpus = bundle.to_corpus();
assert!(
corpus.related.states().any(|(id, state)| {
*id == related_id && state.as_ref().map(|s| s.as_ref()) == Some(&[42u8, 43][..])
}),
"related state must reach the corpus as RelatedContracts, or a contract \
that needs it still cannot be executed"
);
}
#[tokio::test]
async fn the_total_related_byte_allowance_holds() {
let mut samplers = HashMap::new();
let (max_state, max_total) = test_related_budgets();
let chunk = max_state;
let entries = (max_total / chunk) + 1;
assert!(
entries >= 2,
"fixture needs at least two entries to test a TOTAL bound"
);
for i in 0..entries {
let mut observed = observation();
observed.related = vec![(ContractInstanceId::new([i as u8; 32]), vec![7u8; chunk])];
let _evicted = record(&mut samplers, &SamplingScope::Wide, observed);
}
let refused = samplers
.values()
.next()
.expect("one contract tracked")
.refused_related;
assert_eq!(
refused.too_large, 0,
"entries were refused on the PER-STATE cap, so the total was never tested"
);
assert!(
refused.over_budget > 0,
"nothing was refused on the total, so the allowance never bound"
);
let tracked = samplers.values().next().expect("one contract tracked");
let held: usize = tracked.related.values().map(Vec::len).sum();
assert!(
held <= test_related_budgets().1,
"related state totalled {held} bytes against an allowance of {}",
test_related_budgets().1
);
assert!(
tracked.related.len() < entries,
"all {entries} entries were admitted, so the total allowance is not binding"
);
let before = tracked.related.len();
let mut again = observation();
again.related = vec![(ContractInstanceId::new([0; 32]), vec![0; chunk])];
let _evicted = record(&mut samplers, &SamplingScope::Wide, again);
let tracked = samplers.values().next().expect("one contract tracked");
assert_eq!(
tracked.related.len(),
before,
"replacing an entry changed the number held, so its bytes were not \
discounted from the total"
);
}
#[tokio::test]
async fn a_reloaded_bundle_cannot_exceed_the_related_bounds() {
let dir = tempfile::TempDir::new().expect("tempdir");
let instance = ContractInstanceId::new([1; 32]);
let mut bundle = super::super::bundle::ReplayBundle::new(vec![9, 9], vec![3]);
bundle.instance = Some(instance);
bundle.states = vec![vec![1, 2], vec![2, 3]];
bundle.related = (0..(MAX_RELATED_CONTRACTS + 6))
.map(|i| (ContractInstanceId::new([i as u8; 32]), vec![i as u8; 32]))
.collect();
bundle
.write_to(&dir.path().join(format!("{instance}.bundle")))
.expect("write bundle");
let samplers = reload(dir.path());
let tracked = samplers
.values()
.next()
.expect("the bundle should have been reloaded");
assert!(
tracked.related.len() <= MAX_RELATED_CONTRACTS,
"reload admitted {} related contracts, over the cap of {}",
tracked.related.len(),
MAX_RELATED_CONTRACTS
);
assert!(
tracked.related.values().map(Vec::len).sum::<usize>() <= test_related_budgets().1,
"reload admitted more related bytes than the allowance"
);
}
#[tokio::test]
async fn related_contract_state_is_bounded() {
let mut samplers = HashMap::new();
for i in 0..(MAX_RELATED_CONTRACTS + 4) {
let mut observed = observation();
observed.related = vec![(ContractInstanceId::new([i as u8; 32]), vec![i as u8; 16])];
let _evicted = record(&mut samplers, &SamplingScope::Wide, observed);
}
let tracked = samplers.values().next().expect("one contract tracked");
assert!(
tracked.related.len() <= MAX_RELATED_CONTRACTS,
"related contracts must be capped, held {}",
tracked.related.len()
);
let held_before = tracked.related.len();
let mut huge = observation();
huge.related = vec![(
ContractInstanceId::new([200; 32]),
vec![0u8; test_related_budgets().0 + 1],
)];
let _evicted = record(&mut samplers, &SamplingScope::Wide, huge);
let tracked = samplers.values().next().expect("one contract tracked");
assert_eq!(
tracked.related.len(),
held_before,
"an oversized related state must be refused, not admitted or swapped in"
);
assert!(
tracked.related.values().map(Vec::len).sum::<usize>() <= test_related_budgets().1,
"the related-state allowance must hold"
);
}
#[tokio::test]
async fn a_full_queue_skips_building_the_observation_entirely() {
let (tx, rx) = mpsc::channel(1);
let handle = CaptureHandle {
tx,
dropped: Arc::new(AtomicU64::new(0)),
queued_bytes: Arc::new(AtomicUsize::new(0)),
};
let builds = std::cell::Cell::new(0usize);
let build = || {
builds.set(builds.get() + 1);
observation()
};
handle.observe_with(observation().queued_bytes(), build);
assert_eq!(builds.get(), 1, "the first observation should be built");
assert_eq!(handle.dropped(), 0);
handle.observe_with(observation().queued_bytes(), build);
assert_eq!(
builds.get(),
1,
"a full queue must not pay for the copies; the closure ran anyway"
);
assert_eq!(handle.dropped(), 1, "the drop must still be counted");
drop(rx);
handle.observe_with(observation().queued_bytes(), build);
assert_eq!(builds.get(), 1, "a dead writer must not pay for the copies");
assert_eq!(handle.dropped(), 2);
}
#[tokio::test]
async fn a_written_bundle_identifies_its_contract() {
let mut samplers = HashMap::new();
let _evicted = record(&mut samplers, &SamplingScope::Wide, observation());
let dir = tempfile::TempDir::new().expect("tempdir");
write_all(dir.path(), &samplers, 0, 0).await;
let path = dir
.path()
.join(format!("{}.bundle", ContractInstanceId::new([1; 32])));
let bundle = super::super::bundle::ReplayBundle::read_from(&path).expect("read back");
assert_eq!(bundle.code_hash, Some([2; 32]));
assert_eq!(bundle.parameters, vec![3]);
assert!(bundle.instance.is_some());
assert!(
bundle.resolve_code(Some(vec![9, 9])).is_err(),
"a bundle must refuse code that does not match the contract it recorded"
);
}
#[tokio::test]
async fn a_contract_whose_states_are_all_oversized_leaves_no_misleading_bundle() {
let mut samplers = HashMap::new();
let mut oversized = observation();
let ceiling = SamplerConfig::default().max_state_bytes;
oversized.base_state = vec![7; ceiling + 1];
oversized.incoming_state = Some(vec![8; ceiling + 1]);
oversized.result_state = vec![9; ceiling + 1];
let _evicted = record(&mut samplers, &SamplingScope::Wide, oversized);
assert_eq!(
samplers
.values()
.map(|tracked| tracked.refused_too_large)
.sum::<u64>(),
1,
"the refusal must be counted where it happens, not inferred afterwards"
);
let dir = tempfile::TempDir::new().expect("tempdir");
write_all(dir.path(), &samplers, 0, 0).await;
let path = dir
.path()
.join(format!("{}.bundle", ContractInstanceId::new([1; 32])));
assert!(
!path.exists(),
"a bundle holding no states must not be written: it replays as an \
empty corpus and invites the reader to conclude the contract was quiet"
);
}
#[tokio::test]
async fn a_restart_resumes_from_what_is_already_on_disk() {
let dir = tempfile::TempDir::new().expect("tempdir");
let mut samplers = HashMap::new();
for i in 0..6u8 {
let mut obs = observation();
obs.base_state = vec![i; 16];
obs.result_state = vec![i; 17];
let _evicted = record(&mut samplers, &SamplingScope::Wide, obs);
}
write_all(dir.path(), &samplers, 0, 0).await;
let before = samplers
.values()
.next()
.expect("one contract")
.sampler
.distinct_states();
assert!(before > 1, "fixture did not accumulate anything to lose");
let resumed = reload(dir.path());
let after = resumed
.values()
.next()
.expect("contract should have been reloaded")
.sampler
.distinct_states();
assert_eq!(
after, before,
"restart lost sampled states: had {before}, resumed with {after}"
);
assert_eq!(
resumed.values().next().unwrap().code_hash,
[2; 32],
"restart lost the contract identity, so the corpus could no longer be \
verified against the WASM it came from"
);
}
#[test]
fn the_writer_actually_resumes_on_startup() {
let src = include_str!("capture.rs");
let start = src
.find("async fn run_writer(")
.expect("run_writer not found");
let after = &src[start..];
let end = after
.find("struct TrackedContract")
.expect("run_writer no longer precedes TrackedContract");
let body = after[..end]
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n");
assert!(
body.contains("reload(&dir)"),
"run_writer no longer reloads existing bundles on startup, so a node \
restart will overwrite each capture with only what it has seen since \
boot — silently, because the file is still there and still recent"
);
}
#[test]
fn a_corrupt_bundle_is_skipped_rather_than_fatal() {
let dir = tempfile::TempDir::new().expect("tempdir");
std::fs::write(dir.path().join("junk.bundle"), b"not a bundle at all").expect("write");
assert!(reload(dir.path()).is_empty());
}
#[test]
fn the_number_of_tracked_contracts_is_bounded() {
let mut samplers = HashMap::new();
for i in 0..(MAX_TRACKED_CONTRACTS + 50) {
let mut obs = observation();
obs.contract = ContractInstanceId::new([(i % 251) as u8; 32]);
let _evicted = record(&mut samplers, &SamplingScope::Wide, obs);
}
assert!(samplers.len() <= MAX_TRACKED_CONTRACTS);
}
#[test]
fn the_byte_budget_override_is_honoured_and_bad_input_falls_back() {
let default = sampler_config_from(None);
let raised = sampler_config_from(Some(" 33554432 "));
assert_eq!(raised.max_bytes, 33_554_432, "override should be applied");
assert!(
raised.max_state_bytes >= default.max_state_bytes,
"raising the total budget must never lower the per-state ceiling"
);
assert!(
raised.max_state_bytes < raised.max_bytes,
"a per-state ceiling at or above the whole budget would let one state \
evict every other sample"
);
let lowered = sampler_config_from(Some("4096"));
assert_eq!(lowered.max_bytes, 4096);
assert!(
lowered.max_state_bytes < lowered.max_bytes,
"lowering the total budget must lower the per-state ceiling with it: \
ceiling {} against a total of {}",
lowered.max_state_bytes,
lowered.max_bytes
);
assert!(
lowered.max_state_bytes >= 1,
"the ceiling must never reach zero, which would admit nothing at all"
);
for bad in ["", "0", "lots", "-1", "4MB"] {
assert_eq!(
sampler_config_from(Some(bad)).max_bytes,
default.max_bytes,
"{bad:?} should fall back to the default budget"
);
}
}
}