From 7d45a415b1bd47b8e1a65d051e0a5e50180eae68 Mon Sep 17 00:00:00 2001 From: Sabaun Taraki Date: Wed, 28 Aug 2024 09:35:50 +0300 Subject: [PATCH] feat(gtest): Introduce full waitlist management to `gtest` (#4194) --- common/src/auxiliary/waitlist.rs | 1 + gtest/src/lib.rs | 11 +- gtest/src/manager.rs | 1109 +--------------------- gtest/src/manager/exec.rs | 420 ++++++++ gtest/src/manager/expend.rs | 125 +++ gtest/src/manager/hold_bound.rs | 25 +- gtest/src/manager/journal.rs | 78 +- gtest/src/manager/memory.rs | 109 +++ gtest/src/manager/reservations.rs | 75 +- gtest/src/manager/send_dispatch.rs | 512 ++++++++++ gtest/src/manager/task.rs | 75 +- gtest/src/manager/wait_wake.rs | 146 +++ gtest/src/program.rs | 2 +- gtest/src/{ => state}/accounts.rs | 0 gtest/src/{ => state}/actors.rs | 0 gtest/src/{ => state}/bank.rs | 6 +- gtest/src/{ => state}/blocks.rs | 0 gtest/src/{ => state}/gas_tree.rs | 35 +- gtest/src/{ => state}/mailbox.rs | 0 gtest/src/{ => state}/mailbox/actor.rs | 0 gtest/src/{ => state}/mailbox/manager.rs | 2 +- gtest/src/state/mod.rs | 28 + gtest/src/{ => state}/task_pool.rs | 0 gtest/src/{ => state}/waitlist.rs | 4 +- gtest/src/system.rs | 5 +- 25 files changed, 1555 insertions(+), 1213 deletions(-) create mode 100644 gtest/src/manager/exec.rs create mode 100644 gtest/src/manager/expend.rs create mode 100644 gtest/src/manager/memory.rs create mode 100644 gtest/src/manager/send_dispatch.rs create mode 100644 gtest/src/manager/wait_wake.rs rename gtest/src/{ => state}/accounts.rs (100%) rename gtest/src/{ => state}/actors.rs (100%) rename gtest/src/{ => state}/bank.rs (97%) rename gtest/src/{ => state}/blocks.rs (100%) rename gtest/src/{ => state}/gas_tree.rs (86%) rename gtest/src/{ => state}/mailbox.rs (100%) rename gtest/src/{ => state}/mailbox/actor.rs (100%) rename gtest/src/{ => state}/mailbox/manager.rs (98%) create mode 100644 gtest/src/state/mod.rs rename gtest/src/{ => state}/task_pool.rs (100%) rename gtest/src/{ => state}/waitlist.rs (95%) 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();