use either::Either;
use freenet_stdlib::prelude::*;
use crate::contract::executor::mock_wasm_runtime::MockWasmRuntime;
use crate::contract::executor::{ContractExecutor, Executor};
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"
);
}