From f0fb2bad35990ed0883cb9a6bd74376cf6777813 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Mon, 21 Oct 2024 16:17:35 +0400 Subject: [PATCH 01/18] replace storage-used types with newtypes --- Cargo.lock | 1 + ethexe/cli/src/tests.rs | 4 +- ethexe/processor/src/host/threads.rs | 5 +- ethexe/processor/src/tests.rs | 2 +- ethexe/runtime/common/Cargo.toml | 1 + ethexe/runtime/common/src/journal.rs | 2 +- ethexe/runtime/common/src/lib.rs | 2 +- ethexe/runtime/common/src/state.rs | 85 ++++++++++++++++++++++++---- 8 files changed, 85 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd513d4afba..3d2789f568e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5242,6 +5242,7 @@ name = "ethexe-runtime-common" version = "1.6.2" dependencies = [ "anyhow", + "derive_more 0.99.18", "ethexe-common", "gear-core", "gear-core-errors", diff --git a/ethexe/cli/src/tests.rs b/ethexe/cli/src/tests.rs index a15a9f1fda9..e3fdd78a13f 100644 --- a/ethexe/cli/src/tests.rs +++ b/ethexe/cli/src/tests.rs @@ -263,7 +263,7 @@ async fn mailbox() { .mailbox_hash .with_hash_or_default(|hash| node.db.read_mailbox(hash).unwrap()); - assert_eq!(mailbox, expected_mailbox); + assert_eq!(mailbox.0, expected_mailbox); mirror .send_reply(ping_expected_message, "PONG", 0) @@ -291,7 +291,7 @@ async fn mailbox() { BTreeMap::from_iter([(mid_expected_message, (0, expiry))]), )]); - assert_eq!(mailbox, expected_mailbox); + assert_eq!(mailbox.0, expected_mailbox); mirror.claim_value(mid_expected_message).await.unwrap(); diff --git a/ethexe/processor/src/host/threads.rs b/ethexe/processor/src/host/threads.rs index ddfb6e3ec95..97db3d4bf3d 100644 --- a/ethexe/processor/src/host/threads.rs +++ b/ethexe/processor/src/host/threads.rs @@ -64,7 +64,10 @@ impl ThreadParams { }; if let MaybeHash::Hash(mem_root) = pages_hash { - self.db.read_pages(mem_root.hash).expect(UNKNOWN_STATE) + self.db + .read_pages(mem_root.hash) + .expect(UNKNOWN_STATE) + .into() } else { Default::default() } diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index b2afb513d00..b7afa4e7dab 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -583,7 +583,7 @@ fn many_waits() { let waitlist_hash = state.waitlist_hash.with_hash(|h| h).unwrap(); let waitlist = processor.db.read_waitlist(waitlist_hash).unwrap(); - for (mid, (dispatch, expiry)) in waitlist { + for (mid, (dispatch, expiry)) in waitlist.0 { assert_eq!(mid, dispatch.id); expected_schedule .entry(expiry) diff --git a/ethexe/runtime/common/Cargo.toml b/ethexe/runtime/common/Cargo.toml index 0e7b00ddb19..61c3f24e60b 100644 --- a/ethexe/runtime/common/Cargo.toml +++ b/ethexe/runtime/common/Cargo.toml @@ -21,6 +21,7 @@ gear-core-errors.workspace = true anyhow.workspace = true parity-scale-codec.workspace = true log.workspace = true +derive_more.workspace = true [features] default = ["std"] diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index dd4aa0e9601..85bb01c2d3b 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -411,7 +411,7 @@ impl JournalHandler for Handler<'_, S> { .flat_map(|i| i.to_iter()) .collect(); - *allocations = new_allocations; + *allocations = new_allocations.into(); })?; *pages_hash = storage.modify_memory_pages(pages_hash.clone(), |pages| { diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index 085f8f8351e..f427cf19241 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -251,7 +251,7 @@ where .expect("Cannot get memory pages") }); let actor_data = ExecutableActorData { - allocations, + allocations: allocations.into(), code_id, code_exports: code.exports().clone(), static_pages: code.static_pages(), diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 09de4c925df..4cba642c96d 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -23,7 +23,10 @@ use alloc::{ vec::Vec, }; use anyhow::{anyhow, Result}; -use core::num::NonZero; +use core::{ + num::NonZero, + ops::{Deref, DerefMut}, +}; use ethexe_common::router::OutgoingMessage; use gear_core::{ code::InstrumentedCode, @@ -276,18 +279,78 @@ impl Dispatch { pub type ValueWithExpiry = (T, u32); -pub type MessageQueue = VecDeque; - -pub type Waitlist = BTreeMap>; - -pub type DispatchStash = BTreeMap)>>; +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct MessageQueue(pub VecDeque); + +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct Waitlist(pub BTreeMap>); + +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct DispatchStash(pub BTreeMap)>>); // TODO (breathx): consider here LocalMailbox for each user. -pub type Mailbox = BTreeMap>>; - -pub type MemoryPages = BTreeMap; - -pub type Allocations = IntervalsTree; +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct Mailbox(pub BTreeMap>>); + +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct MemoryPages(pub BTreeMap); + +#[derive( + Default, + Debug, + Encode, + Decode, + derive_more::Deref, + derive_more::DerefMut, + derive_more::From, + derive_more::Into, +)] +pub struct Allocations(pub IntervalsTree); pub trait Storage { /// Reads program state by state hash. From 51cf0d1c50944829a1017a694fcf2d177637f9d4 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Mon, 21 Oct 2024 16:33:52 +0400 Subject: [PATCH 02/18] patch newtype value with expiry --- ethexe/cli/src/tests.rs | 23 ++++++----- ethexe/processor/src/handling/events.rs | 25 +++++++----- ethexe/processor/src/tests.rs | 11 +++++- ethexe/runtime/common/src/journal.rs | 51 ++++++++++++++++++------- ethexe/runtime/common/src/schedule.rs | 47 ++++++++++++++++++----- ethexe/runtime/common/src/state.rs | 26 ++++++++++++- 6 files changed, 137 insertions(+), 46 deletions(-) diff --git a/ethexe/cli/src/tests.rs b/ethexe/cli/src/tests.rs index e3fdd78a13f..02537ee414b 100644 --- a/ethexe/cli/src/tests.rs +++ b/ethexe/cli/src/tests.rs @@ -32,7 +32,7 @@ use ethexe_db::{BlockMetaStorage, Database, MemDb, ScheduledTask}; use ethexe_ethereum::{router::RouterQuery, Ethereum}; use ethexe_observer::{Event, MockBlobReader, Observer, Query}; use ethexe_processor::Processor; -use ethexe_runtime_common::state::Storage; +use ethexe_runtime_common::state::{Mailbox, Storage}; use ethexe_sequencer::Sequencer; use ethexe_signer::Signer; use ethexe_validator::Validator; @@ -247,13 +247,15 @@ async fn mailbox() { assert_eq!(schedule, expected_schedule); - let expected_mailbox = BTreeMap::from_iter([( + let expected_mailbox: Mailbox = BTreeMap::from_iter([( env.sender_id, BTreeMap::from_iter([ - (mid_expected_message, (0, expiry)), - (ping_expected_message, (0, expiry)), + (mid_expected_message, (0u128, expiry).into()), + (ping_expected_message, (0, expiry).into()), ]), - )]); + )]) + .into(); + let mirror = env.ethereum.mirror(pid.try_into().unwrap()); let state_hash = mirror.query().state_hash().await.unwrap(); @@ -263,7 +265,7 @@ async fn mailbox() { .mailbox_hash .with_hash_or_default(|hash| node.db.read_mailbox(hash).unwrap()); - assert_eq!(mailbox.0, expected_mailbox); + assert_eq!(mailbox, expected_mailbox); mirror .send_reply(ping_expected_message, "PONG", 0) @@ -286,12 +288,13 @@ async fn mailbox() { .mailbox_hash .with_hash_or_default(|hash| node.db.read_mailbox(hash).unwrap()); - let expected_mailbox = BTreeMap::from_iter([( + let expected_mailbox: Mailbox = BTreeMap::from_iter([( env.sender_id, - BTreeMap::from_iter([(mid_expected_message, (0, expiry))]), - )]); + BTreeMap::from_iter([(mid_expected_message, (0u128, expiry).into())]), + )]) + .into(); - assert_eq!(mailbox.0, expected_mailbox); + assert_eq!(mailbox, expected_mailbox); mirror.claim_value(mid_expected_message).await.unwrap(); diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index ab366488257..6661f719b1e 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -25,7 +25,7 @@ use ethexe_common::{ }; use ethexe_db::{CodesStorage, ScheduledTask}; use ethexe_runtime_common::{ - state::{ComplexStorage as _, Dispatch, Storage}, + state::{ComplexStorage as _, Dispatch, Storage, ValueWithExpiry}, InBlockTransitions, }; use gear_core::{ @@ -236,17 +236,22 @@ impl Processor { ) -> Result> { self.db .mutate_state_returning(state_hash, |db, state| { - let Some(((claimed_value, expiry), mailbox_hash)) = - db.modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| { - let local_mailbox = mailbox.get_mut(&user_id)?; - let claimed_value = local_mailbox.remove(&mailboxed_id)?; + let Some(( + ValueWithExpiry { + value: claimed_value, + expiry, + }, + mailbox_hash, + )) = db.modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| { + let local_mailbox = mailbox.get_mut(&user_id)?; + let claimed_value = local_mailbox.remove(&mailboxed_id)?; - if local_mailbox.is_empty() { - mailbox.remove(&user_id); - } + if local_mailbox.is_empty() { + mailbox.remove(&user_id); + } - Some(claimed_value) - })? + Some(claimed_value) + })? else { return Ok(None); }; diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index b7afa4e7dab..520fa5847ec 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -21,7 +21,7 @@ use ethexe_common::{ mirror::RequestEvent as MirrorEvent, router::RequestEvent as RouterEvent, BlockRequestEvent, }; use ethexe_db::{BlockHeader, BlockMetaStorage, CodesStorage, MemDb, ScheduledTask}; -use ethexe_runtime_common::state::{ComplexStorage, Dispatch}; +use ethexe_runtime_common::state::{ComplexStorage, Dispatch, ValueWithExpiry}; use gear_core::{ ids::{prelude::CodeIdExt, ProgramId}, message::DispatchKind, @@ -583,7 +583,14 @@ fn many_waits() { let waitlist_hash = state.waitlist_hash.with_hash(|h| h).unwrap(); let waitlist = processor.db.read_waitlist(waitlist_hash).unwrap(); - for (mid, (dispatch, expiry)) in waitlist.0 { + for ( + mid, + ValueWithExpiry { + value: dispatch, + expiry, + }, + ) in waitlist.0 + { assert_eq!(mid, dispatch.id); expected_schedule .entry(expiry) diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index 85bb01c2d3b..30337725739 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -1,7 +1,7 @@ use crate::{ state::{ self, ActiveProgram, ComplexStorage, Dispatch, HashAndLen, MaybeHash, Program, - ProgramState, Storage, MAILBOX_VALIDITY, + ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY, }, InBlockTransitions, }; @@ -203,8 +203,13 @@ impl JournalHandler for Handler<'_, S> { state.stash_hash = storage.modify_stash(state.stash_hash.clone(), |stash| { - let r = - stash.insert(dispatch.id, ((dispatch, Some(user_id)), expiry)); + let r = stash.insert( + dispatch.id, + ValueWithExpiry { + value: (dispatch, Some(user_id)), + expiry, + }, + ); debug_assert!(r.is_none()); })?; @@ -224,10 +229,13 @@ impl JournalHandler for Handler<'_, S> { self.update_state_with_storage(dispatch.source(), |storage, state| { state.mailbox_hash = storage.modify_mailbox(state.mailbox_hash.clone(), |mailbox| { - mailbox - .entry(user_id) - .or_default() - .insert(dispatch.id(), (dispatch.value(), expiry)); + mailbox.entry(user_id).or_default().insert( + dispatch.id(), + ValueWithExpiry { + value: dispatch.value(), + expiry, + }, + ); })?; Ok(()) @@ -258,7 +266,13 @@ impl JournalHandler for Handler<'_, S> { self.update_state_with_storage(destination, |storage, state| { state.stash_hash = storage.modify_stash(state.stash_hash.clone(), |stash| { - let r = stash.insert(dispatch.id, ((dispatch, None), expiry)); + let r = stash.insert( + dispatch.id, + ValueWithExpiry { + value: (dispatch, None), + expiry, + }, + ); debug_assert!(r.is_none()); })?; @@ -309,7 +323,13 @@ impl JournalHandler for Handler<'_, S> { // TODO (breathx): impl Copy for MaybeHash? state.waitlist_hash = storage.modify_waitlist(state.waitlist_hash.clone(), |waitlist| { - let r = waitlist.insert(dispatch.id, (dispatch, expiry)); + let r = waitlist.insert( + dispatch.id, + ValueWithExpiry { + value: dispatch, + expiry, + }, + ); debug_assert!(r.is_none()); })?; @@ -333,10 +353,15 @@ impl JournalHandler for Handler<'_, S> { let mut expiry_if_found = None; self.update_state_with_storage(program_id, |storage, state| { - let Some(((dispatch, expiry), new_waitlist_hash)) = storage - .modify_waitlist_if_changed(state.waitlist_hash.clone(), |waitlist| { - waitlist.remove(&awakening_id) - })? + let Some(( + ValueWithExpiry { + value: dispatch, + expiry, + }, + new_waitlist_hash, + )) = storage.modify_waitlist_if_changed(state.waitlist_hash.clone(), |waitlist| { + waitlist.remove(&awakening_id) + })? else { return Ok(()); }; diff --git a/ethexe/runtime/common/src/schedule.rs b/ethexe/runtime/common/src/schedule.rs index 4f169b2bb39..5f7166924a5 100644 --- a/ethexe/runtime/common/src/schedule.rs +++ b/ethexe/runtime/common/src/schedule.rs @@ -1,5 +1,8 @@ use crate::{ - state::{ComplexStorage, Dispatch, MaybeHash, ProgramState, Storage, MAILBOX_VALIDITY}, + state::{ + ComplexStorage, Dispatch, MaybeHash, ProgramState, Storage, ValueWithExpiry, + MAILBOX_VALIDITY, + }, InBlockTransitions, }; use alloc::vec; @@ -44,7 +47,13 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { let mut value_claim = None; self.update_state_with_storage(program_id, |storage, state| { - let ((claimed_value, expiry), new_mailbox_hash) = storage + let ( + ValueWithExpiry { + value: claimed_value, + expiry, + }, + new_mailbox_hash, + ) = storage .modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| { let local_mailbox = mailbox.get_mut(&user_id)?; let claimed_value = local_mailbox.remove(&message_id)?; @@ -92,7 +101,13 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { fn send_dispatch(&mut self, (program_id, message_id): (ProgramId, MessageId)) -> u64 { self.update_state_with_storage(program_id, |storage, state| { - let (((dispatch, user_id), _expiry), new_stash_hash) = storage + let ( + ValueWithExpiry { + value: (dispatch, user_id), + .. + }, + new_stash_hash, + ) = storage .modify_stash_if_changed(state.stash_hash.clone(), |stash| { stash.remove(&message_id) })? @@ -115,7 +130,13 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { let mut dispatch_and_user = None; self.update_state_with_storage(program_id, |storage, state| { - let (((dispatch, user_id), _expiry), new_stash_hash) = storage + let ( + ValueWithExpiry { + value: (dispatch, user_id), + .. + }, + new_stash_hash, + ) = storage .modify_stash_if_changed(state.stash_hash.clone(), |stash| { stash.remove(&stashed_message_id) })? @@ -139,10 +160,13 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { self.update_state_with_storage(program_id, |storage, state| { state.mailbox_hash = storage.modify_mailbox(state.mailbox_hash.clone(), |mailbox| { - let r = mailbox - .entry(user_id) - .or_default() - .insert(dispatch.id, (dispatch.value, expiry)); + let r = mailbox.entry(user_id).or_default().insert( + dispatch.id, + ValueWithExpiry { + value: dispatch.value, + expiry, + }, + ); debug_assert!(r.is_none()); })?; @@ -167,7 +191,12 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { log::trace!("Running scheduled task wake message {message_id} to {program_id}"); self.update_state_with_storage(program_id, |storage, state| { - let ((dispatch, _expiry), new_waitlist_hash) = storage + let ( + ValueWithExpiry { + value: dispatch, .. + }, + new_waitlist_hash, + ) = storage .modify_waitlist_if_changed(state.waitlist_hash.clone(), |waitlist| { waitlist.remove(&message_id) })? diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 4cba642c96d..820c721d473 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -195,7 +195,7 @@ impl ProgramState { } } -#[derive(Clone, Debug, Encode, Decode)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] pub struct Dispatch { /// Message id. pub id: MessageId, @@ -277,13 +277,25 @@ impl Dispatch { } } -pub type ValueWithExpiry = (T, u32); +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq)] +pub struct ValueWithExpiry { + pub value: T, + pub expiry: u32, +} + +impl From<(T, u32)> for ValueWithExpiry { + fn from((value, expiry): (T, u32)) -> Self { + Self { value, expiry } + } +} #[derive( Default, Debug, Encode, Decode, + PartialEq, + Eq, derive_more::Deref, derive_more::DerefMut, derive_more::From, @@ -296,6 +308,8 @@ pub struct MessageQueue(pub VecDeque); Debug, Encode, Decode, + PartialEq, + Eq, derive_more::Deref, derive_more::DerefMut, derive_more::From, @@ -308,6 +322,8 @@ pub struct Waitlist(pub BTreeMap>); Debug, Encode, Decode, + PartialEq, + Eq, derive_more::Deref, derive_more::DerefMut, derive_more::From, @@ -321,6 +337,8 @@ pub struct DispatchStash(pub BTreeMap); Debug, Encode, Decode, + PartialEq, + Eq, derive_more::Deref, derive_more::DerefMut, derive_more::From, From a4d4e777752f0ef63613757f1cd4a24cd05ffdee Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Wed, 23 Oct 2024 17:07:30 +0400 Subject: [PATCH 03/18] migrate approach of handlers --- ethexe/runtime/common/Cargo.toml | 2 +- ethexe/runtime/common/src/journal.rs | 389 ++++------ ethexe/runtime/common/src/lib.rs | 48 +- ethexe/runtime/common/src/schedule.rs | 200 ++---- ethexe/runtime/common/src/state.rs | 856 +++++++++++++---------- ethexe/runtime/common/src/transitions.rs | 33 +- 6 files changed, 740 insertions(+), 788 deletions(-) diff --git a/ethexe/runtime/common/Cargo.toml b/ethexe/runtime/common/Cargo.toml index 61c3f24e60b..2ee56ed08f0 100644 --- a/ethexe/runtime/common/Cargo.toml +++ b/ethexe/runtime/common/Cargo.toml @@ -19,7 +19,7 @@ gsys.workspace = true gear-core-errors.workspace = true anyhow.workspace = true -parity-scale-codec.workspace = true +parity-scale-codec = { workspace = true, features = ["derive"] } log.workspace = true derive_more.workspace = true diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index 30337725739..13e83275604 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -1,7 +1,7 @@ use crate::{ state::{ - self, ActiveProgram, ComplexStorage, Dispatch, HashAndLen, MaybeHash, Program, - ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY, + self, ActiveProgram, Dispatch, MaybeHashOf, Program, ProgramState, Storage, + ValueWithExpiry, MAILBOX_VALIDITY, }, InBlockTransitions, }; @@ -34,34 +34,89 @@ pub struct Handler<'a, S: Storage> { } impl Handler<'_, S> { - pub fn update_state( + fn update_state( &mut self, program_id: ProgramId, - f: impl FnOnce(&mut ProgramState) -> Result<()>, - ) -> H256 { - crate::update_state(self.in_block_transitions, self.storage, program_id, f) + f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, + ) -> T { + crate::update_state(program_id, self.storage, self.in_block_transitions, f) } - pub fn update_state_with_storage( + fn send_dispatch_to_program( &mut self, - program_id: ProgramId, - f: impl FnOnce(&S, &mut ProgramState) -> Result<()>, - ) -> H256 { - crate::update_state_with_storage(self.in_block_transitions, self.storage, program_id, f) + message_id: MessageId, + destination: ActorId, + dispatch: Dispatch, + delay: u32, + ) { + self.update_state(destination, |state, storage, transitions| { + if let Ok(non_zero_delay) = delay.try_into() { + let expiry = transitions.schedule_task( + non_zero_delay, + ScheduledTask::SendDispatch((destination, dispatch.id)), + ); + + state.stash_hash.modify_stash(storage, |stash| { + stash.add_to_program(dispatch.id, dispatch, expiry); + }) + } else { + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(dispatch)); + } + }) } - fn pop_queue_message(state: &ProgramState, storage: &S) -> (H256, MessageId) { - let mut queue = state - .queue_hash - .with_hash_or_default(|hash| storage.read_queue(hash).expect("Failed to read queue")); + fn send_dispatch_to_user( + &mut self, + message_id: MessageId, + dispatch: StoredDispatch, + delay: u32, + ) { + if dispatch.is_reply() { + self.in_block_transitions + .modify_transition(dispatch.source(), |transition| { + transition.messages.push(dispatch.into_parts().1.into()) + }); - let dispatch = queue - .pop_front() - .unwrap_or_else(|| unreachable!("Queue must not be empty in message consume")); + return; + } - let new_queue_hash = storage.write_queue(queue); + self.update_state(dispatch.source(), |state, storage, transitions| { + if let Ok(non_zero_delay) = delay.try_into() { + let expiry = transitions.schedule_task( + non_zero_delay, + ScheduledTask::SendUserMessage { + message_id: dispatch.id(), + to_mailbox: dispatch.source(), + }, + ); - (new_queue_hash, dispatch.id) + let user_id = dispatch.destination(); + let dispatch = Dispatch::from_stored(storage, dispatch); + + state.stash_hash.modify_stash(storage, |stash| { + stash.add_to_user(dispatch.id, dispatch, expiry, user_id); + }); + } else { + let expiry = transitions.schedule_task( + MAILBOX_VALIDITY.try_into().expect("infallible"), + ScheduledTask::RemoveFromMailbox( + (dispatch.source(), dispatch.destination()), + dispatch.id(), + ), + ); + + state.mailbox_hash.modify_mailbox(storage, |mailbox| { + mailbox.add( + dispatch.destination(), + dispatch.id(), + dispatch.value(), + expiry, + ); + }); + } + }); } } @@ -80,7 +135,7 @@ impl JournalHandler for Handler<'_, S> { DispatchOutcome::InitSuccess { program_id } => { log::trace!("Dispatch {message_id} successfully initialized program {program_id}"); - self.update_state(program_id, |state| { + self.update_state(program_id, |state, _, _| { match &mut state.program { Program::Active(ActiveProgram { initialized, .. }) if *initialized => { bail!("an attempt to initialize already initialized program") @@ -103,9 +158,8 @@ impl JournalHandler for Handler<'_, S> { } => { log::trace!("Dispatch {message_id} failed init of program {program_id}: {reason}"); - self.update_state(program_id, |state| { - state.program = Program::Terminated(origin); - Ok(()) + self.update_state(program_id, |state, _, _| { + state.program = Program::Terminated(origin) }); } @@ -127,47 +181,36 @@ impl JournalHandler for Handler<'_, S> { fn exit_dispatch(&mut self, id_exited: ProgramId, value_destination: ProgramId) { // TODO (breathx): handle rest of value cases; exec balance into value_to_receive. - let mut balance = 0; - - self.update_state(id_exited, |state| { + let balance = self.update_state(id_exited, |state, _, transitions| { state.program = Program::Exited(value_destination); - balance = mem::replace(&mut state.balance, 0); - Ok(()) - }); - if self - .in_block_transitions - .state_of(&value_destination) - .is_some() - { - self.update_state(value_destination, |state| { - state.balance += balance; - Ok(()) + transitions.modify_transition(id_exited, |transition| { + transition.inheritor = value_destination }); - } - self.in_block_transitions - .modify_transition(id_exited, |_state_hash, transition| { - transition.inheritor = value_destination + mem::replace(&mut state.balance, 0) + }); + + if self.in_block_transitions.is_program(&value_destination) { + self.update_state(value_destination, |state, _, _| { + state.balance += balance; }) - .expect("infallible"); + } } fn message_consumed(&mut self, message_id: MessageId) { - self.update_state_with_storage(self.program_id, |storage, state| { - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - let queue_head = queue - .pop_front() + self.update_state(self.program_id, |state, storage, _| { + state.queue_hash.modify_queue(storage, |queue| { + let head = queue + .dequeue() .expect("an attempt to consume message from empty queue"); assert_eq!( - queue_head.id, message_id, + head.id, message_id, "queue head doesn't match processed message" ); - })?; - - Ok(()) - }); + }); + }) } fn send_dispatch( @@ -181,110 +224,15 @@ impl JournalHandler for Handler<'_, S> { unreachable!("deprecated: {dispatch:?}"); } - if self - .in_block_transitions - .state_of(&dispatch.destination()) - .is_none() - { - let user_id = dispatch.destination(); - - if !dispatch.is_reply() { - if let Ok(non_zero_delay) = delay.try_into() { - let expiry = self.in_block_transitions.schedule_task( - non_zero_delay, - ScheduledTask::SendUserMessage { - message_id: dispatch.id(), - to_mailbox: dispatch.source(), - }, - ); - - self.update_state_with_storage(dispatch.source(), |storage, state| { - let dispatch = Dispatch::from_stored(storage, dispatch.into_stored()); - - state.stash_hash = - storage.modify_stash(state.stash_hash.clone(), |stash| { - let r = stash.insert( - dispatch.id, - ValueWithExpiry { - value: (dispatch, Some(user_id)), - expiry, - }, - ); - debug_assert!(r.is_none()); - })?; - - Ok(()) - }); - - return; - } else { - let expiry = self.in_block_transitions.schedule_task( - MAILBOX_VALIDITY.try_into().expect("infallible"), - ScheduledTask::RemoveFromMailbox( - (dispatch.source(), user_id), - dispatch.id(), - ), - ); - - self.update_state_with_storage(dispatch.source(), |storage, state| { - state.mailbox_hash = - storage.modify_mailbox(state.mailbox_hash.clone(), |mailbox| { - mailbox.entry(user_id).or_default().insert( - dispatch.id(), - ValueWithExpiry { - value: dispatch.value(), - expiry, - }, - ); - })?; - - Ok(()) - }); - } - } - - let source = dispatch.source(); - let message = dispatch.into_parts().1; - - self.in_block_transitions - .modify_transition(source, |_state_hash, transition| { - transition.messages.push(OutgoingMessage::from(message)) - }) - .expect("must exist"); - - return; - } - let destination = dispatch.destination(); - let dispatch = Dispatch::from_stored(self.storage, dispatch.into_stored()); + let dispatch = dispatch.into_stored(); - if let Ok(non_zero_delay) = delay.try_into() { - let expiry = self.in_block_transitions.schedule_task( - non_zero_delay, - ScheduledTask::SendDispatch((destination, dispatch.id)), - ); + if self.in_block_transitions.is_program(&destination) { + let dispatch = Dispatch::from_stored(self.storage, dispatch); - self.update_state_with_storage(destination, |storage, state| { - state.stash_hash = storage.modify_stash(state.stash_hash.clone(), |stash| { - let r = stash.insert( - dispatch.id, - ValueWithExpiry { - value: (dispatch, None), - expiry, - }, - ); - debug_assert!(r.is_none()); - })?; - - Ok(()) - }); + self.send_dispatch_to_program(message_id, destination, dispatch, delay); } else { - self.update_state_with_storage(destination, |storage, state| { - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - queue.push_back(dispatch); - })?; - Ok(()) - }); + self.send_dispatch_to_user(message_id, dispatch, delay); } } @@ -301,42 +249,32 @@ impl JournalHandler for Handler<'_, S> { let in_blocks = NonZero::::try_from(duration).expect("must be checked on backend side"); - let expiry = self.in_block_transitions.schedule_task( - in_blocks, - ScheduledTask::WakeMessage(dispatch.destination(), dispatch.id()), - ); + self.update_state(self.program_id, |state, storage, transitions| { + let expiry = transitions.schedule_task( + in_blocks, + ScheduledTask::WakeMessage(dispatch.destination(), dispatch.id()), + ); - let dispatch = Dispatch::from_stored(self.storage, dispatch); + let dispatch = Dispatch::from_stored(storage, dispatch); - self.update_state_with_storage(self.program_id, |storage, state| { - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - let queue_head = queue - .pop_front() + state.queue_hash.modify_queue(storage, |queue| { + let head = queue + .dequeue() .expect("an attempt to wait message from empty queue"); assert_eq!( - queue_head.id, dispatch.id, + head.id, dispatch.id, "queue head doesn't match processed message" ); - })?; - - // TODO (breathx): impl Copy for MaybeHash? - state.waitlist_hash = - storage.modify_waitlist(state.waitlist_hash.clone(), |waitlist| { - let r = waitlist.insert( - dispatch.id, - ValueWithExpiry { - value: dispatch, - expiry, - }, - ); - debug_assert!(r.is_none()); - })?; + }); - Ok(()) + state.waitlist_hash.modify_waitlist(storage, |waitlist| { + waitlist.wait(dispatch.id, dispatch, expiry); + }); }); } + // TODO (breathx): deprecate delayed wakes? fn wake_message( &mut self, message_id: MessageId, @@ -350,40 +288,28 @@ impl JournalHandler for Handler<'_, S> { log::trace!("Dispatch {message_id} tries to wake {awakening_id}"); - let mut expiry_if_found = None; - - self.update_state_with_storage(program_id, |storage, state| { - let Some(( - ValueWithExpiry { - value: dispatch, - expiry, - }, - new_waitlist_hash, - )) = storage.modify_waitlist_if_changed(state.waitlist_hash.clone(), |waitlist| { - waitlist.remove(&awakening_id) - })? + self.update_state(program_id, |state, storage, transitions| { + let Some(ValueWithExpiry { + value: dispatch, + expiry, + }) = state + .waitlist_hash + .modify_waitlist(storage, |waitlist| waitlist.wake(&awakening_id)) else { - return Ok(()); + return; }; - expiry_if_found = Some(expiry); - - state.waitlist_hash = new_waitlist_hash; - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - queue.push_back(dispatch); - })?; - - Ok(()) - }); + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(dispatch)); - if let Some(expiry) = expiry_if_found { - self.in_block_transitions + transitions .remove_task( expiry, &ScheduledTask::WakeMessage(program_id, awakening_id), ) .expect("failed to remove scheduled task"); - } + }); } fn update_pages_data( @@ -391,7 +317,11 @@ impl JournalHandler for Handler<'_, S> { program_id: ProgramId, pages_data: BTreeMap, ) { - self.update_state_with_storage(program_id, |storage, state| { + if pages_data.is_empty() { + return; + } + + self.update_state(program_id, |state, storage, _| { let Program::Active(ActiveProgram { ref mut pages_hash, .. }) = state.program @@ -399,13 +329,9 @@ impl JournalHandler for Handler<'_, S> { bail!("an attempt to update pages data of inactive program"); }; - let new_pages = storage.store_pages(pages_data); - - *pages_hash = storage.modify_memory_pages(pages_hash.clone(), |pages| { - for (page, data) in new_pages { - pages.insert(page, data); - } - })?; + pages_hash.modify_pages(storage, |pages| { + pages.update(storage.write_pages_data(pages_data)); + }); Ok(()) }); @@ -416,34 +342,25 @@ impl JournalHandler for Handler<'_, S> { program_id: ProgramId, new_allocations: IntervalsTree, ) { - self.update_state_with_storage(program_id, |storage, state| { + self.update_state(program_id, |state, storage, _| { let Program::Active(ActiveProgram { - ref mut allocations_hash, - ref mut pages_hash, + allocations_hash, + pages_hash, .. - }) = state.program + }) = &mut state.program else { bail!("an attempt to update allocations of inactive program"); }; - let mut removed_pages = vec![]; - - *allocations_hash = - storage.modify_allocations(allocations_hash.clone(), |allocations| { - removed_pages = allocations - .difference(&new_allocations) - .flat_map(|i| i.iter()) - .flat_map(|i| i.to_iter()) - .collect(); - - *allocations = new_allocations.into(); - })?; + allocations_hash.modify_allocations(storage, |allocations| { + let removed_pages = allocations.update(new_allocations); - *pages_hash = storage.modify_memory_pages(pages_hash.clone(), |pages| { - for page in removed_pages { - pages.remove(&page); + if !removed_pages.is_empty() { + pages_hash.modify_pages(storage, |pages| { + pages.remove(&removed_pages); + }) } - })?; + }); Ok(()) }); @@ -456,16 +373,12 @@ impl JournalHandler for Handler<'_, S> { return; } - self.update_state(to, |state| { + self.update_state(to, |state, _, transitions| { state.balance += value; - Ok(()) - }); - self.in_block_transitions - .modify_transition(to, |_state_hash, transition| { - transition.value_to_receive += value - }) - .expect("must exist"); + transitions + .modify_transition(to, |transition| transition.value_to_receive += value); + }); } } diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index f427cf19241..6afcb6d0c27 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -37,6 +37,7 @@ use core_processor::{ use gear_core::{ code::InstrumentedCode, ids::ProgramId, + memory::PageBuf, message::{DispatchKind, IncomingDispatch, IncomingMessage, Value}, pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, program::MemoryInfix, @@ -47,8 +48,8 @@ use gprimitives::{CodeId, H256}; use gsys::{GasMultiplier, Percent}; use parity_scale_codec::{Decode, Encode}; use state::{ - ActiveProgram, ComplexStorage, Dispatch, HashAndLen, InitStatus, MaybeHash, MessageQueue, - ProgramState, Storage, Waitlist, + ActiveProgram, Dispatch, HashOf, InitStatus, MaybeHashOf, MessageQueue, ProgramState, Storage, + Waitlist, }; pub use core_processor::configs::BlockInfo; @@ -70,39 +71,32 @@ pub trait RuntimeInterface { type LazyPages: LazyPagesInterface + 'static; fn block_info(&self) -> BlockInfo; - fn init_lazy_pages(&self, pages_map: BTreeMap); + fn init_lazy_pages(&self, pages_map: BTreeMap>); fn random_data(&self) -> (Vec, u32); fn storage(&self) -> &S; } -pub(crate) fn update_state( - in_block_transitions: &mut InBlockTransitions, - storage: &S, +pub(crate) fn update_state( program_id: ProgramId, - f: impl FnOnce(&mut ProgramState) -> Result<()>, -) -> H256 { - update_state_with_storage(in_block_transitions, storage, program_id, |_s, state| { - f(state) - }) -} - -pub(crate) fn update_state_with_storage( - in_block_transitions: &mut InBlockTransitions, storage: &S, - program_id: ProgramId, - f: impl FnOnce(&S, &mut ProgramState) -> Result<()>, -) -> H256 { - let state_hash = in_block_transitions + transitions: &mut InBlockTransitions, + f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, +) -> T { + let state_hash = transitions .state_of(&program_id) .expect("failed to find program in known states"); - let new_state_hash = storage - .mutate_state(state_hash, f) - .expect("failed to mutate state"); + let mut state = storage + .read_state(state_hash) + .expect("failed to read state from storage"); + + let res = f(&mut state, storage, transitions); + + let new_state_hash = storage.write_state(state); - in_block_transitions.modify_state(program_id, new_state_hash); + transitions.modify_state(program_id, new_state_hash); - new_state_hash + res } pub fn process_next_message( @@ -187,7 +181,7 @@ where value, details, context, - } = queue.pop_front().unwrap(); + } = queue.dequeue().unwrap(); // TODO (breathx): why unwrap? if active_state.initialized && kind == DispatchKind::Init { // Panic is impossible, because gear protocol does not provide functionality @@ -239,7 +233,7 @@ where let context = match core_processor::precharge_for_allocations( &block_config, context, - allocations.intervals_amount() as u32, + allocations.tree_len(), ) { Ok(context) => context, Err(journal) => return journal, @@ -280,7 +274,7 @@ where let random_data = ri.random_data(); - ri.init_lazy_pages(pages_map.clone()); + ri.init_lazy_pages(pages_map.into()); core_processor::process::>(&block_config, execution_context, random_data) .unwrap_or_else(|err| unreachable!("{err}")) diff --git a/ethexe/runtime/common/src/schedule.rs b/ethexe/runtime/common/src/schedule.rs index 5f7166924a5..d213cf93f92 100644 --- a/ethexe/runtime/common/src/schedule.rs +++ b/ethexe/runtime/common/src/schedule.rs @@ -1,8 +1,5 @@ use crate::{ - state::{ - ComplexStorage, Dispatch, MaybeHash, ProgramState, Storage, ValueWithExpiry, - MAILBOX_VALIDITY, - }, + state::{Dispatch, MaybeHashOf, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY}, InBlockTransitions, }; use alloc::vec; @@ -21,20 +18,12 @@ pub struct Handler<'a, S: Storage> { } impl Handler<'_, S> { - pub fn update_state( + fn update_state( &mut self, program_id: ProgramId, - f: impl FnOnce(&mut ProgramState) -> Result<()>, - ) -> H256 { - crate::update_state(self.in_block_transitions, self.storage, program_id, f) - } - - pub fn update_state_with_storage( - &mut self, - program_id: ProgramId, - f: impl FnOnce(&S, &mut ProgramState) -> Result<()>, - ) -> H256 { - crate::update_state_with_storage(self.in_block_transitions, self.storage, program_id, f) + f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, + ) -> T { + crate::update_state(program_id, self.storage, self.in_block_transitions, f) } } @@ -44,144 +33,73 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { (program_id, user_id): (ProgramId, ActorId), message_id: MessageId, ) -> u64 { - let mut value_claim = None; - - self.update_state_with_storage(program_id, |storage, state| { - let ( - ValueWithExpiry { - value: claimed_value, - expiry, - }, - new_mailbox_hash, - ) = storage - .modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| { - let local_mailbox = mailbox.get_mut(&user_id)?; - let claimed_value = local_mailbox.remove(&message_id)?; - - if local_mailbox.is_empty() { - mailbox.remove(&user_id); - } - - Some(claimed_value) - })? - .ok_or_else(|| anyhow!("failed to find message in mailbox"))?; - - state.mailbox_hash = new_mailbox_hash; - - value_claim = Some(ValueClaim { - message_id, - destination: user_id, - value: claimed_value, + self.update_state(program_id, |state, storage, transitions| { + let ValueWithExpiry { value, expiry } = + state.mailbox_hash.modify_mailbox(storage, |mailbox| { + mailbox + .remove(user_id, message_id) + .expect("failed to find message in mailbox") + }); + + transitions.modify_transition(program_id, |transition| { + transition.claims.push(ValueClaim { + message_id, + destination: user_id, + value, + }) }); let reply = Dispatch::reply( message_id, user_id, - MaybeHash::Empty, + MaybeHashOf::empty(), 0, SuccessReplyReason::Auto, ); - state.queue_hash = - storage.modify_queue(state.queue_hash.clone(), |queue| queue.push_back(reply))?; - - Ok(()) + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(reply)); }); - if let Some(value_claim) = value_claim { - self.in_block_transitions - .modify_transition(program_id, |_state_hash, transition| { - transition.claims.push(value_claim) - }) - .expect("can't be None"); - } - 0 } fn send_dispatch(&mut self, (program_id, message_id): (ProgramId, MessageId)) -> u64 { - self.update_state_with_storage(program_id, |storage, state| { - let ( - ValueWithExpiry { - value: (dispatch, user_id), - .. - }, - new_stash_hash, - ) = storage - .modify_stash_if_changed(state.stash_hash.clone(), |stash| { - stash.remove(&message_id) - })? - .ok_or_else(|| anyhow!("failed to find message in stash"))?; - - debug_assert!(user_id.is_none()); - - state.stash_hash = new_stash_hash; - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - queue.push_back(dispatch); - })?; - - Ok(()) + self.update_state(program_id, |state, storage, _| { + state.queue_hash.modify_queue(storage, |queue| { + let dispatch = state + .stash_hash + .modify_stash(storage, |stash| stash.remove_to_program(&message_id)); + + queue.queue(dispatch); + }); }); 0 } fn send_user_message(&mut self, stashed_message_id: MessageId, program_id: ProgramId) -> u64 { - let mut dispatch_and_user = None; - - self.update_state_with_storage(program_id, |storage, state| { - let ( - ValueWithExpiry { - value: (dispatch, user_id), - .. - }, - new_stash_hash, - ) = storage - .modify_stash_if_changed(state.stash_hash.clone(), |stash| { - stash.remove(&stashed_message_id) - })? - .ok_or_else(|| anyhow!("failed to find message in stash"))?; - - state.stash_hash = new_stash_hash; - dispatch_and_user = Some(( - dispatch, - user_id.expect("the message intended to user contains no id"), - )); - - Ok(()) - }); + self.update_state(program_id, |state, storage, transitions| { + let (dispatch, user_id) = state + .stash_hash + .modify_stash(storage, |stash| stash.remove_to_user(&stashed_message_id)); - if let Some((dispatch, user_id)) = dispatch_and_user { - let expiry = self.in_block_transitions.schedule_task( + let expiry = transitions.schedule_task( MAILBOX_VALIDITY.try_into().expect("infallible"), ScheduledTask::RemoveFromMailbox((program_id, user_id), stashed_message_id), ); - self.update_state_with_storage(program_id, |storage, state| { - state.mailbox_hash = - storage.modify_mailbox(state.mailbox_hash.clone(), |mailbox| { - let r = mailbox.entry(user_id).or_default().insert( - dispatch.id, - ValueWithExpiry { - value: dispatch.value, - expiry, - }, - ); - - debug_assert!(r.is_none()); - })?; - - Ok(()) + state.mailbox_hash.modify_mailbox(storage, |mailbox| { + mailbox.add(user_id, stashed_message_id, dispatch.value, expiry); }); - let outgoing_message = dispatch.into_outgoing(self.storage, user_id); - - self.in_block_transitions - .modify_transition(program_id, |_state_hash, transition| { - transition.messages.push(outgoing_message) - }) - .expect("must be") - } + transitions.modify_transition(program_id, |transition| { + transition + .messages + .push(dispatch.into_outgoing(storage, user_id)) + }) + }); 0 } @@ -190,24 +108,18 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { fn wake_message(&mut self, program_id: ProgramId, message_id: MessageId) -> u64 { log::trace!("Running scheduled task wake message {message_id} to {program_id}"); - self.update_state_with_storage(program_id, |storage, state| { - let ( - ValueWithExpiry { - value: dispatch, .. - }, - new_waitlist_hash, - ) = storage - .modify_waitlist_if_changed(state.waitlist_hash.clone(), |waitlist| { - waitlist.remove(&message_id) - })? - .ok_or_else(|| anyhow!("failed to find message in waitlist"))?; - - state.waitlist_hash = new_waitlist_hash; - state.queue_hash = storage.modify_queue(state.queue_hash.clone(), |queue| { - queue.push_back(dispatch); - })?; - - Ok(()) + self.update_state(program_id, |state, storage, _| { + let ValueWithExpiry { + value: dispatch, .. + } = state.waitlist_hash.modify_waitlist(storage, |waitlist| { + waitlist + .wake(&message_id) + .expect("failed to find message in waitlist") + }); + + state.queue_hash.modify_queue(storage, |queue| { + queue.queue(dispatch); + }) }); 0 diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 820c721d473..e895b9c9e43 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -24,6 +24,7 @@ use alloc::{ }; use anyhow::{anyhow, Result}; use core::{ + marker::PhantomData, num::NonZero, ops::{Deref, DerefMut}, }; @@ -44,72 +45,268 @@ use gear_core_errors::ReplyCode; use gprimitives::{ActorId, CodeId, MessageId, H256}; use gsys::BlockNumber; use parity_scale_codec::{Decode, Encode}; +use private::Sealed; pub use gear_core::program::ProgramState as InitStatus; /// 3h validity in mailbox for 12s blocks. pub const MAILBOX_VALIDITY: u32 = 54_000; -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] -pub struct HashAndLen { - pub hash: H256, - pub len: NonZero, +mod private { + use super::*; + + pub trait Sealed {} + + impl Sealed for Allocations {} + impl Sealed for DispatchStash {} + impl Sealed for Mailbox {} + impl Sealed for MemoryPages {} + impl Sealed for MessageQueue {} + impl Sealed for Payload {} + impl Sealed for PageBuf {} + // TODO (breathx): FIX ME WITHIN THE PR + // impl Sealed for ProgramState {} + impl Sealed for Waitlist {} +} + +#[derive(Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct HashOf { + hash: H256, + #[into(ignore)] + _phantom: PhantomData, } -// TODO: temporary solution to avoid lengths taking in account -impl From for HashAndLen { - fn from(value: H256) -> Self { +impl Clone for HashOf { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for HashOf {} + +impl HashOf { + /// # Safety + /// Use it only for low-level storage implementations or tests. + pub unsafe fn new(hash: H256) -> Self { Self { - hash: value, - len: NonZero::::new(1).expect("impossible"), + hash, + _phantom: PhantomData, } } -} -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] -pub enum MaybeHash { - Hash(HashAndLen), - Empty, + pub fn hash(&self) -> H256 { + self.hash + } } -// TODO: temporary solution to avoid lengths taking in account -impl From for MaybeHash { - fn from(value: H256) -> Self { - MaybeHash::Hash(HashAndLen::from(value)) +#[derive(Debug, Encode, Decode, PartialEq, Eq)] +pub struct MaybeHashOf(Option>); + +impl Clone for MaybeHashOf { + fn clone(&self) -> Self { + *self } } -impl MaybeHash { - pub fn is_empty(&self) -> bool { - matches!(self, MaybeHash::Empty) +impl Copy for MaybeHashOf {} + +impl MaybeHashOf { + pub const fn empty() -> Self { + Self(None) } - pub fn with_hash(&self, f: impl FnOnce(H256) -> T) -> Option { - let Self::Hash(HashAndLen { hash, .. }) = self else { - return None; - }; + pub const fn is_empty(&self) -> bool { + self.0.is_none() + } + + pub fn hash(&self) -> Option> { + self.0 + } - Some(f(*hash)) + pub fn with_hash(&self, f: impl FnOnce(HashOf) -> T) -> Option { + self.hash().map(f) } - pub fn with_hash_or_default(&self, f: impl FnOnce(H256) -> T) -> T { + pub fn with_hash_or_default(&self, f: impl FnOnce(HashOf) -> T) -> T { self.with_hash(f).unwrap_or_default() } - pub fn with_hash_or_default_result( + pub fn with_hash_or_default_fallible( &self, - f: impl FnOnce(H256) -> Result, + f: impl FnOnce(HashOf) -> Result, ) -> Result { self.with_hash(f).unwrap_or_else(|| Ok(Default::default())) } + + pub fn replace(&mut self, other: Option) { + if let Some(other) = other { + *self = other; + } + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage.read_allocations(hash).ok_or(anyhow!( + "failed to read ['Allocations'] from storage by hash" + )) + }) + } + + pub fn modify_allocations( + &mut self, + storage: &S, + f: impl FnOnce(&mut Allocations) -> T, + ) -> T { + let mut allocations = self.query(storage).expect("failed to modify allocations"); + + let r = f(&mut allocations); + + self.replace(allocations.store(storage)); + + r + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage.read_stash(hash).ok_or(anyhow!( + "failed to read ['DispatchStash'] from storage by hash" + )) + }) + } + + pub fn modify_stash( + &mut self, + storage: &S, + f: impl FnOnce(&mut DispatchStash) -> T, + ) -> T { + let mut stash = self.query(storage).expect("failed to modify stash"); + + let r = f(&mut stash); + + *self = stash.store(storage); + + r + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage + .read_mailbox(hash) + .ok_or(anyhow!("failed to read ['Mailbox'] from storage by hash")) + }) + } + + pub fn modify_mailbox( + &mut self, + storage: &S, + f: impl FnOnce(&mut Mailbox) -> T, + ) -> T { + let mut mailbox = self.query(storage).expect("failed to modify mailbox"); + + let r = f(&mut mailbox); + + self.replace(mailbox.store(storage)); + + r + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage.read_pages(hash).ok_or(anyhow!( + "failed to read ['MemoryPages'] from storage by hash" + )) + }) + } + + pub fn modify_pages( + &mut self, + storage: &S, + f: impl FnOnce(&mut MemoryPages) -> T, + ) -> T { + let mut pages = self.query(storage).expect("failed to modify memory pages"); + + let r = f(&mut pages); + + *self = pages.store(storage); + + r + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage.read_queue(hash).ok_or(anyhow!( + "failed to read ['MessageQueue'] from storage by hash" + )) + }) + } + + pub fn modify_queue( + &mut self, + storage: &S, + f: impl FnOnce(&mut MessageQueue) -> T, + ) -> T { + let mut queue = self.query(storage).expect("failed to modify queue"); + + let r = f(&mut queue); + + *self = queue.store(storage); + + r + } +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage + .read_payload(hash) + .ok_or(anyhow!("failed to read ['Payload'] from storage by hash")) + }) + } + + // TODO (breathx): enum for caught value +} + +impl MaybeHashOf { + pub fn query(&self, storage: &S) -> Result { + self.with_hash_or_default_fallible(|hash| { + storage + .read_waitlist(hash) + .ok_or(anyhow!("failed to read ['Waitlist'] from storage by hash")) + }) + } + + pub fn modify_waitlist( + &mut self, + storage: &S, + f: impl FnOnce(&mut Waitlist) -> T, + ) -> T { + let mut waitlist = self.query(storage).expect("failed to modify waitlist"); + + let r = f(&mut waitlist); + + self.replace(waitlist.store(storage)); + + r + } } #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq)] pub struct ActiveProgram { /// Hash of wasm memory pages allocations, see [`Allocations`]. - pub allocations_hash: MaybeHash, + pub allocations_hash: MaybeHashOf, /// Hash of memory pages table, see [`MemoryPages`]. - pub pages_hash: MaybeHash, + pub pages_hash: MaybeHashOf, /// Program memory infix. pub memory_infix: MemoryInfix, /// Program initialization status. @@ -145,13 +342,13 @@ pub struct ProgramState { /// Active, exited or terminated program state. pub program: Program, /// Hash of incoming message queue, see [`MessageQueue`]. - pub queue_hash: MaybeHash, + pub queue_hash: MaybeHashOf, /// Hash of waiting messages list, see [`Waitlist`]. - pub waitlist_hash: MaybeHash, + pub waitlist_hash: MaybeHashOf, /// Hash of dispatch stash, see [`DispatchStash`]. - pub stash_hash: MaybeHash, + pub stash_hash: MaybeHashOf, /// Hash of mailboxed messages, see [`Mailbox`]. - pub mailbox_hash: MaybeHash, + pub mailbox_hash: MaybeHashOf, /// Reducible balance. pub balance: Value, /// Executable balance. @@ -162,15 +359,15 @@ impl ProgramState { pub const fn zero() -> Self { Self { program: Program::Active(ActiveProgram { - allocations_hash: MaybeHash::Empty, - pages_hash: MaybeHash::Empty, + allocations_hash: MaybeHashOf::empty(), + pages_hash: MaybeHashOf::empty(), memory_infix: MemoryInfix::new(0), initialized: false, }), - queue_hash: MaybeHash::Empty, - waitlist_hash: MaybeHash::Empty, - stash_hash: MaybeHash::Empty, - mailbox_hash: MaybeHash::Empty, + queue_hash: MaybeHashOf::empty(), + waitlist_hash: MaybeHashOf::empty(), + stash_hash: MaybeHashOf::empty(), + mailbox_hash: MaybeHashOf::empty(), balance: 0, executable_balance: 0, } @@ -204,7 +401,7 @@ pub struct Dispatch { /// Message source. pub source: ProgramId, /// Message payload. - pub payload_hash: MaybeHash, + pub payload_hash: MaybeHashOf, /// Message value. pub value: Value, /// Message details like reply message ID, status code, etc. @@ -217,7 +414,7 @@ impl Dispatch { pub fn reply( reply_to: MessageId, source: ActorId, - payload_hash: MaybeHash, + payload_hash: MaybeHashOf, value: u128, reply_code: impl Into, ) -> Self { @@ -236,9 +433,11 @@ impl Dispatch { let (kind, message, context) = value.into_parts(); let (id, source, destination, payload, value, details) = message.into_parts(); - let payload_hash = storage - .store_payload(payload.into_vec()) - .expect("infallible due to recasts (only panics on len)"); + // TODO (breathx): FIX ME WITHIN THE PR + let payload_hash = MaybeHashOf::empty(); + // let payload_hash = storage + // .store_payload(payload.into_vec()) + // .expect("infallible due to recasts (only panics on len)"); Self { id, @@ -289,380 +488,299 @@ impl From<(T, u32)> for ValueWithExpiry { } } -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct MessageQueue(pub VecDeque); - -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct Waitlist(pub BTreeMap>); - -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct DispatchStash(pub BTreeMap)>>); +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct MessageQueue(VecDeque); -// TODO (breathx): consider here LocalMailbox for each user. -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct Mailbox(pub BTreeMap>>); - -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct MemoryPages(pub BTreeMap); - -#[derive( - Default, - Debug, - Encode, - Decode, - PartialEq, - Eq, - derive_more::Deref, - derive_more::DerefMut, - derive_more::From, - derive_more::Into, -)] -pub struct Allocations(pub IntervalsTree); +impl MessageQueue { + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } -pub trait Storage { - /// Reads program state by state hash. - fn read_state(&self, hash: H256) -> Option; + pub fn queue(&mut self, dispatch: Dispatch) { + self.0.push_back(dispatch); + } - /// Writes program state and returns its hash. - fn write_state(&self, state: ProgramState) -> H256; + pub fn dequeue(&mut self) -> Option { + self.0.pop_front() + } - /// Reads message queue by queue hash. - fn read_queue(&self, hash: H256) -> Option; + pub fn store(self, storage: &S) -> MaybeHashOf { + MaybeHashOf((!self.0.is_empty()).then(|| storage.write_queue(self))) + } +} - /// Writes message queue and returns its hash. - fn write_queue(&self, queue: MessageQueue) -> H256; +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct Waitlist { + inner: BTreeMap>, + #[into(ignore)] + #[codec(skip)] + changed: bool, +} - /// Reads waitlist by waitlist hash. - fn read_waitlist(&self, hash: H256) -> Option; +impl Waitlist { + pub fn wait(&mut self, message_id: MessageId, dispatch: Dispatch, expiry: u32) { + self.changed = true; + + let r = self.inner.insert( + message_id, + ValueWithExpiry { + value: dispatch, + expiry, + }, + ); + debug_assert!(r.is_none()) + } - /// Writes waitlist and returns its hash. - fn write_waitlist(&self, waitlist: Waitlist) -> H256; + pub fn wake(&mut self, message_id: &MessageId) -> Option> { + self.inner + .remove(message_id) + .inspect(|_| self.changed = true) + } - /// Reads dispatch stash by its hash. - fn read_stash(&self, hash: H256) -> Option; + pub fn store(self, storage: &S) -> Option> { + self.changed + .then(|| MaybeHashOf((!self.inner.is_empty()).then(|| storage.write_waitlist(self)))) + } +} - /// Writes dispatch stash and returns its hash. - fn write_stash(&self, stash: DispatchStash) -> H256; +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct DispatchStash(BTreeMap)>>); + +impl DispatchStash { + pub fn add_to_program(&mut self, message_id: MessageId, dispatch: Dispatch, expiry: u32) { + let r = self.0.insert( + message_id, + ValueWithExpiry { + value: (dispatch, None), + expiry, + }, + ); + debug_assert!(r.is_none()); + } - /// Reads mailbox by mailbox hash. - fn read_mailbox(&self, hash: H256) -> Option; + pub fn add_to_user( + &mut self, + message_id: MessageId, + dispatch: Dispatch, + expiry: u32, + user_id: ActorId, + ) { + let r = self.0.insert( + message_id, + ValueWithExpiry { + value: (dispatch, Some(user_id)), + expiry, + }, + ); + debug_assert!(r.is_none()); + } - /// Writes mailbox and returns its hash. - fn write_mailbox(&self, mailbox: Mailbox) -> H256; + pub fn remove_to_program(&mut self, message_id: &MessageId) -> Dispatch { + let ValueWithExpiry { + value: (dispatch, user_id), + expiry, + } = self + .0 + .remove(message_id) + .expect("unknown mid queried from stash"); + + if user_id.is_some() { + panic!("stashed message was intended to be sent to program, but keeps data for user"); + } - /// Reads memory pages by pages hash. - fn read_pages(&self, hash: H256) -> Option; + dispatch + } - /// Writes memory pages and returns its hash. - fn write_pages(&self, pages: MemoryPages) -> H256; + pub fn remove_to_user(&mut self, message_id: &MessageId) -> (Dispatch, ActorId) { + let ValueWithExpiry { + value: (dispatch, user_id), + expiry, + } = self + .0 + .remove(message_id) + .expect("unknown mid queried from stash"); - /// Reads allocations by allocations hash. - fn read_allocations(&self, hash: H256) -> Option; + let user_id = user_id + .expect("stashed mid was intended to be sent to user, but keeps no data for user"); - /// Writes allocations and returns its hash. - fn write_allocations(&self, allocations: Allocations) -> H256; + (dispatch, user_id) + } - /// Reads payload by payload hash. - fn read_payload(&self, hash: H256) -> Option; + pub fn store(self, storage: &S) -> MaybeHashOf { + MaybeHashOf((!self.0.is_empty()).then(|| storage.write_stash(self))) + } +} - /// Writes payload and returns its hash. - fn write_payload(&self, payload: Payload) -> H256; +// TODO (breathx): consider here LocalMailbox for each user. +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct Mailbox { + inner: BTreeMap>>, + #[into(ignore)] + #[codec(skip)] + changed: bool, +} - /// Reads page data by page data hash. - fn read_page_data(&self, hash: H256) -> Option; +impl Mailbox { + pub fn add(&mut self, user_id: ActorId, message_id: MessageId, value: Value, expiry: u32) { + self.changed = true; - /// Writes page data and returns its hash. - fn write_page_data(&self, data: PageBuf) -> H256; -} + let r = self + .inner + .entry(user_id) + .or_default() + .insert(message_id, ValueWithExpiry { value, expiry }); + debug_assert!(r.is_none()) + } -pub trait ComplexStorage: Storage { - fn store_payload(&self, payload: Vec) -> Result { - let payload = - Payload::try_from(payload).map_err(|_| anyhow!("failed to save payload: too large"))?; + pub fn remove( + &mut self, + user_id: ActorId, + message_id: MessageId, + ) -> Option> { + let local_mailbox = self.inner.get_mut(&user_id)?; + let claimed_value = local_mailbox.remove(&message_id)?; + + self.changed = true; + + if local_mailbox.is_empty() { + self.inner.remove(&user_id); + } - Ok(payload - .inner() - .is_empty() - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_payload(payload).into())) + Some(claimed_value) } - fn store_pages(&self, pages: BTreeMap) -> BTreeMap { - pages - .into_iter() - .map(|(k, v)| (k, self.write_page_data(v))) - .collect() + pub fn store(self, storage: &S) -> Option> { + self.changed + .then(|| MaybeHashOf((!self.inner.is_empty()).then(|| storage.write_mailbox(self)))) } +} - fn modify_memory_pages( - &self, - pages_hash: MaybeHash, - f: impl FnOnce(&mut MemoryPages), - ) -> Result { - let mut pages = pages_hash.with_hash_or_default_result(|pages_hash| { - self.read_pages(pages_hash) - .ok_or_else(|| anyhow!("failed to read pages by their hash ({pages_hash})")) - })?; +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct MemoryPages(BTreeMap>); - f(&mut pages); +impl MemoryPages { + pub fn update(&mut self, new_pages: BTreeMap>) { + for (page, data) in new_pages { + self.0.insert(page, data); + } + } - let pages_hash = pages - .is_empty() - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_pages(pages).into()); + pub fn remove(&mut self, pages: &Vec) { + for page in pages { + self.0.remove(page); + } + } - Ok(pages_hash) + pub fn store(self, storage: &S) -> MaybeHashOf { + MaybeHashOf((!self.0.is_empty()).then(|| storage.write_pages(self))) } +} - fn modify_allocations( - &self, - allocations_hash: MaybeHash, - f: impl FnOnce(&mut Allocations), - ) -> Result { - let mut allocations = allocations_hash.with_hash_or_default_result(|allocations_hash| { - self.read_allocations(allocations_hash).ok_or_else(|| { - anyhow!("failed to read allocations by their hash ({allocations_hash})") - }) - })?; +#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +pub struct Allocations { + inner: IntervalsTree, + #[into(ignore)] + #[codec(skip)] + changed: bool, +} - f(&mut allocations); +impl Allocations { + pub fn tree_len(&self) -> u32 { + self.inner.intervals_amount() as u32 + } - let allocations_hash = allocations - .intervals_amount() - .eq(&0) - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_allocations(allocations).into()); + pub fn update(&mut self, allocations: IntervalsTree) -> Vec { + let len = self.tree_len(); + + let removed_pages: Vec<_> = self + .inner + .difference(&allocations) + .flat_map(|i| i.iter()) + .flat_map(|i| i.to_iter()) + .collect(); + + if !removed_pages.is_empty() + || allocations.intervals_amount() != self.inner.intervals_amount() + { + self.changed = true; + self.inner = allocations; + } - Ok(allocations_hash) + removed_pages } - /// Usage: for optimized performance, please remove entries if empty. - /// Always updates storage. - fn modify_waitlist( - &self, - waitlist_hash: MaybeHash, - f: impl FnOnce(&mut Waitlist), - ) -> Result { - self.modify_waitlist_if_changed(waitlist_hash, |waitlist| { - f(waitlist); - Some(()) + pub fn store(self, storage: &S) -> Option> { + self.changed.then(|| { + MaybeHashOf( + (self.inner.intervals_amount() != 0).then(|| storage.write_allocations(self)), + ) }) - .map(|v| v.expect("`Some` passed above; infallible").1) } +} - /// Usage: for optimized performance, please remove entries if empty. - /// Waitlist is treated changed if f() returns Some. - fn modify_waitlist_if_changed( - &self, - waitlist_hash: MaybeHash, - f: impl FnOnce(&mut Waitlist) -> Option, - ) -> Result> { - let mut waitlist = waitlist_hash.with_hash_or_default_result(|waitlist_hash| { - self.read_waitlist(waitlist_hash) - .ok_or_else(|| anyhow!("failed to read waitlist by its hash ({waitlist_hash})")) - })?; - - let res = if let Some(v) = f(&mut waitlist) { - let maybe_hash = waitlist - .is_empty() - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_waitlist(waitlist).into()); - - Some((v, maybe_hash)) - } else { - None - }; - - Ok(res) - } - - /// Usage: for optimized performance, please remove entries if empty. - /// Always updates storage. - fn modify_stash( - &self, - stash_hash: MaybeHash, - f: impl FnOnce(&mut DispatchStash), - ) -> Result { - self.modify_stash_if_changed(stash_hash, |stash| { - f(stash); - Some(()) - }) - .map(|v| v.expect("`Some` passed above; infallible").1) - } +pub trait Storage { + /// Reads program state by state hash. + fn read_state(&self, hash: H256) -> Option; - /// Usage: for optimized performance, please remove entries if empty. - /// DispatchStash is treated changed if f() returns Some. - fn modify_stash_if_changed( - &self, - stash_hash: MaybeHash, - f: impl FnOnce(&mut DispatchStash) -> Option, - ) -> Result> { - let mut stash = stash_hash.with_hash_or_default_result(|stash_hash| { - self.read_stash(stash_hash) - .ok_or_else(|| anyhow!("failed to read dispatch stash by its hash ({stash_hash})")) - })?; + /// Writes program state and returns its hash. + fn write_state(&self, state: ProgramState) -> H256; - let res = if let Some(v) = f(&mut stash) { - let maybe_hash = stash - .is_empty() - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_stash(stash).into()); + /// Reads message queue by queue hash. + fn read_queue(&self, hash: HashOf) -> Option; - Some((v, maybe_hash)) - } else { - None - }; + /// Writes message queue and returns its hash. + fn write_queue(&self, queue: MessageQueue) -> HashOf; - Ok(res) - } + /// Reads waitlist by waitlist hash. + fn read_waitlist(&self, hash: HashOf) -> Option; - fn modify_queue( - &self, - queue_hash: MaybeHash, - f: impl FnOnce(&mut MessageQueue), - ) -> Result { - self.modify_queue_returning(queue_hash, f) - .map(|((), queue_hash)| queue_hash) - } + /// Writes waitlist and returns its hash. + fn write_waitlist(&self, waitlist: Waitlist) -> HashOf; - fn modify_queue_returning( - &self, - queue_hash: MaybeHash, - f: impl FnOnce(&mut MessageQueue) -> T, - ) -> Result<(T, MaybeHash)> { - let mut queue = queue_hash.with_hash_or_default_result(|queue_hash| { - self.read_queue(queue_hash) - .ok_or_else(|| anyhow!("failed to read queue by its hash ({queue_hash})")) - })?; + /// Reads dispatch stash by its hash. + fn read_stash(&self, hash: HashOf) -> Option; - let res = f(&mut queue); + /// Writes dispatch stash and returns its hash. + fn write_stash(&self, stash: DispatchStash) -> HashOf; - let queue_hash = queue - .is_empty() - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_queue(queue).into()); + /// Reads mailbox by mailbox hash. + fn read_mailbox(&self, hash: HashOf) -> Option; - Ok((res, queue_hash)) - } + /// Writes mailbox and returns its hash. + fn write_mailbox(&self, mailbox: Mailbox) -> HashOf; - /// Usage: for optimized performance, please remove map entries if empty. - /// Always updates storage. - fn modify_mailbox( - &self, - mailbox_hash: MaybeHash, - f: impl FnOnce(&mut Mailbox), - ) -> Result { - self.modify_mailbox_if_changed(mailbox_hash, |mailbox| { - f(mailbox); - Some(()) - }) - .map(|v| v.expect("`Some` passed above; infallible").1) - } + /// Reads memory pages by pages hash. + fn read_pages(&self, hash: HashOf) -> Option; - /// Usage: for optimized performance, please remove map entries if empty. - /// Mailbox is treated changed if f() returns Some. - fn modify_mailbox_if_changed( - &self, - mailbox_hash: MaybeHash, - f: impl FnOnce(&mut Mailbox) -> Option, - ) -> Result> { - let mut mailbox = mailbox_hash.with_hash_or_default_result(|mailbox_hash| { - self.read_mailbox(mailbox_hash) - .ok_or_else(|| anyhow!("failed to read mailbox by its hash ({mailbox_hash})")) - })?; - - let res = if let Some(v) = f(&mut mailbox) { - let maybe_hash = mailbox - .values() - .all(|v| v.is_empty()) - .then_some(MaybeHash::Empty) - .unwrap_or_else(|| self.write_mailbox(mailbox).into()); - - Some((v, maybe_hash)) - } else { - None - }; - - Ok(res) - } - - fn mutate_state( - &self, - state_hash: H256, - f: impl FnOnce(&Self, &mut ProgramState) -> Result<()>, - ) -> Result { - self.mutate_state_returning(state_hash, f) - .map(|((), hash)| hash) - } + /// Writes memory pages and returns its hash. + fn write_pages(&self, pages: MemoryPages) -> HashOf; - fn mutate_state_returning( - &self, - state_hash: H256, - f: impl FnOnce(&Self, &mut ProgramState) -> Result, - ) -> Result<(T, H256)> { - let mut state = self - .read_state(state_hash) - .ok_or_else(|| anyhow!("failed to read state by its hash ({state_hash})"))?; + /// Reads allocations by allocations hash. + fn read_allocations(&self, hash: HashOf) -> Option; + + /// Writes allocations and returns its hash. + fn write_allocations(&self, allocations: Allocations) -> HashOf; + + /// Reads payload by payload hash. + fn read_payload(&self, hash: HashOf) -> Option; + + /// Writes payload and returns its hash. + fn write_payload(&self, payload: Payload) -> HashOf; - let res = f(self, &mut state)?; + /// Reads page data by page data hash. + fn read_page_data(&self, hash: HashOf) -> Option; + + /// Writes page data and returns its hash. + fn write_page_data(&self, data: PageBuf) -> HashOf; - Ok((res, self.write_state(state))) + /// Writes multiple pages data and returns their hashes. + fn write_pages_data( + &self, + pages: BTreeMap, + ) -> BTreeMap> { + pages + .into_iter() + .map(|(k, v)| (k, self.write_page_data(v))) + .collect() } } - -impl ComplexStorage for T {} diff --git a/ethexe/runtime/common/src/transitions.rs b/ethexe/runtime/common/src/transitions.rs index 812f5de6d69..78f5f9242e2 100644 --- a/ethexe/runtime/common/src/transitions.rs +++ b/ethexe/runtime/common/src/transitions.rs @@ -51,6 +51,10 @@ impl InBlockTransitions { &self.header } + pub fn is_program(&self, actor_id: &ActorId) -> bool { + self.state_of(actor_id).is_some() + } + pub fn state_of(&self, actor_id: &ActorId) -> Option { self.states.get(actor_id).cloned() } @@ -110,12 +114,29 @@ impl InBlockTransitions { self.modifications.insert(actor_id, Default::default()); } + pub fn modify_state(&mut self, actor_id: ActorId, new_state_hash: H256) { + self.modify(actor_id, |state_hash, _transition| { + *state_hash = new_state_hash + }) + } + pub fn modify_transition( + &mut self, + actor_id: ActorId, + f: impl FnOnce(&mut NonFinalTransition) -> T, + ) -> T { + self.modify(actor_id, |_state_hash, transition| f(transition)) + } + + pub fn modify( &mut self, actor_id: ActorId, f: impl FnOnce(&mut H256, &mut NonFinalTransition) -> T, - ) -> Option { - let initial_state = self.states.get_mut(&actor_id)?; + ) -> T { + let initial_state = self + .states + .get_mut(&actor_id) + .expect("couldn't modify transition for unknown actor"); let transition = self .modifications @@ -125,13 +146,7 @@ impl InBlockTransitions { ..Default::default() }); - Some(f(initial_state, transition)) - } - - pub fn modify_state(&mut self, actor_id: ActorId, new_state_hash: H256) -> Option<()> { - self.modify_transition(actor_id, |state_hash, _transition| { - *state_hash = new_state_hash - }) + f(initial_state, transition) } pub fn finalize(self) -> (Vec, BTreeMap, Schedule) { From 4ef8aedafb786646d58ee261d73f9dd6c6335381 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Wed, 23 Oct 2024 17:34:42 +0400 Subject: [PATCH 04/18] keep adjusting codebase --- ethexe/common/src/router.rs | 8 +-- ethexe/db/src/database.rs | 85 ++++++++++++----------- ethexe/processor/src/handling/events.rs | 31 ++++----- ethexe/processor/src/handling/mod.rs | 5 +- ethexe/processor/src/host/threads.rs | 17 ++--- ethexe/processor/src/tests.rs | 6 +- ethexe/runtime/common/src/state.rs | 4 ++ ethexe/runtime/src/wasm/storage.rs | 91 ++++++++++++------------- 8 files changed, 117 insertions(+), 130 deletions(-) diff --git a/ethexe/common/src/router.rs b/ethexe/common/src/router.rs index 93d6de6c5c0..b784a17f896 100644 --- a/ethexe/common/src/router.rs +++ b/ethexe/common/src/router.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use alloc::vec::Vec; -use gear_core::message::{Message, ReplyDetails}; +use gear_core::message::{ReplyDetails, StoredMessage}; use gprimitives::{ActorId, CodeId, MessageId, H256}; use parity_scale_codec::{Decode, Encode}; @@ -73,9 +73,9 @@ pub struct OutgoingMessage { pub reply_details: Option, } -impl From for OutgoingMessage { - fn from(value: Message) -> Self { - let (id, _source, destination, payload, _gas_limit, value, details) = value.into_parts(); +impl From for OutgoingMessage { + fn from(value: StoredMessage) -> Self { + let (id, _source, destination, payload, value, details) = value.into_parts(); Self { id, destination, diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index da82d71f0d2..4b6bcac1647 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -28,7 +28,8 @@ use ethexe_common::{ BlockRequestEvent, }; use ethexe_runtime_common::state::{ - Allocations, DispatchStash, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, Waitlist, + Allocations, DispatchStash, HashOf, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, + Waitlist, }; use gear_core::{ code::InstrumentedCode, @@ -482,83 +483,85 @@ impl Storage for Database { self.cas.write(&state.encode()) } - fn read_queue(&self, hash: H256) -> Option { - let data = self.cas.read(&hash)?; - Some( - MessageQueue::decode(&mut &data[..]) - .expect("Failed to decode data into `MessageQueue`"), - ) + fn read_queue(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { + MessageQueue::decode(&mut &data[..]).expect("Failed to decode data into `MessageQueue`") + }) } - fn write_queue(&self, queue: MessageQueue) -> H256 { - self.cas.write(&queue.encode()) + fn write_queue(&self, queue: MessageQueue) -> HashOf { + unsafe { HashOf::new(self.cas.write(&queue.encode())) } } - fn read_waitlist(&self, hash: H256) -> Option { - self.cas.read(&hash).map(|data| { + fn read_waitlist(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { Waitlist::decode(&mut data.as_slice()).expect("Failed to decode data into `Waitlist`") }) } - fn write_waitlist(&self, waitlist: Waitlist) -> H256 { - self.cas.write(&waitlist.encode()) + fn write_waitlist(&self, waitlist: Waitlist) -> HashOf { + unsafe { HashOf::new(self.cas.write(&waitlist.encode())) } } - fn read_stash(&self, hash: H256) -> Option { - self.cas.read(&hash).map(|data| { + fn read_stash(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { DispatchStash::decode(&mut data.as_slice()) .expect("Failed to decode data into `DispatchStash`") }) } - fn write_stash(&self, stash: DispatchStash) -> H256 { - self.cas.write(&stash.encode()) + fn write_stash(&self, stash: DispatchStash) -> HashOf { + unsafe { HashOf::new(self.cas.write(&stash.encode())) } } - fn read_mailbox(&self, hash: H256) -> Option { - self.cas.read(&hash).map(|data| { + fn read_mailbox(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { Mailbox::decode(&mut data.as_slice()).expect("Failed to decode data into `Mailbox`") }) } - fn write_mailbox(&self, mailbox: Mailbox) -> H256 { - self.cas.write(&mailbox.encode()) + fn write_mailbox(&self, mailbox: Mailbox) -> HashOf { + unsafe { HashOf::new(self.cas.write(&mailbox.encode())) } } - fn read_pages(&self, hash: H256) -> Option { - let data = self.cas.read(&hash)?; - Some(MemoryPages::decode(&mut &data[..]).expect("Failed to decode data into `MemoryPages`")) + fn read_pages(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { + MemoryPages::decode(&mut &data[..]).expect("Failed to decode data into `MemoryPages`") + }) } - fn write_pages(&self, pages: MemoryPages) -> H256 { - self.cas.write(&pages.encode()) + fn write_pages(&self, pages: MemoryPages) -> HashOf { + unsafe { HashOf::new(self.cas.write(&pages.encode())) } } - fn read_allocations(&self, hash: H256) -> Option { - let data = self.cas.read(&hash)?; - Some(Allocations::decode(&mut &data[..]).expect("Failed to decode data into `Allocations`")) + fn read_allocations(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { + Allocations::decode(&mut &data[..]).expect("Failed to decode data into `Allocations`") + }) } - fn write_allocations(&self, allocations: Allocations) -> H256 { - self.cas.write(&allocations.encode()) + fn write_allocations(&self, allocations: Allocations) -> HashOf { + unsafe { HashOf::new(self.cas.write(&allocations.encode())) } } - fn read_payload(&self, hash: H256) -> Option { - let data = self.cas.read(&hash)?; - Some(Payload::try_from(data).expect("Failed to decode data into `Payload`")) + fn read_payload(&self, hash: HashOf) -> Option { + self.cas + .read(&hash.hash()) + .map(|data| Payload::try_from(data).expect("Failed to decode data into `Payload`")) } - fn write_payload(&self, payload: Payload) -> H256 { - self.cas.write(payload.inner()) + fn write_payload(&self, payload: Payload) -> HashOf { + unsafe { HashOf::new(self.cas.write(payload.inner())) } } - fn read_page_data(&self, hash: H256) -> Option { - let data = self.cas.read(&hash)?; - Some(PageBuf::decode(&mut data.as_slice()).expect("Failed to decode data into `PageBuf`")) + fn read_page_data(&self, hash: HashOf) -> Option { + self.cas.read(&hash.hash()).map(|data| { + PageBuf::decode(&mut data.as_slice()).expect("Failed to decode data into `PageBuf`") + }) } - fn write_page_data(&self, data: PageBuf) -> H256 { - self.cas.write(&data) + fn write_page_data(&self, data: PageBuf) -> HashOf { + unsafe { HashOf::new(self.cas.write(&data)) } } } diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index 6661f719b1e..7b6307b8d6d 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -25,7 +25,7 @@ use ethexe_common::{ }; use ethexe_db::{CodesStorage, ScheduledTask}; use ethexe_runtime_common::{ - state::{ComplexStorage as _, Dispatch, Storage, ValueWithExpiry}, + state::{Dispatch, Storage, ValueWithExpiry}, InBlockTransitions, }; use gear_core::{ @@ -73,9 +73,7 @@ impl Processor { match event { MirrorEvent::ExecutableBalanceTopUpRequested { value } => { let new_state_hash = self.handle_executable_balance_top_up(state_hash, value)?; - in_block_transitions - .modify_state(actor_id, new_state_hash) - .ok_or_else(|| anyhow!("failed to modify state of recognized program"))?; + in_block_transitions.modify_state(actor_id, new_state_hash); } MirrorEvent::MessageQueueingRequested { id, @@ -107,9 +105,7 @@ impl Processor { }; let new_state_hash = self.handle_message_queueing(state_hash, dispatch)?; - in_block_transitions - .modify_state(actor_id, new_state_hash) - .ok_or_else(|| anyhow!("failed to modify state of recognized program"))?; + in_block_transitions.modify_state(actor_id, new_state_hash); } MirrorEvent::ReplyQueueingRequested { replied_to, @@ -120,12 +116,10 @@ impl Processor { if let Some((value_claim, expiry, new_state_hash)) = self.handle_reply_queueing(state_hash, replied_to, source, payload, value)? { - in_block_transitions - .modify_transition(actor_id, |state_hash, transition| { - *state_hash = new_state_hash; - transition.claims.push(value_claim); - }) - .ok_or_else(|| anyhow!("failed to modify state of recognized program"))?; + in_block_transitions.modify(actor_id, |state_hash, transition| { + *state_hash = new_state_hash; + transition.claims.push(value_claim); + }); in_block_transitions.remove_task( expiry, @@ -137,12 +131,10 @@ impl Processor { if let Some((value_claim, expiry, new_state_hash)) = self.handle_value_claiming(state_hash, claimed_id, source)? { - in_block_transitions - .modify_transition(actor_id, |state_hash, transition| { - *state_hash = new_state_hash; - transition.claims.push(value_claim); - }) - .ok_or_else(|| anyhow!("failed to modify state of recognized program"))?; + in_block_transitions.modify(actor_id, |state_hash, transition| { + *state_hash = new_state_hash; + transition.claims.push(value_claim); + }); in_block_transitions.remove_task( expiry, @@ -163,6 +155,7 @@ impl Processor { match event { WVaraEvent::Transfer { from, to, value } => { if let Some(state_hash) = in_block_transitions.state_of(&to) { + // TODO (breathx): FIX ME WITHIN THE PR impl handler similar to runtime common. if in_block_transitions.state_of(&from).is_none() { let new_state_hash = self.db.mutate_state(state_hash, |_, state| { state.balance += value; diff --git a/ethexe/processor/src/handling/mod.rs b/ethexe/processor/src/handling/mod.rs index 46e9cd7927d..51068a586d1 100644 --- a/ethexe/processor/src/handling/mod.rs +++ b/ethexe/processor/src/handling/mod.rs @@ -19,10 +19,7 @@ use crate::Processor; use anyhow::{ensure, Result}; use ethexe_db::CodesStorage; -use ethexe_runtime_common::{ - state::{ComplexStorage as _, Dispatch}, - InBlockTransitions, ScheduleHandler, -}; +use ethexe_runtime_common::{state::Dispatch, InBlockTransitions, ScheduleHandler}; use gprimitives::{CodeId, H256}; pub(crate) mod events; diff --git a/ethexe/processor/src/host/threads.rs b/ethexe/processor/src/host/threads.rs index 97db3d4bf3d..1f2370f01ef 100644 --- a/ethexe/processor/src/host/threads.rs +++ b/ethexe/processor/src/host/threads.rs @@ -22,10 +22,10 @@ use crate::Database; use core::fmt; use ethexe_db::BlockMetaStorage; use ethexe_runtime_common::{ - state::{ActiveProgram, MaybeHash, Program, ProgramState, Storage}, + state::{ActiveProgram, HashOf, Program, ProgramState, Storage}, BlockInfo, }; -use gear_core::{ids::ProgramId, pages::GearPage}; +use gear_core::{ids::ProgramId, memory::PageBuf, pages::GearPage}; use gear_lazy_pages::LazyPagesStorage; use gprimitives::H256; use parity_scale_codec::{Decode, DecodeAll}; @@ -42,7 +42,7 @@ pub struct ThreadParams { pub db: Database, pub block_info: BlockInfo, pub state_hash: H256, - pub pages: Option>, + pub pages: Option>>, } impl fmt::Debug for ThreadParams { @@ -52,7 +52,7 @@ impl fmt::Debug for ThreadParams { } impl ThreadParams { - pub fn pages(&mut self) -> &BTreeMap { + pub fn pages(&mut self) -> &BTreeMap> { self.pages.get_or_insert_with(|| { let ProgramState { program: Program::Active(ActiveProgram { pages_hash, .. }), @@ -63,14 +63,7 @@ impl ThreadParams { panic!("Couldn't get pages hash for inactive program!") }; - if let MaybeHash::Hash(mem_root) = pages_hash { - self.db - .read_pages(mem_root.hash) - .expect(UNKNOWN_STATE) - .into() - } else { - Default::default() - } + pages_hash.query(&self.db).expect(UNKNOWN_STATE).into() }) } } diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index 520fa5847ec..f5caa36870a 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -21,7 +21,7 @@ use ethexe_common::{ mirror::RequestEvent as MirrorEvent, router::RequestEvent as RouterEvent, BlockRequestEvent, }; use ethexe_db::{BlockHeader, BlockMetaStorage, CodesStorage, MemDb, ScheduledTask}; -use ethexe_runtime_common::state::{ComplexStorage, Dispatch, ValueWithExpiry}; +use ethexe_runtime_common::state::{Dispatch, ValueWithExpiry}; use gear_core::{ ids::{prelude::CodeIdExt, ProgramId}, message::DispatchKind, @@ -540,7 +540,7 @@ fn many_waits() { } for (pid, state_hash) in changes { - in_block_transitions.modify_state(pid, state_hash).unwrap(); + in_block_transitions.modify_state(pid, state_hash); } processor.run_schedule(&mut in_block_transitions); @@ -589,7 +589,7 @@ fn many_waits() { value: dispatch, expiry, }, - ) in waitlist.0 + ) in waitlist.into_inner() { assert_eq!(mid, dispatch.id); expected_schedule diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index e895b9c9e43..f07e5a33ef7 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -541,6 +541,10 @@ impl Waitlist { self.changed .then(|| MaybeHashOf((!self.inner.is_empty()).then(|| storage.write_waitlist(self)))) } + + pub fn into_inner(self) -> BTreeMap> { + self.into() + } } #[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] diff --git a/ethexe/runtime/src/wasm/storage.rs b/ethexe/runtime/src/wasm/storage.rs index eb650ceecba..d1233f39383 100644 --- a/ethexe/runtime/src/wasm/storage.rs +++ b/ethexe/runtime/src/wasm/storage.rs @@ -21,8 +21,8 @@ use alloc::{collections::BTreeMap, vec::Vec}; use core_processor::configs::BlockInfo; use ethexe_runtime_common::{ state::{ - Allocations, DispatchStash, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, - Waitlist, + Allocations, DispatchStash, HashOf, Mailbox, MemoryPages, MessageQueue, ProgramState, + Storage, Waitlist, }, RuntimeInterface, }; @@ -34,85 +34,82 @@ use gprimitives::H256; pub struct RuntimeInterfaceStorage; impl Storage for RuntimeInterfaceStorage { - fn read_allocations(&self, hash: H256) -> Option { + fn read_state(&self, hash: H256) -> Option { database_ri::read_unwrapping(&hash) } - fn read_page_data(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) - } + fn write_state(&self, state: ProgramState) -> H256 { + // TODO (breathx): FIX ME WITHIN THE PR + if state.is_zero() { + return H256::zero(); + } - fn read_pages(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) + database_ri::write(state) } - fn read_payload(&self, hash: H256) -> Option { - // TODO: review this. - database_ri::read_raw(&hash).map(|slice| slice.to_vec().try_into().unwrap()) + fn read_queue(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn read_queue(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) + fn write_queue(&self, queue: MessageQueue) -> HashOf { + unsafe { HashOf::new(database_ri::write(queue)) } } - fn read_state(&self, hash: H256) -> Option { - if hash.is_zero() { - return Some(ProgramState::zero()); - } - - database_ri::read_unwrapping(&hash) + fn read_waitlist(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn read_waitlist(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) + fn write_waitlist(&self, waitlist: Waitlist) -> HashOf { + unsafe { HashOf::new(database_ri::write(waitlist)) } } - fn read_stash(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) + fn read_stash(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn read_mailbox(&self, hash: H256) -> Option { - database_ri::read_unwrapping(&hash) + fn write_stash(&self, stash: DispatchStash) -> HashOf { + unsafe { HashOf::new(database_ri::write(stash)) } } - fn write_allocations(&self, allocations: Allocations) -> H256 { - database_ri::write(allocations) + fn read_mailbox(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn write_page_data(&self, data: PageBuf) -> H256 { - database_ri::write(data) + fn write_mailbox(&self, mailbox: Mailbox) -> HashOf { + unsafe { HashOf::new(database_ri::write(mailbox)) } } - fn write_pages(&self, pages: MemoryPages) -> H256 { - database_ri::write(pages) + fn read_pages(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn write_payload(&self, payload: Payload) -> H256 { - database_ri::write(payload) + fn write_pages(&self, pages: MemoryPages) -> HashOf { + unsafe { HashOf::new(database_ri::write(pages)) } } - fn write_queue(&self, queue: MessageQueue) -> H256 { - database_ri::write(queue) + fn read_allocations(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn write_state(&self, state: ProgramState) -> H256 { - if state.is_zero() { - return H256::zero(); - } + fn write_allocations(&self, allocations: Allocations) -> HashOf { + unsafe { HashOf::new(database_ri::write(allocations)) } + } - database_ri::write(state) + fn read_payload(&self, hash: HashOf) -> Option { + // TODO: review this. + database_ri::read_raw(&hash.hash()).map(|slice| slice.to_vec().try_into().unwrap()) } - fn write_waitlist(&self, waitlist: Waitlist) -> H256 { - database_ri::write(waitlist) + fn write_payload(&self, payload: Payload) -> HashOf { + unsafe { HashOf::new(database_ri::write(payload)) } } - fn write_stash(&self, stash: DispatchStash) -> H256 { - database_ri::write(stash) + fn read_page_data(&self, hash: HashOf) -> Option { + database_ri::read_unwrapping(&hash.hash()) } - fn write_mailbox(&self, mailbox: Mailbox) -> H256 { - database_ri::write(mailbox) + fn write_page_data(&self, data: PageBuf) -> HashOf { + unsafe { HashOf::new(database_ri::write(data)) } } } @@ -129,7 +126,7 @@ impl RuntimeInterface for NativeRuntimeInterface { self.block_info } - fn init_lazy_pages(&self, _: BTreeMap) { + fn init_lazy_pages(&self, _: BTreeMap>) { assert!(Self::LazyPages::try_to_enable_lazy_pages(Default::default())) } From eaaffd39890c4dff7d5789895cc3200b3b1ebfb6 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 15:44:01 +0400 Subject: [PATCH 05/18] impl TransitionOperator as unified instance of db and in block transitions --- ethexe/processor/src/handling/run.rs | 8 +-- ethexe/runtime/common/src/journal.rs | 32 ++++++------ ethexe/runtime/common/src/lib.rs | 40 +++++++++------ ethexe/runtime/common/src/schedule.rs | 16 ++---- ethexe/runtime/common/src/state.rs | 64 +++++++++++++++++++++--- ethexe/runtime/common/src/transitions.rs | 2 +- 6 files changed, 106 insertions(+), 56 deletions(-) diff --git a/ethexe/processor/src/handling/run.rs b/ethexe/processor/src/handling/run.rs index 774e30b30c9..031b1019db6 100644 --- a/ethexe/processor/src/handling/run.rs +++ b/ethexe/processor/src/handling/run.rs @@ -19,7 +19,7 @@ use crate::host::{InstanceCreator, InstanceWrapper}; use core_processor::common::JournalNote; use ethexe_db::{CodesStorage, Database}; -use ethexe_runtime_common::{InBlockTransitions, JournalHandler}; +use ethexe_runtime_common::{InBlockTransitions, JournalHandler, TransitionOperator}; use gear_core::ids::ProgramId; use gprimitives::H256; use std::collections::BTreeMap; @@ -94,8 +94,10 @@ async fn run_in_async( for (program_id, journal) in super_journal { let mut handler = JournalHandler { program_id, - in_block_transitions, - storage: &db, + operator: TransitionOperator { + transitions: in_block_transitions, + storage: &db, + }, }; core_processor::handle_journal(journal, &mut handler); } diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index 13e83275604..bb3b8311e84 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -3,7 +3,7 @@ use crate::{ self, ActiveProgram, Dispatch, MaybeHashOf, Program, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY, }, - InBlockTransitions, + InBlockTransitions, TransitionOperator, }; use alloc::{collections::BTreeMap, vec, vec::Vec}; use anyhow::{bail, Result}; @@ -27,21 +27,15 @@ use gear_core::{ use gear_core_errors::SignalCode; use gprimitives::{ActorId, CodeId, MessageId, ReservationId, H256}; +#[derive(derive_more::Deref, derive_more::DerefMut)] pub struct Handler<'a, S: Storage> { pub program_id: ProgramId, - pub in_block_transitions: &'a mut InBlockTransitions, - pub storage: &'a S, + #[deref] + #[deref_mut] + pub operator: TransitionOperator<'a, S>, } impl Handler<'_, S> { - fn update_state( - &mut self, - program_id: ProgramId, - f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, - ) -> T { - crate::update_state(program_id, self.storage, self.in_block_transitions, f) - } - fn send_dispatch_to_program( &mut self, message_id: MessageId, @@ -74,7 +68,7 @@ impl Handler<'_, S> { delay: u32, ) { if dispatch.is_reply() { - self.in_block_transitions + self.transitions .modify_transition(dispatch.source(), |transition| { transition.messages.push(dispatch.into_parts().1.into()) }); @@ -191,7 +185,7 @@ impl JournalHandler for Handler<'_, S> { mem::replace(&mut state.balance, 0) }); - if self.in_block_transitions.is_program(&value_destination) { + if self.transitions.is_program(&value_destination) { self.update_state(value_destination, |state, _, _| { state.balance += balance; }) @@ -199,7 +193,9 @@ impl JournalHandler for Handler<'_, S> { } fn message_consumed(&mut self, message_id: MessageId) { - self.update_state(self.program_id, |state, storage, _| { + let program_id = self.program_id; + + self.update_state(program_id, |state, storage, _| { state.queue_hash.modify_queue(storage, |queue| { let head = queue .dequeue() @@ -227,7 +223,7 @@ impl JournalHandler for Handler<'_, S> { let destination = dispatch.destination(); let dispatch = dispatch.into_stored(); - if self.in_block_transitions.is_program(&destination) { + if self.transitions.is_program(&destination) { let dispatch = Dispatch::from_stored(self.storage, dispatch); self.send_dispatch_to_program(message_id, destination, dispatch, delay); @@ -249,7 +245,9 @@ impl JournalHandler for Handler<'_, S> { let in_blocks = NonZero::::try_from(duration).expect("must be checked on backend side"); - self.update_state(self.program_id, |state, storage, transitions| { + let program_id = self.program_id; + + self.update_state(program_id, |state, storage, transitions| { let expiry = transitions.schedule_task( in_blocks, ScheduledTask::WakeMessage(dispatch.destination(), dispatch.id()), @@ -369,7 +367,7 @@ impl JournalHandler for Handler<'_, S> { fn send_value(&mut self, from: ProgramId, to: Option, value: u128) { // TODO (breathx): implement rest of cases. if let Some(to) = to { - if self.in_block_transitions.state_of(&from).is_some() { + if self.transitions.state_of(&from).is_some() { return; } diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index 6afcb6d0c27..3ec652dbd20 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -76,27 +76,35 @@ pub trait RuntimeInterface { fn storage(&self) -> &S; } -pub(crate) fn update_state( - program_id: ProgramId, - storage: &S, - transitions: &mut InBlockTransitions, - f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, -) -> T { - let state_hash = transitions - .state_of(&program_id) - .expect("failed to find program in known states"); +pub struct TransitionOperator<'a, S: Storage> { + pub storage: &'a S, + pub transitions: &'a mut InBlockTransitions, +} - let mut state = storage - .read_state(state_hash) - .expect("failed to read state from storage"); +impl<'a, S: Storage> TransitionOperator<'a, S> { + pub fn update_state( + &mut self, + program_id: ProgramId, + f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, + ) -> T { + let state_hash = self + .transitions + .state_of(&program_id) + .expect("failed to find program in known states"); - let res = f(&mut state, storage, transitions); + let mut state = self + .storage + .read_state(state_hash) + .expect("failed to read state from storage"); - let new_state_hash = storage.write_state(state); + let res = f(&mut state, self.storage, self.transitions); - transitions.modify_state(program_id, new_state_hash); + let new_state_hash = self.storage.write_state(state); - res + self.transitions.modify_state(program_id, new_state_hash); + + res + } } pub fn process_next_message( diff --git a/ethexe/runtime/common/src/schedule.rs b/ethexe/runtime/common/src/schedule.rs index d213cf93f92..bca73e151dc 100644 --- a/ethexe/runtime/common/src/schedule.rs +++ b/ethexe/runtime/common/src/schedule.rs @@ -1,6 +1,6 @@ use crate::{ state::{Dispatch, MaybeHashOf, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY}, - InBlockTransitions, + InBlockTransitions, TransitionOperator, }; use alloc::vec; use anyhow::{anyhow, Result}; @@ -12,19 +12,9 @@ use gear_core::{ids::ProgramId, message::ReplyMessage, tasks::TaskHandler}; use gear_core_errors::SuccessReplyReason; use gprimitives::{ActorId, CodeId, MessageId, ReservationId, H256}; +#[derive(derive_more::Deref, derive_more::DerefMut)] pub struct Handler<'a, S: Storage> { - pub in_block_transitions: &'a mut InBlockTransitions, - pub storage: &'a S, -} - -impl Handler<'_, S> { - fn update_state( - &mut self, - program_id: ProgramId, - f: impl FnOnce(&mut ProgramState, &S, &mut InBlockTransitions) -> T, - ) -> T { - crate::update_state(program_id, self.storage, self.in_block_transitions, f) - } + pub operator: TransitionOperator<'a, S>, } impl<'a, S: Storage> TaskHandler for Handler<'a, S> { diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index f07e5a33ef7..d60abd13bbf 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -41,7 +41,7 @@ use gear_core::{ program::MemoryInfix, reservation::GasReservationMap, }; -use gear_core_errors::ReplyCode; +use gear_core_errors::{ReplyCode, SuccessReplyReason}; use gprimitives::{ActorId, CodeId, MessageId, H256}; use gsys::BlockNumber; use parity_scale_codec::{Decode, Encode}; @@ -411,6 +411,51 @@ pub struct Dispatch { } impl Dispatch { + pub fn new( + storage: &S, + id: MessageId, + source: ActorId, + payload: Vec, + value: u128, + is_init: bool, + ) -> Result { + let payload_hash = storage.write_payload_raw(payload)?; + + let kind = if is_init { + DispatchKind::Init + } else { + DispatchKind::Handle + }; + + Ok(Self { + id, + kind, + source, + payload_hash, + value, + details: None, + context: None, + }) + } + + pub fn new_reply( + storage: &S, + replied_to: MessageId, + source: ActorId, + payload: Vec, + value: u128, + ) -> Result { + let payload_hash = storage.write_payload_raw(payload)?; + + Ok(Self::reply( + replied_to, + source, + payload_hash, + value, + SuccessReplyReason::Manual, + )) + } + pub fn reply( reply_to: MessageId, source: ActorId, @@ -433,11 +478,9 @@ impl Dispatch { let (kind, message, context) = value.into_parts(); let (id, source, destination, payload, value, details) = message.into_parts(); - // TODO (breathx): FIX ME WITHIN THE PR - let payload_hash = MaybeHashOf::empty(); - // let payload_hash = storage - // .store_payload(payload.into_vec()) - // .expect("infallible due to recasts (only panics on len)"); + let payload_hash = storage + .write_payload_raw(payload.into_vec()) + .expect("infallible due to recasts (only panics on len)"); Self { id, @@ -771,6 +814,15 @@ pub trait Storage { /// Writes payload and returns its hash. fn write_payload(&self, payload: Payload) -> HashOf; + /// Writes payload if it doesnt exceed limits. + fn write_payload_raw(&self, payload: Vec) -> Result> { + Payload::try_from(payload) + .map(|payload| { + MaybeHashOf((!payload.inner().is_empty()).then(|| self.write_payload(payload))) + }) + .map_err(|_| anyhow!("payload exceeds size limit")) + } + /// Reads page data by page data hash. fn read_page_data(&self, hash: HashOf) -> Option; diff --git a/ethexe/runtime/common/src/transitions.rs b/ethexe/runtime/common/src/transitions.rs index 78f5f9242e2..ad07a8994f5 100644 --- a/ethexe/runtime/common/src/transitions.rs +++ b/ethexe/runtime/common/src/transitions.rs @@ -52,7 +52,7 @@ impl InBlockTransitions { } pub fn is_program(&self, actor_id: &ActorId) -> bool { - self.state_of(actor_id).is_some() + self.states.contains_key(actor_id) } pub fn state_of(&self, actor_id: &ActorId) -> Option { From 9d226ea297872df11b7eaf0e2b719289beadd142 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 15:44:26 +0400 Subject: [PATCH 06/18] update processor with operator --- ethexe/processor/src/handling/events.rs | 300 ++++++++---------------- ethexe/processor/src/handling/mod.rs | 101 +++++--- ethexe/processor/src/lib.rs | 58 ++--- ethexe/processor/src/tests.rs | 2 +- 4 files changed, 172 insertions(+), 289 deletions(-) diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index 7b6307b8d6d..24380f784b2 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -16,34 +16,34 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::Processor; -use anyhow::{anyhow, ensure, Result}; +use super::ProcessingHandler; +use anyhow::{ensure, Result}; use ethexe_common::{ mirror::RequestEvent as MirrorEvent, router::{RequestEvent as RouterEvent, ValueClaim}, wvara::RequestEvent as WVaraEvent, }; use ethexe_db::{CodesStorage, ScheduledTask}; -use ethexe_runtime_common::{ - state::{Dispatch, Storage, ValueWithExpiry}, - InBlockTransitions, -}; -use gear_core::{ - ids::ProgramId, - message::{DispatchKind, SuccessReplyReason}, -}; -use gprimitives::{ActorId, CodeId, MessageId, H256}; +use ethexe_runtime_common::state::{Dispatch, MaybeHashOf, ValueWithExpiry}; +use gear_core::{ids::ProgramId, message::SuccessReplyReason}; -impl Processor { - pub(crate) fn handle_router_event( - &mut self, - in_block_transitions: &mut InBlockTransitions, - event: RouterEvent, - ) -> Result<()> { +impl ProcessingHandler { + pub(crate) fn handle_router_event(&mut self, event: RouterEvent) -> Result<()> { match event { RouterEvent::ProgramCreated { actor_id, code_id } => { - self.handle_new_program(actor_id, code_id)?; - in_block_transitions.register_new(actor_id); + ensure!( + self.db.original_code(code_id).is_some(), + "code existence must be checked on router" + ); + + ensure!( + self.db.program_code_id(actor_id).is_none(), + "program duplicates must be checked on router" + ); + + self.db.set_program_code_id(actor_id, code_id); + + self.transitions.register_new(actor_id); } RouterEvent::CodeValidationRequested { .. } | RouterEvent::BaseWeightChanged { .. } @@ -51,7 +51,6 @@ impl Processor { | RouterEvent::ValidatorsSetChanged | RouterEvent::ValuePerWeightChanged { .. } => { log::debug!("Handler not yet implemented: {event:?}"); - return Ok(()); } }; @@ -60,20 +59,20 @@ impl Processor { pub(crate) fn handle_mirror_event( &mut self, - in_block_transitions: &mut InBlockTransitions, actor_id: ProgramId, event: MirrorEvent, ) -> Result<()> { - let Some(state_hash) = in_block_transitions.state_of(&actor_id) else { + if !self.transitions.is_program(&actor_id) { log::debug!("Received event from unrecognized mirror ({actor_id}): {event:?}"); return Ok(()); - }; + } match event { MirrorEvent::ExecutableBalanceTopUpRequested { value } => { - let new_state_hash = self.handle_executable_balance_top_up(state_hash, value)?; - in_block_transitions.modify_state(actor_id, new_state_hash); + self.update_state(actor_id, |state, _, _| { + state.executable_balance += value; + }); } MirrorEvent::MessageQueueingRequested { id, @@ -81,31 +80,17 @@ impl Processor { payload, value, } => { - let payload_hash = self.db.store_payload(payload)?; + self.update_state(actor_id, |state, storage, _| -> Result<()> { + let is_init = state.requires_init_message(); - let state = self - .db - .read_state(state_hash) - .ok_or_else(|| anyhow!("program should exist"))?; + let dispatch = Dispatch::new(storage, id, source, payload, value, is_init)?; - let kind = if state.requires_init_message() { - DispatchKind::Init - } else { - DispatchKind::Handle - }; + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(dispatch)); - let dispatch = Dispatch { - id, - kind, - source, - payload_hash, - value, - details: None, - context: None, - }; - - let new_state_hash = self.handle_message_queueing(state_hash, dispatch)?; - in_block_transitions.modify_state(actor_id, new_state_hash); + Ok(()) + })?; } MirrorEvent::ReplyQueueingRequested { replied_to, @@ -113,181 +98,84 @@ impl Processor { payload, value, } => { - if let Some((value_claim, expiry, new_state_hash)) = - self.handle_reply_queueing(state_hash, replied_to, source, payload, value)? - { - in_block_transitions.modify(actor_id, |state_hash, transition| { - *state_hash = new_state_hash; - transition.claims.push(value_claim); + self.update_state(actor_id, |state, storage, transitions| -> Result<()> { + let Some(ValueWithExpiry { + value: claimed_value, + expiry, + }) = state + .mailbox_hash + .modify_mailbox(storage, |mailbox| mailbox.remove(source, replied_to)) + else { + return Ok(()); + }; + + transitions.modify_transition(actor_id, |transition| { + transition.claims.push(ValueClaim { + message_id: replied_to, + destination: source, + value: claimed_value, + }); }); - in_block_transitions.remove_task( + transitions.remove_task( expiry, &ScheduledTask::RemoveFromMailbox((actor_id, source), replied_to), - )?; - } + ); + + let reply = Dispatch::new_reply(storage, replied_to, source, payload, value)?; + + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(reply)); + + Ok(()) + })?; } MirrorEvent::ValueClaimingRequested { claimed_id, source } => { - if let Some((value_claim, expiry, new_state_hash)) = - self.handle_value_claiming(state_hash, claimed_id, source)? - { - in_block_transitions.modify(actor_id, |state_hash, transition| { - *state_hash = new_state_hash; - transition.claims.push(value_claim); - }); - - in_block_transitions.remove_task( - expiry, - &ScheduledTask::RemoveFromMailbox((actor_id, source), claimed_id), - )?; - } + self.update_state(actor_id, |state, storage, transitions| { + if let Some(ValueWithExpiry { value, expiry }) = state + .mailbox_hash + .modify_mailbox(storage, |mailbox| mailbox.remove(source, claimed_id)) + { + transitions.modify_transition(actor_id, |transition| { + transition.claims.push(ValueClaim { + message_id: claimed_id, + destination: source, + value, + }); + }); + + transitions.remove_task( + expiry, + &ScheduledTask::RemoveFromMailbox((actor_id, source), claimed_id), + ); + + let reply = Dispatch::reply( + claimed_id, + source, + MaybeHashOf::empty(), + 0, + SuccessReplyReason::Auto, + ); + + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(reply)); + } + }); } }; Ok(()) } - pub(crate) fn handle_wvara_event( - &mut self, - in_block_transitions: &mut InBlockTransitions, - event: WVaraEvent, - ) -> Result<()> { + pub(crate) fn handle_wvara_event(&mut self, event: WVaraEvent) { match event { WVaraEvent::Transfer { from, to, value } => { - if let Some(state_hash) = in_block_transitions.state_of(&to) { - // TODO (breathx): FIX ME WITHIN THE PR impl handler similar to runtime common. - if in_block_transitions.state_of(&from).is_none() { - let new_state_hash = self.db.mutate_state(state_hash, |_, state| { - state.balance += value; - Ok(()) - })?; - - in_block_transitions - .modify_state(to, new_state_hash) - .expect("queried above so infallible here"); - } + if self.transitions.is_program(&to) && !self.transitions.is_program(&from) { + self.update_state(to, |state, _, _| state.balance += value); } - - Ok(()) } } } - - pub fn handle_executable_balance_top_up( - &mut self, - state_hash: H256, - value: u128, - ) -> Result { - self.db.mutate_state(state_hash, |_, state| { - state.executable_balance += value; - Ok(()) - }) - } - - pub(crate) fn handle_reply_queueing( - &mut self, - state_hash: H256, - mailboxed_id: MessageId, - user_id: ActorId, - payload: Vec, - value: u128, - ) -> Result> { - self.handle_mailboxed_message_impl( - state_hash, - mailboxed_id, - user_id, - payload, - value, - SuccessReplyReason::Manual, - ) - } - - pub(crate) fn handle_value_claiming( - &mut self, - state_hash: H256, - mailboxed_id: MessageId, - user_id: ActorId, - ) -> Result> { - self.handle_mailboxed_message_impl( - state_hash, - mailboxed_id, - user_id, - vec![], - 0, - SuccessReplyReason::Auto, - ) - } - - pub(crate) fn handle_mailboxed_message_impl( - &mut self, - state_hash: H256, - mailboxed_id: MessageId, - user_id: ActorId, - payload: Vec, - value: u128, - reply_reason: SuccessReplyReason, - ) -> Result> { - self.db - .mutate_state_returning(state_hash, |db, state| { - let Some(( - ValueWithExpiry { - value: claimed_value, - expiry, - }, - mailbox_hash, - )) = db.modify_mailbox_if_changed(state.mailbox_hash.clone(), |mailbox| { - let local_mailbox = mailbox.get_mut(&user_id)?; - let claimed_value = local_mailbox.remove(&mailboxed_id)?; - - if local_mailbox.is_empty() { - mailbox.remove(&user_id); - } - - Some(claimed_value) - })? - else { - return Ok(None); - }; - - state.mailbox_hash = mailbox_hash; - - let payload_hash = db.store_payload(payload)?; - let reply = - Dispatch::reply(mailboxed_id, user_id, payload_hash, value, reply_reason); - - state.queue_hash = - db.modify_queue(state.queue_hash.clone(), |queue| queue.push_back(reply))?; - - Ok(Some(( - ValueClaim { - message_id: mailboxed_id, - destination: user_id, - value: claimed_value, - }, - expiry, - ))) - }) - .map(|(claim_with_expiry, hash)| { - if claim_with_expiry.is_none() { - debug_assert_eq!(hash, state_hash); - } - claim_with_expiry.map(|(claim, expiry)| (claim, expiry, hash)) - }) - } - - pub fn handle_new_program(&mut self, program_id: ProgramId, code_id: CodeId) -> Result<()> { - ensure!( - self.db.original_code(code_id).is_some(), - "code existence must be checked on router" - ); - - ensure!( - self.db.program_code_id(program_id).is_none(), - "program duplicates must be checked on router" - ); - - self.db.set_program_code_id(program_id, code_id); - - Ok(()) - } } diff --git a/ethexe/processor/src/handling/mod.rs b/ethexe/processor/src/handling/mod.rs index 51068a586d1..6e2a888e064 100644 --- a/ethexe/processor/src/handling/mod.rs +++ b/ethexe/processor/src/handling/mod.rs @@ -17,57 +17,61 @@ // along with this program. If not, see . use crate::Processor; -use anyhow::{ensure, Result}; -use ethexe_db::CodesStorage; -use ethexe_runtime_common::{state::Dispatch, InBlockTransitions, ScheduleHandler}; -use gprimitives::{CodeId, H256}; +use anyhow::{anyhow, Result}; +use ethexe_db::{BlockMetaStorage, CodesStorage, Database}; +use ethexe_runtime_common::{ + state::ProgramState, InBlockTransitions, ScheduleHandler, TransitionOperator, +}; +use gprimitives::{ActorId, CodeId, H256}; pub(crate) mod events; pub(crate) mod run; -impl Processor { - pub fn run_schedule(&mut self, in_block_transitions: &mut InBlockTransitions) { - let tasks = in_block_transitions.take_actual_tasks(); - - log::debug!( - "Running schedule for #{}: tasks are {tasks:?}", - in_block_transitions.header().height - ); +pub struct ProcessingHandler { + pub db: Database, + pub transitions: InBlockTransitions, +} - let mut handler = ScheduleHandler { - in_block_transitions, +impl ProcessingHandler { + pub fn operator(&mut self) -> TransitionOperator<'_, Database> { + TransitionOperator { storage: &self.db, - }; - - for task in tasks { - let _gas = task.process_with(&mut handler); + transitions: &mut self.transitions, } } - pub(crate) fn handle_message_queueing( + pub fn update_state( &mut self, - state_hash: H256, - dispatch: Dispatch, - ) -> Result { - self.handle_messages_queueing(state_hash, vec![dispatch]) + program_id: ActorId, + f: impl FnOnce(&mut ProgramState, &Database, &mut InBlockTransitions) -> T, + ) -> T { + self.operator().update_state(program_id, f) } +} - pub(crate) fn handle_messages_queueing( - &mut self, - state_hash: H256, - dispatches: Vec, - ) -> Result { - if dispatches.is_empty() { - return Ok(state_hash); - } - - self.db.mutate_state(state_hash, |processor, state| { - ensure!(state.program.is_active(), "program should be active"); - - state.queue_hash = processor - .modify_queue(state.queue_hash.clone(), |queue| queue.extend(dispatches))?; - - Ok(()) +impl Processor { + pub fn handler(&self, block_hash: H256) -> Result { + let header = self + .db + .block_header(block_hash) + .ok_or_else(|| anyhow!("failed to get block header for under-processing block"))?; + + let states = self + .db + .block_start_program_states(block_hash) + .ok_or_else(|| { + anyhow!("failed to get block start program states for under-processing block") + })?; + + let schedule = self.db.block_start_schedule(block_hash).ok_or_else(|| { + anyhow!("failed to get block start schedule for under-processing block") + })?; + + let transitions = InBlockTransitions::new(header, states, schedule); + + Ok(ProcessingHandler { + db: self.db.clone(), + transitions, }) } @@ -95,3 +99,22 @@ impl Processor { Ok(Some(code_id)) } } + +impl ProcessingHandler { + pub fn run_schedule(&mut self) { + let tasks = self.transitions.take_actual_tasks(); + + log::debug!( + "Running schedule for #{}: tasks are {tasks:?}", + self.transitions.header().height + ); + + let mut handler = ScheduleHandler { + operator: self.operator(), + }; + + for task in tasks { + let _gas = task.process_with(&mut handler); + } + } +} diff --git a/ethexe/processor/src/lib.rs b/ethexe/processor/src/lib.rs index dcc94933e5a..29660d78652 100644 --- a/ethexe/processor/src/lib.rs +++ b/ethexe/processor/src/lib.rs @@ -77,42 +77,26 @@ impl Processor { ) -> Result> { log::debug!("Processing events for {block_hash:?}: {events:#?}"); - let header = self - .db - .block_header(block_hash) - .ok_or_else(|| anyhow!("failed to get block header for under-processing block"))?; - - let states = self - .db - .block_start_program_states(block_hash) - .ok_or_else(|| { - anyhow!("failed to get block start program states for under-processing block") - })?; - - let schedule = self.db.block_start_schedule(block_hash).ok_or_else(|| { - anyhow!("failed to get block start schedule for under-processing block") - })?; - - let mut in_block_transitions = InBlockTransitions::new(header, states, schedule); + let mut handler = self.handler(block_hash)?; for event in events { match event { BlockRequestEvent::Router(event) => { - self.handle_router_event(&mut in_block_transitions, event)?; + handler.handle_router_event(event)?; } BlockRequestEvent::Mirror { address, event } => { - self.handle_mirror_event(&mut in_block_transitions, address, event)?; + handler.handle_mirror_event(address, event)?; } BlockRequestEvent::WVara(event) => { - self.handle_wvara_event(&mut in_block_transitions, event)?; + handler.handle_wvara_event(event); } } } - self.run_schedule(&mut in_block_transitions); - self.run(block_hash, &mut in_block_transitions); + handler.run_schedule(); + self.run(block_hash, &mut handler.transitions); - let (transitions, states, schedule) = in_block_transitions.finalize(); + let (transitions, states, schedule) = handler.transitions.finalize(); self.db.set_block_end_program_states(block_hash, states); self.db.set_block_end_schedule(block_hash, schedule); @@ -152,26 +136,14 @@ impl OverlaidProcessor { ) -> Result { self.0.creator.set_chain_head(block_hash); - let header = self - .0 - .db - .block_header(block_hash) - .ok_or_else(|| anyhow!("failed to find block header for given block hash"))?; - - let states = self - .0 - .db - .block_start_program_states(block_hash) - .unwrap_or_default(); - - let mut in_block_transitions = InBlockTransitions::new(header, states, Default::default()); + let mut handler = self.0.handler(block_hash)?; - let state_hash = in_block_transitions + let state_hash = handler + .transitions .state_of(&program_id) .ok_or_else(|| anyhow!("unknown program at specified block hash"))?; - let state = self - .0 + let state = handler .db .read_state(state_hash) .ok_or_else(|| anyhow!("unreachable: state partially presents in storage"))?; @@ -181,8 +153,7 @@ impl OverlaidProcessor { "program isn't yet initialized" ); - self.0.handle_mirror_event( - &mut in_block_transitions, + handler.handle_mirror_event( program_id, MirrorEvent::MessageQueueingRequested { id: MessageId::zero(), @@ -196,10 +167,11 @@ impl OverlaidProcessor { 8, self.0.db.clone(), self.0.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); - let res = in_block_transitions + let res = handler + .transitions .current_messages() .into_iter() .find_map(|(_source, message)| { diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index f5caa36870a..ed38d75bb1f 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -330,7 +330,7 @@ fn create_message_full( payload: impl AsRef<[u8]>, ) -> Dispatch { let payload = payload.as_ref().to_vec(); - let payload_hash = processor.db.store_payload(payload).unwrap(); + let payload_hash = processor.db.write_payload_raw(payload).unwrap(); Dispatch { id, From 8e2695266262ba26b612f27fda882499aa4e2568 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 16:59:52 +0400 Subject: [PATCH 07/18] fix tests --- ethexe/cli/src/tests.rs | 22 +- ethexe/processor/src/handling/events.rs | 59 ++--- ethexe/processor/src/tests.rs | 305 +++++++++++------------ ethexe/runtime/common/src/state.rs | 4 + ethexe/runtime/common/src/transitions.rs | 8 +- 5 files changed, 203 insertions(+), 195 deletions(-) diff --git a/ethexe/cli/src/tests.rs b/ethexe/cli/src/tests.rs index 02537ee414b..84bd615d278 100644 --- a/ethexe/cli/src/tests.rs +++ b/ethexe/cli/src/tests.rs @@ -32,7 +32,7 @@ use ethexe_db::{BlockMetaStorage, Database, MemDb, ScheduledTask}; use ethexe_ethereum::{router::RouterQuery, Ethereum}; use ethexe_observer::{Event, MockBlobReader, Observer, Query}; use ethexe_processor::Processor; -use ethexe_runtime_common::state::{Mailbox, Storage}; +use ethexe_runtime_common::state::{Storage, ValueWithExpiry}; use ethexe_sequencer::Sequencer; use ethexe_signer::Signer; use ethexe_validator::Validator; @@ -247,14 +247,13 @@ async fn mailbox() { assert_eq!(schedule, expected_schedule); - let expected_mailbox: Mailbox = BTreeMap::from_iter([( + let expected_mailbox = BTreeMap::from_iter([( env.sender_id, BTreeMap::from_iter([ - (mid_expected_message, (0u128, expiry).into()), - (ping_expected_message, (0, expiry).into()), + (mid_expected_message, ValueWithExpiry { value: 0, expiry }), + (ping_expected_message, ValueWithExpiry { value: 0, expiry }), ]), - )]) - .into(); + )]); let mirror = env.ethereum.mirror(pid.try_into().unwrap()); let state_hash = mirror.query().state_hash().await.unwrap(); @@ -265,7 +264,7 @@ async fn mailbox() { .mailbox_hash .with_hash_or_default(|hash| node.db.read_mailbox(hash).unwrap()); - assert_eq!(mailbox, expected_mailbox); + assert_eq!(mailbox.into_inner(), expected_mailbox); mirror .send_reply(ping_expected_message, "PONG", 0) @@ -288,13 +287,12 @@ async fn mailbox() { .mailbox_hash .with_hash_or_default(|hash| node.db.read_mailbox(hash).unwrap()); - let expected_mailbox: Mailbox = BTreeMap::from_iter([( + let expected_mailbox = BTreeMap::from_iter([( env.sender_id, - BTreeMap::from_iter([(mid_expected_message, (0u128, expiry).into())]), - )]) - .into(); + BTreeMap::from_iter([(mid_expected_message, ValueWithExpiry { value: 0, expiry })]), + )]); - assert_eq!(mailbox, expected_mailbox); + assert_eq!(mailbox.into_inner(), expected_mailbox); mirror.claim_value(mid_expected_message).await.unwrap(); diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index 24380f784b2..e52432dfb43 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -120,7 +120,7 @@ impl ProcessingHandler { transitions.remove_task( expiry, &ScheduledTask::RemoveFromMailbox((actor_id, source), replied_to), - ); + )?; let reply = Dispatch::new_reply(storage, replied_to, source, payload, value)?; @@ -132,37 +132,40 @@ impl ProcessingHandler { })?; } MirrorEvent::ValueClaimingRequested { claimed_id, source } => { - self.update_state(actor_id, |state, storage, transitions| { - if let Some(ValueWithExpiry { value, expiry }) = state + self.update_state(actor_id, |state, storage, transitions| -> Result<()> { + let Some(ValueWithExpiry { value, expiry }) = state .mailbox_hash .modify_mailbox(storage, |mailbox| mailbox.remove(source, claimed_id)) - { - transitions.modify_transition(actor_id, |transition| { - transition.claims.push(ValueClaim { - message_id: claimed_id, - destination: source, - value, - }); + else { + return Ok(()); + }; + transitions.modify_transition(actor_id, |transition| { + transition.claims.push(ValueClaim { + message_id: claimed_id, + destination: source, + value, }); + }); - transitions.remove_task( - expiry, - &ScheduledTask::RemoveFromMailbox((actor_id, source), claimed_id), - ); - - let reply = Dispatch::reply( - claimed_id, - source, - MaybeHashOf::empty(), - 0, - SuccessReplyReason::Auto, - ); - - state - .queue_hash - .modify_queue(storage, |queue| queue.queue(reply)); - } - }); + transitions.remove_task( + expiry, + &ScheduledTask::RemoveFromMailbox((actor_id, source), claimed_id), + )?; + + let reply = Dispatch::reply( + claimed_id, + source, + MaybeHashOf::empty(), + 0, + SuccessReplyReason::Auto, + ); + + state + .queue_hash + .modify_queue(storage, |queue| queue.queue(reply)); + + Ok(()) + })?; } }; diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index ed38d75bb1f..2ba8775c095 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -21,11 +21,8 @@ use ethexe_common::{ mirror::RequestEvent as MirrorEvent, router::RequestEvent as RouterEvent, BlockRequestEvent, }; use ethexe_db::{BlockHeader, BlockMetaStorage, CodesStorage, MemDb, ScheduledTask}; -use ethexe_runtime_common::state::{Dispatch, ValueWithExpiry}; -use gear_core::{ - ids::{prelude::CodeIdExt, ProgramId}, - message::DispatchKind, -}; +use ethexe_runtime_common::state::ValueWithExpiry; +use gear_core::ids::{prelude::CodeIdExt, ProgramId}; use gprimitives::{ActorId, MessageId}; use parity_scale_codec::Encode; use std::collections::{BTreeMap, BTreeSet}; @@ -245,56 +242,63 @@ fn ping_pong() { let db = MemDb::default(); let mut processor = Processor::new(Database::from_one(&db, Default::default())).unwrap(); - init_new_block(&mut processor, Default::default()); + let ch0 = init_new_block(&mut processor, Default::default()); let user_id = ActorId::from(10); - let program_id = ProgramId::from(0x10000); + let actor_id = ProgramId::from(0x10000); let code_id = processor .handle_new_code(demo_ping::WASM_BINARY) .expect("failed to call runtime api") .expect("code failed verification or instrumentation"); - processor - .handle_new_program(program_id, code_id) + let mut handler = processor.handler(ch0).unwrap(); + + handler + .handle_router_event(RouterEvent::ProgramCreated { actor_id, code_id }) .expect("failed to create new program"); - let state_hash = processor - .handle_executable_balance_top_up(H256::zero(), 10_000_000_000) + handler + .handle_mirror_event( + actor_id, + MirrorEvent::ExecutableBalanceTopUpRequested { + value: 10_000_000_000, + }, + ) .expect("failed to top up balance"); - let messages = vec![ - create_message_full( - &mut processor, - MessageId::from(1), - DispatchKind::Init, - user_id, - "PING", - ), - create_message_full( - &mut processor, - MessageId::from(2), - DispatchKind::Handle, - user_id, - "PING", - ), - ]; - let state_hash = processor - .handle_messages_queueing(state_hash, messages) - .expect("failed to populate message queue"); + handler + .handle_mirror_event( + actor_id, + MirrorEvent::MessageQueueingRequested { + id: MessageId::from(1), + source: user_id, + payload: b"PING".to_vec(), + value: 0, + }, + ) + .expect("failed to send message"); - let states = BTreeMap::from_iter([(program_id, state_hash)]); - let mut in_block_transitions = - InBlockTransitions::new(BlockHeader::dummy(0), states, Default::default()); + handler + .handle_mirror_event( + actor_id, + MirrorEvent::MessageQueueingRequested { + id: MessageId::from(2), + source: user_id, + payload: b"PING".to_vec(), + value: 0, + }, + ) + .expect("failed to send message"); run::run( 8, processor.db.clone(), processor.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); - let to_users = in_block_transitions.current_messages(); + let to_users = handler.transitions.current_messages(); assert_eq!(to_users.len(), 2); @@ -307,42 +311,6 @@ fn ping_pong() { assert_eq!(message.payload, b"PONG"); } -#[allow(unused)] -fn create_message( - processor: &mut Processor, - kind: DispatchKind, - payload: impl AsRef<[u8]>, -) -> Dispatch { - create_message_full( - processor, - H256::random().into(), - kind, - H256::random().into(), - payload, - ) -} - -fn create_message_full( - processor: &mut Processor, - id: MessageId, - kind: DispatchKind, - source: ActorId, - payload: impl AsRef<[u8]>, -) -> Dispatch { - let payload = payload.as_ref().to_vec(); - let payload_hash = processor.db.write_payload_raw(payload).unwrap(); - - Dispatch { - id, - kind, - source, - payload_hash, - value: 0, - details: None, - context: None, - } -} - #[test] fn async_and_ping() { init_logger(); @@ -357,7 +325,7 @@ fn async_and_ping() { let db = MemDb::default(); let mut processor = Processor::new(Database::from_one(&db, Default::default())).unwrap(); - init_new_block(&mut processor, Default::default()); + let ch0 = init_new_block(&mut processor, Default::default()); let ping_id = ProgramId::from(0x10000000); let async_id = ProgramId::from(0x20000000); @@ -372,64 +340,86 @@ fn async_and_ping() { .expect("failed to call runtime api") .expect("code failed verification or instrumentation"); - processor - .handle_new_program(ping_id, ping_code_id) + let mut handler = processor.handler(ch0).unwrap(); + + handler + .handle_router_event(RouterEvent::ProgramCreated { + actor_id: ping_id, + code_id: ping_code_id, + }) .expect("failed to create new program"); - let state_hash = processor - .handle_executable_balance_top_up(H256::zero(), 10_000_000_000) + handler + .handle_mirror_event( + ping_id, + MirrorEvent::ExecutableBalanceTopUpRequested { + value: 10_000_000_000, + }, + ) .expect("failed to top up balance"); - let message = create_message_full( - &mut processor, - get_next_message_id(), - DispatchKind::Init, - user_id, - "PING", - ); - let ping_state_hash = processor - .handle_message_queueing(state_hash, message) - .expect("failed to populate message queue"); + handler + .handle_mirror_event( + ping_id, + MirrorEvent::MessageQueueingRequested { + id: get_next_message_id(), + source: user_id, + payload: b"PING".to_vec(), + value: 0, + }, + ) + .expect("failed to send message"); - processor - .handle_new_program(async_id, upload_code_id) + handler + .handle_router_event(RouterEvent::ProgramCreated { + actor_id: async_id, + code_id: upload_code_id, + }) .expect("failed to create new program"); - let message = create_message_full( - &mut processor, - get_next_message_id(), - DispatchKind::Init, - user_id, - ping_id.encode(), - ); - let async_state_hash = processor - .handle_message_queueing(state_hash, message) - .expect("failed to populate message queue"); + handler + .handle_mirror_event( + async_id, + MirrorEvent::ExecutableBalanceTopUpRequested { + value: 10_000_000_000, + }, + ) + .expect("failed to top up balance"); + + handler + .handle_mirror_event( + async_id, + MirrorEvent::MessageQueueingRequested { + id: get_next_message_id(), + source: user_id, + payload: ping_id.encode(), + value: 0, + }, + ) + .expect("failed to send message"); let wait_for_reply_to = get_next_message_id(); - let message = create_message_full( - &mut processor, - wait_for_reply_to, - DispatchKind::Handle, - user_id, - demo_async::Command::Common.encode(), - ); - let async_state_hash = processor - .handle_message_queueing(async_state_hash, message) - .expect("failed to populate message queue"); - let states = BTreeMap::from_iter([(ping_id, ping_state_hash), (async_id, async_state_hash)]); - let mut in_block_transitions = - InBlockTransitions::new(BlockHeader::dummy(0), states, Default::default()); + handler + .handle_mirror_event( + async_id, + MirrorEvent::MessageQueueingRequested { + id: wait_for_reply_to, + source: user_id, + payload: demo_async::Command::Common.encode(), + value: 0, + }, + ) + .expect("failed to send message"); run::run( 8, processor.db.clone(), processor.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); - let to_users = in_block_transitions.current_messages(); + let to_users = handler.transitions.current_messages(); assert_eq!(to_users.len(), 3); @@ -493,75 +483,85 @@ fn many_waits() { .expect("failed to call runtime api") .expect("code failed verification or instrumentation"); + let mut handler = processor.handler(ch0).unwrap(); + let amount = 10000; - let mut states = BTreeMap::new(); for i in 0..amount { let program_id = ProgramId::from(i); - processor - .handle_new_program(program_id, code_id) + handler + .handle_router_event(RouterEvent::ProgramCreated { + actor_id: program_id, + code_id, + }) .expect("failed to create new program"); - let state_hash = processor - .handle_executable_balance_top_up(H256::zero(), 10_000_000_000) + handler + .handle_mirror_event( + program_id, + MirrorEvent::ExecutableBalanceTopUpRequested { + value: 10_000_000_000, + }, + ) .expect("failed to top up balance"); - let message = create_message(&mut processor, DispatchKind::Init, b""); - let state_hash = processor - .handle_message_queueing(state_hash, message) - .expect("failed to populate message queue"); - - states.insert(program_id, state_hash); + handler + .handle_mirror_event( + program_id, + MirrorEvent::MessageQueueingRequested { + id: H256::random().0.into(), + source: H256::random().0.into(), + payload: Default::default(), + value: 0, + }, + ) + .expect("failed to send message"); } - let mut in_block_transitions = - InBlockTransitions::new(BlockHeader::dummy(1), states, Default::default()); - - processor.run_schedule(&mut in_block_transitions); + handler.run_schedule(); run::run( threads_amount, processor.db.clone(), processor.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); assert_eq!( - in_block_transitions.current_messages().len(), + handler.transitions.current_messages().len(), amount as usize ); - let mut changes = BTreeMap::new(); - - for (pid, state_hash) in in_block_transitions.states_iter().clone() { - let message = create_message(&mut processor, DispatchKind::Handle, b""); - let new_state_hash = processor - .handle_message_queueing(*state_hash, message) - .expect("failed to populate message queue"); - changes.insert(*pid, new_state_hash); - } - - for (pid, state_hash) in changes { - in_block_transitions.modify_state(pid, state_hash); + for pid in handler.transitions.known_programs() { + handler + .handle_mirror_event( + pid, + MirrorEvent::MessageQueueingRequested { + id: H256::random().0.into(), + source: H256::random().0.into(), + payload: Default::default(), + value: 0, + }, + ) + .expect("failed to send message"); } - processor.run_schedule(&mut in_block_transitions); run::run( threads_amount, processor.db.clone(), processor.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); // unchanged assert_eq!( - in_block_transitions.current_messages().len(), + handler.transitions.current_messages().len(), amount as usize ); - let (_outcomes, states, schedule) = in_block_transitions.finalize(); + let (_outcomes, states, schedule) = handler.transitions.finalize(); processor.db.set_block_end_program_states(ch0, states); processor.db.set_block_end_schedule(ch0, schedule); let mut parent = ch0; - for _ in 0..10 { + for _ in 0..9 { parent = init_new_block_from_parent(&mut processor, parent); let states = processor.db.block_start_program_states(parent).unwrap(); processor.db.set_block_end_program_states(parent, states); @@ -603,23 +603,22 @@ fn many_waits() { assert_eq!(schedule, expected_schedule); } - let mut in_block_transitions = - InBlockTransitions::new(BlockHeader::dummy(11), states, schedule); + let mut handler = processor.handler(ch11).unwrap(); - processor.run_schedule(&mut in_block_transitions); + handler.run_schedule(); run::run( threads_amount, processor.db.clone(), processor.creator.clone(), - &mut in_block_transitions, + &mut handler.transitions, ); assert_eq!( - in_block_transitions.current_messages().len(), + handler.transitions.current_messages().len(), amount as usize ); - for (_pid, message) in in_block_transitions.current_messages() { + for (_pid, message) in handler.transitions.current_messages() { assert_eq!(message.payload, b"Hello, world!"); } } diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index d60abd13bbf..b8911ae1e3f 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -700,6 +700,10 @@ impl Mailbox { self.changed .then(|| MaybeHashOf((!self.inner.is_empty()).then(|| storage.write_mailbox(self)))) } + + pub fn into_inner(self) -> BTreeMap>> { + self.into() + } } #[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] diff --git a/ethexe/runtime/common/src/transitions.rs b/ethexe/runtime/common/src/transitions.rs index ad07a8994f5..9673781fa9f 100644 --- a/ethexe/runtime/common/src/transitions.rs +++ b/ethexe/runtime/common/src/transitions.rs @@ -29,7 +29,7 @@ use ethexe_common::{ use gprimitives::{ActorId, CodeId, H256}; use parity_scale_codec::{Decode, Encode}; -#[derive(Default)] +#[derive(Debug, Default)] pub struct InBlockTransitions { header: BlockHeader, states: BTreeMap, @@ -67,6 +67,10 @@ impl InBlockTransitions { self.states.iter() } + pub fn known_programs(&self) -> Vec { + self.states.keys().cloned().collect() + } + pub fn current_messages(&self) -> Vec<(ActorId, OutgoingMessage)> { self.modifications .iter() @@ -181,7 +185,7 @@ impl InBlockTransitions { } } -#[derive(Default)] +#[derive(Debug, Default)] pub struct NonFinalTransition { initial_state: H256, pub inheritor: ActorId, From 569ca14d0fdcff90b0ef1cc1d4d2b13d69bf775b Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 17:09:21 +0400 Subject: [PATCH 08/18] fix message sending --- ethexe/runtime/common/src/journal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index bb3b8311e84..1eb533c3dab 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -109,6 +109,10 @@ impl Handler<'_, S> { expiry, ); }); + + transitions.modify_transition(dispatch.source(), |transition| { + transition.messages.push(dispatch.into_parts().1.into()) + }); } }); } From ac5599e537b27954f51ab66fbf2acdd8e73fba58 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 18:20:20 +0400 Subject: [PATCH 09/18] improved debug display for HashOf and MaybeHash --- ethexe/runtime/common/src/state.rs | 39 +++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index b8911ae1e3f..6c6763aee19 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -24,6 +24,7 @@ use alloc::{ }; use anyhow::{anyhow, Result}; use core::{ + any::Any, marker::PhantomData, num::NonZero, ops::{Deref, DerefMut}, @@ -67,12 +68,24 @@ mod private { // TODO (breathx): FIX ME WITHIN THE PR // impl Sealed for ProgramState {} impl Sealed for Waitlist {} + + pub fn shortname() -> &'static str { + core::any::type_name::() + .split("::") + .last() + .expect("name is empty") + } } -#[derive(Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] -pub struct HashOf { +#[derive( + Encode, Decode, PartialEq, Eq, derive_more::Into, derive_more::DebugCustom, derive_more::Display, +)] +#[debug(fmt = "HashOf<{}>({hash:?})", "private::shortname::()")] +#[display(fmt = "{hash}")] +pub struct HashOf { hash: H256, #[into(ignore)] + #[codec(skip)] _phantom: PhantomData, } @@ -99,8 +112,26 @@ impl HashOf { } } -#[derive(Debug, Encode, Decode, PartialEq, Eq)] -pub struct MaybeHashOf(Option>); +#[derive( + Encode, + Decode, + PartialEq, + Eq, + derive_more::Into, + derive_more::From, + derive_more::DebugCustom, + derive_more::Display, +)] +#[debug( + fmt = "MaybeHashOf<{}>({})", + "private::shortname::()", + "self.hash().map(|v| v.to_string()).unwrap_or_else(|| String::from(\"\"))" +)] +#[display( + fmt = "{}", + "_0.map(|v| v.to_string()).unwrap_or_else(|| String::from(\"\"))" +)] +pub struct MaybeHashOf(Option>); impl Clone for MaybeHashOf { fn clone(&self) -> Self { From 0db8cd3e4c0e1fed5e28b8a93a5cf366a38fdecb Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 18:32:51 +0400 Subject: [PATCH 10/18] impl maybehash from hash --- ethexe/runtime/common/src/state.rs | 8 +++++++- ethexe/runtime/src/wasm/storage.rs | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 6c6763aee19..ca29efeff7e 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -65,7 +65,7 @@ mod private { impl Sealed for MessageQueue {} impl Sealed for Payload {} impl Sealed for PageBuf {} - // TODO (breathx): FIX ME WITHIN THE PR + // TODO (breathx): consider using HashOf everywhere. // impl Sealed for ProgramState {} impl Sealed for Waitlist {} @@ -176,6 +176,12 @@ impl MaybeHashOf { } } +impl From> for MaybeHashOf { + fn from(value: HashOf) -> Self { + Self(Some(value)) + } +} + impl MaybeHashOf { pub fn query(&self, storage: &S) -> Result { self.with_hash_or_default_fallible(|hash| { diff --git a/ethexe/runtime/src/wasm/storage.rs b/ethexe/runtime/src/wasm/storage.rs index d1233f39383..855c58fe128 100644 --- a/ethexe/runtime/src/wasm/storage.rs +++ b/ethexe/runtime/src/wasm/storage.rs @@ -39,7 +39,6 @@ impl Storage for RuntimeInterfaceStorage { } fn write_state(&self, state: ProgramState) -> H256 { - // TODO (breathx): FIX ME WITHIN THE PR if state.is_zero() { return H256::zero(); } From 7edc6631023e4f345ba9f16cd79abe9fd5b1bcf8 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Thu, 24 Oct 2024 20:22:56 +0400 Subject: [PATCH 11/18] fix ethexe runtime compile --- ethexe/runtime/common/src/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index ca29efeff7e..8d169c8a01d 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -18,6 +18,8 @@ //! State-related data structures. +#[cfg(not(feature = "std"))] +use alloc::string::{String, ToString}; use alloc::{ collections::{BTreeMap, VecDeque}, vec::Vec, From d8417769aa0792f56cddd8f29dedd58f062a8ed3 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Mon, 4 Nov 2024 18:22:15 +0400 Subject: [PATCH 12/18] review remarks: extend error message --- ethexe/processor/src/handling/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index e52432dfb43..6bdd2ddab2e 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -33,12 +33,12 @@ impl ProcessingHandler { RouterEvent::ProgramCreated { actor_id, code_id } => { ensure!( self.db.original_code(code_id).is_some(), - "code existence must be checked on router" + "db corrupted: missing code [OR] code existence wasn't checked on Eth" ); ensure!( self.db.program_code_id(actor_id).is_none(), - "program duplicates must be checked on router" + "db corrupted: unrecognized program [OR] program duplicates wasn't checked on Eth" ); self.db.set_program_code_id(actor_id, code_id); From 55c4281a6231d182709e012f9117ed54a8ea63b4 Mon Sep 17 00:00:00 2001 From: Dmitrii Novikov Date: Tue, 5 Nov 2024 13:20:42 +0400 Subject: [PATCH 13/18] feat(ethexe): Held small payloads directly in dispatches (#4313) --- Cargo.lock | 8 +- core/src/buffer.rs | 5 ++ ethexe/processor/src/handling/events.rs | 4 +- ethexe/runtime/common/src/lib.rs | 5 +- ethexe/runtime/common/src/schedule.rs | 7 +- ethexe/runtime/common/src/state.rs | 109 +++++++++++++++++++----- 6 files changed, 105 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acb457394a2..e3ffbc77ff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13028,9 +13028,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -14803,9 +14803,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ "ahash 0.8.11", "cfg-if", diff --git a/core/src/buffer.rs b/core/src/buffer.rs index b4488470833..a40fe1805c4 100644 --- a/core/src/buffer.rs +++ b/core/src/buffer.rs @@ -89,6 +89,11 @@ impl TryFrom> for LimitedVec { } impl LimitedVec { + /// Constructs a new, empty `LimitedVec`. + pub const fn new() -> Self { + Self(Vec::new(), PhantomData) + } + /// Tries to create new limited vector of length `len` /// with default initialized elements. pub fn try_new_default(len: usize) -> Result { diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index 6bdd2ddab2e..3223c43d3d4 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -24,7 +24,7 @@ use ethexe_common::{ wvara::RequestEvent as WVaraEvent, }; use ethexe_db::{CodesStorage, ScheduledTask}; -use ethexe_runtime_common::state::{Dispatch, MaybeHashOf, ValueWithExpiry}; +use ethexe_runtime_common::state::{Dispatch, PayloadLookup, ValueWithExpiry}; use gear_core::{ids::ProgramId, message::SuccessReplyReason}; impl ProcessingHandler { @@ -155,7 +155,7 @@ impl ProcessingHandler { let reply = Dispatch::reply( claimed_id, source, - MaybeHashOf::empty(), + PayloadLookup::empty(), 0, SuccessReplyReason::Auto, ); diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index 3ec652dbd20..b12a18b15ab 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -185,7 +185,7 @@ where id: dispatch_id, kind, source, - payload_hash, + payload, value, details, context, @@ -206,8 +206,7 @@ where todo!("Process messages to uninitialized program"); } - let payload = payload_hash - .with_hash_or_default(|hash| ri.storage().read_payload(hash).expect("Cannot get payload")); + let payload = payload.query(ri.storage()).expect("failed to get payload"); let gas_limit = block_config .gas_multiplier diff --git a/ethexe/runtime/common/src/schedule.rs b/ethexe/runtime/common/src/schedule.rs index bca73e151dc..6bf7554131d 100644 --- a/ethexe/runtime/common/src/schedule.rs +++ b/ethexe/runtime/common/src/schedule.rs @@ -1,5 +1,8 @@ use crate::{ - state::{Dispatch, MaybeHashOf, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY}, + state::{ + Dispatch, MaybeHashOf, PayloadLookup, ProgramState, Storage, ValueWithExpiry, + MAILBOX_VALIDITY, + }, InBlockTransitions, TransitionOperator, }; use alloc::vec; @@ -42,7 +45,7 @@ impl<'a, S: Storage> TaskHandler for Handler<'a, S> { let reply = Dispatch::reply( message_id, user_id, - MaybeHashOf::empty(), + PayloadLookup::empty(), 0, SuccessReplyReason::Auto, ); diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 8d169c8a01d..24bd258e7dc 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -28,6 +28,7 @@ use anyhow::{anyhow, Result}; use core::{ any::Any, marker::PhantomData, + mem, num::NonZero, ops::{Deref, DerefMut}, }; @@ -79,6 +80,70 @@ mod private { } } +/// Represents payload provider (lookup). +/// +/// Directly keeps payload inside of itself, or keeps hash of payload stored in database. +/// +/// Motivation for usage: it's more optimized to held small payloads in place. +/// Zero payload should always be stored directly. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum PayloadLookup { + Direct(Payload), + Stored(HashOf), +} + +impl Default for PayloadLookup { + fn default() -> Self { + Self::empty() + } +} + +impl From> for PayloadLookup { + fn from(value: HashOf) -> Self { + Self::Stored(value) + } +} + +impl PayloadLookup { + /// Lower len to be stored in storage instead of holding value itself; 1 KB. + pub const STORING_THRESHOLD: usize = 1024; + + pub const fn empty() -> Self { + Self::Direct(Payload::new()) + } + + pub fn is_empty(&self) -> bool { + if let Self::Direct(payload) = self { + payload.inner().is_empty() + } else { + false + } + } + + pub fn force_stored(&mut self, storage: &S) -> HashOf { + let hash = match self { + Self::Direct(payload) => { + let payload = mem::replace(payload, Payload::new()); + storage.write_payload(payload) + } + Self::Stored(hash) => *hash, + }; + + *self = hash.into(); + + hash + } + + pub fn query(self, storage: &S) -> Result { + match self { + Self::Direct(payload) => Ok(payload), + Self::Stored(hash) => storage + .read_payload(hash) + .ok_or_else(|| anyhow!("failed to read ['Payload'] from storage by hash")), + } + } +} + #[derive( Encode, Decode, PartialEq, Eq, derive_more::Into, derive_more::DebugCustom, derive_more::Display, )] @@ -309,7 +374,7 @@ impl MaybeHashOf { self.with_hash_or_default_fallible(|hash| { storage .read_payload(hash) - .ok_or(anyhow!("failed to read ['Payload'] from storage by hash")) + .ok_or_else(|| anyhow!("failed to read ['Payload'] from storage by hash")) }) } @@ -440,7 +505,7 @@ pub struct Dispatch { /// Message source. pub source: ProgramId, /// Message payload. - pub payload_hash: MaybeHashOf, + pub payload: PayloadLookup, /// Message value. pub value: Value, /// Message details like reply message ID, status code, etc. @@ -458,7 +523,7 @@ impl Dispatch { value: u128, is_init: bool, ) -> Result { - let payload_hash = storage.write_payload_raw(payload)?; + let payload = storage.write_payload_raw(payload)?; let kind = if is_init { DispatchKind::Init @@ -470,7 +535,7 @@ impl Dispatch { id, kind, source, - payload_hash, + payload, value, details: None, context: None, @@ -498,7 +563,7 @@ impl Dispatch { pub fn reply( reply_to: MessageId, source: ActorId, - payload_hash: MaybeHashOf, + payload: PayloadLookup, value: u128, reply_code: impl Into, ) -> Self { @@ -506,7 +571,7 @@ impl Dispatch { id: MessageId::generate_reply(reply_to), kind: DispatchKind::Reply, source, - payload_hash, + payload, value, details: Some(ReplyDetails::new(reply_to, reply_code.into()).into()), context: None, @@ -517,7 +582,7 @@ impl Dispatch { let (kind, message, context) = value.into_parts(); let (id, source, destination, payload, value, details) = message.into_parts(); - let payload_hash = storage + let payload = storage .write_payload_raw(payload.into_vec()) .expect("infallible due to recasts (only panics on len)"); @@ -525,7 +590,7 @@ impl Dispatch { id, kind, source, - payload_hash, + payload, value, details, context, @@ -535,18 +600,13 @@ impl Dispatch { pub fn into_outgoing(self, storage: &S, destination: ActorId) -> OutgoingMessage { let Self { id, - payload_hash, + payload, value, details, .. } = self; - let payload = payload_hash.with_hash_or_default(|payload_hash| { - storage - .read_payload(payload_hash) - .expect("must be found") - .into_vec() - }); + let payload = payload.query(storage).expect("must be found").into_vec(); OutgoingMessage { id, @@ -857,13 +917,18 @@ pub trait Storage { /// Writes payload and returns its hash. fn write_payload(&self, payload: Payload) -> HashOf; - /// Writes payload if it doesnt exceed limits. - fn write_payload_raw(&self, payload: Vec) -> Result> { - Payload::try_from(payload) - .map(|payload| { - MaybeHashOf((!payload.inner().is_empty()).then(|| self.write_payload(payload))) - }) - .map_err(|_| anyhow!("payload exceeds size limit")) + /// Writes payload if it doesnt exceed limits, returning lookup. + fn write_payload_raw(&self, payload: Vec) -> Result { + let payload = + Payload::try_from(payload).map_err(|_| anyhow!("payload exceeds size limit"))?; + + let res = if payload.inner().len() < PayloadLookup::STORING_THRESHOLD { + PayloadLookup::Direct(payload) + } else { + PayloadLookup::Stored(self.write_payload(payload)) + }; + + Ok(res) } /// Reads page data by page data hash. From 166701b1169af96ac3923a1677d6495e3b93149d Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 5 Nov 2024 13:53:34 +0400 Subject: [PATCH 14/18] fix serde implementations --- ethexe/rpc/src/apis/program.rs | 12 ++++++------ ethexe/runtime/common/src/state.rs | 23 ++++++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ethexe/rpc/src/apis/program.rs b/ethexe/rpc/src/apis/program.rs index 19dc1154f49..59fe0b05a81 100644 --- a/ethexe/rpc/src/apis/program.rs +++ b/ethexe/rpc/src/apis/program.rs @@ -20,7 +20,7 @@ use crate::{common::block_header_at_or_latest, errors}; use ethexe_db::{CodesStorage, Database}; use ethexe_processor::Processor; use ethexe_runtime_common::state::{ - Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, Waitlist, + HashOf, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, Waitlist, }; use gear_core::message::ReplyInfo; use gprimitives::{H160, H256}; @@ -131,32 +131,32 @@ impl ProgramServer for ProgramApi { async fn read_queue(&self, hash: H256) -> RpcResult { self.db - .read_queue(hash) + .read_queue(unsafe { HashOf::new(hash) }) .ok_or_else(|| errors::db("Failed to read queue by hash")) } async fn read_mailbox(&self, hash: H256) -> RpcResult { self.db - .read_mailbox(hash) + .read_mailbox(unsafe { HashOf::new(hash) }) .ok_or_else(|| errors::db("Failed to read mailbox by hash")) } async fn read_pages(&self, hash: H256) -> RpcResult { self.db - .read_pages(hash) + .read_pages(unsafe { HashOf::new(hash) }) .ok_or_else(|| errors::db("Failed to read pages by hash")) } // TODO: read the whole program state in a single query async fn read_waitlist(&self, hash: H256) -> RpcResult { self.db - .read_waitlist(hash) + .read_waitlist(unsafe { HashOf::new(hash) }) .ok_or_else(|| errors::db("Failed to read waitlist by hash")) } async fn read_page_data(&self, hash: H256) -> RpcResult { self.db - .read_page_data(hash) + .read_page_data(unsafe { HashOf::new(hash) }) .map(|buf| buf.encode().into()) .ok_or_else(|| errors::db("Failed to read page data by hash")) } diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index cdf18dad5da..1f9967adf8d 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -192,7 +192,11 @@ impl HashOf { derive_more::DebugCustom, derive_more::Display, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] #[debug( fmt = "MaybeHashOf<{}>({})", "private::shortname::()", @@ -626,7 +630,8 @@ impl Dispatch { } } -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct ValueWithExpiry { pub value: T, pub expiry: u32, @@ -638,7 +643,8 @@ impl From<(T, u32)> for ValueWithExpiry { } } -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct MessageQueue(VecDeque); impl MessageQueue { @@ -659,7 +665,8 @@ impl MessageQueue { } } -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Waitlist { inner: BTreeMap>, #[into(ignore)] @@ -697,7 +704,7 @@ impl Waitlist { } } -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] pub struct DispatchStash(BTreeMap)>>); impl DispatchStash { @@ -766,7 +773,8 @@ impl DispatchStash { } // TODO (breathx): consider here LocalMailbox for each user. -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Mailbox { inner: BTreeMap>>, #[into(ignore)] @@ -813,7 +821,8 @@ impl Mailbox { } } -#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct MemoryPages(BTreeMap>); impl MemoryPages { From 4e51c8069ffd0bb5dd702aed145ba3f8e4299592 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 5 Nov 2024 13:53:42 +0400 Subject: [PATCH 15/18] fix typos --- pallets/gear-messenger/src/tests.rs | 2 +- pallets/gear/src/manager/journal.rs | 2 +- utils/node-loader/src/batch_pool.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/gear-messenger/src/tests.rs b/pallets/gear-messenger/src/tests.rs index 4a63541fc00..888f84fa8e1 100644 --- a/pallets/gear-messenger/src/tests.rs +++ b/pallets/gear-messenger/src/tests.rs @@ -244,7 +244,7 @@ fn queue_works() { assert_eq!(DequeuedOf::get(), 2); assert_eq!(QueueOf::len(), 2); - // Push front used only for requeueing element, + // Push front used only for requeuing element, // which was already in queue in current block, // because it decreased dequeued amount. assert!(QueueProcessingOf::allowed()); diff --git a/pallets/gear/src/manager/journal.rs b/pallets/gear/src/manager/journal.rs index 79a3bdeddad..c18afd7dc82 100644 --- a/pallets/gear/src/manager/journal.rs +++ b/pallets/gear/src/manager/journal.rs @@ -505,7 +505,7 @@ where ); GasAllowanceOf::::decrease(gas_burned); - // TODO: #3112. Rework requeueing logic to avoid blocked queue. + // TODO: #3112. Rework requeuing logic to avoid blocked queue. QueueOf::::requeue(dispatch).unwrap_or_else(|e| { let err_msg = format!( "JournalHandler::stop_processing: failed requeuing message. Got error - {e:?}" diff --git a/utils/node-loader/src/batch_pool.rs b/utils/node-loader/src/batch_pool.rs index ad23c5836da..70a7826fccf 100644 --- a/utils/node-loader/src/batch_pool.rs +++ b/utils/node-loader/src/batch_pool.rs @@ -59,7 +59,7 @@ impl BatchPool { /// Consume `BatchPool` and spawn tasks. /// /// - `run_pool_task` - the main task for sending and processing batches. - /// - `inpect_crash_task` - background task monitors when message processing stops. + /// - `inspect_crash_task` - background task monitors when message processing stops. /// - `renew_balance_task` - periodically setting a new balance for the user account. /// /// Wait for any task to return result with `tokio::select!`. From ceff13cb72324957e006c1e2a9a1344d700e9955 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 5 Nov 2024 17:42:39 +0400 Subject: [PATCH 16/18] fix feature gate --- ethexe/runtime/common/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 1f9967adf8d..ef49911db1e 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -155,7 +155,7 @@ pub struct HashOf { hash: H256, #[into(ignore)] #[codec(skip)] - #[serde(skip)] + #[cfg_attr(feature = "std", serde(skip))] _phantom: PhantomData, } From af166d5eea8799bab5453e77b5213f9139558734 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 5 Nov 2024 18:13:08 +0400 Subject: [PATCH 17/18] rename: TransitionOperator -> TransitionController --- ethexe/processor/src/handling/mod.rs | 10 +++++----- ethexe/processor/src/handling/run.rs | 4 ++-- ethexe/runtime/common/src/journal.rs | 4 ++-- ethexe/runtime/common/src/lib.rs | 4 ++-- ethexe/runtime/common/src/schedule.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ethexe/processor/src/handling/mod.rs b/ethexe/processor/src/handling/mod.rs index 6e2a888e064..e126f8cd980 100644 --- a/ethexe/processor/src/handling/mod.rs +++ b/ethexe/processor/src/handling/mod.rs @@ -20,7 +20,7 @@ use crate::Processor; use anyhow::{anyhow, Result}; use ethexe_db::{BlockMetaStorage, CodesStorage, Database}; use ethexe_runtime_common::{ - state::ProgramState, InBlockTransitions, ScheduleHandler, TransitionOperator, + state::ProgramState, InBlockTransitions, ScheduleHandler, TransitionController, }; use gprimitives::{ActorId, CodeId, H256}; @@ -33,8 +33,8 @@ pub struct ProcessingHandler { } impl ProcessingHandler { - pub fn operator(&mut self) -> TransitionOperator<'_, Database> { - TransitionOperator { + pub fn controller(&mut self) -> TransitionController<'_, Database> { + TransitionController { storage: &self.db, transitions: &mut self.transitions, } @@ -45,7 +45,7 @@ impl ProcessingHandler { program_id: ActorId, f: impl FnOnce(&mut ProgramState, &Database, &mut InBlockTransitions) -> T, ) -> T { - self.operator().update_state(program_id, f) + self.controller().update_state(program_id, f) } } @@ -110,7 +110,7 @@ impl ProcessingHandler { ); let mut handler = ScheduleHandler { - operator: self.operator(), + controller: self.adapter(), }; for task in tasks { diff --git a/ethexe/processor/src/handling/run.rs b/ethexe/processor/src/handling/run.rs index 031b1019db6..d9a66560cd0 100644 --- a/ethexe/processor/src/handling/run.rs +++ b/ethexe/processor/src/handling/run.rs @@ -19,7 +19,7 @@ use crate::host::{InstanceCreator, InstanceWrapper}; use core_processor::common::JournalNote; use ethexe_db::{CodesStorage, Database}; -use ethexe_runtime_common::{InBlockTransitions, JournalHandler, TransitionOperator}; +use ethexe_runtime_common::{InBlockTransitions, JournalHandler, TransitionController}; use gear_core::ids::ProgramId; use gprimitives::H256; use std::collections::BTreeMap; @@ -94,7 +94,7 @@ async fn run_in_async( for (program_id, journal) in super_journal { let mut handler = JournalHandler { program_id, - operator: TransitionOperator { + controller: TransitionController { transitions: in_block_transitions, storage: &db, }, diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index 1eb533c3dab..4888a23f6a4 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -3,7 +3,7 @@ use crate::{ self, ActiveProgram, Dispatch, MaybeHashOf, Program, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY, }, - InBlockTransitions, TransitionOperator, + InBlockTransitions, TransitionController, }; use alloc::{collections::BTreeMap, vec, vec::Vec}; use anyhow::{bail, Result}; @@ -32,7 +32,7 @@ pub struct Handler<'a, S: Storage> { pub program_id: ProgramId, #[deref] #[deref_mut] - pub operator: TransitionOperator<'a, S>, + pub controller: TransitionController<'a, S>, } impl Handler<'_, S> { diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index b12a18b15ab..cafbb52a661 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -76,12 +76,12 @@ pub trait RuntimeInterface { fn storage(&self) -> &S; } -pub struct TransitionOperator<'a, S: Storage> { +pub struct TransitionController<'a, S: Storage> { pub storage: &'a S, pub transitions: &'a mut InBlockTransitions, } -impl<'a, S: Storage> TransitionOperator<'a, S> { +impl<'a, S: Storage> TransitionController<'a, S> { pub fn update_state( &mut self, program_id: ProgramId, diff --git a/ethexe/runtime/common/src/schedule.rs b/ethexe/runtime/common/src/schedule.rs index 6bf7554131d..4ef2aae498a 100644 --- a/ethexe/runtime/common/src/schedule.rs +++ b/ethexe/runtime/common/src/schedule.rs @@ -3,7 +3,7 @@ use crate::{ Dispatch, MaybeHashOf, PayloadLookup, ProgramState, Storage, ValueWithExpiry, MAILBOX_VALIDITY, }, - InBlockTransitions, TransitionOperator, + InBlockTransitions, TransitionController, }; use alloc::vec; use anyhow::{anyhow, Result}; @@ -17,7 +17,7 @@ use gprimitives::{ActorId, CodeId, MessageId, ReservationId, H256}; #[derive(derive_more::Deref, derive_more::DerefMut)] pub struct Handler<'a, S: Storage> { - pub operator: TransitionOperator<'a, S>, + pub controller: TransitionController<'a, S>, } impl<'a, S: Storage> TaskHandler for Handler<'a, S> { From 5e7fbc3842cdf5eadfcea6b0714175e4e7f3469a Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 5 Nov 2024 18:25:46 +0400 Subject: [PATCH 18/18] fix clippy --- ethexe/processor/src/handling/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethexe/processor/src/handling/mod.rs b/ethexe/processor/src/handling/mod.rs index e126f8cd980..752a4ff87b1 100644 --- a/ethexe/processor/src/handling/mod.rs +++ b/ethexe/processor/src/handling/mod.rs @@ -110,7 +110,7 @@ impl ProcessingHandler { ); let mut handler = ScheduleHandler { - controller: self.adapter(), + controller: self.controller(), }; for task in tasks {