From 444f858d27db10ea86894c46e8c920884055ee27 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:30:35 +0700 Subject: [PATCH 01/24] refactor(db): move announces from BlockMeta to AnnounceStorage --- ethexe/common/src/db.rs | 11 +++++++---- ethexe/db/src/database.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index 4b688e29d65..b2ec177fd23 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -42,9 +42,6 @@ pub struct BlockMeta { /// Block has been prepared, meaning: /// all metadata is ready, all predecessors till start block are prepared too. pub prepared: bool, - // TODO: #4945 remove announces from here - /// Set of announces included in the block. - pub announces: Option>>, /// Queue of code ids waiting for validation status commitment on-chain. pub codes_queue: Option>, /// Last committed on-chain batch hash. @@ -57,7 +54,6 @@ impl BlockMeta { pub fn default_prepared() -> Self { Self { prepared: true, - announces: Some(Default::default()), codes_queue: Some(Default::default()), last_committed_batch: Some(Default::default()), last_committed_announce: Some(Default::default()), @@ -157,11 +153,13 @@ pub trait AnnounceStorageRO { fn announce_outcome(&self, announce_hash: HashOf) -> Option>; fn announce_schedule(&self, announce_hash: HashOf) -> Option; fn announce_meta(&self, announce_hash: HashOf) -> AnnounceMeta; + fn block_announces(&self, block_hash: H256) -> Option>>; } #[auto_impl::auto_impl(&)] pub trait AnnounceStorageRW: AnnounceStorageRO { fn set_announce(&self, announce: Announce) -> HashOf; + fn set_block_announces(&self, block_hash: H256, announces: BTreeSet>); fn set_announce_program_states( &self, announce_hash: HashOf, @@ -175,6 +173,11 @@ pub trait AnnounceStorageRW: AnnounceStorageRO { announce_hash: HashOf, f: impl FnOnce(&mut AnnounceMeta), ); + fn mutate_block_announces( + &self, + block_hash: H256, + f: impl FnOnce(&mut BTreeSet>), + ); } #[derive(Debug, Clone, Default, Encode, Decode, PartialEq, Eq)] diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index b966df2d12e..d67e4248be9 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -77,6 +77,8 @@ enum Key { // TODO kuzmindev: temporal solution - must move into block meta or something else. LatestEraValidatorsCommitted(H256), + + BlockAnnounces(H256) = 17, } impl Key { @@ -93,6 +95,7 @@ impl Key { match self { Self::BlockSmallData(hash) | Self::BlockEvents(hash) + | Self::BlockAnnounces(hash) | Self::LatestEraValidatorsCommitted(hash) => [prefix.as_ref(), hash.as_ref()].concat(), Self::ValidatorSet(era_index) => { @@ -643,6 +646,15 @@ impl AnnounceStorageRO for Database { }) .unwrap_or_default() } + + fn block_announces(&self, block_hash: H256) -> Option>> { + self.kv + .get(&Key::BlockAnnounces(block_hash).to_bytes()) + .map(|data| { + BTreeSet::>::decode(&mut data.as_slice()) + .expect("Failed to decode data into `BTreeSet>`") + }) + } } impl AnnounceStorageRW for Database { @@ -652,6 +664,14 @@ impl AnnounceStorageRW for Database { unsafe { HashOf::new(self.cas.write(&announce.encode())) } } + fn set_block_announces(&self, block_hash: H256, announces: BTreeSet>) { + tracing::trace!("Set block {block_hash} announces: len {}", announces.len()); + self.kv.put( + &Key::BlockAnnounces(block_hash).to_bytes(), + announces.encode(), + ); + } + fn set_announce_program_states( &self, announce_hash: HashOf, @@ -700,6 +720,20 @@ impl AnnounceStorageRW for Database { self.kv .put(&Key::AnnounceMeta(announce_hash).to_bytes(), meta.encode()); } + + fn mutate_block_announces( + &self, + block_hash: H256, + f: impl FnOnce(&mut BTreeSet>), + ) { + tracing::trace!("For block {block_hash} mutate announces"); + let mut announces = self.block_announces(block_hash).unwrap_or_default(); + f(&mut announces); + self.kv.put( + &Key::BlockAnnounces(block_hash).to_bytes(), + announces.encode(), + ); + } } impl LatestDataStorageRO for Database { From 9a042d4b083219e743698f63522dad9aed8859a2 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:31:51 +0700 Subject: [PATCH 02/24] refactor(ethexe/compute): use AnnounceStorage --- ethexe/compute/src/tests.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ethexe/compute/src/tests.rs b/ethexe/compute/src/tests.rs index 5f5db2ef935..d7c0fc996dd 100644 --- a/ethexe/compute/src/tests.rs +++ b/ethexe/compute/src/tests.rs @@ -204,9 +204,10 @@ impl TestEnv { let processed_announce = event.unwrap_announce_computed(); assert_eq!(processed_announce, announce_hash); - self.db.mutate_block_meta(announce.block_hash, |meta| { - meta.announces.get_or_insert_default().insert(announce_hash); - }); + self.db + .mutate_block_announces(announce.block_hash, |announces| { + announces.insert(announce_hash); + }); } } @@ -251,11 +252,10 @@ async fn multiple_preparation_and_one_processing() -> Result<()> { // append announces to prepared blocks, except the last one, so that it can be computed for i in 1..3 { let announce = new_announce(&env.db, env.chain.blocks[i].hash, Some(100)); - env.db.mutate_block_meta(announce.block_hash, |meta| { - meta.announces - .get_or_insert_default() - .insert(announce.to_hash()); - }); + env.db + .mutate_block_announces(announce.block_hash, |announces| { + announces.insert(announce.to_hash()); + }); env.db.set_announce(announce); } From 4a066526b48745f8a039e5aafd5b3d1aa3cec919 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:32:13 +0700 Subject: [PATCH 03/24] refactor(ethexe/common): use AnnounceStorage for block announces --- ethexe/common/src/mock.rs | 10 ++++++---- ethexe/common/src/utils.rs | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ethexe/common/src/mock.rs b/ethexe/common/src/mock.rs index 377cfe324b1..4d9775ef5c1 100644 --- a/ethexe/common/src/mock.rs +++ b/ethexe/common/src/mock.rs @@ -409,10 +409,13 @@ impl BlockChain { }); } + if let Some(announces) = announces { + db.set_block_announces(hash, announces); + } + db.mutate_block_meta(hash, |meta| { *meta = BlockMeta { prepared: true, - announces, codes_queue: Some(codes_queue), last_committed_batch: Some(last_committed_batch), last_committed_announce: Some(last_committed_announce), @@ -544,7 +547,7 @@ pub trait DBMockExt { fn top_announce_hash(&self, block: H256) -> HashOf; } -impl DBMockExt for DB { +impl DBMockExt for DB { #[track_caller] fn simple_block_data(&self, block: H256) -> SimpleBlockData { let header = self.block_header(block).expect("block header not found"); @@ -556,8 +559,7 @@ impl DBMockExt for DB { #[track_caller] fn top_announce_hash(&self, block: H256) -> HashOf { - self.block_meta(block) - .announces + self.block_announces(block) .expect("block announces not found") .into_iter() .next() diff --git a/ethexe/common/src/utils.rs b/ethexe/common/src/utils.rs index 59a61de9ba6..d96ec563713 100644 --- a/ethexe/common/src/utils.rs +++ b/ethexe/common/src/utils.rs @@ -149,7 +149,7 @@ pub fn setup_genesis_in_db< } } -pub fn setup_block_in_db( +pub fn setup_block_in_db( db: &DB, block_hash: H256, block_data: FullBlockData, @@ -158,10 +158,11 @@ pub fn setup_block_in_db( db.set_block_events(block_hash, &block_data.events); db.set_block_synced(block_hash); + db.set_block_announces(block_hash, block_data.announces); + db.mutate_block_meta(block_hash, |meta| { *meta = BlockMeta { prepared: true, - announces: Some(block_data.announces), codes_queue: Some(block_data.codes_queue), last_committed_batch: Some(block_data.last_committed_batch), last_committed_announce: Some(block_data.last_committed_announce), From 82364772af0fa33421236563571bc817dc063200 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:32:29 +0700 Subject: [PATCH 04/24] refactor(ethexe/consensus): use AnnounceStorage for block announces --- ethexe/consensus/src/announces.rs | 57 ++++++++++------------- ethexe/consensus/src/validator/core.rs | 9 +--- ethexe/consensus/src/validator/initial.rs | 6 +-- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/ethexe/consensus/src/announces.rs b/ethexe/consensus/src/announces.rs index fbbe7fdab16..4823870c94f 100644 --- a/ethexe/consensus/src/announces.rs +++ b/ethexe/consensus/src/announces.rs @@ -147,7 +147,7 @@ impl< .block_header(current_block) .ok_or_else(|| anyhow!("header not found for block({current_block})"))?; - if self.block_meta(current_block).announces.is_some() { + if self.block_announces(current_block).is_some() { break; } @@ -168,11 +168,10 @@ impl< let announce_hash = self.set_announce(announce); let mut newly_included = None; - self.mutate_block_meta(block_hash, |meta| { - if let Some(announces) = &mut meta.announces { - newly_included = Some(announces.insert(announce_hash)); - } - }); + if let Some(mut announces) = self.block_announces(block_hash) { + newly_included = Some(announces.insert(announce_hash)); + self.set_block_announces(block_hash, announces); + } if let Some(newly_included) = newly_included { Ok((announce_hash, newly_included)) @@ -190,7 +189,7 @@ impl< } self.announce(announce_hash) - .and_then(|announce| self.block_meta(announce.block_hash).announces) + .and_then(|announce| self.block_announces(announce.block_hash)) .map(|announces| announces.contains(&announce_hash)) .unwrap_or(false) } @@ -225,7 +224,7 @@ pub fn propagate_announces( // iterate over the collected blocks from oldest to newest and propagate announces for block in chain { debug_assert!( - db.block_meta(block.hash).announces.is_none(), + db.block_announces(block.hash).is_none(), "Block {} should not have announces propagated yet", block.hash ); @@ -249,15 +248,14 @@ pub fn propagate_announces( )?; let mut new_base_announces = BTreeSet::new(); - for parent_announce_hash in db - .block_meta(block.header.parent_hash) - .announces - .ok_or_else(|| { - anyhow!( - "Parent block({}) announces are missing", - block.header.parent_hash - ) - })? + for parent_announce_hash in + db.block_announces(block.header.parent_hash) + .ok_or_else(|| { + anyhow!( + "Parent block({}) announces are missing", + block.header.parent_hash + ) + })? { if let Some(new_base_announce) = propagate_one_base_announce( db, @@ -278,14 +276,12 @@ pub fn propagate_announces( block.hash ); - db.mutate_block_meta(block.hash, |meta| { - debug_assert!( - meta.announces.is_none(), - "block({}) announces must be None before propagation", - block.hash - ); - meta.announces = Some(new_base_announces); - }); + debug_assert!( + db.block_announces(block.hash).is_none(), + "block({}) announces must be None before propagation", + block.hash + ); + db.set_block_announces(block.hash, new_base_announces); } Ok(()) @@ -446,8 +442,7 @@ fn propagate_one_base_announce( // Check neighbor announces to be last committed announce if db - .block_meta(current_announce.block_hash) - .announces + .block_announces(current_announce.block_hash) .ok_or_else(|| { anyhow!( "announces are missing for block({})", @@ -568,8 +563,7 @@ fn find_announces_common_predecessor( .start_announce_hash; let mut announces = db - .block_meta(block_hash) - .announces + .block_announces(block_hash) .ok_or_else(|| anyhow!("announces not found for block {block_hash}"))?; for _ in 0..commitment_delay_limit { @@ -612,7 +606,7 @@ pub fn best_parent_announce( // so we take parents of all announces from `block_hash`, // to be sure that we take only not expired parent announces. let parent_announces = - db.announces_parents(db.block_meta(block_hash).announces.into_iter().flatten())?; + db.announces_parents(db.block_announces(block_hash).into_iter().flatten())?; best_announce(db, parent_announces, commitment_delay_limit) } @@ -800,8 +794,7 @@ mod tests { ) -> (H256, usize) { let block_hash = chain.blocks[idx].hash; let announces_amount = db - .block_meta(block_hash) - .announces + .block_announces(block_hash) .unwrap_or_else(|| panic!("announces not found for block {block_hash}")) .len(); (block_hash, announces_amount) diff --git a/ethexe/consensus/src/validator/core.rs b/ethexe/consensus/src/validator/core.rs index 8bfcadf9319..6ae4c1d5f10 100644 --- a/ethexe/consensus/src/validator/core.rs +++ b/ethexe/consensus/src/validator/core.rs @@ -28,7 +28,7 @@ use async_trait::async_trait; use ethexe_common::{ Address, Announce, Digest, HashOf, ProtocolTimelines, SimpleBlockData, ToDigest, ValidatorsVec, consensus::BatchCommitmentValidationRequest, - db::{BlockMetaStorageRO, OnChainStorageRO}, + db::{AnnounceStorageRO, BlockMetaStorageRO, OnChainStorageRO}, ecdsa::{ContractSignature, PublicKey}, gear::{ BatchCommitment, ChainCommitment, CodeCommitment, RewardsCommitment, ValidatorsCommitment, @@ -262,12 +262,7 @@ impl ValidatorCore { // TODO #4791: support commitment head from another block in chain, // have to check head block is predecessor of current block - let candidates = self - .db - .block_meta(block.hash) - .announces - .into_iter() - .flatten(); + let candidates = self.db.block_announces(block.hash).into_iter().flatten(); let best_announce_hash = announces::best_announce(&self.db, candidates, self.commitment_delay_limit)?; diff --git a/ethexe/consensus/src/validator/initial.rs b/ethexe/consensus/src/validator/initial.rs index be85e66041a..b992536ce50 100644 --- a/ethexe/consensus/src/validator/initial.rs +++ b/ethexe/consensus/src/validator/initial.rs @@ -439,11 +439,11 @@ mod tests { let ctx = state.into_context(); assert_eq!(ctx.output, vec![]); for i in last - 5..last - 5 + ctx.core.commitment_delay_limit as usize { - let announces = ctx.core.db.block_meta(chain.blocks[i].hash).announces; + let announces = ctx.core.db.block_announces(chain.blocks[i].hash); assert_eq!(announces.unwrap().len(), 2); } for i in last - 5 + ctx.core.commitment_delay_limit as usize..=last { - let announces = ctx.core.db.block_meta(chain.blocks[i].hash).announces; + let announces = ctx.core.db.block_announces(chain.blocks[i].hash); assert_eq!(announces.unwrap().len(), 1); } } @@ -475,7 +475,7 @@ mod tests { assert_eq!(ctx.output, vec![]); (last - 9..=last).for_each(|idx| { let block_hash = chain.blocks[idx].hash; - let announces = ctx.core.db.block_meta(block_hash).announces; + let announces = ctx.core.db.block_announces(block_hash); assert!( announces.is_some(), "expected announces to be propagated for block {block_hash}" From 642dd9d814fedabcdc6824202f2f04fbbaac5db4 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:32:38 +0700 Subject: [PATCH 05/24] refactor(ethexe/db): use AnnounceStorage for block announces --- ethexe/db/src/iterator.rs | 5 ++--- ethexe/db/src/verifier.rs | 9 ++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ethexe/db/src/iterator.rs b/ethexe/db/src/iterator.rs index f98aaec4716..faffed2a2f6 100644 --- a/ethexe/db/src/iterator.rs +++ b/ethexe/db/src/iterator.rs @@ -526,14 +526,13 @@ where fn iter_block_meta(&mut self, BlockMetaNode { block, meta }: &BlockMetaNode) { let BlockMeta { prepared: _, - announces, codes_queue, last_committed_batch: _, last_committed_announce: _, } = meta; - if let Some(announces) = announces { - for &announce_hash in announces { + if let Some(announces) = self.storage.block_announces(*block) { + for announce_hash in announces.into_iter() { try_push_node!(with_hash: self.announce(announce_hash)); } } else { diff --git a/ethexe/db/src/verifier.rs b/ethexe/db/src/verifier.rs index 75d9cadf207..8f6178c7ebb 100644 --- a/ethexe/db/src/verifier.rs +++ b/ethexe/db/src/verifier.rs @@ -23,7 +23,7 @@ use crate::{ }; use ethexe_common::{ Announce, BlockHeader, HashOf, ScheduledTask, - db::{AnnounceStorageRO, BlockMeta, BlockMetaStorageRO, OnChainStorageRO}, + db::{AnnounceStorageRO, BlockMeta, OnChainStorageRO}, }; use ethexe_runtime_common::state::{MessageQueue, MessageQueueHashWithSize}; use gear_core::code::CodeMetadata; @@ -162,7 +162,7 @@ impl DatabaseVisitor for IntegrityVerifier { self.errors .push(IntegrityVerifierError::NoBlockLastCommittedAnnounce(block)); } - if let Some(announces) = meta.announces { + if let Some(announces) = self.db.block_announces(block) { if announces.is_empty() { self.errors .push(IntegrityVerifierError::BlockAnnouncesIsEmpty(block)); @@ -181,8 +181,7 @@ impl DatabaseVisitor for IntegrityVerifier { } if self .db - .block_meta(announce.block_hash) - .announces + .block_announces(announce.block_hash) .map(|announces| announces.iter().all(|a| *a != announce_hash)) .unwrap_or(true) { @@ -690,11 +689,11 @@ mod tests { db.set_block_header(block_hash, block_header); db.set_block_events(block_hash, &[]); + db.set_block_announces(block_hash, [announce_hash].into()); db.mutate_block_meta(block_hash, |meta| { meta.prepared = true; meta.last_committed_batch = Some(Digest::random()); meta.last_committed_announce = Some(announce_hash); - meta.announces = Some([announce_hash].into()); meta.codes_queue = Some(Default::default()); }); db.set_block_synced(block_hash); From 587480a3858d40f4b181f6c12b4ce8bb9b5f5768 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:32:54 +0700 Subject: [PATCH 06/24] refactor(ethexe/network): use AnnounceStorage for block announces --- ethexe/network/src/db_sync/mod.rs | 11 +++++------ ethexe/network/src/db_sync/responses.rs | 5 ++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ethexe/network/src/db_sync/mod.rs b/ethexe/network/src/db_sync/mod.rs index 8b843c93b16..b7f398bf8d5 100644 --- a/ethexe/network/src/db_sync/mod.rs +++ b/ethexe/network/src/db_sync/mod.rs @@ -1323,13 +1323,12 @@ pub(crate) mod tests { .set_programs_code_ids_at(program_ids.clone(), H256::zero(), code_ids.clone()) .await; - let mut announce_hash = HashOf::zero(); - right_db.mutate_block_meta(H256::zero(), |meta| { - assert!(meta.announces.is_none()); - let announce = Announce::base(H256::zero(), HashOf::zero()); - announce_hash = announce.to_hash(); - meta.announces = Some([announce_hash].into()); + let announce = Announce::base(H256::zero(), HashOf::zero()); + let announce_hash = announce.to_hash(); + right_db.mutate_block_announces(H256::zero(), |announces| { + announces.insert(announce_hash); }); + right_db.mutate_block_meta(H256::zero(), |_meta| {}); right_db.set_announce_program_states( announce_hash, diff --git a/ethexe/network/src/db_sync/responses.rs b/ethexe/network/src/db_sync/responses.rs index 479e29e7d8e..0268df2e03d 100644 --- a/ethexe/network/src/db_sync/responses.rs +++ b/ethexe/network/src/db_sync/responses.rs @@ -25,7 +25,7 @@ use crate::{ }; use ethexe_common::{ Announce, HashOf, - db::{AnnounceStorageRO, BlockMetaStorageRO, HashStorageRO, LatestData, LatestDataStorageRO}, + db::{AnnounceStorageRO, HashStorageRO, LatestData, LatestDataStorageRO}, network::{AnnouncesRequest, AnnouncesRequestUntil, AnnouncesResponse}, }; use libp2p::request_response; @@ -81,8 +81,7 @@ impl OngoingResponses { ) .into(), InnerRequest::ProgramIds(request) => InnerProgramIdsResponse( - db.block_meta(request.at) - .announces + db.block_announces(request.at) .into_iter() .flatten() .find_map(|announce_hash| db.announce_program_states(announce_hash)) From 98b9839ed2d086be6ce2a84e1154acd3101b67ed Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:33:07 +0700 Subject: [PATCH 07/24] refactor(ethexe/processor): use AnnounceStorage for block announces --- ethexe/processor/src/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index 06031198ab5..3054df126ae 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -981,9 +981,10 @@ async fn overlay_execution_noop() { // ----------------------------------------------------------------------------- // Setup the block3 block meta - processor.db.mutate_block_meta(block3, |meta| { - meta.announces = Some(BTreeSet::from([block3_announce_hash])); + processor.db.mutate_block_announces(block3, |announces| { + announces.insert(block3_announce_hash); }); + // Set announce so overlay finds it let block3_announce_hash = processor.db.set_announce(block3_announce); From 05512551386cd60b3f450694f5fc457f5886390e Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:33:16 +0700 Subject: [PATCH 08/24] refactor(ethexe/processor): use AnnounceStorage for block announces --- ethexe/rpc/src/utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethexe/rpc/src/utils.rs b/ethexe/rpc/src/utils.rs index 02966e9570f..fee89e25c6e 100644 --- a/ethexe/rpc/src/utils.rs +++ b/ethexe/rpc/src/utils.rs @@ -58,8 +58,7 @@ pub fn announce_at_or_latest_computed< ) -> RpcResult> { if let Some(at) = at.into() { let computed_announces: Vec<_> = db - .block_meta(at) - .announces + .block_announces(at) .into_iter() .flatten() .filter(|announce_hash| db.announce_meta(*announce_hash).computed) From 096c614406dd75b2e26fb40b0d9a12190af575c5 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 06:33:40 +0700 Subject: [PATCH 09/24] refactor(ethexe/service/tests): use AnnounceStorage for block announces --- ethexe/service/src/tests/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ethexe/service/src/tests/mod.rs b/ethexe/service/src/tests/mod.rs index 83699d33900..2efbf7f363a 100644 --- a/ethexe/service/src/tests/mod.rs +++ b/ethexe/service/src/tests/mod.rs @@ -1598,9 +1598,9 @@ async fn fast_sync() { bob_meta.last_committed_batch ); - let Some((alice_announces, bob_announces)) = - alice_meta.announces.zip(bob_meta.announces) - else { + let alice_announces = alice.db.block_announces(block); + let bob_announces = bob.db.block_announces(block); + let Some((alice_announces, bob_announces)) = alice_announces.zip(bob_announces) else { panic!("alice or bob has no announces"); }; @@ -2810,8 +2810,7 @@ async fn announces_conflicts() { let timelines = env.db.protocol_timelines().unwrap(); let era_index = timelines.era_from_ts(block.header.timestamp); let parent = validator1_db - .block_meta(block.header.parent_hash) - .announces + .block_announces(block.header.parent_hash) .into_iter() .flatten() .find(|&announce_hash| validator1_db.announce(announce_hash).unwrap().is_base()) From 48dc70b76058b4b73d3b689c83ac15d6f6115ebb Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 07:03:19 +0700 Subject: [PATCH 10/24] update tests.rs --- ethexe/processor/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index 3054df126ae..09b64359617 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -981,9 +981,9 @@ async fn overlay_execution_noop() { // ----------------------------------------------------------------------------- // Setup the block3 block meta - processor.db.mutate_block_announces(block3, |announces| { - announces.insert(block3_announce_hash); - }); + processor + .db + .set_block_announces(block3, BTreeSet::from([block3_announce_hash])); // Set announce so overlay finds it let block3_announce_hash = processor.db.set_announce(block3_announce); From 9c61733e6f8b0c3beef1e59493f3ca1d22008e73 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 21:59:09 +0700 Subject: [PATCH 11/24] feat(ethexe/db): add take_block_announces --- ethexe/common/src/db.rs | 2 ++ ethexe/db/src/database.rs | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index b2ec177fd23..455cba8b3c8 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -178,6 +178,8 @@ pub trait AnnounceStorageRW: AnnounceStorageRO { block_hash: H256, f: impl FnOnce(&mut BTreeSet>), ); + + fn take_block_announces(&self, block_hash: H256) -> Option>>; } #[derive(Debug, Clone, Default, Encode, Decode, PartialEq, Eq)] diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index d67e4248be9..e42ef6d84fb 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -734,6 +734,15 @@ impl AnnounceStorageRW for Database { announces.encode(), ); } + + fn take_block_announces(&self, block_hash: H256) -> Option>> { + self.kv + .take(&Key::BlockAnnounces(block_hash).to_bytes()) + .map(|data| { + BTreeSet::>::decode(&mut data.as_slice()) + .expect("Failed to decode data into `BTreeSet>`") + }) + } } impl LatestDataStorageRO for Database { From 7a9c824cd2ec0d21edaedbc9316321cd7ebe67f1 Mon Sep 17 00:00:00 2001 From: playX18 Date: Thu, 8 Jan 2026 21:59:23 +0700 Subject: [PATCH 12/24] fix(ethexe/mock): take block announces in setup if there are any --- ethexe/common/src/mock.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethexe/common/src/mock.rs b/ethexe/common/src/mock.rs index 4d9775ef5c1..e5d784fabf7 100644 --- a/ethexe/common/src/mock.rs +++ b/ethexe/common/src/mock.rs @@ -411,6 +411,9 @@ impl BlockChain { if let Some(announces) = announces { db.set_block_announces(hash, announces); + } else { + // take announces, might've been set-up by previous test. + db.take_block_announces(hash); } db.mutate_block_meta(hash, |meta| { From 63427f61220d7f7752289986868e36009271b172 Mon Sep 17 00:00:00 2001 From: playX18 Date: Wed, 4 Feb 2026 10:24:11 +0700 Subject: [PATCH 13/24] typos fix --- gsdk/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsdk/src/api.rs b/gsdk/src/api.rs index 209f3bbeccb..c11e8406ea9 100644 --- a/gsdk/src/api.rs +++ b/gsdk/src/api.rs @@ -85,7 +85,7 @@ impl<'a> ApiBuilder<'a> { self.uri(Api::DEV_ENDPOINT) } - /// Sets the dfault node URI of Vara testnet. + /// Sets the default node URI of Vara testnet. pub fn testnet(self) -> Self { self.uri(Api::VARA_TESTNET_ENDPOINT) } From d5ace9c1221c611d7236ea25eb340b6e32b29abe Mon Sep 17 00:00:00 2001 From: playX18 Date: Tue, 24 Mar 2026 08:03:12 +0700 Subject: [PATCH 14/24] fix after merge --- Cargo.lock | 4 +- ethexe/processor/src/tests.rs | 1004 -------------------------- ethexe/rpc/src/apis/mod.rs | 4 +- ethexe/rpc/src/utils.rs | 2 +- utils/gear-workspace-hack/Cargo.toml | 16 +- 5 files changed, 14 insertions(+), 1016 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index defccd6e057..62ce1739585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7343,6 +7343,7 @@ dependencies = [ "const-hex", "constant_time_eq 0.4.2", "crc32fast", + "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "crunchy", @@ -7415,7 +7416,7 @@ dependencies = [ "indexmap 2.13.0", "ipnet", "itertools 0.10.5", - "itertools 0.11.0", + "itertools 0.13.0", "js-sys", "jsonrpsee", "jsonrpsee-client-transport", @@ -7544,7 +7545,6 @@ dependencies = [ "signature", "slice-group-by", "smallvec", - "socket2 0.4.10", "soketto", "sp-allocator", "sp-api", diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index 2868e56ae2d..6bb7c7fb246 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -40,1010 +40,6 @@ use parity_scale_codec::Encode; use tokio::sync::mpsc; use utils::*; -fn init_genesis_block(processor: &mut Processor) -> H256 { - let genesis_block_hash = init_new_block(processor, Default::default()); - - // Set zero hash announce for genesis block (genesis announce hash) - let genesis_announce_hash = HashOf::zero(); - - processor - .db - .set_announce_program_states(genesis_announce_hash, Default::default()); - processor - .db - .set_announce_schedule(genesis_announce_hash, Default::default()); - - genesis_block_hash -} - -fn init_new_block(processor: &mut Processor, header: BlockHeader) -> H256 { - let chain_head = H256::random(); - processor.db.set_block_header(chain_head, header); - processor.creator.set_chain_head(chain_head); - chain_head -} - -#[track_caller] -fn init_new_block_from_parent(processor: &mut Processor, parent_hash: H256) -> H256 { - let parent_block_header = processor.db.block_header(parent_hash).unwrap_or_default(); - let height = parent_block_header.height + 1; - let timestamp = parent_block_header.timestamp + 12; - - init_new_block( - processor, - BlockHeader { - height, - timestamp, - parent_hash, - }, - ) -} - -fn setup_test_env_and_load_codes( - codes: &[&[u8]; N], -) -> (Processor, ProcessingHandler, [CodeId; N]) { - let mut code_ids = Vec::new(); - - let mut processor = Processor::new(Database::memory()).unwrap(); - - let genesis = init_genesis_block(&mut processor); - let block = init_new_block_from_parent(&mut processor, genesis); - let block_announce = Announce::with_default_gas(block, HashOf::zero()); - - for code in codes { - let code_id = processor - .handle_new_code(code) - .expect("failed to call runtime api") - .expect("code failed verification or instrumentation"); - - code_ids.push(code_id); - } - - let handler = processor.handler(block_announce).unwrap(); - - (processor, handler, code_ids.try_into().unwrap()) -} - -fn handle_injected_message( - handler: &mut ProcessingHandler, - actor_id: ActorId, - message_id: MessageId, - source: ActorId, - payload: Vec, - value: u128, - call_reply: bool, -) -> Result<()> { - handler.update_state(actor_id, |state, storage, _| -> Result<()> { - let is_init = state.requires_init_message(); - - let dispatch = Dispatch::new( - storage, - message_id, - source, - payload, - value, - is_init, - MessageType::Injected, - call_reply, - )?; - - state - .injected_queue - .modify_queue(storage, |queue| queue.queue(dispatch)); - - Ok(()) - })?; - - Ok(()) -} - -fn executable_balance(handler: &ProcessingHandler, actor_id: ActorId) -> u128 { - let state_hash = handler - .transitions - .state_of(&actor_id) - .expect("failed to get actor state") - .hash; - - let state = handler - .db - .program_state(state_hash) - .expect("failed to get program state"); - - state.executable_balance -} - -#[tokio::test(flavor = "multi_thread")] -async fn process_observer_event() { - init_logger(); - - let mut processor = Processor::new(Database::memory()).expect("failed to create processor"); - - let genesis = init_genesis_block(&mut processor); - let block1 = init_new_block_from_parent(&mut processor, genesis); - - let code = demo_ping::WASM_BINARY.to_vec(); - let code_id = CodeId::generate(&code); - let code_and_id = CodeAndIdUnchecked { code, code_id }; - - let valid = processor - .process_upload_code(code_and_id) - .expect("failed to upload code"); - assert!(valid); - - let block1_announce = Announce::with_default_gas(block1, HashOf::zero()); - let block1_announce_hash = block1_announce.to_hash(); - - // Process and save results - let FinalizedBlockTransitions { - states, schedule, .. - } = processor - .process_announce(block1_announce, vec![]) - .await - .unwrap(); - processor - .db - .set_announce_program_states(block1_announce_hash, states); - processor - .db - .set_announce_schedule(block1_announce_hash, schedule); - - let block2 = init_new_block_from_parent(&mut processor, block1); - - let actor_id = ActorId::from(42); - - let create_program_events = vec![ - BlockRequestEvent::Router(RouterRequestEvent::ProgramCreated { actor_id, code_id }), - BlockRequestEvent::mirror( - actor_id, - MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 10_000_000_000, - }, - ), - BlockRequestEvent::mirror( - actor_id, - MirrorRequestEvent::MessageQueueingRequested { - id: H256::random().0.into(), - source: H256::random().0.into(), - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ), - ]; - - let block2_announce = Announce::with_default_gas(block2, block1_announce_hash); - let block2_announce_hash = block2_announce.to_hash(); - - // Process block2 announce and save results - let FinalizedBlockTransitions { - states, schedule, .. - } = processor - .process_announce(block2_announce, create_program_events) - .await - .expect("failed to process create program"); - processor - .db - .set_announce_program_states(block2_announce_hash, states); - processor - .db - .set_announce_schedule(block2_announce_hash, schedule); - - let block3 = init_new_block_from_parent(&mut processor, block2); - - let send_message_event = BlockRequestEvent::mirror( - actor_id, - MirrorRequestEvent::MessageQueueingRequested { - id: H256::random().0.into(), - source: H256::random().0.into(), - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ); - - let block3_announce = Announce::with_default_gas(block3, block2_announce_hash); - - // Process block3 announce - processor - .process_announce(block3_announce, vec![send_message_event]) - .await - .expect("failed to process send message"); -} - -#[test] -fn handle_new_code_valid() { - init_logger(); - - let mut processor = Processor::new(Database::memory()).expect("failed to create processor"); - - init_genesis_block(&mut processor); - - let (code_id, original_code) = utils::wat_to_wasm(utils::VALID_PROGRAM); - let original_code_len = original_code.len(); - - assert!(processor.db.original_code(code_id).is_none()); - assert!( - processor - .db - .instrumented_code(ethexe_runtime_common::VERSION, code_id) - .is_none() - ); - - assert!(processor.db.code_metadata(code_id).is_none()); - - let calculated_id = processor - .handle_new_code(&original_code) - .expect("failed to call runtime api") - .expect("code failed verification or instrumentation"); - - assert_eq!(calculated_id, code_id); - - assert_eq!( - processor - .db - .original_code(code_id) - .expect("failed to read original code"), - original_code - ); - - assert!( - processor - .db - .instrumented_code(ethexe_runtime_common::VERSION, code_id) - .expect("failed to read instrumented code") - .bytes() - .len() - > original_code_len - ); - - assert_eq!( - processor - .db - .code_metadata(code_id) - .expect("failed to read code metadata") - .original_code_len(), - original_code_len as u32 - ); -} - -#[test] -fn handle_new_code_invalid() { - init_logger(); - - let mut processor = Processor::new(Database::memory()).expect("failed to create processor"); - - init_genesis_block(&mut processor); - - let (code_id, original_code) = utils::wat_to_wasm(utils::INVALID_PROGRAM); - - assert!(processor.db.original_code(code_id).is_none()); - assert!( - processor - .db - .instrumented_code(ethexe_runtime_common::VERSION, code_id) - .is_none() - ); - - assert!(processor.db.code_metadata(code_id).is_none()); - - assert!( - processor - .handle_new_code(&original_code) - .expect("failed to call runtime api") - .is_none() - ); - - assert!(processor.db.original_code(code_id).is_none()); - assert!( - processor - .db - .instrumented_code(ethexe_runtime_common::VERSION, code_id) - .is_none() - ); - - assert!(processor.db.code_metadata(code_id).is_none()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn ping_pong() { - init_logger(); - - let (mut processor, mut handler, [code_id, ..]) = - setup_test_env_and_load_codes(&[demo_ping::WASM_BINARY, demo_async::WASM_BINARY]); - - let user_id = ActorId::from(10); - let actor_id = ActorId::from(0x10000); - - handler - .handle_router_event(RouterRequestEvent::ProgramCreated { actor_id, code_id }) - .expect("failed to create new program"); - - handler - .handle_mirror_event( - actor_id, - MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 150_000_000_000, - }, - ) - .expect("failed to top up balance"); - - handler - .handle_mirror_event( - actor_id, - MirrorRequestEvent::MessageQueueingRequested { - id: MessageId::from(1), - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - handler - .handle_mirror_event( - actor_id, - MirrorRequestEvent::MessageQueueingRequested { - id: MessageId::from(2), - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - processor.process_queue(&mut handler).await; - - let to_users = handler.transitions.current_messages(); - - assert_eq!(to_users.len(), 2); - - let message = &to_users[0].1; - assert_eq!(message.destination, user_id); - assert_eq!(message.payload, b"PONG"); - - let message = &to_users[1].1; - assert_eq!(message.destination, user_id); - assert_eq!(message.payload, b"PONG"); -} - -#[tokio::test(flavor = "multi_thread")] -async fn async_and_ping() { - init_logger(); - - let mut message_nonce: u64 = 0; - let mut get_next_message_id = || { - message_nonce += 1; - MessageId::from(message_nonce) - }; - - let (mut processor, mut handler, [ping_code_id, upload_code_id, ..]) = - setup_test_env_and_load_codes(&[demo_ping::WASM_BINARY, demo_async::WASM_BINARY]); - - let user_id = ActorId::from(10); - let ping_id = ActorId::from(0x10000000); - let async_id = ActorId::from(0x20000000); - - handler - .handle_router_event(RouterRequestEvent::ProgramCreated { - actor_id: ping_id, - code_id: ping_code_id, - }) - .expect("failed to create new program"); - - handler - .handle_mirror_event( - ping_id, - MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 350_000_000_000, - }, - ) - .expect("failed to top up balance"); - - handler - .handle_mirror_event( - ping_id, - MirrorRequestEvent::MessageQueueingRequested { - id: get_next_message_id(), - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - handler - .handle_router_event(RouterRequestEvent::ProgramCreated { - actor_id: async_id, - code_id: upload_code_id, - }) - .expect("failed to create new program"); - - handler - .handle_mirror_event( - async_id, - MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 1_500_000_000_000, - }, - ) - .expect("failed to top up balance"); - - handler - .handle_mirror_event( - async_id, - MirrorRequestEvent::MessageQueueingRequested { - id: get_next_message_id(), - source: user_id, - payload: ping_id.encode(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - let wait_for_reply_to = get_next_message_id(); - - handler - .handle_mirror_event( - async_id, - MirrorRequestEvent::MessageQueueingRequested { - id: wait_for_reply_to, - source: user_id, - payload: demo_async::Command::Common.encode(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - processor.process_queue(&mut handler).await; - - let to_users = handler.transitions.current_messages(); - - assert_eq!(to_users.len(), 3); - - let message = &to_users[0].1; - assert_eq!(message.destination, user_id); - assert_eq!(message.payload, b"PONG"); - - let message = &to_users[1].1; - assert_eq!(message.destination, user_id); - assert_eq!(message.payload, b""); - - let message = &to_users[2].1; - assert_eq!(message.destination, user_id); - assert_eq!(message.payload, wait_for_reply_to.into_bytes()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn many_waits() { - init_logger(); - - let wat = r#" - (module - (import "env" "memory" (memory 1)) - (import "env" "gr_reply" (func $reply (param i32 i32 i32 i32))) - (import "env" "gr_wait_for" (func $wait_for (param i32))) - (export "handle" (func $handle)) - (func $handle - (if - (i32.eqz (i32.load (i32.const 0x200))) - (then - (i32.store (i32.const 0x200) (i32.const 1)) - (call $wait_for (i32.const 10)) - ) - (else - (call $reply (i32.const 0) (i32.const 13) (i32.const 0x400) (i32.const 0x600)) - ) - ) - ) - (data (i32.const 0) "Hello, world!") - ) - "#; - - let (_, code) = wat_to_wasm(wat); - - let mut processor = Processor::new(Database::memory()).unwrap(); - - let genesis = init_genesis_block(&mut processor); - let block1 = init_new_block_from_parent(&mut processor, genesis); - let block1_announce = Announce::with_default_gas(block1, HashOf::zero()); - let block1_announce_hash = block1_announce.to_hash(); - - let code_id = processor - .handle_new_code(code) - .expect("failed to call runtime api") - .expect("code failed verification or instrumentation"); - - let mut handler = processor.handler(block1_announce).unwrap(); - - let amount = 10000; - for i in 0..amount { - let program_id = ActorId::from(i); - - handler - .handle_router_event(RouterRequestEvent::ProgramCreated { - actor_id: program_id, - code_id, - }) - .expect("failed to create new program"); - - handler - .handle_mirror_event( - program_id, - MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 150_000_000_000, - }, - ) - .expect("failed to top up balance"); - - handler - .handle_mirror_event( - program_id, - MirrorRequestEvent::MessageQueueingRequested { - id: H256::random().0.into(), - source: H256::random().0.into(), - payload: Default::default(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - } - - handler.run_schedule(); - processor.process_queue(&mut handler).await; - - assert_eq!( - handler.transitions.current_messages().len(), - amount as usize - ); - - for pid in handler.transitions.known_programs() { - handler - .handle_mirror_event( - pid, - MirrorRequestEvent::MessageQueueingRequested { - id: H256::random().0.into(), - source: H256::random().0.into(), - payload: Default::default(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - } - - processor.process_queue(&mut handler).await; - - // unchanged - assert_eq!( - handler.transitions.current_messages().len(), - amount as usize - ); - - let FinalizedBlockTransitions { - states, schedule, .. - } = handler.transitions.finalize(); - processor - .db - .set_announce_program_states(block1_announce_hash, states); - processor - .db - .set_announce_schedule(block1_announce_hash, schedule); - - let mut block = block1; - let mut block_announce_hash = block1_announce_hash; - for _ in 0..9 { - block = init_new_block_from_parent(&mut processor, block); - let block_announce = Announce::with_default_gas(block, block_announce_hash); - let parent_announce_hash = block_announce_hash; - block_announce_hash = block_announce.to_hash(); - - let states = processor - .db - .announce_program_states(parent_announce_hash) - .unwrap(); - processor - .db - .set_announce_program_states(block_announce_hash, states); - let schedule = processor - .db - .announce_schedule(parent_announce_hash) - .unwrap(); - processor - .db - .set_announce_schedule(block_announce_hash, schedule); - } - - let block12 = init_new_block_from_parent(&mut processor, block); - let block12_announce = Announce::with_default_gas(block12, block_announce_hash); - - let states = processor - .db - .announce_program_states(block_announce_hash) - .unwrap(); - let schedule = processor.db.announce_schedule(block_announce_hash).unwrap(); - - // Reproducibility test. - let restored_schedule = ScheduleRestorer::from_storage(&processor.db, &states, 0) - .unwrap() - .restore(); - // This could fail in case of handling more scheduled ops: please, update test than. - assert_eq!(schedule, restored_schedule); - - let mut handler = processor.handler(block12_announce).unwrap(); - handler.run_schedule(); - processor.process_queue(&mut handler).await; - - assert_eq!( - handler.transitions.current_messages().len(), - amount as usize - ); - - for (_pid, message) in handler.transitions.current_messages() { - assert_eq!(message.payload, b"Hello, world!"); - } -} - -// Tests that when overlay execution is performed, it doesn't change the original state. -#[tokio::test(flavor = "multi_thread")] -async fn overlay_execution_noop() { - init_logger(); - - // Define message id generator. - let mut message_nonce: u64 = 0; - let mut get_next_message_id = || { - message_nonce += 1; - MessageId::from(message_nonce) - }; - - // Define function to get message queue from state hash. - let get_mq_from_state_hash = - |state_hash: H256, processor: &Processor| -> Result { - let state = processor - .db - .program_state(state_hash) - .ok_or(anyhow!("failed to read pid state"))?; - - state.canonical_queue.query(&processor.db) - }; - - // Define function to get message queue from a specific block for a specific program. - let get_program_mq = |pid: ActorId, - announce_hash: HashOf, - processor: &Processor| - -> Result { - let states = processor - .db - .announce_program_states(announce_hash) - .ok_or(anyhow!("failed to get block states"))?; - let pid_state = states - .get(&pid) - .ok_or(anyhow!("failed to get pid state hash"))?; - - get_mq_from_state_hash(pid_state.hash, processor) - }; - - let user_id = ActorId::from(10); - - let db = MemDb::default(); - let mut processor = Processor::new(Database::from_one(&db)).unwrap(); - - // ----------------------------------------------------------------------------- - // ----------------------------- Initialize db --------------------------------- - // ----------------------------------------------------------------------------- - let parent = init_genesis_block(&mut processor); - let parent_announce_hash = HashOf::zero(); - let block1 = init_new_block_from_parent(&mut processor, parent); - - let block1_announce = Announce::with_default_gas(block1, parent_announce_hash); - let block1_announce_hash = block1_announce.to_hash(); - - let ping_id = ActorId::from(0x10000000); - let async_id = ActorId::from(0x20000000); - - // ----------------------------------------------------------------------------- - // ----------------------------- Upload codes ---------------------------------- - // ----------------------------------------------------------------------------- - let ping_code_id = processor - .handle_new_code(demo_ping::WASM_BINARY) - .expect("failed to call runtime api") - .expect("code failed verification or instrumentation"); - - let async_code_id = processor - .handle_new_code(demo_async::WASM_BINARY) - .expect("failed to call runtime api") - .expect("code failed verification or instrumentation"); - - let events = vec![ - // Create ping program, top up balance and send init message. - BlockRequestEvent::Router(RouterRequestEvent::ProgramCreated { - actor_id: ping_id, - code_id: ping_code_id, - }), - BlockRequestEvent::Mirror { - actor_id: ping_id, - event: MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 400_000_000_000, - }, - }, - BlockRequestEvent::Mirror { - actor_id: ping_id, - event: MirrorRequestEvent::MessageQueueingRequested { - id: get_next_message_id(), - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - }, - // Сreate async program, top up balance and send init message. - BlockRequestEvent::Router(RouterRequestEvent::ProgramCreated { - actor_id: async_id, - code_id: async_code_id, - }), - BlockRequestEvent::Mirror { - actor_id: async_id, - event: MirrorRequestEvent::ExecutableBalanceTopUpRequested { - value: 1_500_000_000_000, - }, - }, - BlockRequestEvent::Mirror { - actor_id: async_id, - event: MirrorRequestEvent::MessageQueueingRequested { - id: get_next_message_id(), - source: user_id, - payload: ping_id.encode(), - value: 0, - call_reply: false, - }, - }, - ]; - - // Check no block states before processing events. - let res = get_program_mq(ping_id, block1_announce_hash, &processor); - assert_eq!( - res.unwrap_err().to_string(), - "failed to get block states".to_string() - ); - assert!(get_program_mq(async_id, block1_announce_hash, &processor).is_err()); - - // Process events - let FinalizedBlockTransitions { - states, schedule, .. - } = processor - .process_announce(block1_announce, events) - .await - .expect("failed to process events"); - - processor - .db - .set_announce_program_states(block1_announce_hash, states); - processor - .db - .set_announce_schedule(block1_announce_hash, schedule); - - // Check that program have empty queues - let ping_mq = - get_program_mq(ping_id, block1_announce_hash, &processor).expect("ping mq wasn't found"); - let async_mq = - get_program_mq(async_id, block1_announce_hash, &processor).expect("async mq wasn't found"); - assert!(ping_mq.is_empty()); - assert!(async_mq.is_empty()); - - // ----------------------------------------------------------------------------- - // ------------------ Create a block with non-empty queues --------------------- - // ----------------------------------------------------------------------------- - // This block won't be processed, but there will be messages saved into corresponding queues. - // This is needed to test a case when RPC calculate reply for handle procedure is called when - // programs already have some state. - - let block2 = init_new_block_from_parent(&mut processor, block1); - let block2_announce = Announce::with_default_gas(block2, block1_announce_hash); - let block2_announce_hash = block2_announce.to_hash(); - - let mut handler_block2 = processor.handler(block2_announce).unwrap(); - - // Manually add messages to programs queues - let new_block_ping_mid1 = get_next_message_id(); - handler_block2 - .handle_mirror_event( - ping_id, - MirrorRequestEvent::MessageQueueingRequested { - id: new_block_ping_mid1, - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - let new_block_ping_mid2 = get_next_message_id(); - handler_block2 - .handle_mirror_event( - ping_id, - MirrorRequestEvent::MessageQueueingRequested { - id: new_block_ping_mid2, - source: user_id, - payload: b"PING".to_vec(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - let new_block_async_mid1 = get_next_message_id(); - handler_block2 - .handle_mirror_event( - async_id, - MirrorRequestEvent::MessageQueueingRequested { - id: new_block_async_mid1, - source: user_id, - payload: demo_async::Command::Common.encode().encode(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - let new_block_async_mid2 = get_next_message_id(); - handler_block2 - .handle_mirror_event( - async_id, - MirrorRequestEvent::MessageQueueingRequested { - id: new_block_async_mid2, - source: user_id, - payload: demo_async::Command::Common.encode().encode(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - let new_block_async_mid3 = get_next_message_id(); - handler_block2 - .handle_mirror_event( - async_id, - MirrorRequestEvent::MessageQueueingRequested { - id: new_block_async_mid3, - source: user_id, - payload: demo_async::Command::Common.encode().encode(), - value: 0, - call_reply: false, - }, - ) - .expect("failed to send message"); - - // Handler ops wrote to the storage states of particular programs, - // but block programs states are not updated yet. That the reason state hash - // can't be obtained from the db. - let ping_state_hash = handler_block2 - .transitions - .state_of(&ping_id) - .expect("failed to get ping state"); - let ping_mq = get_mq_from_state_hash(ping_state_hash.hash, &processor) - .expect("failed to get ping message queue"); - assert_eq!(ping_mq.len(), 2); - - let async_state_hash = handler_block2 - .transitions - .state_of(&async_id) - .expect("failed to get async state"); - let async_mq = get_mq_from_state_hash(async_state_hash.hash, &processor) - .expect("failed to get async message queue"); - assert_eq!(async_mq.len(), 3); - - // Finalize (from the ethexe-processor point of view) the block - let FinalizedBlockTransitions { - states, schedule, .. - } = handler_block2.transitions.finalize(); - processor - .db - .set_announce_program_states(block2_announce_hash, states); - processor - .db - .set_announce_schedule(block2_announce_hash, schedule); - - // Same checks as above, but with obtaining states from db - let ping_mq = - get_program_mq(ping_id, block2_announce_hash, &processor).expect("ping mq wasn't found"); - assert_eq!(ping_mq.len(), 2); - let async_mq = - get_program_mq(async_id, block2_announce_hash, &processor).expect("async mq wasn't found"); - assert_eq!(async_mq.len(), 3); - - // ----------------------------------------------------------------------------- - // -------------- Create a new block without processing queues ----------------- - // ----------------------------------------------------------------------------- - let block3 = init_new_block_from_parent(&mut processor, block2); - let block3_announce = Announce::with_default_gas(block3, block2_announce_hash); - let block3_announce_hash = block3_announce.to_hash(); - - let handler_block3 = processor.handler(block3_announce).unwrap(); - let block3_announce = handler_block3.announce; - let FinalizedBlockTransitions { - states, schedule, .. - } = handler_block3.transitions.finalize(); - processor - .db - .set_announce_program_states(block3_announce_hash, states); - processor - .db - .set_announce_schedule(block3_announce_hash, schedule); - - // Check queues are still not empty in the block3. - let ping_mq = - get_program_mq(ping_id, block3_announce_hash, &processor).expect("ping mq wasn't found"); - assert_eq!(ping_mq.len(), 2); - let async_mq = - get_program_mq(async_id, block3_announce_hash, &processor).expect("async mq wasn't found"); - assert_eq!(async_mq.len(), 3); - - // ----------------------------------------------------------------------------- - // ------------------------ Run in overlay a message --------------------------- - // ----------------------------------------------------------------------------- - - // Setup the block3 block meta - processor - .db - .set_block_announces(block3, BTreeSet::from([block3_announce_hash])); - - // Set announce so overlay finds it - let block3_announce_hash = processor.db.set_announce(block3_announce); - - // Now send message using overlay on the block3. - let mut overlaid_processor = processor.clone().overlaid(); - let runner_config = RunnerConfig::overlay( - processor.config().chunk_processing_threads, - DEFAULT_BLOCK_GAS_LIMIT, - DEFAULT_BLOCK_GAS_LIMIT_MULTIPLIER, - ); - let reply_info = overlaid_processor - .execute_for_reply( - block3_announce_hash, - user_id, - async_id, - demo_async::Command::Common.encode(), - 0, - runner_config, - ) - .await - .expect("failed to call execute_for_reply"); - assert_eq!(reply_info.payload, MessageId::zero().encode()); - - // ----------------------------------------------------------------------------- - // -------------------------- Check message queues ----------------------------- - // ----------------------------------------------------------------------------- - // Check mq states on overlaid processor for block3 - let ping_mq = get_program_mq(ping_id, block3_announce_hash, &overlaid_processor.0) - .expect("ping mq wasn't found"); - assert_eq!(ping_mq.len(), 0); - let async_mq = get_program_mq(async_id, block3_announce_hash, &overlaid_processor.0) - .expect("async mq wasn't found"); - assert_eq!(async_mq.len(), 0); - - // Check mq states on the main processor for block3 - let mut ping_mq = - get_program_mq(ping_id, block3_announce_hash, &processor).expect("ping mq wasn't found"); - assert_eq!(ping_mq.len(), 2); - let ping_msg1 = ping_mq.dequeue().expect("mq is empty"); - assert_eq!(ping_msg1.id, new_block_ping_mid1); - let ping_msg2 = ping_mq.dequeue().expect("mq is empty"); - assert_eq!(ping_msg2.id, new_block_ping_mid2); - - let mut async_mq = - get_program_mq(async_id, block3_announce_hash, &processor).expect("async mq wasn't found"); - assert_eq!(async_mq.len(), 3); - let async_msg1 = async_mq.dequeue().expect("mq is empty"); - assert_eq!(async_msg1.id, new_block_async_mid1); - let async_msg2 = async_mq.dequeue().expect("mq is empty"); - assert_eq!(async_msg2.id, new_block_async_mid2); - let async_msg3 = async_mq.dequeue().expect("mq is empty"); - assert_eq!(async_msg3.id, new_block_async_mid3); -} - mod utils { use super::*; diff --git a/ethexe/rpc/src/apis/mod.rs b/ethexe/rpc/src/apis/mod.rs index 8ed642f4ca1..8a0d0275584 100644 --- a/ethexe/rpc/src/apis/mod.rs +++ b/ethexe/rpc/src/apis/mod.rs @@ -24,9 +24,11 @@ mod program; pub use block::{BlockApi, BlockServer}; pub use code::{CodeApi, CodeServer}; pub use injected::{InjectedApi, InjectedServer}; -pub use program::{FullProgramState, ProgramApi, ProgramServer}; +pub use program::{ProgramApi, ProgramServer}; #[cfg(feature = "client")] pub use crate::apis::{ block::BlockClient, code::CodeClient, injected::InjectedClient, program::ProgramClient, }; +#[cfg(feature = "client")] +pub use program::FullProgramState; diff --git a/ethexe/rpc/src/utils.rs b/ethexe/rpc/src/utils.rs index 84fd06b2c4f..a96aeafbd7c 100644 --- a/ethexe/rpc/src/utils.rs +++ b/ethexe/rpc/src/utils.rs @@ -19,7 +19,7 @@ use crate::errors; use ethexe_common::{ Announce, HashOf, SimpleBlockData, - db::{AnnounceStorageRO, BlockMetaStorageRO, GlobalsStorageRO, OnChainStorageRO}, + db::{AnnounceStorageRO, GlobalsStorageRO, OnChainStorageRO}, }; use ethexe_db::Database; use jsonrpsee::core::RpcResult; diff --git a/utils/gear-workspace-hack/Cargo.toml b/utils/gear-workspace-hack/Cargo.toml index 147d61f8e92..6d7413ed7f8 100644 --- a/utils/gear-workspace-hack/Cargo.toml +++ b/utils/gear-workspace-hack/Cargo.toml @@ -262,6 +262,7 @@ concurrent-queue = { version = "2" } const-hex = { version = "1", features = ["core-error", "serde"] } constant_time_eq = { version = "0.4", default-features = false, features = ["std"] } crc32fast = { version = "1" } +crossbeam-channel = { version = "0.5" } crossbeam-utils = { version = "0.8" } crunchy = { version = "0.2", features = ["std"] } crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } @@ -403,7 +404,6 @@ sha3 = { version = "0.10", features = ["asm"] } signature = { version = "2", default-features = false, features = ["digest", "rand_core", "std"] } slice-group-by = { version = "0.3" } smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union"] } -socket2 = { version = "0.4", default-features = false, features = ["all"] } soketto = { version = "0.8", features = ["http"] } sp-allocator = { git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-polkadot-stable2409-wasm32v1-none" } sp-api = { git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-polkadot-stable2409-wasm32v1-none", features = ["frame-metadata"] } @@ -539,6 +539,7 @@ concurrent-queue = { version = "2" } const-hex = { version = "1", features = ["core-error", "serde"] } constant_time_eq = { version = "0.4", default-features = false, features = ["std"] } crc32fast = { version = "1" } +crossbeam-channel = { version = "0.5" } crossbeam-utils = { version = "0.8" } crunchy = { version = "0.2", features = ["std"] } crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } @@ -688,7 +689,6 @@ sha3 = { version = "0.10", features = ["asm"] } signature = { version = "2", default-features = false, features = ["digest", "rand_core", "std"] } slice-group-by = { version = "0.3" } smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union"] } -socket2 = { version = "0.4", default-features = false, features = ["all"] } soketto = { version = "0.8", features = ["http"] } sp-allocator = { git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-polkadot-stable2409-wasm32v1-none" } sp-api = { git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-polkadot-stable2409-wasm32v1-none", features = ["frame-metadata"] } @@ -781,7 +781,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } mio = { version = "1", features = ["net", "os-ext"] } @@ -806,7 +806,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } mio = { version = "1", features = ["net", "os-ext"] } @@ -832,7 +832,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } mio = { version = "1", features = ["net", "os-ext"] } @@ -856,7 +856,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } mio = { version = "1", features = ["net", "os-ext"] } @@ -881,7 +881,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } nom = { version = "7" } @@ -903,7 +903,7 @@ errno = { version = "0.3" } gimli = { version = "0.28" } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "logging", "ring", "tls12", "webpki-tokio"] } hyper-util = { version = "0.1", default-features = false, features = ["client-proxy"] } -itertools-a6292c17cd707f01 = { package = "itertools", version = "0.11" } +itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false, features = ["extra_traits"] } miniz_oxide = { version = "0.8", default-features = false, features = ["simd", "with-alloc"] } nom = { version = "7" } From e1599864cc6353b4535586f136aab78b87847acf Mon Sep 17 00:00:00 2001 From: playX18 Date: Tue, 24 Mar 2026 09:36:10 +0700 Subject: [PATCH 15/24] remove take_block_announces --- ethexe/common/src/db.rs | 2 -- ethexe/common/src/mock.rs | 3 --- ethexe/db/src/database.rs | 9 --------- 3 files changed, 14 deletions(-) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index 04a39a6824e..677e6caa37d 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -173,8 +173,6 @@ pub trait AnnounceStorageRW: AnnounceStorageRO { block_hash: H256, f: impl FnOnce(&mut BTreeSet>), ); - - fn take_block_announces(&self, block_hash: H256) -> Option>>; } pub struct PreparedBlockData { diff --git a/ethexe/common/src/mock.rs b/ethexe/common/src/mock.rs index 3a5560d11ab..de1746f0570 100644 --- a/ethexe/common/src/mock.rs +++ b/ethexe/common/src/mock.rs @@ -441,9 +441,6 @@ impl BlockChain { { if let Some(announces) = announces { db.set_block_announces(hash, announces); - } else { - // take announces, might've been set-up by previous test. - db.take_block_announces(hash); } db.mutate_block_meta(hash, |meta| { diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index 33bd78f5a20..3bfb63326f8 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -473,14 +473,6 @@ impl AnnounceStorageRW for RawDatabase { ); } - fn take_block_announces(&self, block_hash: H256) -> Option>> { - // SAFETY: `take` removes and returns a value for the given key. - // The key is correctly constructed and the data is decoded safely. - unsafe { self.kv.take(&Key::BlockAnnounces(block_hash).to_bytes()) }.map(|data| { - BTreeSet::>::decode(&mut data.as_slice()) - .expect("Failed to decode data into `BTreeSet>`") - }) - } } impl OnChainStorageRO for RawDatabase { @@ -931,7 +923,6 @@ impl AnnounceStorageRW for Database { block_hash: H256, f: impl FnOnce(&mut BTreeSet>), ); - fn take_block_announces(&self, block_hash: H256) -> Option>>; }); } From 55a6c0364ab9fb431ea8b9d527d23b7eee6b5c0a Mon Sep 17 00:00:00 2001 From: playX18 Date: Tue, 24 Mar 2026 09:36:54 +0700 Subject: [PATCH 16/24] fmt --- ethexe/db/src/database.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index 3bfb63326f8..615ac679beb 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -472,7 +472,6 @@ impl AnnounceStorageRW for RawDatabase { announces.encode(), ); } - } impl OnChainStorageRO for RawDatabase { From a8dceb2103b99e6d93fd63bbd5b5c1ea2b622b3d Mon Sep 17 00:00:00 2001 From: playX18 Date: Tue, 24 Mar 2026 10:05:46 +0700 Subject: [PATCH 17/24] clean db in process_announces_response_rejected --- ethexe/consensus/src/validator/initial.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethexe/consensus/src/validator/initial.rs b/ethexe/consensus/src/validator/initial.rs index 26799677cca..53201e33516 100644 --- a/ethexe/consensus/src/validator/initial.rs +++ b/ethexe/consensus/src/validator/initial.rs @@ -549,7 +549,6 @@ mod tests { let (ctx, _, _) = mock_validator_context(); let block = BlockChain::mock(1) - .setup(&ctx.core.db) .tap_mut(|chain| { chain.blocks[1].as_prepared_mut().announces = None; chain.blocks[1].as_prepared_mut().last_committed_announce = HashOf::random(); From f8493efc282efeefbfc85ae69aa5af1f287b7342 Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Wed, 25 Mar 2026 18:46:56 +0300 Subject: [PATCH 18/24] Change `Key::BlockAnnounces` variant position and discriminant --- ethexe/db/src/database.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index 919e36d1d9d..96f119567e6 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -59,6 +59,7 @@ enum Key { // TODO (kuzmindev): use `HashOf` here BlockSmallData(H256) = 0, BlockEvents(H256) = 1, + BlockAnnounces(H256) = 13, ValidatorSet(u64) = 2, @@ -75,14 +76,11 @@ enum Key { InjectedTransaction(HashOf) = 12, - // TODO kuzmindev: make keys prefixes consistent. We don't change it to avoid corrupting existing key layout. Globals = 14, Config = 15, // TODO kuzmindev: temporal solution - must move into block meta or something else. LatestEraValidatorsCommitted(H256), - - BlockAnnounces(H256) = 17, } impl Key { From b5385214107f81d5068669d87273ba173baf2318 Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Wed, 25 Mar 2026 18:54:22 +0300 Subject: [PATCH 19/24] Add a migration --- ethexe/db/src/database.rs | 11 ++-- ethexe/db/src/migrations/mod.rs | 5 +- ethexe/db/src/migrations/v0.rs | 22 ++++++- ethexe/db/src/migrations/v2.rs | 110 ++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 ethexe/db/src/migrations/v2.rs diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index 96f119567e6..6d16f3a808a 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -48,6 +48,7 @@ use gear_core::{ }; use gprimitives::H256; use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; use std::{ collections::BTreeSet, mem::size_of, @@ -829,11 +830,11 @@ impl HashStorageRO for Database { } } -#[derive(Debug, Clone, Default, Encode, Decode, PartialEq, Eq)] -struct BlockSmallData { - block_header: Option, - block_is_synced: bool, - meta: BlockMeta, +#[derive(Debug, Clone, Default, Encode, Decode, TypeInfo, PartialEq, Eq)] +pub(crate) struct BlockSmallData { + pub block_header: Option, + pub block_is_synced: bool, + pub meta: BlockMeta, } impl BlockMetaStorageRO for Database { diff --git a/ethexe/db/src/migrations/mod.rs b/ethexe/db/src/migrations/mod.rs index ce9ad2a33db..db216e4d05e 100644 --- a/ethexe/db/src/migrations/mod.rs +++ b/ethexe/db/src/migrations/mod.rs @@ -31,10 +31,11 @@ mod migration; mod v0; mod v1; +mod v2; pub const OLDEST_SUPPORTED_VERSION: u32 = v0::VERSION; -pub const LATEST_VERSION: u32 = v1::VERSION; -pub const MIGRATIONS: &[&dyn Migration] = &[&v1::migration_from_v0]; +pub const LATEST_VERSION: u32 = v2::VERSION; +pub const MIGRATIONS: &[&dyn Migration] = &[&v1::migration_from_v0, &v2::migration_from_v1]; const _: () = assert!( (LATEST_VERSION - OLDEST_SUPPORTED_VERSION) as usize == MIGRATIONS.len(), diff --git a/ethexe/db/src/migrations/v0.rs b/ethexe/db/src/migrations/v0.rs index 70947abb2d2..3dcb27cdae1 100644 --- a/ethexe/db/src/migrations/v0.rs +++ b/ethexe/db/src/migrations/v0.rs @@ -16,10 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use ethexe_common::{Announce, HashOf, SimpleBlockData}; -use gprimitives::H256; +use ethexe_common::{Announce, BlockHeader, HashOf, SimpleBlockData}; +use gprimitives::{CodeId, H256}; +use gsigner::Digest; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; +use std::collections::{BTreeSet, VecDeque}; pub const VERSION: u32 = 0; @@ -40,3 +42,19 @@ pub struct ProtocolTimelines { pub era: u64, pub election: u64, } + +#[derive(Encode, Decode, TypeInfo)] +pub struct BlockMeta { + pub prepared: bool, + pub announces: Option>>, + pub codes_queue: Option>, + pub last_committed_batch: Option, + pub last_committed_announce: Option>, +} + +#[derive(Encode, Decode, TypeInfo)] +pub struct BlockSmallData { + pub block_header: Option, + pub block_is_synced: bool, + pub meta: BlockMeta, +} diff --git a/ethexe/db/src/migrations/v2.rs b/ethexe/db/src/migrations/v2.rs new file mode 100644 index 00000000000..85aac8cae71 --- /dev/null +++ b/ethexe/db/src/migrations/v2.rs @@ -0,0 +1,110 @@ +// This file is part of Gear. +// +// Copyright (C) 2026 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::v0; +use crate::{InitConfig, RawDatabase, database::BlockSmallData}; +use anyhow::{Context, Result}; +use ethexe_common::db::{BlockMeta, DBConfig}; +use gprimitives::H256; +use parity_scale_codec::{Decode, Encode}; + +pub const VERSION: u32 = 2; + +pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { + // Changes from v1 to v2: + // - Block announces are moved from `BlockMeta` to `AnnounceStorage`. + + let block_small_data_prefix = H256::from_low_u64_be(0); + let block_announces_prefix = H256::from_low_u64_be(13); + + for (key, value) in db.kv.iter_prefix(block_small_data_prefix.as_bytes()) { + let v0::BlockSmallData { + block_header, + block_is_synced, + meta: + v0::BlockMeta { + prepared, + announces, + codes_queue, + last_committed_batch, + last_committed_announce, + }, + } = v0::BlockSmallData::decode(&mut value.as_slice())?; + + let block_hash = &key[32..]; + let announces_key = [block_announces_prefix.as_bytes(), block_hash].concat(); + + db.kv.put(&announces_key, announces.encode()); + + db.kv.put( + &key, + BlockSmallData { + block_header, + block_is_synced, + meta: BlockMeta { + prepared, + codes_queue, + last_committed_batch, + last_committed_announce, + }, + } + .encode(), + ); + } + + let config_key = [H256::from_low_u64_be(15).0.as_slice(), &[0u8; 8]].concat(); + + let old_config = db + .kv + .get(&config_key) + .context("Database config are guaranteed for version 1, but not found") + .and_then(|bytes| Ok(DBConfig::decode(&mut bytes.as_slice())?))?; + + db.kv.put( + &config_key, + DBConfig { + version: VERSION, + ..old_config + } + .encode(), + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::migrations::test::assert_migration_types_hash; + use scale_info::meta_type; + + #[test] + fn ensure_migration_types() { + assert_migration_types_hash( + "v1->v2", + vec![ + meta_type::(), + meta_type::(), + meta_type::(), + meta_type::(), + meta_type::(), + ], + "6506461993fe4e74645148eb4af27aecfef09e5b4789b5b9936c86adab62a8ff", + ); + } +} From d0d9e8a1dcf223a0060959a170c810066a292404 Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Wed, 25 Mar 2026 19:29:11 +0300 Subject: [PATCH 20/24] Slight adjustments --- ethexe/db/src/migrations/v2.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ethexe/db/src/migrations/v2.rs b/ethexe/db/src/migrations/v2.rs index 85aac8cae71..edc0fba42b1 100644 --- a/ethexe/db/src/migrations/v2.rs +++ b/ethexe/db/src/migrations/v2.rs @@ -16,8 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::v0; -use crate::{InitConfig, RawDatabase, database::BlockSmallData}; +use crate::{InitConfig, RawDatabase, database::BlockSmallData, migrations::v0}; use anyhow::{Context, Result}; use ethexe_common::db::{BlockMeta, DBConfig}; use gprimitives::H256; @@ -27,7 +26,7 @@ pub const VERSION: u32 = 2; pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { // Changes from v1 to v2: - // - Block announces are moved from `BlockMeta` to `AnnounceStorage`. + // - Block announces are moved from `BlockMeta` to `BlockAnnounces` key. let block_small_data_prefix = H256::from_low_u64_be(0); let block_announces_prefix = H256::from_low_u64_be(13); From 774939acf72f6929ed45ab7209219cf76de6090c Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Wed, 25 Mar 2026 19:30:30 +0300 Subject: [PATCH 21/24] Update type info hash --- ethexe/common/src/db.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index 677e6caa37d..e015223b9a1 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -263,7 +263,7 @@ mod tests { #[test] fn ensure_types_unchanged() { const EXPECTED_TYPE_INFO_HASH: &str = - "36d0e8436bb8fa8ea920012e1b4b079f9b6a83414e016771afc977568d30f29b"; + "fe6086aaff64c357bccf5f0dd0c931467de148eb4ef34d8ca2114169c8008d35"; let types = [ meta_type::(), From b6f9405eb063a0fe056b80f3a215e84524473c2a Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Mon, 16 Mar 2026 15:58:02 +0300 Subject: [PATCH 22/24] refactor: merge `LatestEraValidatorsCommitted` into `BlockMeta` --- ethexe/common/src/db.rs | 16 +------ ethexe/common/src/mock.rs | 48 +++++++------------ ethexe/common/src/utils.rs | 6 +-- ethexe/compute/src/prepare.rs | 9 ++-- ethexe/compute/src/tests.rs | 4 +- ethexe/consensus/src/announces.rs | 39 ++++++++------- .../consensus/src/validator/batch/manager.rs | 9 +--- ethexe/consensus/src/validator/batch/tests.rs | 16 ++++--- ethexe/consensus/src/validator/batch/utils.rs | 7 +-- ethexe/consensus/src/validator/initial.rs | 30 +++++++----- ethexe/consensus/src/validator/tx_pool.rs | 2 +- ethexe/db/src/database.rs | 28 ++--------- ethexe/db/src/iterator.rs | 7 +-- ethexe/db/src/migrations/v2.rs | 11 +++++ 14 files changed, 95 insertions(+), 137 deletions(-) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index e015223b9a1..b002ad6e791 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -49,17 +49,8 @@ pub struct BlockMeta { pub last_committed_batch: Option, /// Last committed on-chain announce hash. pub last_committed_announce: Option>, -} - -impl BlockMeta { - pub fn default_prepared() -> Self { - Self { - prepared: true, - codes_queue: Some(Default::default()), - last_committed_batch: Some(Default::default()), - last_committed_announce: Some(Default::default()), - } - } + /// Latest era with committed validators. + pub latest_era_validators_committed: u64, } #[auto_impl::auto_impl(&, Box)] @@ -108,8 +99,6 @@ pub trait OnChainStorageRO { fn code_blob_info(&self, code_id: CodeId) -> Option; fn block_synced(&self, block_hash: H256) -> bool; fn validators(&self, era_index: u64) -> Option; - // TODO kuzmindev: temporal solution - must move into block meta or something else. - fn block_validators_committed_for_era(&self, block_hash: H256) -> Option; } #[auto_impl::auto_impl(&)] @@ -118,7 +107,6 @@ pub trait OnChainStorageRW: OnChainStorageRO { fn set_block_events(&self, block_hash: H256, events: &[BlockEvent]); fn set_code_blob_info(&self, code_id: CodeId, code_info: CodeBlobInfo); fn set_validators(&self, era_index: u64, validator_set: ValidatorsVec); - fn set_block_validators_committed_for_era(&self, block_hash: H256, era_index: u64); fn set_block_synced(&self, block_hash: H256); } diff --git a/ethexe/common/src/mock.rs b/ethexe/common/src/mock.rs index de1746f0570..f1386794b1a 100644 --- a/ethexe/common/src/mock.rs +++ b/ethexe/common/src/mock.rs @@ -225,36 +225,26 @@ pub struct PreparedBlockData { #[derive(Debug, Clone, PartialEq, Eq)] pub struct BlockFullData { pub hash: H256, - pub synced: Option, + pub synced: SyncedBlockData, pub prepared: Option, } impl BlockFullData { #[track_caller] - pub fn as_synced(&self) -> &SyncedBlockData { - self.synced.as_ref().expect("block not synced") + pub fn assert_prepared(&self) -> &PreparedBlockData { + self.prepared.as_ref().expect("block is not prepared") } #[track_caller] - pub fn as_prepared(&self) -> &PreparedBlockData { - self.prepared.as_ref().expect("block not prepared") - } - - #[track_caller] - pub fn as_synced_mut(&mut self) -> &mut SyncedBlockData { - self.synced.as_mut().expect("block not synced") - } - - #[track_caller] - pub fn as_prepared_mut(&mut self) -> &mut PreparedBlockData { - self.prepared.as_mut().expect("block not prepared") + pub fn assert_prepared_mut(&mut self) -> &mut PreparedBlockData { + self.prepared.as_mut().expect("block is not prepared") } #[track_caller] pub fn to_simple(&self) -> SimpleBlockData { SimpleBlockData { hash: self.hash, - header: self.as_synced().header, + header: self.synced.header, } } } @@ -336,7 +326,7 @@ impl BlockChain { self.blocks .get(block_index) .expect("block index overflow") - .as_prepared() + .assert_prepared() .announces .iter() .flatten() @@ -385,7 +375,7 @@ impl BlockChain { self.announces.insert(new_announce_hash, announce_data); self.blocks[block_index] - .as_prepared_mut() + .assert_prepared_mut() .announces .as_mut() .expect("block announces not found") @@ -418,19 +408,16 @@ impl BlockChain { for BlockFullData { hash, - synced, + synced: SyncedBlockData { header, events }, prepared, } in blocks { - if let Some(SyncedBlockData { header, events }) = synced { - db.set_block_header(hash, header); - db.set_block_events(hash, &events); - db.set_block_synced(hash); - - let block_era = config.timelines.era_from_ts(header.timestamp); - db.set_validators(block_era, validators.clone()); - db.set_block_validators_committed_for_era(hash, block_era); - } + db.set_block_header(hash, header); + db.set_block_events(hash, &events); + db.set_block_synced(hash); + + let block_era = config.timelines.era_from_ts(header.timestamp); + db.set_validators(block_era, validators.clone()); if let Some(PreparedBlockData { codes_queue, @@ -449,6 +436,7 @@ impl BlockChain { codes_queue: Some(codes_queue), last_committed_batch: Some(last_committed_batch), last_committed_announce: Some(last_committed_announce), + latest_era_validators_committed: block_era, } }); } @@ -510,14 +498,14 @@ impl Mock<(u32, ValidatorsVec)> for BlockChain { |((parent_hash, _, _), (block_hash, block_height, block_timestamp))| { BlockFullData { hash: block_hash, - synced: Some(SyncedBlockData { + synced: SyncedBlockData { header: BlockHeader { height: block_height, timestamp: block_timestamp as u64, parent_hash, }, events: Default::default(), - }), + }, prepared: Some(PreparedBlockData { codes_queue: Default::default(), announces: Some(Default::default()), // empty here, filled below with announces diff --git a/ethexe/common/src/utils.rs b/ethexe/common/src/utils.rs index 013181b2493..b44215135f7 100644 --- a/ethexe/common/src/utils.rs +++ b/ethexe/common/src/utils.rs @@ -61,13 +61,9 @@ pub fn setup_block_in_db( diff --git a/ethexe/compute/src/prepare.rs b/ethexe/compute/src/prepare.rs index 24ae9634fee..6718c36dc7d 100644 --- a/ethexe/compute/src/prepare.rs +++ b/ethexe/compute/src/prepare.rs @@ -297,9 +297,7 @@ fn prepare_one_block Vec { // Return a map with `CodeId` and corresponding code bytes fn insert_code_events(chain: &mut BlockChain, events_in_block: u32) { let mut nonce = 0; - for data in chain.blocks.iter_mut().map(|data| data.as_synced_mut()) { + for BlockFullData { synced: data, .. } in &mut chain.blocks { data.events = (0..events_in_block) .map(|_| { nonce += 1; @@ -309,7 +309,7 @@ async fn code_validation_request_does_not_block_preparation() -> Result<()> { let mut env = TestEnv::new(1, 3); - let mut block_events = env.chain.blocks[1].as_synced().events.clone(); + let mut block_events = env.chain.blocks[1].synced.events.clone(); // add invalid event which shouldn't stop block prepare block_events.push(BlockEvent::Router(RouterEvent::CodeValidationRequested( diff --git a/ethexe/consensus/src/announces.rs b/ethexe/consensus/src/announces.rs index cf792cf0736..668b6e527ba 100644 --- a/ethexe/consensus/src/announces.rs +++ b/ethexe/consensus/src/announces.rs @@ -790,7 +790,7 @@ mod tests { let mut chain = BlockChain::mock(last as u32); (fnp..=last).for_each(|i| { chain.blocks[i] - .as_prepared_mut() + .assert_prepared_mut() .announces .take() .iter() @@ -807,7 +807,7 @@ mod tests { ); let announce_hash = announce.to_hash(); chain.blocks[wta] - .as_prepared_mut() + .assert_prepared_mut() .announces .as_mut() .unwrap() @@ -922,7 +922,7 @@ mod tests { let mut chain = make_chain(last, fnp, wta); (fnp..=last).for_each(|i| { - chain.blocks[i].as_prepared_mut().last_committed_announce = + chain.blocks[i].assert_prepared_mut().last_committed_announce = chain.block_top_announce_hash(wta); }); @@ -962,7 +962,7 @@ mod tests { let committed_announce_hash = chain.block_top_announce(wta).announce.to_hash(); for i in committed_at..=last { - chain.blocks[i].as_prepared_mut().last_committed_announce = committed_announce_hash; + chain.blocks[i].assert_prepared_mut().last_committed_announce = committed_announce_hash; } let chain = chain.setup(&db); @@ -1002,7 +1002,7 @@ mod tests { let missing_announce_hash = missing_announce.to_hash(); (committed_at..=last).for_each(|i| { - chain.blocks[i].as_prepared_mut().last_committed_announce = missing_announce_hash; + chain.blocks[i].assert_prepared_mut().last_committed_announce = missing_announce_hash; }); let chain = chain.setup(&db); @@ -1056,21 +1056,20 @@ mod tests { let chain = BlockChain::mock(10) .tap_mut(|chain| { - chain.blocks[10].as_synced_mut().events = - (0..MAX_TOUCHED_PROGRAMS_PER_ANNOUNCE / 2 + 1) - .map(|i| BlockEvent::Mirror { - actor_id: ActorId::from(i as u64), - event: MirrorEvent::MessageQueueingRequested( - MessageQueueingRequestedEvent { - id: MessageId::zero(), - source: ActorId::zero(), - payload: vec![], - value: 0, - call_reply: false, - }, - ), - }) - .collect(); + chain.blocks[10].synced.events = (0..MAX_TOUCHED_PROGRAMS_PER_ANNOUNCE / 2 + 1) + .map(|i| BlockEvent::Mirror { + actor_id: ActorId::from(i as u64), + event: MirrorEvent::MessageQueueingRequested( + MessageQueueingRequestedEvent { + id: MessageId::zero(), + source: ActorId::zero(), + payload: vec![], + value: 0, + call_reply: false, + }, + ), + }) + .collect(); chain .block_top_announce_mut(9) diff --git a/ethexe/consensus/src/validator/batch/manager.rs b/ethexe/consensus/src/validator/batch/manager.rs index 54838ce82bc..1de63e617d9 100644 --- a/ethexe/consensus/src/validator/batch/manager.rs +++ b/ethexe/consensus/src/validator/batch/manager.rs @@ -321,13 +321,8 @@ impl BatchCommitmentManager { let latest_era_validators_committed = self .db - .block_validators_committed_for_era(block.hash) - .ok_or_else(|| { - anyhow!( - "not found latest_era_validators_committed in database for block: {}", - block.hash - ) - })?; + .block_meta(block.hash) + .latest_era_validators_committed; if latest_era_validators_committed == block_era + 1 { tracing::trace!( diff --git a/ethexe/consensus/src/validator/batch/tests.rs b/ethexe/consensus/src/validator/batch/tests.rs index 7288c94ed4f..2d246a3f6f4 100644 --- a/ethexe/consensus/src/validator/batch/tests.rs +++ b/ethexe/consensus/src/validator/batch/tests.rs @@ -250,7 +250,7 @@ async fn rejects_code_not_processed_yet() { let chain = BlockChain::mock(10) .tap_mut(|chain| { chain.blocks[10] - .as_prepared_mut() + .assert_prepared_mut() .codes_queue .push_front(code_id); chain.codes.insert( @@ -470,9 +470,9 @@ async fn test_aggregate_validators_commitment() { assert_eq!(commitment.era_index, 1); // Inside election period validators already committed - ctx.core - .db - .set_block_validators_committed_for_era(chain.blocks[7].hash, 1); + ctx.core.db.mutate_block_meta(chain.blocks[7].hash, |meta| { + meta.latest_era_validators_committed = 1; + }); let commitment = ctx .core .batch_manager @@ -484,7 +484,9 @@ async fn test_aggregate_validators_commitment() { // Election for era 2 but validators are not committed for era 1 ctx.core .db - .set_block_validators_committed_for_era(chain.blocks[15].hash, 0); + .mutate_block_meta(chain.blocks[15].hash, |meta| { + meta.latest_era_validators_committed = 0; + }); let commitment = ctx .core .batch_manager @@ -498,7 +500,9 @@ async fn test_aggregate_validators_commitment() { // Election for era 2 but validators for era 3 are already committed ctx.core .db - .set_block_validators_committed_for_era(chain.blocks[15].hash, 3); + .mutate_block_meta(chain.blocks[15].hash, |meta| { + meta.latest_era_validators_committed = 3; + }); ctx.core .batch_manager .aggregate_validators_commitment(&chain.blocks[15].to_simple()) diff --git a/ethexe/consensus/src/validator/batch/utils.rs b/ethexe/consensus/src/validator/batch/utils.rs index c8cd14ce6b4..aa449faa8a4 100644 --- a/ethexe/consensus/src/validator/batch/utils.rs +++ b/ethexe/consensus/src/validator/batch/utils.rs @@ -294,8 +294,9 @@ mod tests { .as_computed_mut() .outcome .push(StateTransition::mock(())); - chain.blocks[10].as_prepared_mut().last_committed_announce = - chain.block_top_announce_hash(3); + chain.blocks[10] + .assert_prepared_mut() + .last_committed_announce = chain.block_top_announce_hash(3); }) .setup(&db); let block = chain.blocks[10].to_simple(); @@ -419,7 +420,7 @@ mod tests { let chain = BlockChain::mock(10) .tap_mut(|c| { c.block_top_announce_mut(10).announce.gas_allowance = Some(10); - c.blocks[10].as_prepared_mut().announces = + c.blocks[10].assert_prepared_mut().announces = Some([c.block_top_announce(10).announce.to_hash()].into()); }) .setup(&db); diff --git a/ethexe/consensus/src/validator/initial.rs b/ethexe/consensus/src/validator/initial.rs index 53201e33516..0160c5f4da3 100644 --- a/ethexe/consensus/src/validator/initial.rs +++ b/ethexe/consensus/src/validator/initial.rs @@ -346,7 +346,7 @@ mod tests { let last = 9; let mut chain = BlockChain::mock(last as u32); - chain.blocks[last].as_prepared_mut().announces = None; + chain.blocks[last].assert_prepared_mut().announces = None; // create 2 missing announces from blocks last - 2 and last - 1 let announce2 = Announce::with_default_gas( @@ -356,7 +356,9 @@ mod tests { let announce1 = Announce::with_default_gas(chain.blocks[last - 1].hash, announce2.to_hash()); - chain.blocks[last].as_prepared_mut().last_committed_announce = announce1.to_hash(); + chain.blocks[last] + .assert_prepared_mut() + .last_committed_announce = announce1.to_hash(); let chain = chain.setup(&ctx.core.db); ctx.core.timelines = chain.config.timelines; let block = chain.blocks[last].to_simple(); @@ -371,7 +373,7 @@ mod tests { let tail = chain.block_top_announce_hash(last - 4); let expected_request = AnnouncesRequest { - head: chain.blocks[last].as_prepared().last_committed_announce, + head: chain.blocks[last].assert_prepared().last_committed_announce, until: tail.into(), }; assert_eq!(state.context().output, vec![expected_request.into()]); @@ -407,7 +409,7 @@ mod tests { .tap_mut(|chain| { // remove announces from 5 latest blocks (last - 4..=last).for_each(|idx| { - chain.blocks[idx].as_prepared_mut().announces = None; + chain.blocks[idx].assert_prepared_mut().announces = None; }); // append one more announce to the block last - 5 @@ -416,7 +418,7 @@ mod tests { chain.block_top_announce_hash(last - 6), ); chain.blocks[last - 5] - .as_prepared_mut() + .assert_prepared_mut() .announces .as_mut() .unwrap() @@ -462,7 +464,7 @@ mod tests { .tap_mut(|chain| { // remove announces from 10 latest blocks (last - 9..=last).for_each(|idx| { - chain.blocks[idx].as_prepared_mut().announces = None; + chain.blocks[idx].assert_prepared_mut().announces = None; }); }) .setup(&ctx.core.db); @@ -550,8 +552,10 @@ mod tests { let (ctx, _, _) = mock_validator_context(); let block = BlockChain::mock(1) .tap_mut(|chain| { - chain.blocks[1].as_prepared_mut().announces = None; - chain.blocks[1].as_prepared_mut().last_committed_announce = HashOf::random(); + chain.blocks[1].assert_prepared_mut().announces = None; + chain.blocks[1] + .assert_prepared_mut() + .last_committed_announce = HashOf::random(); }) .setup(&ctx.core.db) .blocks[1] @@ -604,17 +608,19 @@ mod tests { // remove announces from 5 latest blocks for idx in last - 4..=last { chain.blocks[idx] - .as_prepared_mut() + .assert_prepared_mut() .announces .iter() .flatten() .for_each(|ah| { chain.announces.remove(ah); }); - chain.blocks[idx].as_prepared_mut().announces = None; + chain.blocks[idx].assert_prepared_mut().announces = None; // set unknown_announce as last committed announce - chain.blocks[idx].as_prepared_mut().last_committed_announce = unknown_announce_hash; + chain.blocks[idx] + .assert_prepared_mut() + .last_committed_announce = unknown_announce_hash; } let chain = chain.setup(&ctx.core.db); @@ -631,7 +637,7 @@ mod tests { assert!(state.is_initial(), "got {:?}", state); let expected_request = AnnouncesRequest { - head: chain.blocks[last].as_prepared().last_committed_announce, + head: chain.blocks[last].assert_prepared().last_committed_announce, until: chain.block_top_announce_hash(last - 8).into(), }; assert_eq!(state.context().output, vec![expected_request.into()]); diff --git a/ethexe/consensus/src/validator/tx_pool.rs b/ethexe/consensus/src/validator/tx_pool.rs index c7045ae7946..a7a6fab8066 100644 --- a/ethexe/consensus/src/validator/tx_pool.rs +++ b/ethexe/consensus/src/validator/tx_pool.rs @@ -309,7 +309,7 @@ mod tests { let chain = BlockChain::mock(10) .tap_mut(|chain| { - chain.blocks[10].as_synced_mut().events = (0..97) + chain.blocks[10].synced.events = (0..97) .map(|i| BlockEvent::Mirror { actor_id: ActorId::from(i), event: MirrorEvent::MessageQueueingRequested( diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index 6d16f3a808a..cb10ba05f79 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -79,9 +79,6 @@ enum Key { Globals = 14, Config = 15, - - // TODO kuzmindev: temporal solution - must move into block meta or something else. - LatestEraValidatorsCommitted(H256), } impl Key { @@ -99,10 +96,9 @@ impl Key { bytes.extend(self.prefix()); match self { - Self::BlockSmallData(hash) - | Self::BlockEvents(hash) - | Self::BlockAnnounces(hash) - | Self::LatestEraValidatorsCommitted(hash) => bytes.extend(hash.as_ref()), + Self::BlockSmallData(hash) | Self::BlockEvents(hash) | Self::BlockAnnounces(hash) => { + bytes.extend(hash.as_ref()) + } Self::ValidatorSet(era_index) => { bytes.extend(era_index.to_le_bytes()); @@ -537,15 +533,6 @@ impl OnChainStorageRO for RawDatabase { .expect("Failed to decode data into `ValidatorsVec`") }) } - - fn block_validators_committed_for_era(&self, block_hash: H256) -> Option { - self.kv - .get(&Key::LatestEraValidatorsCommitted(block_hash).to_bytes()) - .map(|data| { - Decode::decode(&mut data.as_slice()) - .expect("Failed to decode data into `u64` (era_index)") - }) - } } impl OnChainStorageRW for RawDatabase { @@ -580,13 +567,6 @@ impl OnChainStorageRW for RawDatabase { validator_set.encode(), ); } - - fn set_block_validators_committed_for_era(&self, block_hash: H256, era_index: u64) { - self.kv.put( - &Key::LatestEraValidatorsCommitted(block_hash).to_bytes(), - era_index.encode(), - ); - } } impl BlockMetaStorageRO for RawDatabase { @@ -891,7 +871,6 @@ impl OnChainStorageRO for Database { fn code_blob_info(&self, code_id: CodeId) -> Option; fn block_synced(&self, block_hash: H256) -> bool; fn validators(&self, era_index: u64) -> Option; - fn block_validators_committed_for_era(&self, block_hash: H256) -> Option; } } } @@ -904,7 +883,6 @@ impl OnChainStorageRW for Database { fn set_code_blob_info(&self, code_id: CodeId, code_info: CodeBlobInfo); fn set_block_synced(&self, block_hash: H256); fn set_validators(&self, era_index: u64, validator_set: ValidatorsVec); - fn set_block_validators_committed_for_era(&self, block_hash: H256, era_index: u64); } } } diff --git a/ethexe/db/src/iterator.rs b/ethexe/db/src/iterator.rs index 99008a6c91f..28653dce8d3 100644 --- a/ethexe/db/src/iterator.rs +++ b/ethexe/db/src/iterator.rs @@ -523,12 +523,7 @@ where } fn iter_block_meta(&mut self, BlockMetaNode { block, meta }: &BlockMetaNode) { - let BlockMeta { - prepared: _, - codes_queue, - last_committed_batch: _, - last_committed_announce: _, - } = meta; + let BlockMeta { codes_queue, .. } = meta; if let Some(announces) = self.storage.block_announces(*block) { for announce_hash in announces.into_iter() { diff --git a/ethexe/db/src/migrations/v2.rs b/ethexe/db/src/migrations/v2.rs index edc0fba42b1..bf83589aab9 100644 --- a/ethexe/db/src/migrations/v2.rs +++ b/ethexe/db/src/migrations/v2.rs @@ -27,9 +27,11 @@ pub const VERSION: u32 = 2; pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { // Changes from v1 to v2: // - Block announces are moved from `BlockMeta` to `BlockAnnounces` key. + // - `LatestEraValidators` key is merged into `BlockMeta`. let block_small_data_prefix = H256::from_low_u64_be(0); let block_announces_prefix = H256::from_low_u64_be(13); + let latest_era_prefix = H256::from_low_u64_be(16); for (key, value) in db.kv.iter_prefix(block_small_data_prefix.as_bytes()) { let v0::BlockSmallData { @@ -46,7 +48,15 @@ pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { } = v0::BlockSmallData::decode(&mut value.as_slice())?; let block_hash = &key[32..]; + let announces_key = [block_announces_prefix.as_bytes(), block_hash].concat(); + let latest_era_key = [latest_era_prefix.as_bytes(), block_hash].concat(); + + let latest_era_validators_committed = db + .kv + .get(&latest_era_key) + .context("`LatestEraValidators` is not found for block") + .and_then(|bytes| Ok(u64::decode(&mut bytes.as_slice())?))?; db.kv.put(&announces_key, announces.encode()); @@ -60,6 +70,7 @@ pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { codes_queue, last_committed_batch, last_committed_announce, + latest_era_validators_committed, }, } .encode(), From e84479787043f61eb80baf606a698525285c919c Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Mon, 23 Mar 2026 15:16:02 +0300 Subject: [PATCH 23/24] Update version in the end of v2 migration --- ethexe/db/src/migrations/v2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethexe/db/src/migrations/v2.rs b/ethexe/db/src/migrations/v2.rs index bf83589aab9..be2457853f0 100644 --- a/ethexe/db/src/migrations/v2.rs +++ b/ethexe/db/src/migrations/v2.rs @@ -101,6 +101,7 @@ pub async fn migration_from_v1(_: &InitConfig, db: &RawDatabase) -> Result<()> { mod tests { use super::*; use crate::migrations::test::assert_migration_types_hash; + use ethexe_common::db::DBConfig; use scale_info::meta_type; #[test] From 5c921a025a56314ad74a814a57a0420b3fcb6f58 Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Tue, 24 Mar 2026 14:06:00 +0300 Subject: [PATCH 24/24] Update db types hash --- ethexe/common/src/db.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index b002ad6e791..5cda0b19f4b 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -251,7 +251,7 @@ mod tests { #[test] fn ensure_types_unchanged() { const EXPECTED_TYPE_INFO_HASH: &str = - "fe6086aaff64c357bccf5f0dd0c931467de148eb4ef34d8ca2114169c8008d35"; + "4d2864a076a65fc8abf5a4d7a0def202bd3982b086e14751d04e4ff711bc536a"; let types = [ meta_type::(),