diff --git a/examples/custom/src/lib.rs b/examples/custom/src/lib.rs index 39db57ca2d1..4911206b3e5 100644 --- a/examples/custom/src/lib.rs +++ b/examples/custom/src/lib.rs @@ -26,6 +26,7 @@ extern crate alloc; pub mod backend_error; pub mod btree; pub mod capacitor; +pub mod simple_waiter; use alloc::string::String; use parity_scale_codec::{Decode, Encode}; @@ -43,13 +44,14 @@ pub enum InitMessage { Capacitor(String), BTree, BackendError, + SimpleWaiter, } #[cfg(not(feature = "std"))] mod wasm { use super::{ backend_error::wasm as backend_error, btree::wasm as btree, capacitor::wasm as capacitor, - InitMessage, + simple_waiter::wasm as simple_waiter, InitMessage, }; use gstd::msg; @@ -57,6 +59,7 @@ mod wasm { Capacitor(capacitor::State), BTree(btree::State), BackendError(backend_error::State), + SimpleWaiter(simple_waiter::State), } static mut STATE: Option = None; @@ -68,6 +71,7 @@ mod wasm { InitMessage::Capacitor(payload) => State::Capacitor(capacitor::init(payload)), InitMessage::BTree => State::BTree(btree::init()), InitMessage::BackendError => State::BackendError(backend_error::init()), + InitMessage::SimpleWaiter => State::SimpleWaiter(simple_waiter::init()), }; unsafe { STATE = Some(state) }; } @@ -78,6 +82,7 @@ mod wasm { match state { State::Capacitor(state) => capacitor::handle(state), State::BTree(state) => btree::handle(state), + State::SimpleWaiter(state) => simple_waiter::handle(state), _ => {} } } diff --git a/examples/custom/src/simple_waiter.rs b/examples/custom/src/simple_waiter.rs new file mode 100644 index 00000000000..ab0cf567073 --- /dev/null +++ b/examples/custom/src/simple_waiter.rs @@ -0,0 +1,40 @@ +// This file is part of Gear. + +// Copyright (C) 2023 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 . + +#[cfg(not(feature = "std"))] +pub(crate) mod wasm { + use gstd::{exec, msg, prelude::*}; + + #[derive(Default)] + pub(crate) struct State { + triggered: bool, + } + + pub(crate) fn init() -> State { + Default::default() + } + + pub(crate) fn handle(state: &mut State) { + if !state.triggered { + state.triggered = true; + exec::wait_for(20); + } + + msg::send_bytes(msg::source(), b"hello", 0).unwrap(); + } +} diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 366d6836464..8a74a78c129 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -35,8 +35,8 @@ use gear_core::{ ids::{CodeId, MessageId, ProgramId, ReservationId}, memory::PageBuf, message::{ - Dispatch, DispatchKind, MessageWaitedType, ReplyMessage, ReplyPacket, StoredDispatch, - StoredMessage, + Dispatch, DispatchKind, Message, MessageWaitedType, ReplyMessage, ReplyPacket, + StoredDispatch, StoredMessage, }, pages::{GearPage, PageU32Size, WasmPage}, program::Program as CoreProgram, @@ -236,6 +236,7 @@ pub(crate) struct ExtManager { pub(crate) dispatches: VecDeque, pub(crate) mailbox: HashMap>, pub(crate) wait_list: BTreeMap<(ProgramId, MessageId), StoredDispatch>, + pub(crate) wait_list_schedules: BTreeMap>, pub(crate) wait_init_list: BTreeMap>, pub(crate) gas_limits: BTreeMap, pub(crate) delayed_dispatches: HashMap>, @@ -332,6 +333,36 @@ impl ExtManager { .unwrap_or_default() } + /// Process scheduled wait list. + pub(crate) fn process_scheduled_wait_list(&mut self, bn: u32) -> Vec { + self.wait_list_schedules + .remove(&bn) + .map(|ids| { + ids.into_iter() + .filter_map(|key| { + self.wait_list.remove(&key).map(|dispatch| { + let (kind, message, ..) = dispatch.into_parts(); + let message = Message::new( + message.id(), + message.source(), + message.destination(), + message + .payload_bytes() + .to_vec() + .try_into() + .unwrap_or_default(), + self.gas_limits.get(&message.id()).copied(), + message.value(), + message.details(), + ); + self.run_dispatch(Dispatch::new(kind, message)) + }) + }) + .collect() + }) + .unwrap_or_default() + } + /// Check if the current block number should trigger new epoch and reset /// the provided random data. pub(crate) fn check_epoch(&mut self) { @@ -630,11 +661,6 @@ impl ExtManager { self.others_failed = false; self.main_gas_burned = Gas::zero(); self.others_gas_burned = Gas::zero(); - - // TODO: Remove this check after #349. - if !self.dispatches.is_empty() { - panic!("Message queue isn't empty"); - } } fn mark_failed(&mut self, msg_id: MessageId) { @@ -993,14 +1019,21 @@ impl JournalHandler for ExtManager { fn wait_dispatch( &mut self, dispatch: StoredDispatch, - _duration: Option, + duration: Option, _: MessageWaitedType, ) { log::debug!("[{}] wait", dispatch.id()); self.message_consumed(dispatch.id()); - self.wait_list - .insert((dispatch.destination(), dispatch.id()), dispatch); + let dest = dispatch.destination(); + let id = dispatch.id(); + self.wait_list.insert((dest, id), dispatch); + if let Some(duration) = duration { + self.wait_list_schedules + .entry(self.block_info.height.saturating_add(duration)) + .or_default() + .push((dest, id)); + } } fn wake_message( diff --git a/gtest/src/program.rs b/gtest/src/program.rs index f5bd306955c..33980b1beef 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -1065,4 +1065,34 @@ mod tests { assert!(response.contains(&log)); sys.claim_value_from_mailbox(signer); } + + #[test] + fn process_wait_for() { + use demo_custom::{InitMessage, WASM_BINARY}; + let sys = System::new(); + sys.init_logger(); + + let prog = Program::from_opt_and_meta_code_with_id(&sys, 420, WASM_BINARY.to_vec(), None); + + let signer = 42; + + // Init simple waiter + prog.send(signer, InitMessage::SimpleWaiter); + + // Invoke `exec::wait_for` when running for the first time + let result = prog.send_bytes(signer, b"doesn't matter"); + + // No log entries as the program is waiting + assert!(result.log().is_empty()); + + // Spend 20 blocks and make the waiter to wake up + let results = sys.spend_blocks(20); + + let log = Log::builder() + .source(prog.id()) + .dest(signer) + .payload_bytes("hello"); + + assert!(results.iter().any(|result| result.contains(&log))); + } } diff --git a/gtest/src/system.rs b/gtest/src/system.rs index b0796098c5e..a86baaefb57 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -179,7 +179,9 @@ impl System { .block_info .timestamp .saturating_add(BLOCK_DURATION_IN_MSECS); - manager.process_delayed_dispatches(next_block_number) + let mut results = manager.process_delayed_dispatches(next_block_number); + results.extend(manager.process_scheduled_wait_list(next_block_number)); + results }) .collect::>>() .concat()