diff --git a/common/src/auxiliary/waitlist.rs b/common/src/auxiliary/waitlist.rs
index 81c746d11fd..33ae24ac278 100644
--- a/common/src/auxiliary/waitlist.rs
+++ b/common/src/auxiliary/waitlist.rs
@@ -68,6 +68,7 @@ impl AuxiliaryDoubleStorageWrap for WaitlistStorageWrap {
}
/// An implementor of the error returned from calling `Waitlist` trait functions
+#[derive(Debug)]
pub enum WaitlistErrorImpl {
DuplicateKey,
ElementNotFound,
diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs
index 6b88114f3e5..b6766d43105 100644
--- a/gtest/src/lib.rs
+++ b/gtest/src/lib.rs
@@ -492,28 +492,21 @@
#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")]
#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")]
-mod accounts;
-mod actors;
-mod bank;
-mod blocks;
mod error;
-mod gas_tree;
mod log;
-mod mailbox;
mod manager;
mod program;
+mod state;
mod system;
-mod task_pool;
-mod waitlist;
pub use crate::log::{BlockRunResult, CoreLog, Log};
pub use codec;
pub use error::{Result, TestError};
-pub use mailbox::ActorMailbox;
pub use program::{
calculate_program_id, gbuild::ensure_gbuild, Gas, Program, ProgramBuilder, ProgramIdWrapper,
WasmProgram,
};
+pub use state::mailbox::ActorMailbox;
pub use system::System;
pub use constants::Value;
diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs
index 2b0a59b1ac1..ee1daa20645 100644
--- a/gtest/src/manager.rs
+++ b/gtest/src/manager.rs
@@ -1,37 +1,45 @@
// This file is part of Gear.
-
+//
// Copyright (C) 2021-2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
-
+//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-
+//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
-
+//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+mod exec;
+mod expend;
mod hold_bound;
mod journal;
+mod memory;
mod reservations;
+mod send_dispatch;
mod task;
+mod wait_wake;
use crate::{
- accounts::Accounts,
- actors::{Actors, GenuineProgram, Program, TestActor},
- bank::Bank,
- blocks::BlocksManager,
constants::Value,
- gas_tree::GasTreeManager,
log::{BlockRunResult, CoreLog},
- mailbox::MailboxManager,
program::{Gas, WasmProgram},
- task_pool::TaskPoolManager,
+ state::{
+ accounts::Accounts,
+ actors::{Actors, GenuineProgram, Program, TestActor},
+ bank::Bank,
+ blocks::BlocksManager,
+ gas_tree::GasTreeManager,
+ mailbox::MailboxManager,
+ task_pool::TaskPoolManager,
+ waitlist::WaitlistManager,
+ },
Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT,
GAS_ALLOWANCE, GAS_MULTIPLIER, HOST_FUNC_READ_COST, HOST_FUNC_WRITE_AFTER_READ_COST,
HOST_FUNC_WRITE_COST, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL,
@@ -51,7 +59,11 @@ use core_processor::{
ContextChargedForCode, ContextChargedForInstrumentation, Ext,
};
use gear_common::{
- auxiliary::{gas_provider::PlainNodeId, mailbox::MailboxErrorImpl, BlockNumber},
+ auxiliary::{
+ gas_provider::PlainNodeId, mailbox::MailboxErrorImpl, waitlist::WaitlistErrorImpl,
+ BlockNumber,
+ },
+ event::{MessageWaitedReason, MessageWaitedRuntimeReason},
scheduler::{ScheduledTask, StorageType},
storage::Interval,
LockId, Origin,
@@ -98,7 +110,7 @@ pub(crate) struct ExtManager {
pub(crate) dispatches: VecDeque,
pub(crate) mailbox: MailboxManager,
pub(crate) task_pool: TaskPoolManager,
- pub(crate) wait_list: BTreeMap<(ProgramId, MessageId), (StoredDispatch, Option)>,
+ pub(crate) waitlist: WaitlistManager,
pub(crate) gas_tree: GasTreeManager,
pub(crate) gas_allowance: Gas,
pub(crate) dispatches_stash: HashMap)>,
@@ -173,515 +185,6 @@ impl ExtManager {
self.id_nonce
}
- /// Insert message into the delayed queue.
- pub(crate) fn send_delayed_dispatch(
- &mut self,
- origin_msg: MessageId,
- dispatch: Dispatch,
- delay: u32,
- to_user: bool,
- reservation: Option,
- ) {
- if delay.is_zero() {
- let err_msg = "send_delayed_dispatch: delayed sending with zero delay appeared";
-
- unreachable!("{err_msg}");
- }
-
- let message_id = dispatch.id();
-
- if self.dispatches_stash.contains_key(&message_id) {
- let err_msg = format!(
- "send_delayed_dispatch: stash already has the message id - {id}",
- id = dispatch.id()
- );
-
- unreachable!("{err_msg}");
- }
-
- // Validating dispatch wasn't sent from system with delay.
- if dispatch.is_error_reply() || matches!(dispatch.kind(), DispatchKind::Signal) {
- let err_msg = format!(
- "send_delayed_dispatch: message of an invalid kind is sent: {kind:?}",
- kind = dispatch.kind()
- );
-
- unreachable!("{err_msg}");
- }
-
- let mut to_mailbox = false;
-
- let sender_node = reservation
- .map(Origin::into_origin)
- .unwrap_or_else(|| origin_msg.into_origin());
-
- let from = dispatch.source();
- let value = dispatch.value();
-
- let hold_builder = HoldBoundBuilder::new(StorageType::DispatchStash);
-
- let delay_hold = hold_builder.duration(self, delay);
- let gas_for_delay = delay_hold.lock_amount(self);
-
- let interval_finish = if to_user {
- let threshold = MAILBOX_THRESHOLD;
-
- let gas_limit = dispatch
- .gas_limit()
- .or_else(|| {
- let gas_limit = self.gas_tree.get_limit(sender_node).unwrap_or_else(|e| {
- let err_msg = format!(
- "send_delayed_dispatch: failed getting message gas limit. \
- Lock sponsor id - {sender_node:?}. Got error - {e:?}"
- );
-
- unreachable!("{err_msg}");
- });
-
- (gas_limit.saturating_sub(gas_for_delay) >= threshold).then_some(threshold)
- })
- .unwrap_or_default();
-
- to_mailbox = !dispatch.is_reply() && gas_limit >= threshold;
-
- let gas_amount = if to_mailbox {
- gas_for_delay.saturating_add(gas_limit)
- } else {
- gas_for_delay
- };
-
- self.gas_tree
- .cut(sender_node, message_id, gas_amount)
- .unwrap_or_else(|e| {
- let sender_node = sender_node.cast::();
- let err_msg = format!(
- "send_delayed_dispatch: failed creating cut node. \
- Origin node - {sender_node:?}, cut node id - {id}, amount - {gas_amount}. \
- Got error - {e:?}",
- id = dispatch.id()
- );
-
- unreachable!("{err_msg}");
- });
-
- if !to_mailbox {
- self.gas_tree
- .split_with_value(
- true,
- origin_msg,
- MessageId::generate_reply(dispatch.id()),
- 0,
- )
- .expect("failed to split with value gas node");
- }
-
- if let Some(reservation_id) = reservation {
- self.remove_gas_reservation_with_task(dispatch.source(), reservation_id)
- }
-
- // Locking funds for holding.
- let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
- // Dispatch stash storage is guaranteed to have an associated lock id
- let err_msg =
- "send_delayed_dispatch: No associated lock id for the dispatch stash storage";
-
- unreachable!("{err_msg}");
- });
-
- self.gas_tree.lock(dispatch.id(), lock_id, delay_hold.lock_amount(self))
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_delayed_dispatch: failed locking gas for the user message stash hold. \
- Message id - {message_id}, lock amount - {lock}. Got error - {e:?}",
- message_id = dispatch.id(),
- lock = delay_hold.lock_amount(self));
- unreachable!("{err_msg}");
- });
-
- if delay_hold.expected_duration(self).is_zero() {
- let err_msg = format!(
- "send_delayed_dispatch: user message got zero duration hold bound for dispatch stash. \
- Requested duration - {delay}, block cost - {cost}, source - {from:?}",
- cost = Self::cost_by_storage_type(StorageType::DispatchStash)
- );
-
- unreachable!("{err_msg}");
- }
-
- delay_hold.expected()
- } else {
- match (dispatch.gas_limit(), reservation) {
- (Some(gas_limit), None) => self
- .gas_tree
- .split_with_value(
- dispatch.is_reply(),
- sender_node,
- dispatch.id(),
- gas_limit.saturating_add(gas_for_delay),
- )
- .expect("GasTree corrupted"),
-
- (None, None) => self
- .gas_tree
- .split(dispatch.is_reply(), sender_node, dispatch.id())
- .expect("GasTree corrupted"),
- (Some(gas_limit), Some(reservation_id)) => {
- let err_msg = format!(
- "send_delayed_dispatch: sending dispatch with gas from reservation isn't implemented. \
- Message - {message_id}, sender - {sender}, gas limit - {gas_limit}, reservation - {reservation_id}",
- message_id = dispatch.id(),
- sender = dispatch.source(),
- );
-
- unreachable!("{err_msg}");
- }
-
- (None, Some(reservation_id)) => {
- self.gas_tree
- .split(dispatch.is_reply(), reservation_id, dispatch.id())
- .expect("GasTree corrupted");
- self.remove_gas_reservation_with_task(dispatch.source(), reservation_id);
- }
- }
-
- let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
- // Dispatch stash storage is guaranteed to have an associated lock id
- let err_msg =
- "send_delayed_dispatch: No associated lock id for the dispatch stash storage";
-
- unreachable!("{err_msg}");
- });
-
- self.gas_tree
- .lock(dispatch.id(), lock_id, delay_hold.lock_amount(self))
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_delayed_dispatch: failed locking gas for the program message stash hold. \
- Message id - {message_id}, lock amount - {lock}. Got error - {e:?}",
- message_id = dispatch.id(),
- lock = delay_hold.lock_amount(self)
- );
-
- unreachable!("{err_msg}");
- });
-
- if delay_hold.expected_duration(self).is_zero() {
- let err_msg = format!(
- "send_delayed_dispatch: program message got zero duration hold bound for dispatch stash. \
- Requested duration - {delay}, block cost - {cost}, source - {from:?}",
- cost = Self::cost_by_storage_type(StorageType::DispatchStash)
- );
-
- unreachable!("{err_msg}");
- }
-
- delay_hold.expected()
- };
-
- if !dispatch.value().is_zero() {
- self.bank.deposit_value(from, value, false);
- }
-
- let message_id = dispatch.id();
-
- let start_bn = self.block_height();
- let delay_interval = Interval {
- start: start_bn,
- finish: interval_finish,
- };
-
- self.dispatches_stash
- .insert(message_id, (dispatch.into_stored_delayed(), delay_interval));
-
- let task = if to_user {
- ScheduledTask::SendUserMessage {
- message_id,
- to_mailbox,
- }
- } else {
- ScheduledTask::SendDispatch(message_id)
- };
-
- let task_bn = self.block_height().saturating_add(delay);
-
- self.task_pool.add(task_bn, task).unwrap_or_else(|e| {
- let err_msg = format!(
- "send_delayed_dispatch: failed adding task for delayed message sending. \
- Message to user - {to_user}, message id - {message_id}. Got error - {e:?}"
- );
-
- unreachable!("{err_msg}");
- });
- }
-
- pub(crate) fn send_user_message(
- &mut self,
- origin_msg: MessageId,
- message: Message,
- reservation: Option,
- ) {
- let threshold = MAILBOX_THRESHOLD;
-
- let msg_id = reservation
- .map(Origin::into_origin)
- .unwrap_or_else(|| origin_msg.into_origin());
-
- let gas_limit = message
- .gas_limit()
- .or_else(|| {
- let gas_limit = self.gas_tree.get_limit(msg_id).unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message: failed getting message gas limit. \
- Lock sponsor id - {msg_id}. Got error - {e:?}"
- );
-
- unreachable!("{err_msg}");
- });
-
- // If available gas is greater then threshold,
- // than threshold can be used.
- (gas_limit >= threshold).then_some(threshold)
- })
- .unwrap_or_default();
-
- let message_id = message.id();
- let from = message.source();
- let to = message.destination();
- let value = message.value();
-
- let stored_message = message.into_stored();
- let message: UserMessage = stored_message.clone().try_into().unwrap_or_else(|_| {
- let err_msg = format!(
- "send_user_message: failed conversion from stored into user message. \
- Message id - {message_id}, program id - {from}, destination - {to}",
- );
-
- unreachable!("{err_msg}")
- });
-
- if Accounts::balance(from) != 0 {
- self.bank.deposit_value(from, value, false);
- }
- let _ = if message.details().is_none() && gas_limit >= threshold {
- let hold = HoldBoundBuilder::new(StorageType::Mailbox).maximum_for(self, gas_limit);
-
- if hold.expected_duration(self).is_zero() {
- let err_msg = format!(
- "send_user_message: mailbox message got zero duration hold bound for storing. \
- Gas limit - {gas_limit}, block cost - {cost}, source - {from:?}",
- cost = Self::cost_by_storage_type(StorageType::Mailbox)
- );
-
- unreachable!("{err_msg}");
- }
-
- self.gas_tree
- .cut(msg_id, message.id(), gas_limit)
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message: failed creating cut node. \
- Origin node - {msg_id}, cut node id - {id}, amount - {gas_limit}. \
- Got error - {e:?}",
- id = message.id()
- );
-
- unreachable!("{err_msg}");
- });
-
- self.gas_tree
- .lock(message.id(), LockId::Mailbox, gas_limit)
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message: failed locking gas for the user message mailbox. \
- Message id - {message_id}, lock amount - {gas_limit}. Got error - {e:?}",
- message_id = message.id(),
- );
-
- unreachable!("{err_msg}");
- });
-
- let message_id = message.id();
- let message: UserStoredMessage = message.clone().try_into().unwrap_or_else(|_| {
- // Replies never sent to mailbox
- let err_msg = format!(
- "send_user_message: failed conversion from user into user stored message. \
- Message id - {message_id}, program id - {from:?}, destination - {to:?}",
- );
-
- unreachable!("{err_msg}")
- });
-
- self.mailbox
- .insert(message, hold.expected())
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message: failed inserting message into mailbox. \
- Message id - {message_id}, source - {from:?}, destination - {to:?}, \
- expected bn - {bn:?}. Got error - {e:?}",
- bn = hold.expected(),
- );
-
- unreachable!("{err_msg}");
- });
-
- self.task_pool
- .add(
- hold.expected(),
- ScheduledTask::RemoveFromMailbox(to, message_id),
- )
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message: failed adding task for removing from mailbox. \
- Bn - {bn:?}, sent to - {to:?}, message id - {message_id}. \
- Got error - {e:?}",
- bn = hold.expected()
- );
-
- unreachable!("{err_msg}");
- });
-
- Some(hold.expected())
- } else {
- self.bank.transfer_value(from, to, value);
-
- if message.details().is_none() {
- // Creating auto reply message.
- let reply_message = ReplyMessage::auto(message.id());
-
- self.gas_tree
- .split_with_value(true, origin_msg, reply_message.id(), 0)
- .expect("GasTree corrupted");
- // Converting reply message into appropriate type for queueing.
- let reply_dispatch = reply_message.into_stored_dispatch(
- message.destination(),
- message.source(),
- message.id(),
- );
-
- self.dispatches.push_back(reply_dispatch);
- }
-
- None
- };
- self.log.push(stored_message);
-
- if let Some(reservation_id) = reservation {
- self.remove_gas_reservation_with_task(message.source(), reservation_id);
- }
- }
-
- pub(crate) fn send_user_message_after_delay(&mut self, message: UserMessage, to_mailbox: bool) {
- let from = message.source();
- let to = message.destination();
- let value = message.value();
-
- let _ = if to_mailbox {
- let gas_limit = self.gas_tree.get_limit(message.id()).unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message_after_delay: failed getting message gas limit. \
- Message id - {message_id}. Got error - {e:?}",
- message_id = message.id()
- );
-
- unreachable!("{err_msg}");
- });
-
- let hold = HoldBoundBuilder::new(StorageType::Mailbox).maximum_for(self, gas_limit);
-
- if hold.expected_duration(self).is_zero() {
- let err_msg = format!(
- "send_user_message_after_delay: mailbox message (after delay) got zero duration hold bound for storing. \
- Gas limit - {gas_limit}, block cost - {cost}, source - {from:?}",
- cost = Self::cost_by_storage_type(StorageType::Mailbox)
- );
-
- unreachable!("{err_msg}");
- }
-
- self.gas_tree.lock(message.id(), LockId::Mailbox, gas_limit)
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message_after_delay: failed locking gas for the user message mailbox. \
- Message id - {message_id}, lock amount - {gas_limit}. Got error - {e:?}",
- message_id = message.id(),
- );
-
- unreachable!("{err_msg}");
- });
-
- let message_id = message.id();
- let message: UserStoredMessage = message
- .clone()
- .try_into()
- .unwrap_or_else(|_| {
- // Replies never sent to mailbox
- let err_msg = format!(
- "send_user_message_after_delay: failed conversion from user into user stored message. \
- Message id - {message_id}, program id - {from:?}, destination - {to:?}",
- );
-
- unreachable!("{err_msg}")
- });
- self.mailbox
- .insert(message, hold.expected())
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message_after_delay: failed inserting message into mailbox. \
- Message id - {message_id}, source - {from:?}, destination - {to:?}, \
- expected bn - {bn:?}. Got error - {e:?}",
- bn = hold.expected(),
- );
-
- unreachable!("{err_msg}");
- });
-
- // Adding removal request in task pool
-
- self.task_pool
- .add(
- hold.expected(),
- ScheduledTask::RemoveFromMailbox(to, message_id),
- )
- .unwrap_or_else(|e| {
- let err_msg = format!(
- "send_user_message_after_delay: failed adding task for removing from mailbox. \
- Bn - {bn:?}, sent to - {to:?}, message id - {message_id}. \
- Got error - {e:?}",
- bn = hold.expected()
- );
-
- unreachable!("{err_msg}");
- });
-
- Some(hold.expected())
- } else {
- self.bank.transfer_value(from, to, value);
-
- // Message is never reply here, because delayed reply sending forbidden.
- if message.details().is_none() {
- // Creating reply message.
- let reply_message = ReplyMessage::auto(message.id());
-
- // `GasNode` was created on send already.
-
- // Converting reply message into appropriate type for queueing.
- let reply_dispatch = reply_message.into_stored_dispatch(
- message.destination(),
- message.source(),
- message.id(),
- );
-
- // Queueing dispatch.
- self.dispatches.push_back(reply_dispatch);
- }
-
- self.consume_and_retrieve(message.id());
- None
- };
-
- self.log.push(message.into());
- }
-
/// Check if the current block number should trigger new epoch and reset
/// the provided random data.
pub(crate) fn check_epoch(&mut self) {
@@ -715,235 +218,6 @@ impl ExtManager {
});
}
- pub(crate) fn validate_and_route_dispatch(&mut self, dispatch: Dispatch) -> MessageId {
- self.validate_dispatch(&dispatch);
- let gas_limit = dispatch
- .gas_limit()
- .unwrap_or_else(|| unreachable!("message from program API always has gas"));
- self.gas_tree
- .create(
- dispatch.source(),
- dispatch.id(),
- gas_limit,
- dispatch.is_reply(),
- )
- .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));
- self.route_dispatch(dispatch)
- }
-
- pub(crate) fn route_dispatch(&mut self, dispatch: Dispatch) -> MessageId {
- let stored_dispatch = dispatch.into_stored();
- if Actors::is_user(stored_dispatch.destination()) {
- panic!("Program API only sends message to programs.")
- }
-
- let message_id = stored_dispatch.id();
- self.dispatches.push_back(stored_dispatch);
-
- message_id
- }
-
- // TODO #4120 Charge for task pool processing the gas from gas allowance
- // TODO #4121
- #[track_caller]
- pub(crate) fn run_new_block(&mut self, allowance: Gas) -> BlockRunResult {
- self.gas_allowance = allowance;
- self.blocks_manager.next_block();
- let new_block_bn = self.block_height();
-
- self.process_tasks(new_block_bn);
- let total_processed = self.process_messages();
-
- BlockRunResult {
- block_info: self.blocks_manager.get(),
- gas_allowance_spent: Gas(GAS_ALLOWANCE) - self.gas_allowance,
- succeed: mem::take(&mut self.succeed),
- failed: mem::take(&mut self.failed),
- not_executed: mem::take(&mut self.not_executed),
- total_processed,
- log: mem::take(&mut self.log)
- .into_iter()
- .map(CoreLog::from)
- .collect(),
- gas_burned: mem::take(&mut self.gas_burned),
- }
- }
-
- #[track_caller]
- pub(crate) fn process_tasks(&mut self, bn: u32) {
- for task in self.task_pool.drain_prefix_keys(bn) {
- task.process_with(self);
- }
- }
-
- #[track_caller]
- fn process_messages(&mut self) -> u32 {
- self.messages_processing_enabled = true;
-
- let mut total_processed = 0;
- while self.messages_processing_enabled {
- let dispatch = match self.dispatches.pop_front() {
- Some(dispatch) => dispatch,
- None => break,
- };
-
- enum DispatchCase {
- Dormant,
- Normal(ExecutableActorData, InstrumentedCode),
- Mock(Box),
- }
-
- let dispatch_case = Actors::modify(dispatch.destination(), |actor| {
- let actor = actor
- .unwrap_or_else(|| panic!("Somehow message queue contains message for user"));
- if actor.is_dormant() {
- DispatchCase::Dormant
- } else if let Some((data, code)) = actor.get_executable_actor_data() {
- DispatchCase::Normal(data, code)
- } else if let Some(mock) = actor.take_mock() {
- DispatchCase::Mock(mock)
- } else {
- unreachable!();
- }
- });
- let balance = Accounts::reducible_balance(dispatch.destination());
-
- match dispatch_case {
- DispatchCase::Dormant => self.process_dormant(balance, dispatch),
- DispatchCase::Normal(data, code) => {
- self.process_normal(balance, data, code, dispatch)
- }
- DispatchCase::Mock(mock) => self.process_mock(mock, dispatch),
- }
-
- total_processed += 1;
- }
-
- total_processed
- }
-
- #[track_caller]
- fn validate_dispatch(&mut self, dispatch: &Dispatch) {
- let source = dispatch.source();
- let destination = dispatch.destination();
-
- if Actors::is_program(source) {
- panic!("Sending messages allowed only from users id");
- }
-
- // User must exist
- if !Accounts::exists(source) {
- panic!("User's {source} balance is zero; mint value to it first.");
- }
-
- let is_init_msg = dispatch.kind().is_init();
- // We charge ED only for init messages
- let maybe_ed = if is_init_msg { EXISTENTIAL_DEPOSIT } else { 0 };
- let balance = Accounts::balance(source);
-
- let gas_limit = dispatch
- .gas_limit()
- .unwrap_or_else(|| unreachable!("message from program API always has gas"));
- let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit);
-
- // Check sender has enough balance to cover dispatch costs
- if balance < { dispatch.value() + gas_value + maybe_ed } {
- panic!(
- "Insufficient balance: user ({}) tries to send \
- ({}) value, ({}) gas and ED ({}), while his balance ({:?})",
- source,
- dispatch.value(),
- gas_value,
- maybe_ed,
- balance,
- );
- }
-
- // Charge for program ED upon creation
- if is_init_msg {
- Accounts::transfer(source, destination, EXISTENTIAL_DEPOSIT, false);
- }
-
- if dispatch.value() != 0 {
- // Deposit message value
- self.bank.deposit_value(source, dispatch.value(), false);
- }
-
- // Deposit gas
- self.bank.deposit_gas(source, gas_limit, false);
- }
-
- /// Call non-void meta function from actor stored in manager.
- /// Warning! This is a static call that doesn't change actors pages data.
- pub(crate) fn read_state_bytes(
- &mut self,
- payload: Vec,
- program_id: &ProgramId,
- ) -> Result> {
- let executable_actor_data = Actors::modify(*program_id, |actor| {
- if let Some(actor) = actor {
- Ok(actor.get_executable_actor_data())
- } else {
- Err(TestError::ActorNotFound(*program_id))
- }
- })?;
-
- if let Some((data, code)) = executable_actor_data {
- core_processor::informational::execute_for_reply::, _>(
- String::from("state"),
- code,
- Some(data.allocations),
- Some((*program_id, Default::default())),
- payload,
- GAS_ALLOWANCE,
- self.blocks_manager.get(),
- )
- .map_err(TestError::ReadStateError)
- } else if let Some(mut program_mock) = Actors::modify(*program_id, |actor| {
- actor.expect("Checked before").take_mock()
- }) {
- program_mock
- .state()
- .map_err(|err| TestError::ReadStateError(err.into()))
- } else {
- Err(TestError::ActorIsNotExecutable(*program_id))
- }
- }
-
- pub(crate) fn read_state_bytes_using_wasm(
- &mut self,
- payload: Vec,
- program_id: &ProgramId,
- fn_name: &str,
- wasm: Vec,
- args: Option>,
- ) -> Result> {
- let mapping_code = Code::try_new_mock_const_or_no_rules(
- wasm,
- true,
- TryNewCodeConfig::new_no_exports_check(),
- )
- .map_err(|_| TestError::Instrumentation)?;
-
- let mapping_code = InstrumentedCodeAndId::from(CodeAndId::new(mapping_code))
- .into_parts()
- .0;
-
- let mut mapping_code_payload = args.unwrap_or_default();
- mapping_code_payload.append(&mut self.read_state_bytes(payload, program_id)?);
-
- core_processor::informational::execute_for_reply::, _>(
- String::from(fn_name),
- mapping_code,
- None,
- None,
- mapping_code_payload,
- GAS_ALLOWANCE,
- self.blocks_manager.get(),
- )
- .map_err(TestError::ReadStateError)
- }
-
pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Value) {
Accounts::increase(*id, value);
}
@@ -952,31 +226,6 @@ impl ExtManager {
Accounts::balance(*id)
}
- pub(crate) fn read_mailbox_message(
- &mut self,
- to: ProgramId,
- from_mid: MessageId,
- ) -> Result {
- let (message, hold_interval) = self.mailbox.remove(to, from_mid)?;
-
- let expected = hold_interval.finish;
-
- let user_id = message.destination();
- let from = message.source();
-
- self.charge_for_hold(message.id(), hold_interval, StorageType::Mailbox);
- self.consume_and_retrieve(message.id());
-
- self.bank.transfer_value(from, user_id, message.value());
-
- let _ = self.task_pool.delete(
- expected,
- ScheduledTask::RemoveFromMailbox(user_id, message.id()),
- );
-
- Ok(message)
- }
-
#[track_caller]
pub(crate) fn override_balance(&mut self, &id: &ProgramId, balance: Value) {
if Actors::is_user(id) && balance < crate::EXISTENTIAL_DEPOSIT {
@@ -989,23 +238,6 @@ impl ExtManager {
Accounts::override_balance(id, balance);
}
- #[track_caller]
- pub(crate) fn read_memory_pages(&self, program_id: &ProgramId) -> BTreeMap {
- Actors::access(*program_id, |actor| {
- let program = match actor.unwrap_or_else(|| panic!("Actor id {program_id:?} not found"))
- {
- TestActor::Initialized(program) => program,
- TestActor::Uninitialized(_, program) => program.as_ref().unwrap(),
- TestActor::Dormant => panic!("Actor {program_id} isn't dormant"),
- };
-
- match program {
- Program::Genuine(program) => program.pages_data.clone(),
- Program::Mock(_) => panic!("Can't read memory of mock program"),
- }
- })
- }
-
#[track_caller]
fn init_success(&mut self, program_id: ProgramId) {
Actors::modify(program_id, |actor| {
@@ -1028,247 +260,6 @@ impl ExtManager {
}
}
- fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) {
- enum Mocked {
- Reply(Option>),
- Signal,
- }
-
- let message_id = dispatch.id();
- let source = dispatch.source();
- let program_id = dispatch.destination();
- let payload = dispatch.payload_bytes().to_vec();
-
- let response = match dispatch.kind() {
- DispatchKind::Init => mock.init(payload).map(Mocked::Reply),
- DispatchKind::Handle => mock.handle(payload).map(Mocked::Reply),
- DispatchKind::Reply => mock.handle_reply(payload).map(|_| Mocked::Reply(None)),
- DispatchKind::Signal => mock.handle_signal(payload).map(|_| Mocked::Signal),
- };
-
- match response {
- Ok(Mocked::Reply(reply)) => {
- let maybe_reply_message = if let Some(payload) = reply {
- let id = MessageId::generate_reply(message_id);
- let packet = ReplyPacket::new(payload.try_into().unwrap(), 0);
- Some(ReplyMessage::from_packet(id, packet))
- } else {
- (!dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal)
- .then_some(ReplyMessage::auto(message_id))
- };
-
- if let Some(reply_message) = maybe_reply_message {
- ::send_dispatch(
- self,
- message_id,
- reply_message.into_dispatch(program_id, dispatch.source(), message_id),
- 0,
- None,
- );
- }
-
- if let DispatchKind::Init = dispatch.kind() {
- self.message_dispatched(
- message_id,
- source,
- DispatchOutcome::InitSuccess { program_id },
- );
- }
- }
- Ok(Mocked::Signal) => {}
- Err(expl) => {
- mock.debug(expl);
-
- if let DispatchKind::Init = dispatch.kind() {
- self.message_dispatched(
- message_id,
- source,
- DispatchOutcome::InitFailure {
- program_id,
- origin: source,
- reason: expl.to_string(),
- },
- );
- } else {
- self.message_dispatched(
- message_id,
- source,
- DispatchOutcome::MessageTrap {
- program_id,
- trap: expl.to_string(),
- },
- )
- }
-
- if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
- let err = ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic);
- let err_payload = expl
- .as_bytes()
- .to_vec()
- .try_into()
- .unwrap_or_else(|_| unreachable!("Error message is too large"));
-
- let reply_message = ReplyMessage::system(message_id, err_payload, err);
-
- ::send_dispatch(
- self,
- message_id,
- reply_message.into_dispatch(program_id, dispatch.source(), message_id),
- 0,
- None,
- );
- }
- }
- }
-
- // After run either `init_success` is called or `init_failed`.
- // So only active (init success) program can be modified
- Actors::modify(program_id, |actor| {
- if let Some(TestActor::Initialized(old_mock)) = actor {
- *old_mock = Program::Mock(Some(mock));
- }
- })
- }
-
- fn process_normal(
- &mut self,
- balance: u128,
- data: ExecutableActorData,
- code: InstrumentedCode,
- dispatch: StoredDispatch,
- ) {
- self.process_dispatch(balance, Some((data, code)), dispatch);
- }
-
- fn process_dormant(&mut self, balance: u128, dispatch: StoredDispatch) {
- self.process_dispatch(balance, None, dispatch);
- }
-
- #[track_caller]
- fn process_dispatch(
- &mut self,
- balance: u128,
- data: Option<(ExecutableActorData, InstrumentedCode)>,
- dispatch: StoredDispatch,
- ) {
- let dest = dispatch.destination();
- let gas_limit = self
- .gas_tree
- .get_limit(dispatch.id())
- .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));
- let block_config = BlockConfig {
- block_info: self.blocks_manager.get(),
- performance_multiplier: gsys::Percent::new(100),
- forbidden_funcs: Default::default(),
- reserve_for: RESERVE_FOR,
- gas_multiplier: gsys::GasMultiplier::from_value_per_gas(VALUE_PER_GAS),
- costs: ProcessCosts {
- ext: ExtCosts {
- syscalls: Default::default(),
- rent: RentCosts {
- waitlist: WAITLIST_COST.into(),
- dispatch_stash: DISPATCH_HOLD_COST.into(),
- reservation: RESERVATION_COST.into(),
- },
- mem_grow: Default::default(),
- mem_grow_per_page: Default::default(),
- },
- lazy_pages: LazyPagesCosts {
- host_func_read: HOST_FUNC_READ_COST.into(),
- host_func_write: HOST_FUNC_WRITE_COST.into(),
- host_func_write_after_read: HOST_FUNC_WRITE_AFTER_READ_COST.into(),
- load_page_storage_data: LOAD_PAGE_STORAGE_DATA_COST.into(),
- signal_read: SIGNAL_READ_COST.into(),
- signal_write: SIGNAL_WRITE_COST.into(),
- signal_write_after_read: SIGNAL_WRITE_AFTER_READ_COST.into(),
- },
- read: READ_COST.into(),
- read_per_byte: READ_PER_BYTE_COST.into(),
- write: WRITE_COST.into(),
- instrumentation: MODULE_INSTRUMENTATION_COST.into(),
- instrumentation_per_byte: MODULE_INSTRUMENTATION_BYTE_COST.into(),
- instantiation_costs: InstantiationCosts {
- code_section_per_byte: MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST.into(),
- data_section_per_byte: MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST.into(),
- global_section_per_byte: MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST.into(),
- table_section_per_byte: MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST.into(),
- element_section_per_byte: MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST.into(),
- type_section_per_byte: MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST.into(),
- },
- load_allocations_per_interval: LOAD_ALLOCATIONS_PER_INTERVAL.into(),
- },
- existential_deposit: EXISTENTIAL_DEPOSIT,
- mailbox_threshold: MAILBOX_THRESHOLD,
- max_reservations: MAX_RESERVATIONS,
- max_pages: TESTS_MAX_PAGES_NUMBER.into(),
- outgoing_limit: OUTGOING_LIMIT,
- outgoing_bytes_limit: OUTGOING_BYTES_LIMIT,
- };
-
- let context = match core_processor::precharge_for_program(
- &block_config,
- self.gas_allowance.0,
- dispatch.into_incoming(gas_limit),
- dest,
- ) {
- Ok(d) => d,
- Err(journal) => {
- core_processor::handle_journal(journal, self);
- return;
- }
- };
-
- let Some((actor_data, code)) = data else {
- let journal = core_processor::process_non_executable(context);
- core_processor::handle_journal(journal, self);
- return;
- };
-
- let context = match core_processor::precharge_for_allocations(
- &block_config,
- context,
- actor_data.allocations.intervals_amount() as u32,
- ) {
- Ok(c) => c,
- Err(journal) => {
- core_processor::handle_journal(journal, self);
- return;
- }
- };
-
- let context =
- match core_processor::precharge_for_code_length(&block_config, context, actor_data) {
- Ok(c) => c,
- Err(journal) => {
- core_processor::handle_journal(journal, self);
- return;
- }
- };
-
- let context = ContextChargedForCode::from(context);
- let context = ContextChargedForInstrumentation::from(context);
- let context = match core_processor::precharge_for_module_instantiation(
- &block_config,
- context,
- code.instantiated_section_sizes(),
- ) {
- Ok(c) => c,
- Err(journal) => {
- core_processor::handle_journal(journal, self);
- return;
- }
- };
-
- let journal = core_processor::process::>(
- &block_config,
- (context, code, balance).into(),
- self.random_data.clone(),
- )
- .unwrap_or_else(|e| unreachable!("core-processor logic violated: {}", e));
-
- core_processor::handle_journal(journal, self);
- }
-
pub(crate) fn update_genuine_program R>(
&mut self,
id: ProgramId,
@@ -1279,42 +270,28 @@ impl ExtManager {
})
}
- fn cost_by_storage_type(storage_type: StorageType) -> u64 {
- // Cost per block based on the storage used for holding
- match storage_type {
- StorageType::Code => todo!("#646"),
- StorageType::Waitlist => WAITLIST_COST,
- StorageType::Mailbox => MAILBOX_COST,
- StorageType::DispatchStash => DISPATCH_HOLD_COST,
- StorageType::Program => todo!("#646"),
- StorageType::Reservation => RESERVATION_COST,
- }
- }
+ pub(crate) fn read_mailbox_message(
+ &mut self,
+ to: ProgramId,
+ from_mid: MessageId,
+ ) -> Result {
+ let (message, hold_interval) = self.mailbox.remove(to, from_mid)?;
- /// Spends given amount of gas from given `MessageId` in `GasTree`.
- ///
- /// Represents logic of burning gas by transferring gas from
- /// current `GasTree` owner to actual block producer.
- pub fn spend_gas(&mut self, id: MessageId, amount: u64) {
- if amount.is_zero() {
- return;
- }
+ let expected = hold_interval.finish;
- self.gas_tree.spend(id, amount).unwrap_or_else(|e| {
- let err_msg = format!(
- "spend_gas: failed spending gas. Message id - {id}, amount - {amount}. Got error - {e:?}"
- );
+ let user_id = message.destination();
+ let from = message.source();
- unreachable!("{err_msg}");
- });
+ self.charge_for_hold(message.id(), hold_interval, StorageType::Mailbox);
+ self.consume_and_retrieve(message.id());
- let (external, multiplier, _) = self.gas_tree.get_origin_node(id).unwrap_or_else(|e| {
- let err_msg = format!(
- "spend_gas: failed getting origin node for the current one. Message id - {id}, Got error - {e:?}"
- );
- unreachable!("{err_msg}");
- });
+ self.bank.transfer_value(from, user_id, message.value());
- self.bank.spend_gas(external.cast(), amount, multiplier)
+ let _ = self.task_pool.delete(
+ expected,
+ ScheduledTask::RemoveFromMailbox(user_id, message.id()),
+ );
+
+ Ok(message)
}
}
diff --git a/gtest/src/manager/exec.rs b/gtest/src/manager/exec.rs
new file mode 100644
index 00000000000..85ba54ca0ee
--- /dev/null
+++ b/gtest/src/manager/exec.rs
@@ -0,0 +1,420 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+
+impl ExtManager {
+ pub(crate) fn validate_and_route_dispatch(&mut self, dispatch: Dispatch) -> MessageId {
+ self.validate_dispatch(&dispatch);
+ let gas_limit = dispatch
+ .gas_limit()
+ .unwrap_or_else(|| unreachable!("message from program API always has gas"));
+ self.gas_tree
+ .create(
+ dispatch.source(),
+ dispatch.id(),
+ gas_limit,
+ dispatch.is_reply(),
+ )
+ .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));
+ self.route_dispatch(dispatch)
+ }
+
+ #[track_caller]
+ fn validate_dispatch(&mut self, dispatch: &Dispatch) {
+ let source = dispatch.source();
+ let destination = dispatch.destination();
+
+ if Actors::is_program(source) {
+ panic!("Sending messages allowed only from users id");
+ }
+
+ // User must exist
+ if !Accounts::exists(source) {
+ panic!("User's {source} balance is zero; mint value to it first.");
+ }
+
+ let is_init_msg = dispatch.kind().is_init();
+ // We charge ED only for init messages
+ let maybe_ed = if is_init_msg { EXISTENTIAL_DEPOSIT } else { 0 };
+ let balance = Accounts::balance(source);
+
+ let gas_limit = dispatch
+ .gas_limit()
+ .unwrap_or_else(|| unreachable!("message from program API always has gas"));
+ let gas_value = GAS_MULTIPLIER.gas_to_value(gas_limit);
+
+ // Check sender has enough balance to cover dispatch costs
+ if balance < { dispatch.value() + gas_value + maybe_ed } {
+ panic!(
+ "Insufficient balance: user ({}) tries to send \
+ ({}) value, ({}) gas and ED ({}), while his balance ({:?})",
+ source,
+ dispatch.value(),
+ gas_value,
+ maybe_ed,
+ balance,
+ );
+ }
+
+ // Charge for program ED upon creation
+ if is_init_msg {
+ Accounts::transfer(source, destination, EXISTENTIAL_DEPOSIT, false);
+ }
+
+ if dispatch.value() != 0 {
+ // Deposit message value
+ self.bank.deposit_value(source, dispatch.value(), false);
+ }
+
+ // Deposit gas
+ self.bank.deposit_gas(source, gas_limit, false);
+ }
+
+ pub(crate) fn route_dispatch(&mut self, dispatch: Dispatch) -> MessageId {
+ let stored_dispatch = dispatch.into_stored();
+ if Actors::is_user(stored_dispatch.destination()) {
+ panic!("Program API only sends message to programs.")
+ }
+
+ let message_id = stored_dispatch.id();
+ self.dispatches.push_back(stored_dispatch);
+
+ message_id
+ }
+
+ // TODO #4120 Charge for task pool processing the gas from gas allowance
+ // TODO #4121
+ #[track_caller]
+ pub(crate) fn run_new_block(&mut self, allowance: Gas) -> BlockRunResult {
+ self.gas_allowance = allowance;
+ self.blocks_manager.next_block();
+ let new_block_bn = self.block_height();
+
+ self.process_tasks(new_block_bn);
+ let total_processed = self.process_messages();
+
+ BlockRunResult {
+ block_info: self.blocks_manager.get(),
+ gas_allowance_spent: Gas(GAS_ALLOWANCE) - self.gas_allowance,
+ succeed: mem::take(&mut self.succeed),
+ failed: mem::take(&mut self.failed),
+ not_executed: mem::take(&mut self.not_executed),
+ total_processed,
+ log: mem::take(&mut self.log)
+ .into_iter()
+ .map(CoreLog::from)
+ .collect(),
+ gas_burned: mem::take(&mut self.gas_burned),
+ }
+ }
+
+ #[track_caller]
+ pub(crate) fn process_tasks(&mut self, bn: u32) {
+ for task in self.task_pool.drain_prefix_keys(bn) {
+ task.process_with(self);
+ }
+ }
+
+ #[track_caller]
+ fn process_messages(&mut self) -> u32 {
+ self.messages_processing_enabled = true;
+
+ let mut total_processed = 0;
+ while self.messages_processing_enabled {
+ let dispatch = match self.dispatches.pop_front() {
+ Some(dispatch) => dispatch,
+ None => break,
+ };
+
+ enum DispatchCase {
+ Dormant,
+ Normal(ExecutableActorData, InstrumentedCode),
+ Mock(Box),
+ }
+
+ let dispatch_case = Actors::modify(dispatch.destination(), |actor| {
+ let actor = actor
+ .unwrap_or_else(|| panic!("Somehow message queue contains message for user"));
+ if actor.is_dormant() {
+ DispatchCase::Dormant
+ } else if let Some((data, code)) = actor.get_executable_actor_data() {
+ DispatchCase::Normal(data, code)
+ } else if let Some(mock) = actor.take_mock() {
+ DispatchCase::Mock(mock)
+ } else {
+ unreachable!();
+ }
+ });
+ let balance = Accounts::reducible_balance(dispatch.destination());
+
+ match dispatch_case {
+ DispatchCase::Dormant => self.process_dormant(balance, dispatch),
+ DispatchCase::Normal(data, code) => {
+ self.process_normal(balance, data, code, dispatch)
+ }
+ DispatchCase::Mock(mock) => self.process_mock(mock, dispatch),
+ }
+
+ total_processed += 1;
+ }
+
+ total_processed
+ }
+
+ fn process_mock(&mut self, mut mock: Box, dispatch: StoredDispatch) {
+ enum Mocked {
+ Reply(Option>),
+ Signal,
+ }
+
+ let message_id = dispatch.id();
+ let source = dispatch.source();
+ let program_id = dispatch.destination();
+ let payload = dispatch.payload_bytes().to_vec();
+
+ let response = match dispatch.kind() {
+ DispatchKind::Init => mock.init(payload).map(Mocked::Reply),
+ DispatchKind::Handle => mock.handle(payload).map(Mocked::Reply),
+ DispatchKind::Reply => mock.handle_reply(payload).map(|_| Mocked::Reply(None)),
+ DispatchKind::Signal => mock.handle_signal(payload).map(|_| Mocked::Signal),
+ };
+
+ match response {
+ Ok(Mocked::Reply(reply)) => {
+ let maybe_reply_message = if let Some(payload) = reply {
+ let id = MessageId::generate_reply(message_id);
+ let packet = ReplyPacket::new(payload.try_into().unwrap(), 0);
+ Some(ReplyMessage::from_packet(id, packet))
+ } else {
+ (!dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal)
+ .then_some(ReplyMessage::auto(message_id))
+ };
+
+ if let Some(reply_message) = maybe_reply_message {
+ ::send_dispatch(
+ self,
+ message_id,
+ reply_message.into_dispatch(program_id, dispatch.source(), message_id),
+ 0,
+ None,
+ );
+ }
+
+ if let DispatchKind::Init = dispatch.kind() {
+ self.message_dispatched(
+ message_id,
+ source,
+ DispatchOutcome::InitSuccess { program_id },
+ );
+ }
+ }
+ Ok(Mocked::Signal) => {}
+ Err(expl) => {
+ mock.debug(expl);
+
+ if let DispatchKind::Init = dispatch.kind() {
+ self.message_dispatched(
+ message_id,
+ source,
+ DispatchOutcome::InitFailure {
+ program_id,
+ origin: source,
+ reason: expl.to_string(),
+ },
+ );
+ } else {
+ self.message_dispatched(
+ message_id,
+ source,
+ DispatchOutcome::MessageTrap {
+ program_id,
+ trap: expl.to_string(),
+ },
+ )
+ }
+
+ if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
+ let err = ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic);
+ let err_payload = expl
+ .as_bytes()
+ .to_vec()
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Error message is too large"));
+
+ let reply_message = ReplyMessage::system(message_id, err_payload, err);
+
+ ::send_dispatch(
+ self,
+ message_id,
+ reply_message.into_dispatch(program_id, dispatch.source(), message_id),
+ 0,
+ None,
+ );
+ }
+ }
+ }
+
+ // After run either `init_success` is called or `init_failed`.
+ // So only active (init success) program can be modified
+ Actors::modify(program_id, |actor| {
+ if let Some(TestActor::Initialized(old_mock)) = actor {
+ *old_mock = Program::Mock(Some(mock));
+ }
+ })
+ }
+
+ fn process_normal(
+ &mut self,
+ balance: u128,
+ data: ExecutableActorData,
+ code: InstrumentedCode,
+ dispatch: StoredDispatch,
+ ) {
+ self.process_dispatch(balance, Some((data, code)), dispatch);
+ }
+
+ fn process_dormant(&mut self, balance: u128, dispatch: StoredDispatch) {
+ self.process_dispatch(balance, None, dispatch);
+ }
+
+ #[track_caller]
+ fn process_dispatch(
+ &mut self,
+ balance: u128,
+ data: Option<(ExecutableActorData, InstrumentedCode)>,
+ dispatch: StoredDispatch,
+ ) {
+ let dest = dispatch.destination();
+ let gas_limit = self
+ .gas_tree
+ .get_limit(dispatch.id())
+ .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));
+ let block_config = BlockConfig {
+ block_info: self.blocks_manager.get(),
+ performance_multiplier: gsys::Percent::new(100),
+ forbidden_funcs: Default::default(),
+ reserve_for: RESERVE_FOR,
+ gas_multiplier: gsys::GasMultiplier::from_value_per_gas(VALUE_PER_GAS),
+ costs: ProcessCosts {
+ ext: ExtCosts {
+ syscalls: Default::default(),
+ rent: RentCosts {
+ waitlist: WAITLIST_COST.into(),
+ dispatch_stash: DISPATCH_HOLD_COST.into(),
+ reservation: RESERVATION_COST.into(),
+ },
+ mem_grow: Default::default(),
+ mem_grow_per_page: Default::default(),
+ },
+ lazy_pages: LazyPagesCosts {
+ host_func_read: HOST_FUNC_READ_COST.into(),
+ host_func_write: HOST_FUNC_WRITE_COST.into(),
+ host_func_write_after_read: HOST_FUNC_WRITE_AFTER_READ_COST.into(),
+ load_page_storage_data: LOAD_PAGE_STORAGE_DATA_COST.into(),
+ signal_read: SIGNAL_READ_COST.into(),
+ signal_write: SIGNAL_WRITE_COST.into(),
+ signal_write_after_read: SIGNAL_WRITE_AFTER_READ_COST.into(),
+ },
+ read: READ_COST.into(),
+ read_per_byte: READ_PER_BYTE_COST.into(),
+ write: WRITE_COST.into(),
+ instrumentation: MODULE_INSTRUMENTATION_COST.into(),
+ instrumentation_per_byte: MODULE_INSTRUMENTATION_BYTE_COST.into(),
+ instantiation_costs: InstantiationCosts {
+ code_section_per_byte: MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST.into(),
+ data_section_per_byte: MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST.into(),
+ global_section_per_byte: MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST.into(),
+ table_section_per_byte: MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST.into(),
+ element_section_per_byte: MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST.into(),
+ type_section_per_byte: MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST.into(),
+ },
+ load_allocations_per_interval: LOAD_ALLOCATIONS_PER_INTERVAL.into(),
+ },
+ existential_deposit: EXISTENTIAL_DEPOSIT,
+ mailbox_threshold: MAILBOX_THRESHOLD,
+ max_reservations: MAX_RESERVATIONS,
+ max_pages: TESTS_MAX_PAGES_NUMBER.into(),
+ outgoing_limit: OUTGOING_LIMIT,
+ outgoing_bytes_limit: OUTGOING_BYTES_LIMIT,
+ };
+
+ let context = match core_processor::precharge_for_program(
+ &block_config,
+ self.gas_allowance.0,
+ dispatch.into_incoming(gas_limit),
+ dest,
+ ) {
+ Ok(d) => d,
+ Err(journal) => {
+ core_processor::handle_journal(journal, self);
+ return;
+ }
+ };
+
+ let Some((actor_data, code)) = data else {
+ let journal = core_processor::process_non_executable(context);
+ core_processor::handle_journal(journal, self);
+ return;
+ };
+
+ let context = match core_processor::precharge_for_allocations(
+ &block_config,
+ context,
+ actor_data.allocations.intervals_amount() as u32,
+ ) {
+ Ok(c) => c,
+ Err(journal) => {
+ core_processor::handle_journal(journal, self);
+ return;
+ }
+ };
+
+ let context =
+ match core_processor::precharge_for_code_length(&block_config, context, actor_data) {
+ Ok(c) => c,
+ Err(journal) => {
+ core_processor::handle_journal(journal, self);
+ return;
+ }
+ };
+
+ let context = ContextChargedForCode::from(context);
+ let context = ContextChargedForInstrumentation::from(context);
+ let context = match core_processor::precharge_for_module_instantiation(
+ &block_config,
+ context,
+ code.instantiated_section_sizes(),
+ ) {
+ Ok(c) => c,
+ Err(journal) => {
+ core_processor::handle_journal(journal, self);
+ return;
+ }
+ };
+
+ let journal = core_processor::process::>(
+ &block_config,
+ (context, code, balance).into(),
+ self.random_data.clone(),
+ )
+ .unwrap_or_else(|e| unreachable!("core-processor logic violated: {}", e));
+
+ core_processor::handle_journal(journal, self);
+ }
+}
diff --git a/gtest/src/manager/expend.rs b/gtest/src/manager/expend.rs
new file mode 100644
index 00000000000..89c829ff1b7
--- /dev/null
+++ b/gtest/src/manager/expend.rs
@@ -0,0 +1,125 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+use gear_common::gas_provider::Imbalance;
+
+impl ExtManager {
+ /// Spends given amount of gas from given `MessageId` in `GasTree`.
+ ///
+ /// Represents logic of burning gas by transferring gas from
+ /// current `GasTree` owner to actual block producer.
+ pub(crate) fn spend_gas(&mut self, id: MessageId, amount: u64) {
+ if amount.is_zero() {
+ return;
+ }
+
+ self.gas_tree.spend(id, amount).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "spend_gas: failed spending gas. Message id - {id}, amount - {amount}. Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ let (external, multiplier, _) = self.gas_tree.get_origin_node(id).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "spend_gas: failed getting origin node for the current one. Message id - {id}, Got error - {e:?}"
+ );
+ unreachable!("{err_msg}");
+ });
+
+ self.bank.spend_gas(external.cast(), amount, multiplier)
+ }
+
+ pub(crate) fn cost_by_storage_type(storage_type: StorageType) -> u64 {
+ // Cost per block based on the storage used for holding
+ match storage_type {
+ StorageType::Code => todo!("#646"),
+ StorageType::Waitlist => WAITLIST_COST,
+ StorageType::Mailbox => MAILBOX_COST,
+ StorageType::DispatchStash => DISPATCH_HOLD_COST,
+ StorageType::Program => todo!("#646"),
+ StorageType::Reservation => RESERVATION_COST,
+ }
+ }
+
+ pub(crate) fn consume_and_retrieve(&mut self, id: impl Origin) {
+ let outcome = self.gas_tree.consume(id).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "consume_and_retrieve: failed consuming the rest of gas. Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}")
+ });
+
+ if let Some((imbalance, multiplier, external)) = outcome {
+ let gas_left = imbalance.peek();
+
+ if !gas_left.is_zero() {
+ self.bank
+ .withdraw_gas(external.cast(), gas_left, multiplier);
+ }
+ }
+ }
+
+ pub(crate) fn charge_for_hold(
+ &mut self,
+ id: impl Origin,
+ hold_interval: Interval,
+ storage_type: StorageType,
+ ) {
+ let id: MessageId = id.cast();
+ let current = self.block_height();
+
+ // Deadline of the task.
+ let deadline = hold_interval.finish.saturating_add(RESERVE_FOR);
+
+ // The block number, which was the last paid for hold.
+ //
+ // Outdated tasks can end up being store for free - this case has to be
+ // controlled by a correct selection of the `ReserveFor` constant.
+ let paid_until = current.min(deadline);
+
+ // holding duration
+ let duration: u64 = paid_until.saturating_sub(hold_interval.start).into();
+
+ // Cost per block based on the storage used for holding
+ let cost = Self::cost_by_storage_type(storage_type);
+
+ let amount = storage_type.try_into().map_or_else(
+ |_| duration.saturating_mul(cost),
+ |lock_id| {
+ let prepaid = self.gas_tree.unlock_all(id, lock_id).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "charge_for_hold: failed unlocking locked gas.
+ Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ prepaid.min(duration.saturating_mul(cost))
+ },
+ );
+
+ if !amount.is_zero() {
+ self.spend_gas(id, amount);
+ }
+ }
+}
diff --git a/gtest/src/manager/hold_bound.rs b/gtest/src/manager/hold_bound.rs
index 0e41284a14b..579b7b75abf 100644
--- a/gtest/src/manager/hold_bound.rs
+++ b/gtest/src/manager/hold_bound.rs
@@ -19,15 +19,14 @@
//! Implementation of HoldBound and HoldBound builder, specifcying cost of
//! holding data.
-use gear_common::{auxiliary::BlockNumber, scheduler::StorageType, LockId, MessageId};
-
-use crate::RESERVE_FOR;
-
use super::ExtManager;
+use crate::RESERVE_FOR;
+use gear_common::{auxiliary::BlockNumber, scheduler::StorageType, LockId, MessageId};
+use std::cmp::Ordering;
/// Hold bound, specifying cost of storage, expected block number for task to
/// create on it, deadlines and durations of holding.
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoldBound {
cost: u64,
expected: BlockNumber,
@@ -65,6 +64,19 @@ impl HoldBound {
}
}
+impl PartialOrd for HoldBound {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for HoldBound {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.expected.cmp(&other.expected)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
pub struct HoldBoundBuilder {
storage_type: StorageType,
cost: u64,
@@ -101,8 +113,9 @@ impl HoldBoundBuilder {
pub fn maximum_for(self, manager: &ExtManager, gas: u64) -> HoldBound {
let deadline_duration = gas
.saturating_div(self.cost.max(1))
+ // `saturated_into` conversion: try_into + unwrap_or(MAX)
.try_into()
- .expect("not sane deadline");
+ .unwrap_or(u32::MAX);
let deadline = manager
.blocks_manager
.get()
diff --git a/gtest/src/manager/journal.rs b/gtest/src/manager/journal.rs
index 12f1ac682d4..2cee4bd2d21 100644
--- a/gtest/src/manager/journal.rs
+++ b/gtest/src/manager/journal.rs
@@ -16,20 +16,26 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-/// Implementation of the `JournalHandler` trait for the `ExtManager`.
-use std::collections::BTreeMap;
-
-use crate::{accounts::Accounts, actors::Actors, Value, EXISTENTIAL_DEPOSIT};
+//! Implementation of the `JournalHandler` trait for the `ExtManager`.
use super::{ExtManager, Gas, GenuineProgram, Program, TestActor};
+use crate::{
+ state::{accounts::Accounts, actors::Actors},
+ Value, EXISTENTIAL_DEPOSIT,
+};
use core_processor::common::{DispatchOutcome, JournalHandler};
-use gear_common::{scheduler::ScheduledTask, Origin};
+use gear_common::{
+ event::{MessageWaitedRuntimeReason, RuntimeReason},
+ scheduler::ScheduledTask,
+ Origin,
+};
use gear_core::{
code::{Code, CodeAndId, InstrumentedCodeAndId},
ids::{CodeId, MessageId, ProgramId, ReservationId},
memory::PageBuf,
message::{Dispatch, MessageWaitedType, SignalMessage, StoredDispatch},
pages::{
+ num_traits::Zero,
numerated::{iterators::IntervalIterator, tree::IntervalsTree},
GearPage, WasmPage,
},
@@ -37,6 +43,7 @@ use gear_core::{
};
use gear_core_errors::SignalCode;
use gear_wasm_instrument::gas_metering::Schedule;
+use std::collections::BTreeMap;
impl JournalHandler for ExtManager {
fn message_dispatched(
@@ -162,21 +169,15 @@ impl JournalHandler for ExtManager {
&mut self,
dispatch: StoredDispatch,
duration: Option,
- _: MessageWaitedType,
+ waited_type: MessageWaitedType,
) {
log::debug!("[{}] wait", dispatch.id());
- let dest = dispatch.destination();
- let id = dispatch.id();
- let expected_wake = duration.map(|d| {
- let expected_bn = d + self.block_height();
- self.task_pool
- .add(expected_bn, ScheduledTask::WakeMessage(dest, id))
- .unwrap_or_else(|e| unreachable!("TaskPool corrupted: {e:?}"));
-
- expected_bn
- });
- self.wait_list.insert((dest, id), (dispatch, expected_wake));
+ self.wait_dipatch_impl(
+ dispatch,
+ duration,
+ MessageWaitedRuntimeReason::from(waited_type).into_reason(),
+ );
}
fn wake_message(
@@ -184,23 +185,42 @@ impl JournalHandler for ExtManager {
message_id: MessageId,
program_id: ProgramId,
awakening_id: MessageId,
- _delay: u32,
+ delay: u32,
) {
log::debug!("[{message_id}] waked message#{awakening_id}");
- if let Some((msg, expected_bn)) = self.wait_list.remove(&(program_id, awakening_id)) {
- self.dispatches.push_back(msg);
+ if delay.is_zero() {
+ if let Ok(dispatch) = self.wake_dispatch_impl(program_id, awakening_id) {
+ self.dispatches.push_back(dispatch);
- let Some(expected_bn) = expected_bn else {
return;
- };
- self.task_pool
- .delete(
- expected_bn,
- ScheduledTask::WakeMessage(program_id, awakening_id),
- )
- .unwrap_or_else(|e| unreachable!("TaskPool corrupted: {e:?}"));
+ }
+ } else if self.waitlist.contains(program_id, awakening_id) {
+ let expected_bn = self.block_height() + delay;
+ let task = ScheduledTask::WakeMessage(program_id, awakening_id);
+
+ // This validation helps us to avoid returning error on insertion into
+ // `TaskPool` in case of duplicate wake.
+ if !self.task_pool.contains(&expected_bn, &task) {
+ self.task_pool.add(expected_bn, task).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "JournalHandler::wake_message: failed adding task for waking message. \
+ Expected bn - {expected_bn:?}, program id - {program_id}, message_id - {awakening_id}.
+ Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+ }
+
+ return;
}
+
+ log::debug!(
+ "Attempt to wake unknown message {:?} from {:?}",
+ awakening_id,
+ message_id
+ );
}
#[track_caller]
@@ -230,7 +250,7 @@ impl JournalHandler for ExtManager {
#[track_caller]
fn send_value(&mut self, from: ProgramId, to: Option, value: Value) {
- if value == 0 {
+ if value.is_zero() {
// Nothing to do
return;
}
diff --git a/gtest/src/manager/memory.rs b/gtest/src/manager/memory.rs
new file mode 100644
index 00000000000..5b3e08d5ea7
--- /dev/null
+++ b/gtest/src/manager/memory.rs
@@ -0,0 +1,109 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+
+impl ExtManager {
+ /// Call non-void meta function from actor stored in manager.
+ /// Warning! This is a static call that doesn't change actors pages data.
+ pub(crate) fn read_state_bytes(
+ &mut self,
+ payload: Vec,
+ program_id: &ProgramId,
+ ) -> Result> {
+ let executable_actor_data = Actors::modify(*program_id, |actor| {
+ if let Some(actor) = actor {
+ Ok(actor.get_executable_actor_data())
+ } else {
+ Err(TestError::ActorNotFound(*program_id))
+ }
+ })?;
+
+ if let Some((data, code)) = executable_actor_data {
+ core_processor::informational::execute_for_reply::, _>(
+ String::from("state"),
+ code,
+ Some(data.allocations),
+ Some((*program_id, Default::default())),
+ payload,
+ GAS_ALLOWANCE,
+ self.blocks_manager.get(),
+ )
+ .map_err(TestError::ReadStateError)
+ } else if let Some(mut program_mock) = Actors::modify(*program_id, |actor| {
+ actor.expect("Checked before").take_mock()
+ }) {
+ program_mock
+ .state()
+ .map_err(|err| TestError::ReadStateError(err.into()))
+ } else {
+ Err(TestError::ActorIsNotExecutable(*program_id))
+ }
+ }
+
+ pub(crate) fn read_state_bytes_using_wasm(
+ &mut self,
+ payload: Vec,
+ program_id: &ProgramId,
+ fn_name: &str,
+ wasm: Vec,
+ args: Option>,
+ ) -> Result> {
+ let mapping_code = Code::try_new_mock_const_or_no_rules(
+ wasm,
+ true,
+ TryNewCodeConfig::new_no_exports_check(),
+ )
+ .map_err(|_| TestError::Instrumentation)?;
+
+ let mapping_code = InstrumentedCodeAndId::from(CodeAndId::new(mapping_code))
+ .into_parts()
+ .0;
+
+ let mut mapping_code_payload = args.unwrap_or_default();
+ mapping_code_payload.append(&mut self.read_state_bytes(payload, program_id)?);
+
+ core_processor::informational::execute_for_reply::, _>(
+ String::from(fn_name),
+ mapping_code,
+ None,
+ None,
+ mapping_code_payload,
+ GAS_ALLOWANCE,
+ self.blocks_manager.get(),
+ )
+ .map_err(TestError::ReadStateError)
+ }
+
+ #[track_caller]
+ pub(crate) fn read_memory_pages(&self, program_id: &ProgramId) -> BTreeMap {
+ Actors::access(*program_id, |actor| {
+ let program = match actor.unwrap_or_else(|| panic!("Actor id {program_id:?} not found"))
+ {
+ TestActor::Initialized(program) => program,
+ TestActor::Uninitialized(_, program) => program.as_ref().unwrap(),
+ TestActor::Dormant => panic!("Actor {program_id} isn't dormant"),
+ };
+
+ match program {
+ Program::Genuine(program) => program.pages_data.clone(),
+ Program::Mock(_) => panic!("Can't read memory of mock program"),
+ }
+ })
+ }
+}
diff --git a/gtest/src/manager/reservations.rs b/gtest/src/manager/reservations.rs
index 9a15b6c2d0a..c54cb9c7bb6 100644
--- a/gtest/src/manager/reservations.rs
+++ b/gtest/src/manager/reservations.rs
@@ -18,18 +18,13 @@
//! Various reservation related methods for ExtManager
+use super::ExtManager;
use gear_common::{
- auxiliary::BlockNumber,
- gas_provider::Imbalance,
scheduler::{ScheduledTask, StorageType},
storage::Interval,
- MessageId, Origin, ProgramId, ReservationId,
+ ProgramId, ReservationId,
};
-use gear_core::{pages::num_traits::Zero, reservation::GasReservationSlot};
-
-use crate::RESERVE_FOR;
-
-use super::ExtManager;
+use gear_core::reservation::GasReservationSlot;
impl ExtManager {
pub(crate) fn remove_reservation(
@@ -104,68 +99,4 @@ impl ExtManager {
slot
}
-
- pub(crate) fn consume_and_retrieve(&mut self, id: impl Origin) {
- let outcome = self.gas_tree.consume(id).unwrap_or_else(|e| {
- let err_msg = format!(
- "consume_and_retrieve: failed consuming the rest of gas. Got error - {e:?}"
- );
-
- unreachable!("{err_msg}")
- });
-
- if let Some((imbalance, multiplier, external)) = outcome {
- let gas_left = imbalance.peek();
-
- if !gas_left.is_zero() {
- self.bank
- .withdraw_gas(external.cast(), gas_left, multiplier);
- }
- }
- }
-
- pub(crate) fn charge_for_hold(
- &mut self,
- id: impl Origin,
- hold_interval: Interval,
- storage_type: StorageType,
- ) {
- let id: MessageId = id.cast();
- let current = self.block_height();
-
- // Deadline of the task.
- let deadline = hold_interval.finish.saturating_add(RESERVE_FOR);
-
- // The block number, which was the last paid for hold.
- //
- // Outdated tasks can end up being store for free - this case has to be
- // controlled by a correct selection of the `ReserveFor` constant.
- let paid_until = current.min(deadline);
-
- // holding duration
- let duration: u64 = paid_until.saturating_sub(hold_interval.start).into();
-
- // Cost per block based on the storage used for holding
- let cost = Self::cost_by_storage_type(storage_type);
-
- let amount = storage_type.try_into().map_or_else(
- |_| duration.saturating_mul(cost),
- |lock_id| {
- let prepaid = self.gas_tree.unlock_all(id, lock_id).unwrap_or_else(|e| {
- let err_msg = format!(
- "charge_for_hold: failed unlocking locked gas.
- Got error - {e:?}"
- );
-
- unreachable!("{err_msg}");
- });
-
- prepaid.min(duration.saturating_mul(cost))
- },
- );
-
- if !amount.is_zero() {
- self.spend_gas(id, amount);
- }
- }
}
diff --git a/gtest/src/manager/send_dispatch.rs b/gtest/src/manager/send_dispatch.rs
new file mode 100644
index 00000000000..851fcbb3149
--- /dev/null
+++ b/gtest/src/manager/send_dispatch.rs
@@ -0,0 +1,512 @@
+// This file is part of Gear.
+
+// Copyright (C) 2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+
+impl ExtManager {
+ /// Insert message into the delayed queue.
+ pub(crate) fn send_delayed_dispatch(
+ &mut self,
+ origin_msg: MessageId,
+ dispatch: Dispatch,
+ delay: u32,
+ to_user: bool,
+ reservation: Option,
+ ) {
+ if delay.is_zero() {
+ let err_msg = "send_delayed_dispatch: delayed sending with zero delay appeared";
+
+ unreachable!("{err_msg}");
+ }
+
+ let message_id = dispatch.id();
+
+ if self.dispatches_stash.contains_key(&message_id) {
+ let err_msg = format!(
+ "send_delayed_dispatch: stash already has the message id - {id}",
+ id = dispatch.id()
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ // Validating dispatch wasn't sent from system with delay.
+ if dispatch.is_error_reply() || matches!(dispatch.kind(), DispatchKind::Signal) {
+ let err_msg = format!(
+ "send_delayed_dispatch: message of an invalid kind is sent: {kind:?}",
+ kind = dispatch.kind()
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ let mut to_mailbox = false;
+
+ let sender_node = reservation
+ .map(Origin::into_origin)
+ .unwrap_or_else(|| origin_msg.into_origin());
+
+ let from = dispatch.source();
+ let value = dispatch.value();
+
+ let hold_builder = HoldBoundBuilder::new(StorageType::DispatchStash);
+
+ let delay_hold = hold_builder.duration(self, delay);
+ let gas_for_delay = delay_hold.lock_amount(self);
+
+ let interval_finish = if to_user {
+ let threshold = MAILBOX_THRESHOLD;
+
+ let gas_limit = dispatch
+ .gas_limit()
+ .or_else(|| {
+ let gas_limit = self.gas_tree.get_limit(sender_node).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_delayed_dispatch: failed getting message gas limit. \
+ Lock sponsor id - {sender_node:?}. Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ (gas_limit.saturating_sub(gas_for_delay) >= threshold).then_some(threshold)
+ })
+ .unwrap_or_default();
+
+ to_mailbox = !dispatch.is_reply() && gas_limit >= threshold;
+
+ let gas_amount = if to_mailbox {
+ gas_for_delay.saturating_add(gas_limit)
+ } else {
+ gas_for_delay
+ };
+
+ self.gas_tree
+ .cut(sender_node, message_id, gas_amount)
+ .unwrap_or_else(|e| {
+ let sender_node = sender_node.cast::();
+ let err_msg = format!(
+ "send_delayed_dispatch: failed creating cut node. \
+ Origin node - {sender_node:?}, cut node id - {id}, amount - {gas_amount}. \
+ Got error - {e:?}",
+ id = dispatch.id()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ if !to_mailbox {
+ self.gas_tree
+ .split_with_value(
+ true,
+ origin_msg,
+ MessageId::generate_reply(dispatch.id()),
+ 0,
+ )
+ .expect("failed to split with value gas node");
+ }
+
+ if let Some(reservation_id) = reservation {
+ self.remove_gas_reservation_with_task(dispatch.source(), reservation_id)
+ }
+
+ // Locking funds for holding.
+ let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
+ // Dispatch stash storage is guaranteed to have an associated lock id
+ let err_msg =
+ "send_delayed_dispatch: No associated lock id for the dispatch stash storage";
+
+ unreachable!("{err_msg}");
+ });
+
+ self.gas_tree.lock(dispatch.id(), lock_id, delay_hold.lock_amount(self))
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_delayed_dispatch: failed locking gas for the user message stash hold. \
+ Message id - {message_id}, lock amount - {lock}. Got error - {e:?}",
+ message_id = dispatch.id(),
+ lock = delay_hold.lock_amount(self));
+ unreachable!("{err_msg}");
+ });
+
+ if delay_hold.expected_duration(self).is_zero() {
+ let err_msg = format!(
+ "send_delayed_dispatch: user message got zero duration hold bound for dispatch stash. \
+ Requested duration - {delay}, block cost - {cost}, source - {from:?}",
+ cost = Self::cost_by_storage_type(StorageType::DispatchStash)
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ delay_hold.expected()
+ } else {
+ match (dispatch.gas_limit(), reservation) {
+ (Some(gas_limit), None) => self
+ .gas_tree
+ .split_with_value(
+ dispatch.is_reply(),
+ sender_node,
+ dispatch.id(),
+ gas_limit.saturating_add(gas_for_delay),
+ )
+ .expect("GasTree corrupted"),
+
+ (None, None) => self
+ .gas_tree
+ .split(dispatch.is_reply(), sender_node, dispatch.id())
+ .expect("GasTree corrupted"),
+ (Some(gas_limit), Some(reservation_id)) => {
+ let err_msg = format!(
+ "send_delayed_dispatch: sending dispatch with gas from reservation isn't implemented. \
+ Message - {message_id}, sender - {sender}, gas limit - {gas_limit}, reservation - {reservation_id}",
+ message_id = dispatch.id(),
+ sender = dispatch.source(),
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ (None, Some(reservation_id)) => {
+ self.gas_tree
+ .split(dispatch.is_reply(), reservation_id, dispatch.id())
+ .expect("GasTree corrupted");
+ self.remove_gas_reservation_with_task(dispatch.source(), reservation_id);
+ }
+ }
+
+ let lock_id = delay_hold.lock_id().unwrap_or_else(|| {
+ // Dispatch stash storage is guaranteed to have an associated lock id
+ let err_msg =
+ "send_delayed_dispatch: No associated lock id for the dispatch stash storage";
+
+ unreachable!("{err_msg}");
+ });
+
+ self.gas_tree
+ .lock(dispatch.id(), lock_id, delay_hold.lock_amount(self))
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_delayed_dispatch: failed locking gas for the program message stash hold. \
+ Message id - {message_id}, lock amount - {lock}. Got error - {e:?}",
+ message_id = dispatch.id(),
+ lock = delay_hold.lock_amount(self)
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ if delay_hold.expected_duration(self).is_zero() {
+ let err_msg = format!(
+ "send_delayed_dispatch: program message got zero duration hold bound for dispatch stash. \
+ Requested duration - {delay}, block cost - {cost}, source - {from:?}",
+ cost = Self::cost_by_storage_type(StorageType::DispatchStash)
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ delay_hold.expected()
+ };
+
+ if !dispatch.value().is_zero() {
+ self.bank.deposit_value(from, value, false);
+ }
+
+ let message_id = dispatch.id();
+
+ let start_bn = self.block_height();
+ let delay_interval = Interval {
+ start: start_bn,
+ finish: interval_finish,
+ };
+
+ self.dispatches_stash
+ .insert(message_id, (dispatch.into_stored_delayed(), delay_interval));
+
+ let task = if to_user {
+ ScheduledTask::SendUserMessage {
+ message_id,
+ to_mailbox,
+ }
+ } else {
+ ScheduledTask::SendDispatch(message_id)
+ };
+
+ let task_bn = self.block_height().saturating_add(delay);
+
+ self.task_pool.add(task_bn, task).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_delayed_dispatch: failed adding task for delayed message sending. \
+ Message to user - {to_user}, message id - {message_id}. Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+ }
+
+ pub(crate) fn send_user_message(
+ &mut self,
+ origin_msg: MessageId,
+ message: Message,
+ reservation: Option,
+ ) {
+ let threshold = MAILBOX_THRESHOLD;
+
+ let msg_id = reservation
+ .map(Origin::into_origin)
+ .unwrap_or_else(|| origin_msg.into_origin());
+
+ let gas_limit = message
+ .gas_limit()
+ .or_else(|| {
+ let gas_limit = self.gas_tree.get_limit(msg_id).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message: failed getting message gas limit. \
+ Lock sponsor id - {msg_id}. Got error - {e:?}"
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ // If available gas is greater then threshold,
+ // than threshold can be used.
+ (gas_limit >= threshold).then_some(threshold)
+ })
+ .unwrap_or_default();
+
+ let from = message.source();
+ let to = message.destination();
+ let value = message.value();
+
+ let stored_message = message.into_stored();
+ let message: UserMessage = stored_message
+ .clone()
+ .try_into()
+ .expect("failed to convert stored message to user message");
+
+ if Accounts::balance(from) != 0 {
+ self.bank.deposit_value(from, value, false);
+ }
+ let _ = if message.details().is_none() && gas_limit >= threshold {
+ let hold = HoldBoundBuilder::new(StorageType::Mailbox).maximum_for(self, gas_limit);
+
+ if hold.expected_duration(self).is_zero() {
+ let err_msg = format!(
+ "send_user_message: mailbox message got zero duration hold bound for storing. \
+ Gas limit - {gas_limit}, block cost - {cost}, source - {from:?}",
+ cost = Self::cost_by_storage_type(StorageType::Mailbox)
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ self.gas_tree
+ .cut(msg_id, message.id(), gas_limit)
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message: failed creating cut node. \
+ Origin node - {msg_id}, cut node id - {id}, amount - {gas_limit}. \
+ Got error - {e:?}",
+ id = message.id()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ self.gas_tree
+ .lock(message.id(), LockId::Mailbox, gas_limit)
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message: failed locking gas for the user message mailbox. \
+ Message id - {message_id}, lock amount - {gas_limit}. Got error - {e:?}",
+ message_id = message.id(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ let message_id = message.id();
+ let message: UserStoredMessage = message
+ .clone()
+ .try_into()
+ .expect("failed to convert user message to user stored message");
+
+ self.mailbox
+ .insert(message, hold.expected())
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message: failed inserting message into mailbox. \
+ Message id - {message_id}, source - {from:?}, destination - {to:?}, \
+ expected bn - {bn:?}. Got error - {e:?}",
+ bn = hold.expected(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ self.task_pool
+ .add(
+ hold.expected(),
+ ScheduledTask::RemoveFromMailbox(to, message_id),
+ )
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message: failed adding task for removing from mailbox. \
+ Bn - {bn:?}, sent to - {to:?}, message id - {message_id}. \
+ Got error - {e:?}",
+ bn = hold.expected()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ Some(hold.expected())
+ } else {
+ self.bank.transfer_value(from, to, value);
+
+ if message.details().is_none() {
+ // Creating auto reply message.
+ let reply_message = ReplyMessage::auto(message.id());
+
+ self.gas_tree
+ .split_with_value(true, origin_msg, reply_message.id(), 0)
+ .expect("GasTree corrupted");
+ // Converting reply message into appropriate type for queueing.
+ let reply_dispatch = reply_message.into_stored_dispatch(
+ message.destination(),
+ message.source(),
+ message.id(),
+ );
+
+ self.dispatches.push_back(reply_dispatch);
+ }
+
+ None
+ };
+ self.log.push(stored_message);
+
+ if let Some(reservation_id) = reservation {
+ self.remove_gas_reservation_with_task(message.source(), reservation_id);
+ }
+ }
+
+ pub(crate) fn send_user_message_after_delay(&mut self, message: UserMessage, to_mailbox: bool) {
+ let from = message.source();
+ let to = message.destination();
+ let value = message.value();
+
+ let _ = if to_mailbox {
+ let gas_limit = self.gas_tree.get_limit(message.id()).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message_after_delay: failed getting message gas limit. \
+ Message id - {message_id}. Got error - {e:?}",
+ message_id = message.id()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ let hold = HoldBoundBuilder::new(StorageType::Mailbox).maximum_for(self, gas_limit);
+
+ if hold.expected_duration(self).is_zero() {
+ let err_msg = format!(
+ "send_user_message_after_delay: mailbox message (after delay) got zero duration hold bound for storing. \
+ Gas limit - {gas_limit}, block cost - {cost}, source - {from:?}",
+ cost = Self::cost_by_storage_type(StorageType::Mailbox)
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ self.gas_tree.lock(message.id(), LockId::Mailbox, gas_limit)
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message_after_delay: failed locking gas for the user message mailbox. \
+ Message id - {message_id}, lock amount - {gas_limit}. Got error - {e:?}",
+ message_id = message.id(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ let message_id = message.id();
+ let message: UserStoredMessage = message
+ .clone()
+ .try_into()
+ .expect("failed to convert user message to user stored message");
+ self.mailbox
+ .insert(message, hold.expected())
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message_after_delay: failed inserting message into mailbox. \
+ Message id - {message_id}, source - {from:?}, destination - {to:?}, \
+ expected bn - {bn:?}. Got error - {e:?}",
+ bn = hold.expected(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ // Adding removal request in task pool
+
+ self.task_pool
+ .add(
+ hold.expected(),
+ ScheduledTask::RemoveFromMailbox(to, message_id),
+ )
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "send_user_message_after_delay: failed adding task for removing from mailbox. \
+ Bn - {bn:?}, sent to - {to:?}, message id - {message_id}. \
+ Got error - {e:?}",
+ bn = hold.expected()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ Some(hold.expected())
+ } else {
+ self.bank.transfer_value(from, to, value);
+
+ // Message is never reply here, because delayed reply sending forbidden.
+ if message.details().is_none() {
+ // Creating reply message.
+ let reply_message = ReplyMessage::auto(message.id());
+
+ // `GasNode` was created on send already.
+
+ // Converting reply message into appropriate type for queueing.
+ let reply_dispatch = reply_message.into_stored_dispatch(
+ message.destination(),
+ message.source(),
+ message.id(),
+ );
+
+ // Queueing dispatch.
+ self.dispatches.push_back(reply_dispatch);
+ }
+
+ self.consume_and_retrieve(message.id());
+ None
+ };
+
+ self.log.push(message.into());
+ }
+}
diff --git a/gtest/src/manager/task.rs b/gtest/src/manager/task.rs
index 4fcd176c745..08d27cd328a 100644
--- a/gtest/src/manager/task.rs
+++ b/gtest/src/manager/task.rs
@@ -16,16 +16,20 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-/// Implementation of the `TaskHandler` trait for the `ExtManager`.
+//! Implementation of the `TaskHandler` trait for the `ExtManager`.
+
use super::ExtManager;
+use crate::state::actors::Actors;
+use core_processor::common::JournalHandler;
use gear_common::{
scheduler::{StorageType, TaskHandler},
Gas as GearCommonGas,
};
use gear_core::{
ids::{CodeId, MessageId, ProgramId, ReservationId},
- message::ReplyMessage,
+ message::{DispatchKind, ReplyMessage},
};
+use gear_core_errors::{ErrorReplyReason, SignalCode};
impl TaskHandler for ExtManager {
fn pause_program(&mut self, _program_id: ProgramId) -> GearCommonGas {
@@ -62,10 +66,63 @@ impl TaskHandler for ExtManager {
fn remove_from_waitlist(
&mut self,
- _program_id: ProgramId,
- _message_id: MessageId,
+ program_id: ProgramId,
+ message_id: MessageId,
) -> GearCommonGas {
- todo!()
+ let waitlisted = self
+ .wake_dispatch_impl(program_id, message_id)
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "TaskHandler::remove_from_waitlist: failed waking dispatch. \
+ Program id - {program_id}, waking message - {message_id} \
+ Got error - {e:?}."
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ self.send_signal(
+ message_id,
+ waitlisted.destination(),
+ SignalCode::RemovedFromWaitlist,
+ );
+
+ if !waitlisted.is_reply() && waitlisted.kind() != DispatchKind::Signal {
+ let err = ErrorReplyReason::RemovedFromWaitlist;
+
+ let err_payload = err
+ .to_string()
+ .into_bytes()
+ .try_into()
+ .expect("internal error: error reply reason bytes size is too big");
+
+ let trap_reply = ReplyMessage::system(message_id, err_payload, err);
+
+ if Actors::is_program(waitlisted.source()) {
+ let trap_dispatch =
+ trap_reply.into_stored_dispatch(program_id, waitlisted.source(), message_id);
+
+ self.gas_tree
+ .split(
+ trap_dispatch.is_reply(),
+ waitlisted.id(),
+ trap_dispatch.id(),
+ )
+ .unwrap_or_else(|e| unreachable!("GasTree corrupted: {e:?}"));
+ self.dispatches.push_back(trap_dispatch);
+ } else {
+ // TODO #4122
+ }
+ }
+
+ self.consume_and_retrieve(waitlisted.id());
+
+ if waitlisted.kind() == DispatchKind::Init {
+ let origin = waitlisted.source();
+ self.init_failure(program_id, origin);
+ }
+
+ GearCommonGas::MIN
}
fn remove_paused_program(&mut self, _program_id: ProgramId) -> GearCommonGas {
@@ -73,11 +130,9 @@ impl TaskHandler for ExtManager {
}
fn wake_message(&mut self, program_id: ProgramId, message_id: MessageId) -> GearCommonGas {
- let (dispatch, _) = self
- .wait_list
- .remove(&(program_id, message_id))
- .unwrap_or_else(|| unreachable!("TaskPool corrupted!"));
- self.dispatches.push_back(dispatch);
+ if let Ok(dispatch) = self.wake_dispatch_impl(program_id, message_id) {
+ self.dispatches.push_back(dispatch);
+ }
GearCommonGas::MIN
}
diff --git a/gtest/src/manager/wait_wake.rs b/gtest/src/manager/wait_wake.rs
new file mode 100644
index 00000000000..add721dbc2f
--- /dev/null
+++ b/gtest/src/manager/wait_wake.rs
@@ -0,0 +1,146 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::*;
+
+impl ExtManager {
+ pub(crate) fn wait_dipatch_impl(
+ &self,
+ dispatch: StoredDispatch,
+ duration: Option,
+ reason: MessageWaitedReason,
+ ) {
+ use MessageWaitedRuntimeReason::*;
+
+ let hold_builder = HoldBoundBuilder::new(StorageType::Waitlist);
+
+ let maximal_hold = hold_builder.maximum_for_message(self, dispatch.id());
+
+ let hold = if let Some(duration) = duration {
+ hold_builder.duration(self, duration).min(maximal_hold)
+ } else {
+ maximal_hold
+ };
+
+ let message_id = dispatch.id();
+ let destination = dispatch.destination();
+
+ if hold.expected_duration(self).is_zero() {
+ let gas_limit = self.gas_tree.get_limit(dispatch.id()).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "wait_dispatch: failed getting message gas limit. Message id - {message_id}. \
+ Got error - {e:?}",
+ message_id = dispatch.id()
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ let err_msg = format!(
+ "wait_dispatch: message got zero duration hold bound for waitlist. \
+ Requested duration - {duration:?}, gas limit - {gas_limit}, \
+ wait reason - {reason:?}, message id - {}.",
+ dispatch.id(),
+ );
+
+ unreachable!("{err_msg}");
+ }
+
+ // Locking funds for holding.
+ let lock_id = hold.lock_id().unwrap_or_else(|| {
+ // Waitlist storage is guaranteed to have an associated lock id
+ let err_msg = "wait_dispatch: No associated lock id for the waitlist storage";
+
+ unreachable!("{err_msg}");
+ });
+ self.gas_tree
+ .lock(message_id, lock_id, hold.lock_amount(self))
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "wait_dispatch: failed locking gas for the waitlist hold. \
+ Message id - {message_id}, lock amount - {lock}. Got error - {e:?}",
+ lock = hold.lock_amount(self)
+ );
+
+ unreachable!("{err_msg}");
+ });
+
+ match reason {
+ MessageWaitedReason::Runtime(WaitForCalled | WaitUpToCalledFull) => {
+ let expected = hold.expected();
+ let task = ScheduledTask::WakeMessage(destination, message_id);
+
+ if !self.task_pool.contains(&expected, &task) {
+ self.task_pool.add(expected, task).unwrap_or_else(|e| {
+ let err_msg = format!(
+ "wait_dispatch: failed adding task for waking message. \
+ Expected bn - {expected:?}, program id - {destination}, message id - {message_id}. Got error - {e:?}",
+ );
+
+ unreachable!("{err_msg}");
+ });
+ }
+ }
+ MessageWaitedReason::Runtime(WaitCalled | WaitUpToCalled) => {
+ self.task_pool.add(
+ hold.expected(),
+ ScheduledTask::RemoveFromWaitlist(dispatch.destination(), dispatch.id()),
+ )
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "wait_dispatch: failed adding task for removing message from waitlist. \
+ Expected bn - {bn:?}, program id - {destination}, message id - {message_id}. Got error - {e:?}",
+ bn = hold.expected(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+ }
+ MessageWaitedReason::System(reason) => match reason {},
+ }
+
+ self.waitlist.insert(dispatch, hold.expected())
+ .unwrap_or_else(|e| {
+ let err_msg = format!(
+ "wait_dispatch: failed inserting message to the wailist. \
+ Expected bn - {bn:?}, program id - {destination}, message id - {message_id}. Got error - {e:?}",
+ bn = hold.expected(),
+ );
+
+ unreachable!("{err_msg}");
+ });
+ }
+
+ pub(crate) fn wake_dispatch_impl(
+ &mut self,
+ program_id: ProgramId,
+ message_id: MessageId,
+ ) -> Result {
+ let (waitlisted, hold_interval) = self.waitlist.remove(program_id, message_id)?;
+ let expected_bn = hold_interval.finish;
+
+ self.charge_for_hold(waitlisted.id(), hold_interval, StorageType::Waitlist);
+
+ let _ = self.task_pool.delete(
+ expected_bn,
+ ScheduledTask::RemoveFromWaitlist(waitlisted.destination(), waitlisted.id()),
+ );
+
+ Ok(waitlisted)
+ }
+}
diff --git a/gtest/src/program.rs b/gtest/src/program.rs
index f8eff126b6d..9aecac7a2cb 100644
--- a/gtest/src/program.rs
+++ b/gtest/src/program.rs
@@ -17,9 +17,9 @@
// along with this program. If not, see .
use crate::{
- actors::{Actors, GenuineProgram, Program as InnerProgram, TestActor},
default_users_list,
manager::ExtManager,
+ state::actors::{Actors, GenuineProgram, Program as InnerProgram, TestActor},
system::System,
Result, Value, GAS_ALLOWANCE,
};
diff --git a/gtest/src/accounts.rs b/gtest/src/state/accounts.rs
similarity index 100%
rename from gtest/src/accounts.rs
rename to gtest/src/state/accounts.rs
diff --git a/gtest/src/actors.rs b/gtest/src/state/actors.rs
similarity index 100%
rename from gtest/src/actors.rs
rename to gtest/src/state/actors.rs
diff --git a/gtest/src/bank.rs b/gtest/src/state/bank.rs
similarity index 97%
rename from gtest/src/bank.rs
rename to gtest/src/state/bank.rs
index 20c555be1e7..eda3ad42adf 100644
--- a/gtest/src/bank.rs
+++ b/gtest/src/state/bank.rs
@@ -18,11 +18,9 @@
//! `gtest` bank
-use std::collections::HashMap;
-
+use crate::{constants::Value, state::accounts::Accounts, GAS_MULTIPLIER};
use gear_common::{Gas, GasMultiplier, ProgramId};
-
-use crate::{accounts::Accounts, constants::Value, GAS_MULTIPLIER};
+use std::collections::HashMap;
#[derive(Default, Debug)]
struct BankBalance {
diff --git a/gtest/src/blocks.rs b/gtest/src/state/blocks.rs
similarity index 100%
rename from gtest/src/blocks.rs
rename to gtest/src/state/blocks.rs
diff --git a/gtest/src/gas_tree.rs b/gtest/src/state/gas_tree.rs
similarity index 86%
rename from gtest/src/gas_tree.rs
rename to gtest/src/state/gas_tree.rs
index 422ba87f4d1..e9d3a83c8ee 100644
--- a/gtest/src/gas_tree.rs
+++ b/gtest/src/state/gas_tree.rs
@@ -183,23 +183,35 @@ impl GasTreeManager {
/// Unreserve some value from underlying balance.
///
/// Used in gas reservation for system signal.
- pub(crate) fn system_unreserve(&self, key: MessageId) -> Result {
- GasTree::system_unreserve(GasNodeId::from(key.cast::()))
+ pub(crate) fn system_unreserve(&self, message_id: MessageId) -> Result {
+ GasTree::system_unreserve(GasNodeId::from(message_id.cast::()))
}
/// Reserve some value from underlying balance.
///
/// Used in gas reservation for system signal.
- pub(crate) fn system_reserve(&self, key: MessageId, amount: Gas) -> Result<(), GasTreeError> {
- GasTree::system_reserve(GasNodeId::from(key.cast::()), amount)
+ pub(crate) fn system_reserve(
+ &self,
+ message_id: MessageId,
+ amount: Gas,
+ ) -> Result<(), GasTreeError> {
+ GasTree::system_reserve(GasNodeId::from(message_id.cast::()), amount)
}
- pub fn lock(&self, key: MessageId, id: LockId, amount: Gas) -> Result<(), GasTreeError> {
- GasTree::lock(GasNodeId::from(key.cast::()), id, amount)
+ pub fn lock(&self, message_id: MessageId, id: LockId, amount: Gas) -> Result<(), GasTreeError> {
+ GasTree::lock(
+ GasNodeId::from(message_id.cast::()),
+ id,
+ amount,
+ )
}
- pub(crate) fn unlock_all(&self, key: impl Origin, id: LockId) -> Result {
- GasTree::unlock_all(GasNodeId::from(key.cast::()), id)
+ pub(crate) fn unlock_all(
+ &self,
+ message_id: impl Origin,
+ id: LockId,
+ ) -> Result {
+ GasTree::unlock_all(GasNodeId::from(message_id.cast::()), id)
}
/// The id of node, external origin and funds multiplier for a key.
@@ -207,7 +219,10 @@ impl GasTreeManager {
/// Error occurs if the tree is invalidated (has "orphan" nodes), and the
/// node identified by the `key` belongs to a subtree originating at
/// such "orphan" node, or in case of inexistent key.
- pub(crate) fn get_origin_node(&self, key: MessageId) -> Result {
- GasTree::get_origin_node(GasNodeId::from(key.cast::()))
+ pub(crate) fn get_origin_node(
+ &self,
+ message_id: MessageId,
+ ) -> Result {
+ GasTree::get_origin_node(GasNodeId::from(message_id.cast::()))
}
}
diff --git a/gtest/src/mailbox.rs b/gtest/src/state/mailbox.rs
similarity index 100%
rename from gtest/src/mailbox.rs
rename to gtest/src/state/mailbox.rs
diff --git a/gtest/src/mailbox/actor.rs b/gtest/src/state/mailbox/actor.rs
similarity index 100%
rename from gtest/src/mailbox/actor.rs
rename to gtest/src/state/mailbox/actor.rs
diff --git a/gtest/src/mailbox/manager.rs b/gtest/src/state/mailbox/manager.rs
similarity index 98%
rename from gtest/src/mailbox/manager.rs
rename to gtest/src/state/mailbox/manager.rs
index b0791569fbe..35b040011af 100644
--- a/gtest/src/mailbox/manager.rs
+++ b/gtest/src/state/mailbox/manager.rs
@@ -18,7 +18,7 @@
//! Mailbox manager.
-use crate::blocks::GetBlockNumberImpl;
+use crate::state::blocks::GetBlockNumberImpl;
use gear_common::{
auxiliary::{mailbox::*, BlockNumber},
storage::{Interval, IterableByKeyMap, Mailbox, MailboxCallbacks},
diff --git a/gtest/src/state/mod.rs b/gtest/src/state/mod.rs
new file mode 100644
index 00000000000..be48ed43f77
--- /dev/null
+++ b/gtest/src/state/mod.rs
@@ -0,0 +1,28 @@
+// This file is part of Gear.
+//
+// Copyright (C) 2021-2024 Gear Technologies Inc.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// States and state managers that are used to emulate gear runtime.
+
+pub(crate) mod accounts;
+pub(crate) mod actors;
+pub(crate) mod bank;
+pub(crate) mod blocks;
+pub(crate) mod gas_tree;
+pub(crate) mod mailbox;
+pub(crate) mod task_pool;
+pub(crate) mod waitlist;
diff --git a/gtest/src/task_pool.rs b/gtest/src/state/task_pool.rs
similarity index 100%
rename from gtest/src/task_pool.rs
rename to gtest/src/state/task_pool.rs
diff --git a/gtest/src/waitlist.rs b/gtest/src/state/waitlist.rs
similarity index 95%
rename from gtest/src/waitlist.rs
rename to gtest/src/state/waitlist.rs
index 3145ea064d3..a96c520eae3 100644
--- a/gtest/src/waitlist.rs
+++ b/gtest/src/state/waitlist.rs
@@ -20,7 +20,7 @@
#![allow(unused)]
-use crate::blocks::GetBlockNumberImpl;
+use crate::state::blocks::GetBlockNumberImpl;
use gear_common::{
auxiliary::{waitlist::*, BlockNumber},
storage::{Interval, IterableByKeyMap, Waitlist, WaitlistCallbacks},
@@ -35,7 +35,7 @@ pub(crate) struct WaitlistManager;
impl WaitlistManager {
/// Check if message with `message_id` to a program with `program_id` is in
/// the waitlist.
- pub(crate) fn contains(program_id: ProgramId, message_id: MessageId) -> bool {
+ pub(crate) fn contains(&self, program_id: ProgramId, message_id: MessageId) -> bool {
as Waitlist>::contains(&program_id, &message_id)
}
diff --git a/gtest/src/system.rs b/gtest/src/system.rs
index 7339ef3aa04..a088980b597 100644
--- a/gtest/src/system.rs
+++ b/gtest/src/system.rs
@@ -17,12 +17,10 @@
// along with this program. If not, see .
use crate::{
- accounts::Accounts,
- actors::Actors,
log::{BlockRunResult, CoreLog},
- mailbox::ActorMailbox,
manager::ExtManager,
program::{Program, ProgramIdWrapper},
+ state::{accounts::Accounts, actors::Actors, mailbox::ActorMailbox},
Gas, Value, GAS_ALLOWANCE,
};
use codec::{Decode, DecodeAll};
@@ -417,6 +415,7 @@ impl Drop for System {
self.0.borrow().gas_tree.reset();
self.0.borrow().mailbox.reset();
self.0.borrow().task_pool.clear();
+ self.0.borrow().waitlist.reset();
// Clear actors and accounts storages
Actors::clear();