#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use alloc::{
collections::{BTreeMap, VecDeque},
vec::Vec,
};
use anyhow::{Result, anyhow};
use core::{
any::Any,
cell::RefCell,
hash::Hash,
mem,
ops::{Index, IndexMut},
};
use ethexe_common::{
HashOf, MaybeHashOf,
gear::{Message, MessageType},
};
pub use gear_core::program::ProgramState as InitStatus;
use gear_core::{
buffer::Payload,
ids::prelude::MessageIdExt as _,
memory::PageBuf,
message::{ContextStore, DispatchKind, MessageDetails, ReplyDetails, StoredDispatch, Value},
pages::{GearPage, WasmPage, numerated::tree::IntervalsTree},
program::MemoryInfix,
};
use gear_core_errors::{ReplyCode, SuccessReplyReason};
use gprimitives::{ActorId, H256, MessageId};
use parity_scale_codec::{Decode, Encode};
#[allow(unused)]
fn shortname<S: Any>() -> &'static str {
core::any::type_name::<S>()
.split("::")
.last()
.expect("name is empty")
}
#[allow(unused)]
fn option_string<S: ToString>(value: &Option<S>) -> String {
value
.as_ref()
.map(|v| v.to_string())
.unwrap_or_else(|| "<none>".to_string())
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Encode, Decode)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum PayloadLookup {
Direct(Payload),
Stored(HashOf<Payload>),
}
impl Default for PayloadLookup {
fn default() -> Self {
Self::empty()
}
}
impl From<HashOf<Payload>> for PayloadLookup {
fn from(value: HashOf<Payload>) -> Self {
Self::Stored(value)
}
}
impl PayloadLookup {
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.is_empty()
} else {
false
}
}
pub fn force_stored<S: Storage>(&mut self, storage: &S) -> HashOf<Payload> {
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<S: Storage>(self, storage: &S) -> Result<Payload> {
match self {
Self::Direct(payload) => Ok(payload),
Self::Stored(hash) => storage
.payload(hash)
.ok_or_else(|| anyhow!("failed to read ['Payload'] from storage by hash")),
}
}
}
impl<S: Storage> QueryableStorage<Allocations> for S {
fn query(&self, hash: &MaybeHashOf<Allocations>) -> Result<Allocations> {
hash.try_map_or_default(|hash| {
self.allocations(hash).ok_or(anyhow!(
"failed to read ['Allocations'] from storage by hash"
))
})
}
}
impl<S: Storage> ModifiableStorage<Allocations> for S {
fn modify<U>(
&self,
hash: &mut MaybeHashOf<Allocations>,
f: impl FnOnce(&mut Allocations) -> U,
) -> U {
let mut allocations = self.query(hash).expect("failed to modify allocations");
let r = f(&mut allocations);
hash.replace(allocations.store(&self));
r
}
}
impl<S: Storage> QueryableStorage<DispatchStash> for S {
fn query(&self, hash: &MaybeHashOf<DispatchStash>) -> Result<DispatchStash> {
hash.try_map_or_default(|hash| {
self.dispatch_stash(hash).ok_or(anyhow!(
"failed to read ['DispatchStash'] from storage by hash"
))
})
}
}
impl<S: Storage> ModifiableStorage<DispatchStash> for S {
fn modify<U>(
&self,
hash: &mut MaybeHashOf<DispatchStash>,
f: impl FnOnce(&mut DispatchStash) -> U,
) -> U {
let mut stash = self.query(hash).expect("failed to modify stash");
let r = f(&mut stash);
*hash = stash.store(&self);
r
}
}
impl<S: Storage> QueryableStorage<Mailbox> for S {
fn query(&self, hash: &MaybeHashOf<Mailbox>) -> Result<Mailbox> {
hash.try_map_or_default(|hash| {
self.mailbox(hash)
.ok_or(anyhow!("failed to read ['Mailbox'] from storage by hash"))
})
}
}
impl<S: Storage> ModifiableStorage<Mailbox> for S {
fn modify<U>(&self, hash: &mut MaybeHashOf<Mailbox>, f: impl FnOnce(&mut Mailbox) -> U) -> U {
let mut mailbox = self.query(hash).expect("failed to modify mailbox");
let r = f(&mut mailbox);
hash.replace(mailbox.store(&self));
r
}
}
impl<S: Storage> QueryableStorage<UserMailbox> for S {
fn query(&self, hash: &MaybeHashOf<UserMailbox>) -> Result<UserMailbox> {
hash.try_map_or_default(|hash| {
self.user_mailbox(hash).ok_or(anyhow!(
"failed to read ['UserMailbox'] from storage by hash"
))
})
}
}
impl<S: Storage> QueryableStorage<MemoryPages> for S {
fn query(&self, hash: &MaybeHashOf<MemoryPages>) -> Result<MemoryPages> {
hash.try_map_or_default(|hash| {
self.memory_pages(hash).ok_or(anyhow!(
"failed to read ['MemoryPages'] from storage by hash"
))
})
}
}
impl<S: Storage> ModifiableStorage<MemoryPages> for S {
fn modify<U>(
&self,
hash: &mut MaybeHashOf<MemoryPages>,
f: impl FnOnce(&mut MemoryPages) -> U,
) -> U {
let mut pages = self.query(hash).expect("failed to modify memory pages");
let r = f(&mut pages);
*hash = pages.store(&self);
r
}
}
#[derive(Clone, Copy, Debug, Decode, Encode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct MessageQueueHashWithSize {
pub hash: MaybeHashOf<MessageQueue>,
pub cached_queue_size: u8,
}
impl MessageQueueHashWithSize {
pub fn query<S: Storage + ?Sized>(&self, storage: &S) -> Result<MessageQueue> {
self.hash.try_map_or_default(|hash| {
storage.message_queue(hash).ok_or(anyhow!(
"failed to read ['MessageQueue'] from storage by hash"
))
})
}
pub fn modify_queue<S: Storage + ?Sized, T>(
&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.cached_queue_size = queue.len().min(u8::MAX as usize) as u8;
self.hash = queue.store(storage);
r
}
pub fn is_empty(&self) -> bool {
self.hash.is_empty()
}
}
impl<S: Storage> QueryableStorage<Payload> for S {
fn query(&self, hash: &MaybeHashOf<Payload>) -> Result<Payload> {
hash.try_map_or_default(|hash| {
self.payload(hash)
.ok_or_else(|| anyhow!("failed to read ['Payload'] from storage by hash"))
})
}
}
impl<S: Storage> QueryableStorage<Waitlist> for S {
fn query(&self, hash: &MaybeHashOf<Waitlist>) -> Result<Waitlist> {
hash.try_map_or_default(|hash| {
self.waitlist(hash)
.ok_or(anyhow!("failed to read ['Waitlist'] from storage by hash"))
})
}
}
impl<S: Storage> ModifiableStorage<Waitlist> for S {
fn modify<U>(&self, hash: &mut MaybeHashOf<Waitlist>, f: impl FnOnce(&mut Waitlist) -> U) -> U {
let mut waitlist = self.query(hash).expect("failed to modify waitlist");
let r = f(&mut waitlist);
hash.replace(waitlist.store(&self));
r
}
}
#[derive(Copy, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct ActiveProgram {
pub allocations_hash: MaybeHashOf<Allocations>,
pub pages_hash: MaybeHashOf<MemoryPages>,
pub memory_infix: MemoryInfix,
pub initialized: bool,
}
#[derive(Copy, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum Program {
Active(ActiveProgram),
Exited(ActorId),
Terminated(ActorId),
}
impl Program {
pub fn is_active(&self) -> bool {
matches!(self, Self::Active(_))
}
pub fn is_initialized(&self) -> bool {
matches!(
self,
Self::Active(ActiveProgram {
initialized: true,
..
})
)
}
}
#[derive(Copy, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct ProgramState {
pub program: Program,
pub canonical_queue: MessageQueueHashWithSize,
pub injected_queue: MessageQueueHashWithSize,
pub waitlist_hash: MaybeHashOf<Waitlist>,
pub stash_hash: MaybeHashOf<DispatchStash>,
pub mailbox_hash: MaybeHashOf<Mailbox>,
pub balance: Value,
pub executable_balance: Value,
}
impl ProgramState {
pub const fn zero() -> Self {
Self {
program: Program::Active(ActiveProgram {
allocations_hash: MaybeHashOf::empty(),
pages_hash: MaybeHashOf::empty(),
memory_infix: MemoryInfix::new(0),
initialized: false,
}),
canonical_queue: MessageQueueHashWithSize {
hash: MaybeHashOf::empty(),
cached_queue_size: 0,
},
injected_queue: MessageQueueHashWithSize {
hash: MaybeHashOf::empty(),
cached_queue_size: 0,
},
waitlist_hash: MaybeHashOf::empty(),
stash_hash: MaybeHashOf::empty(),
mailbox_hash: MaybeHashOf::empty(),
balance: 0,
executable_balance: 0,
}
}
pub fn is_zero(&self) -> bool {
*self == Self::zero()
}
pub fn requires_init_message(&self) -> bool {
if !matches!(
self.program,
Program::Active(ActiveProgram {
initialized: false,
..
})
) {
return false;
}
self.canonical_queue.hash.is_empty()
&& self.injected_queue.hash.is_empty()
&& self.waitlist_hash.is_empty()
}
pub fn queue_from_msg_type(
&mut self,
message_type: MessageType,
) -> &mut MessageQueueHashWithSize {
match message_type {
MessageType::Canonical => &mut self.canonical_queue,
MessageType::Injected => &mut self.injected_queue,
}
}
}
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Dispatch {
pub id: MessageId,
pub kind: DispatchKind,
pub source: ActorId,
pub payload: PayloadLookup,
pub value: Value,
pub details: Option<MessageDetails>,
pub context: Option<ContextStore>,
pub message_type: MessageType,
pub call: bool,
}
impl Dispatch {
#[expect(clippy::too_many_arguments)]
pub fn new<S: Storage + ?Sized>(
storage: &S,
id: MessageId,
source: ActorId,
payload: Vec<u8>,
value: u128,
is_init: bool,
message_type: MessageType,
call: bool,
) -> Result<Self> {
let payload = storage.write_payload_raw(payload)?;
let kind = if is_init {
DispatchKind::Init
} else {
DispatchKind::Handle
};
Ok(Self {
id,
kind,
source,
payload,
value,
details: None,
context: None,
message_type,
call,
})
}
pub fn new_reply<S: Storage + ?Sized>(
storage: &S,
replied_to: MessageId,
source: ActorId,
payload: Vec<u8>,
value: u128,
message_type: MessageType,
call: bool,
) -> Result<Self> {
let payload_hash = storage.write_payload_raw(payload)?;
Ok(Self::reply(
replied_to,
source,
payload_hash,
value,
SuccessReplyReason::Manual,
message_type,
call,
))
}
pub fn reply(
reply_to: MessageId,
source: ActorId,
payload: PayloadLookup,
value: u128,
reply_code: impl Into<ReplyCode>,
message_type: MessageType,
call: bool,
) -> Self {
Self {
id: MessageId::generate_reply(reply_to),
kind: DispatchKind::Reply,
source,
payload,
value,
details: Some(ReplyDetails::new(reply_to, reply_code.into()).into()),
context: None,
message_type,
call,
}
}
pub fn from_core_stored<S: Storage + ?Sized>(
storage: &S,
value: StoredDispatch,
message_type: MessageType,
call_reply: bool,
) -> Self {
let (kind, message, context) = value.into_parts();
let (id, source, _destination, payload, value, details) = message.into_parts();
let payload = storage
.write_payload_raw(payload.into_vec())
.expect("infallible due to recasts (only panics on len)");
Self {
id,
kind,
source,
payload,
value,
details,
context,
message_type,
call: call_reply,
}
}
pub fn into_message<S: Storage>(self, storage: &S, destination: ActorId) -> Message {
let Self {
id,
payload,
value,
details,
call,
..
} = self;
let payload = payload.query(storage).expect("must be found").into_vec();
Message {
id,
destination,
payload,
value,
reply_details: details.and_then(|d| d.to_reply_details()),
call,
}
}
}
#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Expiring<T> {
pub value: T,
pub expiry: u32,
}
impl<T> From<(T, u32)> for Expiring<T> {
fn from((value, expiry): (T, u32)) -> Self {
Self { value, expiry }
}
}
#[derive(
Clone,
Default,
Debug,
Encode,
Decode,
PartialEq,
Eq,
Hash,
derive_more::Into,
derive_more::AsRef,
derive_more::IntoIterator,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct MessageQueue(VecDeque<Dispatch>);
impl MessageQueue {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn queue(&mut self, dispatch: Dispatch) {
self.0.push_back(dispatch);
}
pub fn dequeue(&mut self) -> Option<Dispatch> {
self.0.pop_front()
}
pub fn peek(&self) -> Option<&Dispatch> {
self.0.front()
}
pub fn store<S: Storage + ?Sized>(self, storage: &S) -> MaybeHashOf<Self> {
MaybeHashOf::from_inner((!self.0.is_empty()).then(|| storage.write_message_queue(self)))
}
}
impl MessageQueue {
pub fn clear(&mut self) {
self.0.clear()
}
pub fn pop_back(&mut self) -> Option<Dispatch> {
self.0.pop_back()
}
}
#[derive(
Clone,
Default,
Debug,
Encode,
Decode,
PartialEq,
Eq,
Hash,
derive_more::Into,
derive_more::AsRef,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Waitlist {
#[as_ref]
inner: BTreeMap<MessageId, Expiring<Dispatch>>,
#[into(ignore)]
#[codec(skip)]
changed: bool,
}
impl Waitlist {
pub fn wait(&mut self, dispatch: Dispatch, expiry: u32) {
self.changed = true;
let r = self.inner.insert(
dispatch.id,
Expiring {
value: dispatch,
expiry,
},
);
debug_assert!(r.is_none())
}
pub fn wake(&mut self, message_id: &MessageId) -> Option<Expiring<Dispatch>> {
self.inner
.remove(message_id)
.inspect(|_| self.changed = true)
}
pub fn store<S: Storage>(self, storage: &S) -> Option<MaybeHashOf<Self>> {
self.changed.then(|| {
MaybeHashOf::from_inner((!self.inner.is_empty()).then(|| storage.write_waitlist(self)))
})
}
pub fn into_inner(self) -> BTreeMap<MessageId, Expiring<Dispatch>> {
self.into()
}
}
#[derive(
Clone,
Default,
Debug,
Encode,
Decode,
PartialEq,
Eq,
Hash,
derive_more::Into,
derive_more::AsRef,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct DispatchStash(BTreeMap<MessageId, Expiring<(Dispatch, Option<ActorId>)>>);
impl DispatchStash {
pub fn add_to_program(&mut self, dispatch: Dispatch, expiry: u32) {
let r = self.0.insert(
dispatch.id,
Expiring {
value: (dispatch, None),
expiry,
},
);
debug_assert!(r.is_none());
}
pub fn add_to_user(&mut self, dispatch: Dispatch, expiry: u32, user_id: ActorId) {
let r = self.0.insert(
dispatch.id,
Expiring {
value: (dispatch, Some(user_id)),
expiry,
},
);
debug_assert!(r.is_none());
}
pub fn remove_to_program(&mut self, message_id: &MessageId) -> Dispatch {
let Expiring {
value: (dispatch, user_id),
..
} = 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");
}
dispatch
}
pub fn remove_to_user(&mut self, message_id: &MessageId) -> (Dispatch, ActorId) {
let Expiring {
value: (dispatch, user_id),
..
} = self
.0
.remove(message_id)
.expect("unknown mid queried from stash");
let user_id = user_id
.expect("stashed mid was intended to be sent to user, but keeps no data for user");
(dispatch, user_id)
}
pub fn store<S: Storage>(self, storage: &S) -> MaybeHashOf<Self> {
MaybeHashOf::from_inner((!self.0.is_empty()).then(|| storage.write_dispatch_stash(self)))
}
}
#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct MailboxMessage {
pub payload: PayloadLookup,
pub value: Value,
pub message_type: MessageType,
}
impl MailboxMessage {
pub fn new(payload: PayloadLookup, value: Value, message_type: MessageType) -> Self {
Self {
payload,
value,
message_type,
}
}
}
impl From<Dispatch> for MailboxMessage {
fn from(dispatch: Dispatch) -> Self {
Self {
payload: dispatch.payload,
value: dispatch.value,
message_type: dispatch.message_type,
}
}
}
#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, Hash, derive_more::Into)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct UserMailbox(BTreeMap<MessageId, Expiring<MailboxMessage>>);
impl UserMailbox {
fn add(&mut self, message_id: MessageId, message: MailboxMessage, expiry: u32) {
let r = self.0.insert(message_id, (message, expiry).into());
debug_assert!(r.is_none())
}
fn remove(&mut self, message_id: MessageId) -> Option<Expiring<MailboxMessage>> {
self.0.remove(&message_id)
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn store<S: Storage + ?Sized>(self, storage: &S) -> MaybeHashOf<Self> {
MaybeHashOf::from_inner((!self.0.is_empty()).then(|| storage.write_user_mailbox(self)))
}
#[cfg(any(test, feature = "mock"))]
pub fn from_inner(inner: BTreeMap<MessageId, Expiring<MailboxMessage>>) -> Self {
Self(inner)
}
}
impl AsRef<BTreeMap<MessageId, Expiring<MailboxMessage>>> for UserMailbox {
fn as_ref(&self) -> &BTreeMap<MessageId, Expiring<MailboxMessage>> {
&self.0
}
}
#[derive(
Clone,
Default,
Debug,
Encode,
Decode,
PartialEq,
Eq,
Hash,
derive_more::Into,
derive_more::AsRef,
)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Mailbox {
#[as_ref]
inner: BTreeMap<ActorId, HashOf<UserMailbox>>,
#[into(ignore)]
#[codec(skip)]
changed: bool,
}
impl Mailbox {
pub fn add_and_store_user_mailbox<S: Storage + ?Sized>(
&mut self,
storage: &S,
user_id: ActorId,
message_id: MessageId,
message: MailboxMessage,
expiry: u32,
) {
self.changed = true;
let maybe_hash: MaybeHashOf<UserMailbox> = self.inner.get(&user_id).cloned().into();
let mut mailbox = storage
.query(&maybe_hash)
.expect("failed to query user mailbox");
mailbox.add(message_id, message, expiry);
let hash = storage.write_user_mailbox(mailbox);
let _ = self.inner.insert(user_id, hash);
}
pub fn remove_and_store_user_mailbox<S: Storage + ?Sized>(
&mut self,
storage: &S,
user_id: ActorId,
message_id: MessageId,
) -> Option<Expiring<MailboxMessage>> {
let maybe_hash: MaybeHashOf<UserMailbox> = self.inner.get(&user_id).cloned().into();
let mut mailbox = storage
.query(&maybe_hash)
.expect("failed to query user mailbox");
let value = mailbox.remove(message_id);
if value.is_some() {
self.changed = true;
if mailbox.is_empty() {
self.inner.remove(&user_id);
} else {
let hash = mailbox
.store(storage)
.to_inner()
.expect("failed to store user mailbox");
self.inner.insert(user_id, hash);
}
}
value
}
pub fn store<S: Storage>(self, storage: &S) -> Option<MaybeHashOf<Self>> {
self.changed.then(|| {
MaybeHashOf::from_inner((!self.inner.is_empty()).then(|| storage.write_mailbox(self)))
})
}
pub fn into_values<S: Storage>(
self,
storage: &S,
) -> BTreeMap<ActorId, BTreeMap<MessageId, Expiring<MailboxMessage>>> {
self.inner
.into_iter()
.map(|(k, v)| {
(
k,
storage
.user_mailbox(v)
.expect("failed to read user mailbox from store")
.0
.into_iter()
.collect(),
)
})
.collect()
}
}
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, Hash, derive_more::Into)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryPages(MemoryPagesInner);
impl Default for MemoryPages {
fn default() -> Self {
Self([MaybeHashOf::empty(); MemoryPages::REGIONS_AMOUNT])
}
}
impl Index<RegionIdx> for MemoryPages {
type Output = MaybeHashOf<MemoryPagesRegion>;
fn index(&self, idx: RegionIdx) -> &Self::Output {
&self.0[idx.0 as usize]
}
}
impl IndexMut<RegionIdx> for MemoryPages {
fn index_mut(&mut self, index: RegionIdx) -> &mut Self::Output {
&mut self.0[index.0 as usize]
}
}
pub type MemoryPagesInner = [MaybeHashOf<MemoryPagesRegion>; MemoryPages::REGIONS_AMOUNT];
impl MemoryPages {
pub const MAX_PAGES: usize = 4 * gear_core::code::MAX_WASM_PAGES_AMOUNT as usize;
pub const REGIONS_AMOUNT: usize = 16;
pub const PAGES_PER_REGION: usize = Self::MAX_PAGES / Self::REGIONS_AMOUNT;
const _DIVISIBILITY_ASSERT: () = assert!(Self::MAX_PAGES.is_multiple_of(Self::REGIONS_AMOUNT));
pub fn page_region(page: GearPage) -> RegionIdx {
RegionIdx((u32::from(page) as usize / Self::PAGES_PER_REGION) as u8)
}
pub fn update_and_store_regions<S: Storage>(
&mut self,
storage: &S,
new_pages: BTreeMap<GearPage, HashOf<PageBuf>>,
) {
let mut updated_regions = BTreeMap::new();
let mut current_region_idx = None;
let mut current_region_entry = None;
for (page, data) in new_pages {
let region_idx = Self::page_region(page);
if current_region_idx != Some(region_idx) {
let region_entry = updated_regions.entry(region_idx).or_insert_with(|| {
self[region_idx]
.to_inner()
.map(|region_hash| {
storage
.memory_pages_region(region_hash)
.expect("failed to read region from storage")
})
.unwrap_or_default()
});
current_region_idx = Some(region_idx);
current_region_entry = Some(region_entry);
}
current_region_entry
.as_mut()
.expect("infallible; inserted above")
.0
.insert(page, data);
}
for (region_idx, region) in updated_regions {
let region_hash = region
.store(storage)
.to_inner()
.expect("infallible; pages are only appended here, none are removed");
self[region_idx] = region_hash.into();
}
}
pub fn remove_and_store_regions<S: Storage>(&mut self, storage: &S, pages: &Vec<GearPage>) {
let mut updated_regions = BTreeMap::new();
let mut current_region_idx = None;
let mut current_region_entry = None;
for page in pages {
let region_idx = Self::page_region(*page);
if current_region_idx != Some(region_idx) {
let region_entry = updated_regions.entry(region_idx).or_insert_with(|| {
self[region_idx]
.to_inner()
.map(|region_hash| {
storage
.memory_pages_region(region_hash)
.expect("failed to read region from storage")
})
.unwrap_or_default()
});
current_region_idx = Some(region_idx);
current_region_entry = Some(region_entry);
}
current_region_entry
.as_mut()
.expect("infallible; inserted above")
.0
.remove(page);
}
for (region_idx, region) in updated_regions {
if let Some(region_hash) = region.store(storage).to_inner() {
self[region_idx] = region_hash.into();
}
}
}
pub fn store<S: Storage>(self, storage: &S) -> MaybeHashOf<Self> {
MaybeHashOf::from_inner((!self.0.is_empty()).then(|| storage.write_memory_pages(self)))
}
pub fn to_inner(&self) -> MemoryPagesInner {
self.0
}
}
#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, Hash, derive_more::Into)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryPagesRegion(MemoryPagesRegionInner);
pub type MemoryPagesRegionInner = BTreeMap<GearPage, HashOf<PageBuf>>;
impl MemoryPagesRegion {
pub fn store<S: Storage>(self, storage: &S) -> MaybeHashOf<Self> {
MaybeHashOf::from_inner(
(!self.0.is_empty()).then(|| storage.write_memory_pages_region(self)),
)
}
pub fn as_inner(&self) -> &MemoryPagesRegionInner {
&self.0
}
#[cfg(any(test, feature = "mock"))]
pub fn from_inner(inner: MemoryPagesRegionInner) -> Self {
Self(inner)
}
}
#[derive(Clone, Copy, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, derive_more::Into)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct RegionIdx(u8);
#[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, Hash, derive_more::Into)]
pub struct Allocations {
inner: IntervalsTree<WasmPage>,
#[into(ignore)]
#[codec(skip)]
changed: bool,
}
impl Allocations {
pub fn tree_len(&self) -> u32 {
self.inner.intervals_amount() as u32
}
pub fn update(&mut self, allocations: IntervalsTree<WasmPage>) -> Vec<GearPage> {
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.difference(&self.inner).next().is_some() {
self.changed = true;
self.inner = allocations;
}
removed_pages
}
pub fn store<S: Storage>(self, storage: &S) -> Option<MaybeHashOf<Self>> {
self.changed.then(|| {
MaybeHashOf::from_inner(
(self.inner.intervals_amount() != 0).then(|| storage.write_allocations(self)),
)
})
}
}
#[auto_impl::auto_impl(&, Box)]
pub trait Storage {
fn program_state(&self, hash: H256) -> Option<ProgramState>;
fn write_program_state(&self, state: ProgramState) -> H256;
fn message_queue(&self, hash: HashOf<MessageQueue>) -> Option<MessageQueue>;
fn write_message_queue(&self, queue: MessageQueue) -> HashOf<MessageQueue>;
fn waitlist(&self, hash: HashOf<Waitlist>) -> Option<Waitlist>;
fn write_waitlist(&self, waitlist: Waitlist) -> HashOf<Waitlist>;
fn dispatch_stash(&self, hash: HashOf<DispatchStash>) -> Option<DispatchStash>;
fn write_dispatch_stash(&self, stash: DispatchStash) -> HashOf<DispatchStash>;
fn mailbox(&self, hash: HashOf<Mailbox>) -> Option<Mailbox>;
fn write_mailbox(&self, mailbox: Mailbox) -> HashOf<Mailbox>;
fn user_mailbox(&self, hash: HashOf<UserMailbox>) -> Option<UserMailbox>;
fn write_user_mailbox(&self, user_mailbox: UserMailbox) -> HashOf<UserMailbox>;
fn memory_pages(&self, hash: HashOf<MemoryPages>) -> Option<MemoryPages>;
fn memory_pages_region(&self, hash: HashOf<MemoryPagesRegion>) -> Option<MemoryPagesRegion>;
fn write_memory_pages(&self, pages: MemoryPages) -> HashOf<MemoryPages>;
fn write_memory_pages_region(
&self,
pages_region: MemoryPagesRegion,
) -> HashOf<MemoryPagesRegion>;
fn allocations(&self, hash: HashOf<Allocations>) -> Option<Allocations>;
fn write_allocations(&self, allocations: Allocations) -> HashOf<Allocations>;
fn payload(&self, hash: HashOf<Payload>) -> Option<Payload>;
fn write_payload(&self, payload: Payload) -> HashOf<Payload>;
fn write_payload_raw(&self, payload: Vec<u8>) -> Result<PayloadLookup> {
let payload =
Payload::try_from(payload).map_err(|_| anyhow!("payload exceeds size limit"))?;
let res = if payload.len() < PayloadLookup::STORING_THRESHOLD {
PayloadLookup::Direct(payload)
} else {
PayloadLookup::Stored(self.write_payload(payload))
};
Ok(res)
}
fn page_data(&self, hash: HashOf<PageBuf>) -> Option<PageBuf>;
fn write_page_data(&self, data: PageBuf) -> HashOf<PageBuf>;
fn write_pages_data(
&self,
pages: BTreeMap<GearPage, PageBuf>,
) -> BTreeMap<GearPage, HashOf<PageBuf>> {
pages
.into_iter()
.map(|(k, v)| (k, self.write_page_data(v)))
.collect()
}
}
pub trait QueryableStorage<T>: Storage {
fn query(&self, hash: &MaybeHashOf<T>) -> Result<T>;
}
pub trait ModifiableStorage<T>: QueryableStorage<T> {
fn modify<U>(&self, hash: &mut MaybeHashOf<T>, f: impl FnOnce(&mut T) -> U) -> U;
}
#[derive(Debug, Default)]
pub struct MemStorage {
inner: RefCell<BTreeMap<H256, Vec<u8>>>,
}
impl MemStorage {
fn read<T: Decode>(&self, hash: H256) -> Option<T> {
self.inner
.borrow()
.get(&hash)
.map(|vec| Decode::decode(&mut &vec[..]).unwrap())
}
fn write<T: Encode>(&self, value: T) -> H256 {
let value = value.encode();
let hash = gear_core::utils::hash(&value);
let hash = H256(hash);
self.inner.borrow_mut().insert(hash, value);
hash
}
}
impl Storage for MemStorage {
fn program_state(&self, hash: H256) -> Option<ProgramState> {
self.read(hash)
}
fn write_program_state(&self, state: ProgramState) -> H256 {
self.write(state)
}
fn message_queue(&self, hash: HashOf<MessageQueue>) -> Option<MessageQueue> {
self.read(hash.inner())
}
fn write_message_queue(&self, queue: MessageQueue) -> HashOf<MessageQueue> {
unsafe { HashOf::new(self.write(queue)) }
}
fn waitlist(&self, hash: HashOf<Waitlist>) -> Option<Waitlist> {
self.read(hash.inner())
}
fn write_waitlist(&self, waitlist: Waitlist) -> HashOf<Waitlist> {
unsafe { HashOf::new(self.write(waitlist)) }
}
fn dispatch_stash(&self, hash: HashOf<DispatchStash>) -> Option<DispatchStash> {
self.read(hash.inner())
}
fn write_dispatch_stash(&self, stash: DispatchStash) -> HashOf<DispatchStash> {
unsafe { HashOf::new(self.write(stash)) }
}
fn mailbox(&self, hash: HashOf<Mailbox>) -> Option<Mailbox> {
self.read(hash.inner())
}
fn write_mailbox(&self, mailbox: Mailbox) -> HashOf<Mailbox> {
unsafe { HashOf::new(self.write(mailbox)) }
}
fn user_mailbox(&self, hash: HashOf<UserMailbox>) -> Option<UserMailbox> {
self.read(hash.inner())
}
fn write_user_mailbox(&self, user_mailbox: UserMailbox) -> HashOf<UserMailbox> {
unsafe { HashOf::new(self.write(user_mailbox)) }
}
fn memory_pages(&self, hash: HashOf<MemoryPages>) -> Option<MemoryPages> {
self.read(hash.inner())
}
fn memory_pages_region(&self, hash: HashOf<MemoryPagesRegion>) -> Option<MemoryPagesRegion> {
self.read(hash.inner())
}
fn write_memory_pages(&self, pages: MemoryPages) -> HashOf<MemoryPages> {
unsafe { HashOf::new(self.write(pages)) }
}
fn write_memory_pages_region(
&self,
pages_region: MemoryPagesRegion,
) -> HashOf<MemoryPagesRegion> {
unsafe { HashOf::new(self.write(pages_region)) }
}
fn allocations(&self, hash: HashOf<Allocations>) -> Option<Allocations> {
self.read(hash.inner())
}
fn write_allocations(&self, allocations: Allocations) -> HashOf<Allocations> {
unsafe { HashOf::new(self.write(allocations)) }
}
fn payload(&self, hash: HashOf<Payload>) -> Option<Payload> {
self.read(hash.inner())
}
fn write_payload(&self, payload: Payload) -> HashOf<Payload> {
unsafe { HashOf::new(self.write(payload)) }
}
fn page_data(&self, hash: HashOf<PageBuf>) -> Option<PageBuf> {
self.read(hash.inner())
}
fn write_page_data(&self, data: PageBuf) -> HashOf<PageBuf> {
unsafe { HashOf::new(self.write(data)) }
}
}