use std::sync::Arc;
use either::Either;
use freenet_stdlib::prelude::*;
use crate::config::ConfigArgs;
use crate::contract::executor::mock_wasm_runtime::MockWasmRuntime;
use crate::contract::executor::{ContractExecutor, Executor, OperationMode};
use crate::node::OpManager;
use crate::ring::AccessType;
use crate::wasm_runtime::MockStateStorage;
use super::super::mock_runtime::test::create_test_contract as test_contract;
async fn cached_executor(storage: MockStateStorage) -> Executor<MockWasmRuntime, MockStateStorage> {
Executor::new_mock_wasm("summarize_delta_cache", storage, None, None)
.await
.expect("create cached MockWasmRuntime executor")
}
#[tokio::test(flavor = "current_thread")]
async fn summarize_is_fresh_after_update_not_stale_cache() {
let storage = MockStateStorage::new();
let mut exec = cached_executor(storage).await;
let contract = test_contract(b"summarize_fresh_after_update");
let key = contract.key();
let state_a = WrappedState::new(vec![1, 2, 3]);
let state_b = WrappedState::new(vec![9, 8, 7, 6]);
exec.upsert_contract_state(
key,
Either::Left(state_a.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT A");
let summary_a = exec
.summarize_contract_state(key)
.await
.expect("summarize A");
assert_eq!(
summary_a.as_ref(),
blake3::hash(state_a.as_ref()).as_bytes(),
"summary of A must be blake3(A)"
);
let summary_a2 = exec
.summarize_contract_state(key)
.await
.expect("summarize A again");
assert_eq!(summary_a2.as_ref(), summary_a.as_ref());
exec.upsert_contract_state(
key,
Either::Left(state_b.clone()),
RelatedContracts::default(),
None,
)
.await
.expect("UPDATE B");
let summary_b = exec
.summarize_contract_state(key)
.await
.expect("summarize B");
assert_eq!(
summary_b.as_ref(),
blake3::hash(state_b.as_ref()).as_bytes(),
"summary must reflect updated state B, not stale cached A"
);
assert_ne!(
summary_b.as_ref(),
summary_a.as_ref(),
"summary must change after the state changed"
);
}
#[tokio::test(flavor = "current_thread")]
async fn delta_is_fresh_after_update_not_stale_cache() {
let storage = MockStateStorage::new();
let mut exec = cached_executor(storage).await;
let contract = test_contract(b"delta_fresh_after_update");
let key = contract.key();
let state_a = WrappedState::new(vec![1, 2, 3]);
let state_b = WrappedState::new(vec![9, 8, 7, 6]);
let peer_summary = StateSummary::from(vec![42u8; 4]);
exec.upsert_contract_state(
key,
Either::Left(state_a.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT A");
let delta_a = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta A");
assert_eq!(
delta_a.as_ref(),
state_a.as_ref(),
"mock delta is the full state A"
);
let delta_a2 = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta A again");
assert_eq!(delta_a2.as_ref(), delta_a.as_ref());
exec.upsert_contract_state(
key,
Either::Left(state_b.clone()),
RelatedContracts::default(),
None,
)
.await
.expect("UPDATE B");
let delta_b = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta B");
assert_eq!(
delta_b.as_ref(),
state_b.as_ref(),
"delta must reflect updated state B, not stale cached A"
);
assert_ne!(
delta_b.as_ref(),
delta_a.as_ref(),
"delta must change after the state changed"
);
}
#[tokio::test(flavor = "current_thread")]
async fn summarize_unchanged_skips_state_load() {
let storage = MockStateStorage::new();
let mut exec = Executor::new_mock_wasm_uncached("summarize_opt", storage.clone())
.await
.expect("create uncached executor");
let contract = test_contract(b"summarize_opt");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3, 4, 5]);
exec.upsert_contract_state(
key,
Either::Left(state.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT");
let _ = exec
.summarize_contract_state(key)
.await
.expect("summarize 1");
let gets_after_first = storage.get_count();
assert!(
gets_after_first >= 1,
"the cold first summarize must load the state at least once"
);
let _ = exec
.summarize_contract_state(key)
.await
.expect("summarize 2");
let _ = exec
.summarize_contract_state(key)
.await
.expect("summarize 3");
assert_eq!(
storage.get_count(),
gets_after_first,
"repeated unchanged summarize must not reload the state (fast path)"
);
let new_state = WrappedState::new(vec![6, 7, 8, 9, 10, 11]);
exec.upsert_contract_state(
key,
Either::Left(new_state.clone()),
RelatedContracts::default(),
None,
)
.await
.expect("UPDATE");
let gets_before_post_update = storage.get_count();
let _ = exec
.summarize_contract_state(key)
.await
.expect("summarize after update");
assert!(
storage.get_count() > gets_before_post_update,
"summarize after an update must reload the changed state"
);
}
#[tokio::test(flavor = "current_thread")]
async fn delta_unchanged_skips_state_load() {
let storage = MockStateStorage::new();
let mut exec = Executor::new_mock_wasm_uncached("delta_opt", storage.clone())
.await
.expect("create uncached executor");
let contract = test_contract(b"delta_opt");
let key = contract.key();
let state = WrappedState::new(vec![5, 4, 3, 2, 1]);
let peer_summary = StateSummary::from(vec![7u8; 3]);
exec.upsert_contract_state(
key,
Either::Left(state.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT");
let _ = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta 1");
let gets_after_first = storage.get_count();
assert!(
gets_after_first >= 1,
"cold first delta must load the state"
);
let _ = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta 2");
let _ = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta 3");
assert_eq!(
storage.get_count(),
gets_after_first,
"repeated unchanged delta must not reload the state (fast path)"
);
let new_state = WrappedState::new(vec![9, 8, 7, 6, 5, 4]);
exec.upsert_contract_state(
key,
Either::Left(new_state.clone()),
RelatedContracts::default(),
None,
)
.await
.expect("UPDATE");
let gets_before_post_update = storage.get_count();
let _ = exec
.get_contract_state_delta(key, peer_summary.clone())
.await
.expect("delta after update");
assert!(
storage.get_count() > gets_before_post_update,
"delta after an update must reload the changed state"
);
}
#[tokio::test(flavor = "current_thread")]
async fn summarize_absent_contract_errors() {
let storage = MockStateStorage::new();
let mut exec = cached_executor(storage).await;
let contract = test_contract(b"summarize_absent");
let key = contract.key();
let result = exec.summarize_contract_state(key).await;
assert!(
result.is_err(),
"summarize of an absent contract must error, not return a phantom summary"
);
}
#[tokio::test(flavor = "current_thread")]
async fn summarize_with_cold_caches_recomputes_correctly() {
let storage = MockStateStorage::new();
let contract = test_contract(b"summarize_restart");
let key = contract.key();
let state = WrappedState::new(vec![3, 1, 4, 1, 5]);
let mut e1 = cached_executor(storage.clone()).await;
e1.upsert_contract_state(
key,
Either::Left(state.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT");
let s1 = e1
.summarize_contract_state(key)
.await
.expect("summarize e1");
assert_eq!(s1.as_ref(), blake3::hash(state.as_ref()).as_bytes());
let mut e2 = cached_executor(storage.clone()).await;
let s2 = e2
.summarize_contract_state(key)
.await
.expect("summarize e2");
assert_eq!(
s2.as_ref(),
blake3::hash(state.as_ref()).as_bytes(),
"cold-cache executor must recompute the correct summary from disk"
);
}
async fn build_op_manager(id: &str) -> (Arc<OpManager>, Box<dyn std::any::Any>) {
let config_args = ConfigArgs {
id: Some(id.to_string()),
mode: Some(OperationMode::Local),
..Default::default()
};
let node_config =
crate::node::NodeConfig::new(config_args.build().await.expect("build Config"))
.await
.expect("build NodeConfig");
let (notification_rx, notification_tx) = crate::node::event_loop_notification_channel();
let (ops_ch_channel, ch_channel, wait_for_event) = crate::contract::contract_handler_channel();
let connection_manager = crate::ring::ConnectionManager::new(&node_config);
let (result_router_tx, result_router_rx) = tokio::sync::mpsc::channel(100);
let task_monitor = crate::node::background_task_monitor::BackgroundTaskMonitor::new();
let op_manager = Arc::new(
OpManager::new(
notification_tx,
ops_ch_channel,
&node_config,
crate::tracing::DynamicRegister::new(vec![]),
connection_manager,
result_router_tx,
&task_monitor,
)
.expect("build OpManager"),
);
op_manager.ring.attach_op_manager(&op_manager);
let guards: Box<dyn std::any::Any> = Box::new((
notification_rx,
ch_channel,
wait_for_event,
result_router_rx,
task_monitor,
));
(op_manager, guards)
}
#[tokio::test(flavor = "current_thread")]
async fn summary_cache_covers_live_hosted_count_not_fixed_cap() {
for n in [1024usize, 3000, 5000] {
let storage = MockStateStorage::new();
let (op_manager, _guards) = build_op_manager(&format!("cache_cover_{n}")).await;
let mut exec = Executor::new_mock_wasm_uncached_with_op_manager(
"cache_cover",
storage.clone(),
op_manager.clone(),
)
.await
.expect("create uncached executor with op_manager");
let mut keys = Vec::with_capacity(n);
for i in 0..n {
let contract = test_contract(format!("cache_cover_{n}_{i}").as_bytes());
let key = contract.key();
let state = WrappedState::new(vec![(i & 0xff) as u8, (i >> 8) as u8, 1, 2, 3]);
exec.upsert_contract_state(
key,
Either::Left(state.clone()),
RelatedContracts::default(),
Some(contract.clone()),
)
.await
.expect("PUT");
op_manager
.ring
.host_contract(key, state.as_ref().len() as u64, AccessType::Get);
keys.push(key);
}
assert_eq!(
op_manager.ring.hosting_contracts_count(),
n,
"all {n} contracts must be hosted (this count is what drives cache sizing)"
);
for key in &keys {
let _ = exec
.summarize_contract_state(*key)
.await
.expect("summarize pass 1");
}
let gets_after_pass1 = storage.get_count();
for key in &keys {
let _ = exec
.summarize_contract_state(*key)
.await
.expect("summarize pass 2");
}
assert_eq!(
storage.get_count(),
gets_after_pass1,
"pass 2 reloaded state for hosted_count={n}: the summary cache must be sized \
to the live hosted count so no cold-module recompute happens on the interest \
heartbeat (a fixed cap < N evicts and reloads)"
);
}
}