From c7de58f1e40e6d0a1cacdef7236c9b107abd453b Mon Sep 17 00:00:00 2001 From: Grigorii Sobol Date: Thu, 28 May 2026 09:09:01 +0200 Subject: [PATCH] =?UTF-8?q?test(ethexe-runtime-common):=20repro=20for=20st?= =?UTF-8?q?ale=20region=20hash=20in=20MemoryPages=20remove=5Fand=5Fstore?= =?UTF-8?q?=5Fregions=20(auto-tester)=20a07d9f041168=20=E2=80=94=20refs=20?= =?UTF-8?q?#5373?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../common/tests/auto_tester_a07d9f041168.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 ethexe/runtime/common/tests/auto_tester_a07d9f041168.rs diff --git a/ethexe/runtime/common/tests/auto_tester_a07d9f041168.rs b/ethexe/runtime/common/tests/auto_tester_a07d9f041168.rs new file mode 100644 index 00000000000..43e569671e9 --- /dev/null +++ b/ethexe/runtime/common/tests/auto_tester_a07d9f041168.rs @@ -0,0 +1,50 @@ +// auto_tester_a07d9f041168 +// scenario: invalid_combination +// param_signature: remove_and_store_regions stale region hash on full region clear + +// Reproduces the bug tracked by `// TODO #5373` at ethexe/runtime/common/src/state.rs:1057-1062. +// Closed upstream issue: https://github.com/gear-tech/gear/issues/5373 +// +// When `remove_and_store_regions` empties a region (last page removed), +// `region.store(storage)` returns `MaybeHashOf::empty()`. The if-let in the +// update loop then never fires, so `self[region_idx]` keeps its old +// (non-empty) hash — the MemoryPages aggregate hash is stale. +// +// Expected: removing the only page in a region must change the MemoryPages +// hash (the region is no longer represented by its old content hash). +// Observed: hash before == hash after. + +use ethexe_runtime_common::state::{MemStorage, MemoryPages, Storage}; +use gear_core::{memory::PageBuf, pages::GearPage}; +use std::collections::BTreeMap; + +#[test] +fn remove_last_page_in_region_must_clear_region_hash() { + let storage = MemStorage::default(); + let mut pages = MemoryPages::default(); + + // Add one page to region 0. + let page0 = GearPage::from(0u16); + let buf = PageBuf::new_zeroed(); + let page_hash = storage.write_page_data(buf); + + let mut page_map = BTreeMap::new(); + page_map.insert(page0, page_hash); + pages.update_and_store_regions(&storage, page_map); + + let hash_with_page = pages.clone().store(&storage).to_inner(); + + // Remove the only page — region 0 is now empty. + pages.remove_and_store_regions(&storage, &vec![page0]); + + let hash_after_remove = pages.clone().store(&storage).to_inner(); + + // The aggregate hash MUST change: an empty region is observationally + // different from a region holding one page. + assert_ne!( + hash_with_page, hash_after_remove, + "MemoryPages hash must change when the last page in a region is removed; \ + see TODO #5373 in state.rs:1057-1062 (the if-let skips empty regions \ + and leaves a stale region hash in self[region_idx])" + ); +}