diff --git a/Cargo.lock b/Cargo.lock index 0b74c6257d6..cb8b763d7bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7605,6 +7605,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "primitive-types", + "rand 0.8.5", "scale-info", "sp-io", "sp-runtime", diff --git a/common/src/storage/complex/messenger.rs b/common/src/storage/complex/messenger.rs index 32d6c4decdd..2455195ba44 100644 --- a/common/src/storage/complex/messenger.rs +++ b/common/src/storage/complex/messenger.rs @@ -59,6 +59,10 @@ pub trait Messenger { /// /// Present to clarify compiler behavior over associated types. type QueuedDispatch; + /// Stored values type for `Self::DispatchStash`. + /// + /// Present to clarify compiler behavior over associated types. + type DelayedDispatch; /// First key of the waitlist storage. /// /// Present to clarify compiler behavior over associated types. @@ -167,7 +171,7 @@ pub trait Messenger { type DispatchStash: MapStorage< Key = Self::DispatchStashKey, - Value = (Self::QueuedDispatch, Interval), + Value = (Self::DelayedDispatch, Interval), >; /// Resets all related to messenger storages. diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index 58ae622d75f..8820c4e56e5 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -87,6 +87,8 @@ pub struct DispatchResult { pub page_update: BTreeMap, /// New allocations set for program if it has been changed. pub allocations: BTreeSet, + /// Whether this execution sent out a reply. + pub reply_sent: bool, } impl DispatchResult { @@ -133,6 +135,9 @@ impl DispatchResult { system_reservation_context, page_update: Default::default(), allocations: Default::default(), + // This function is only used to generate a dispatch result if nothing is executed, + // therefore reply_sent will always be false + reply_sent: false, } } } diff --git a/core-processor/src/executor.rs b/core-processor/src/executor.rs index ed3baed3bea..ca39e413b0d 100644 --- a/core-processor/src/executor.rs +++ b/core-processor/src/executor.rs @@ -342,6 +342,7 @@ where system_reservation_context: info.system_reservation_context, page_update, allocations: info.allocations, + reply_sent: info.reply_sent, }) } diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index cc9cb0c331c..130633a2374 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -168,6 +168,7 @@ pub struct ExtInfo { pub reply_deposits: Vec<(MessageId, u64)>, pub program_candidates_data: BTreeMap>, pub context_store: ContextStore, + pub reply_sent: bool, } /// Trait to which ext must have to work in processor wasm executor. @@ -394,6 +395,7 @@ impl ProcessorExternalities for Ext { outgoing_dispatches: generated_dispatches, awakening, reply_deposits, + reply_sent, } = outcome.drain(); let system_reservation_context = SystemReservationContext { @@ -419,6 +421,7 @@ impl ProcessorExternalities for Ext { reply_deposits, context_store, program_candidates_data, + reply_sent, }; Ok(info) } diff --git a/core-processor/src/processing.rs b/core-processor/src/processing.rs index 5985349eea1..fc836c44d72 100644 --- a/core-processor/src/processing.rs +++ b/core-processor/src/processing.rs @@ -382,6 +382,7 @@ pub fn process_success( context_store, allocations, reply_deposits, + reply_sent, .. } = dispatch_result; @@ -453,7 +454,7 @@ pub fn process_success( // Sending auto-generated reply about success execution. if matches!(kind, SuccessfulDispatchResultKind::Success) - && !context_store.reply_sent() + && !reply_sent && !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal { diff --git a/core/src/message/common.rs b/core/src/message/common.rs index c6d55e05a0c..5e3bbb25221 100644 --- a/core/src/message/common.rs +++ b/core/src/message/common.rs @@ -18,7 +18,10 @@ use crate::{ ids::{MessageId, ProgramId}, - message::{DispatchKind, GasLimit, Payload, StoredDispatch, StoredMessage, Value}, + message::{ + DispatchKind, GasLimit, Payload, StoredDelayedDispatch, StoredDispatch, StoredMessage, + Value, + }, }; use core::ops::Deref; use gear_core_errors::{ReplyCode, SignalCode}; @@ -279,6 +282,12 @@ impl From for StoredDispatch { } } +impl From for StoredDelayedDispatch { + fn from(dispatch: Dispatch) -> StoredDelayedDispatch { + StoredDelayedDispatch::new(dispatch.kind, dispatch.message.into()) + } +} + impl From for (DispatchKind, Message) { fn from(dispatch: Dispatch) -> (DispatchKind, Message) { (dispatch.kind, dispatch.message) @@ -296,6 +305,11 @@ impl Dispatch { self.into() } + /// Convert Dispatch into gasless StoredDelayedDispatch. + pub fn into_stored_delayed(self) -> StoredDelayedDispatch { + self.into() + } + /// Decompose Dispatch for it's components: DispatchKind and Message. pub fn into_parts(self) -> (DispatchKind, Message) { self.into() diff --git a/core/src/message/context.rs b/core/src/message/context.rs index 643dde25e76..d08b102b8c9 100644 --- a/core/src/message/context.rs +++ b/core/src/message/context.rs @@ -118,6 +118,8 @@ pub struct ContextOutcomeDrain { pub awakening: Vec<(MessageId, u32)>, /// Reply deposits to be provided. pub reply_deposits: Vec<(MessageId, u64)>, + /// Whether this execution sent out a reply. + pub reply_sent: bool, } /// Context outcome. @@ -153,6 +155,7 @@ impl ContextOutcome { /// Destructs outcome after execution and returns provided dispatches and awaken message ids. pub fn drain(self) -> ContextOutcomeDrain { let mut dispatches = Vec::new(); + let reply_sent = self.reply.is_some(); for (msg, delay, reservation) in self.init.into_iter() { dispatches.push((msg.into_dispatch(self.program_id), delay, reservation)); @@ -174,6 +177,7 @@ impl ContextOutcome { outgoing_dispatches: dispatches, awakening: self.awakening, reply_deposits: self.reply_deposits, + reply_sent, } } } @@ -184,13 +188,29 @@ pub struct ContextStore { outgoing: BTreeMap>, reply: Option, initialized: BTreeSet, - awaken: BTreeSet, - reply_sent: bool, reservation_nonce: ReservationNonce, system_reservation: Option, } impl ContextStore { + // TODO: Remove, only used in migrations (#issue 3721) + /// Create a new context store with the provided parameters. + pub fn new( + outgoing: BTreeMap>, + reply: Option, + initialized: BTreeSet, + reservation_nonce: ReservationNonce, + system_reservation: Option, + ) -> Self { + Self { + outgoing, + reply, + initialized, + reservation_nonce, + system_reservation, + } + } + /// Returns stored within message context reservation nonce. /// /// Will be non zero, if any reservations were created during @@ -218,11 +238,6 @@ impl ContextStore { pub fn system_reservation(&self) -> Option { self.system_reservation } - - /// Get info about was reply sent. - pub fn reply_sent(&self) -> bool { - self.reply_sent - } } /// Context of currently processing incoming message. @@ -268,7 +283,7 @@ impl MessageContext { /// Return bool defining was reply sent within the execution. pub fn reply_sent(&self) -> bool { - self.store.reply_sent + self.outcome.reply.is_some() } /// Send a new program initialization message. @@ -436,7 +451,6 @@ impl MessageContext { let message = ReplyMessage::from_packet(message_id, packet); self.outcome.reply = Some((message, reservation)); - self.store.reply_sent = true; Ok(message_id) } else { @@ -486,9 +500,8 @@ impl MessageContext { /// Wake message by it's message id. pub fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Error> { - if self.store.awaken.insert(waker_id) { + if !self.outcome.awakening.iter().any(|v| v.0 == waker_id) { self.outcome.awakening.push((waker_id, delay)); - Ok(()) } else { Err(Error::DuplicateWaking) diff --git a/core/src/message/mod.rs b/core/src/message/mod.rs index 1eda47fa5d1..2ffeb831ebb 100644 --- a/core/src/message/mod.rs +++ b/core/src/message/mod.rs @@ -43,7 +43,7 @@ pub use incoming::{IncomingDispatch, IncomingMessage}; pub use init::{InitMessage, InitPacket}; pub use reply::{ReplyMessage, ReplyPacket}; pub use signal::SignalMessage; -pub use stored::{StoredDispatch, StoredMessage}; +pub use stored::{StoredDelayedDispatch, StoredDispatch, StoredMessage}; pub use user::{UserMessage, UserStoredMessage}; use super::buffer::LimitedVec; diff --git a/core/src/message/stored.rs b/core/src/message/stored.rs index 22ad30d1172..2caef7acc18 100644 --- a/core/src/message/stored.rs +++ b/core/src/message/stored.rs @@ -198,3 +198,58 @@ impl Deref for StoredDispatch { self.message() } } + +impl From for StoredDispatch { + fn from(dispatch: StoredDelayedDispatch) -> Self { + StoredDispatch::new(dispatch.kind, dispatch.message, None) + } +} + +/// Stored message with entry point. +/// +/// We could use just [`StoredDispatch`] +/// but delayed messages always don't have [`ContextStore`] +/// so we designate this fact via new type. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo)] +pub struct StoredDelayedDispatch { + /// Entry point. + kind: DispatchKind, + /// Stored message. + message: StoredMessage, +} + +impl From for (DispatchKind, StoredMessage) { + fn from(dispatch: StoredDelayedDispatch) -> (DispatchKind, StoredMessage) { + (dispatch.kind, dispatch.message) + } +} + +impl StoredDelayedDispatch { + /// Create new StoredDelayedDispatch. + pub fn new(kind: DispatchKind, message: StoredMessage) -> Self { + Self { kind, message } + } + + /// Decompose StoredDelayedDispatch for it's components: DispatchKind, StoredMessage. + pub fn into_parts(self) -> (DispatchKind, StoredMessage) { + self.into() + } + + /// Entry point for the message. + pub fn kind(&self) -> DispatchKind { + self.kind + } + + /// Dispatch message reference. + pub fn message(&self) -> &StoredMessage { + &self.message + } +} + +impl Deref for StoredDelayedDispatch { + type Target = StoredMessage; + + fn deref(&self) -> &Self::Target { + self.message() + } +} diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 9cab24997e0..8ac620e37de 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -796,8 +796,6 @@ pub mod runtime_types { >, >, pub initialized: ::std::vec::Vec, - pub awaken: ::std::vec::Vec, - pub reply_sent: ::core::primitive::bool, pub reservation_nonce: runtime_types::gear_core::reservation::ReservationNonce, pub system_reservation: ::core::option::Option<::core::primitive::u64>, @@ -808,6 +806,13 @@ pub mod runtime_types { #[derive( Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, )] + pub struct StoredDelayedDispatch { + pub kind: runtime_types::gear_core::message::DispatchKind, + pub message: runtime_types::gear_core::message::stored::StoredMessage, + } + #[derive( + Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, + )] pub struct StoredDispatch { pub kind: runtime_types::gear_core::message::DispatchKind, pub message: runtime_types::gear_core::message::stored::StoredMessage, diff --git a/pallets/gear-debug/src/lib.rs b/pallets/gear-debug/src/lib.rs index d48cff050b5..4344d0d8f24 100644 --- a/pallets/gear-debug/src/lib.rs +++ b/pallets/gear-debug/src/lib.rs @@ -39,7 +39,7 @@ pub mod pallet { use gear_core::{ ids::ProgramId, memory::PageBuf, - message::{StoredDispatch, StoredMessage}, + message::{StoredDelayedDispatch, StoredDispatch, StoredMessage}, pages::{GearPage, PageU32Size, WasmPage}, }; use primitive_types::H256; @@ -62,7 +62,10 @@ pub mod pallet { /// Storage with codes for programs. type CodeStorage: CodeStorage; - type Messenger: Messenger; + type Messenger: Messenger< + QueuedDispatch = StoredDispatch, + DelayedDispatch = StoredDelayedDispatch, + >; type ProgramStorage: ProgramStorage + IterableMap<(ProgramId, common::Program>)>; diff --git a/pallets/gear-messenger/Cargo.toml b/pallets/gear-messenger/Cargo.toml index b04b8338531..37b96814de0 100644 --- a/pallets/gear-messenger/Cargo.toml +++ b/pallets/gear-messenger/Cargo.toml @@ -38,6 +38,7 @@ pallet-authorship = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } env_logger.workspace = true common = { workspace = true, features = ["std"] } +rand.workspace = true [features] default = ['std'] diff --git a/pallets/gear-messenger/src/lib.rs b/pallets/gear-messenger/src/lib.rs index 8eae6c4ffba..85800bd2654 100644 --- a/pallets/gear-messenger/src/lib.rs +++ b/pallets/gear-messenger/src/lib.rs @@ -136,6 +136,8 @@ #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] #![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] +pub mod migrations; + // Runtime mock for running tests. #[cfg(test)] mod mock; @@ -161,12 +163,12 @@ pub mod pallet { use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ ids::{MessageId, ProgramId}, - message::{StoredDispatch, UserStoredMessage}, + message::{StoredDelayedDispatch, StoredDispatch, UserStoredMessage}, }; use sp_std::{convert::TryInto, marker::PhantomData}; /// The current storage version. - pub(crate) const MESSENGER_STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + pub(crate) const MESSENGER_STORAGE_VERSION: StorageVersion = StorageVersion::new(3); // Gear Messenger Pallet's `Config`. #[pallet::config] @@ -413,14 +415,14 @@ pub mod pallet { // Private storage for dispatch stash elements. #[pallet::storage] pub type DispatchStash = - StorageMap<_, Identity, MessageId, (StoredDispatch, Interval>)>; + StorageMap<_, Identity, MessageId, (StoredDelayedDispatch, Interval>)>; // Public wrap of the dispatch stash elements. common::wrap_storage_map!( storage: DispatchStash, name: DispatchStashWrap, key: MessageId, - value: (StoredDispatch, Interval>) + value: (StoredDelayedDispatch, Interval>) ); // ---- @@ -591,6 +593,7 @@ pub mod pallet { type MailboxSecondKey = MessageId; type MailboxedMessage = UserStoredMessage; type QueuedDispatch = StoredDispatch; + type DelayedDispatch = StoredDelayedDispatch; type WaitlistFirstKey = ProgramId; type WaitlistSecondKey = MessageId; type WaitlistedMessage = StoredDispatch; diff --git a/pallets/gear-messenger/src/migrations.rs b/pallets/gear-messenger/src/migrations.rs new file mode 100644 index 00000000000..c3bc2099999 --- /dev/null +++ b/pallets/gear-messenger/src/migrations.rs @@ -0,0 +1,436 @@ +// 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 . + +use crate::{Config, DispatchStash, Dispatches, Pallet, Waitlist}; +use common::storage::{Interval, LinkedNode}; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use gear_core::ids::MessageId; +use parity_scale_codec::Encode; +use sp_std::marker::PhantomData; +#[cfg(feature = "try-runtime")] +use { + frame_support::{codec::Decode, dispatch::DispatchError}, + sp_std::vec::Vec, +}; + +pub struct MigrateToV3(PhantomData); + +impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log::info!( + "🚚 Running migration with current storage version {current:?} / onchain {onchain:?}" + ); + + // 1 read for on chain storage version + let mut weight = T::DbWeight::get().reads(1); + + if current == 3 && onchain == 2 { + Waitlist::::translate( + |_, _, (dispatch, interval): (v2::StoredDispatch, Interval>)| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + Some((dispatch.into(), interval)) + }, + ); + + Dispatches::::translate(|_, store: LinkedNode| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + Some(LinkedNode { + next: store.next, + value: store.value.into(), + }) + }); + + DispatchStash::::translate( + |_, store: (v2::StoredDispatch, Interval>)| { + if store.0.context.is_some() { + log::error!("Previous context on StoredDispatch in DispatchStash should always be None, but was Some for message id {:?}", store.0.message.id()); + } + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + Some((store.0.into(), store.1)) + }, + ); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + current.put::>(); + + log::info!("Successfully migrated storage"); + } else { + log::info!("❌ Migration did not execute. This probably should be removed"); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + let mut count = v2::Waitlist::::iter().count(); + count += v2::Dispatches::::iter().count(); + count += v2::DispatchStash::::iter().inspect( + |store| { + if store.1.0.context.is_some() { + panic!("Previous context on StoredDispatch in DispatchStash should always be None, but was Some for message id {:?}", store.1.0.message.id()); + } + }, + ).count(); + + Ok((count as u64).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), DispatchError> { + let mut count = Waitlist::::iter().count(); + count += Dispatches::::iter().count(); + count += DispatchStash::::iter().count(); + + let old_count: u64 = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + assert_eq!(count as u64, old_count); + + Ok(()) + } +} + +mod v2 { + use crate::{Config, Pallet}; + #[cfg(feature = "try-runtime")] + use common::storage::{Interval, LinkedNode}; + use frame_support::{ + codec::{Decode, Encode}, + scale_info::TypeInfo, + storage::types::CountedStorageMapInstance, + traits::{PalletInfo, StorageInstance}, + }; + #[cfg(feature = "try-runtime")] + use frame_support::{ + pallet_prelude::{CountedStorageMap, StorageDoubleMap, StorageMap}, + Identity, + }; + use frame_system::pallet_prelude::BlockNumberFor; + use gear_core::{ + ids::{MessageId, ProgramId}, + message::{DispatchKind, Payload, StoredDelayedDispatch, StoredMessage}, + reservation::ReservationNonce, + }; + use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + marker::PhantomData, + }; + + #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo)] + pub struct StoredDispatch { + pub kind: DispatchKind, + pub message: StoredMessage, + pub context: Option, + } + + impl From for gear_core::message::StoredDispatch { + fn from(value: StoredDispatch) -> Self { + Self::new(value.kind, value.message, value.context.map(Into::into)) + } + } + + impl From for StoredDelayedDispatch { + fn from(value: StoredDispatch) -> Self { + StoredDelayedDispatch::new(value.kind, value.message) + } + } + + #[derive( + Clone, Default, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Decode, Encode, TypeInfo, + )] + pub struct ContextStore { + pub outgoing: BTreeMap>, + pub reply: Option, + pub initialized: BTreeSet, + pub awaken: BTreeSet, + pub reply_sent: bool, + pub reservation_nonce: ReservationNonce, + pub system_reservation: Option, + } + + impl From for gear_core::message::ContextStore { + fn from(value: ContextStore) -> Self { + Self::new( + value.outgoing, + value.reply, + value.initialized, + value.reservation_nonce, + value.system_reservation, + ) + } + } + + pub struct DispatchesPrefix(PhantomData); + + impl StorageInstance for DispatchesPrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + const STORAGE_PREFIX: &'static str = "Dispatches"; + } + + impl CountedStorageMapInstance for DispatchesPrefix { + type CounterPrefix = Self; + } + + #[cfg(feature = "try-runtime")] + pub type Dispatches = CountedStorageMap< + DispatchesPrefix, + Identity, + MessageId, + LinkedNode, + >; + + pub struct DispatchStashPrefix(PhantomData); + + impl StorageInstance for DispatchStashPrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + const STORAGE_PREFIX: &'static str = "DispatchStash"; + } + + #[cfg(feature = "try-runtime")] + #[allow(type_alias_bounds)] + pub type DispatchStash = StorageMap< + DispatchStashPrefix, + Identity, + MessageId, + (StoredDispatch, Interval>), + >; + + pub struct WaitlistPrefix(PhantomData); + + impl StorageInstance for WaitlistPrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + const STORAGE_PREFIX: &'static str = "Waitlist"; + } + + #[cfg(feature = "try-runtime")] + #[allow(type_alias_bounds)] + pub type Waitlist = StorageDoubleMap< + WaitlistPrefix, + Identity, + ProgramId, + Identity, + MessageId, + (StoredDispatch, Interval>), + >; +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod tests { + use crate::{ + migrations::{v2, MigrateToV3}, + mock::*, + DispatchStash, Dispatches, Waitlist, + }; + use common::storage::{Interval, LinkedNode}; + use frame_support::{pallet_prelude::StorageVersion, traits::OnRuntimeUpgrade}; + use gear_core::{ + ids::{MessageId, ProgramId}, + message::{ + DispatchKind, MessageDetails, Payload, ReplyDetails, SignalDetails, StoredMessage, + }, + }; + use gear_core_errors::{ReplyCode, SignalCode, SuccessReplyReason}; + use rand::random; + use sp_runtime::traits::Zero; + + fn random_payload() -> Payload { + Payload::try_from(up_to(8 * 1024, random::).collect::>()) + .expect("Len is always smaller than max capacity") + } + + fn up_to(limit: usize, f: fn() -> T) -> impl Iterator { + std::iter::from_fn(move || Some(f())).take(random::() % limit) + } + + fn random_dispatch(no_context: bool) -> v2::StoredDispatch { + let kind = match random::() % 4 { + 0 => DispatchKind::Init, + 1 => DispatchKind::Handle, + 2 => DispatchKind::Reply, + 3 => DispatchKind::Signal, + _ => unreachable!(), + }; + let details = if random() { + if random() { + Some(MessageDetails::Reply(ReplyDetails::new( + MessageId::from(random::()), + ReplyCode::Success(SuccessReplyReason::Auto), + ))) + } else { + Some(MessageDetails::Signal(SignalDetails::new( + MessageId::from(random::()), + SignalCode::RemovedFromWaitlist, + ))) + } + } else { + None + }; + let context = if no_context || random() { + None + } else { + let outgoing = up_to(32, || { + ( + random(), + if random() { + Some(random_payload()) + } else { + None + }, + ) + }) + .collect(); + let initialized = up_to(32, || ProgramId::from(random::())).collect(); + let awaken = up_to(32, || MessageId::from(random::())).collect(); + Some(v2::ContextStore { + outgoing, + reply: if random() { + Some(random_payload()) + } else { + None + }, + initialized, + awaken, + reply_sent: random(), + reservation_nonce: Default::default(), + system_reservation: if random() { Some(random()) } else { None }, + }) + }; + v2::StoredDispatch { + kind, + message: StoredMessage::new( + MessageId::from(random::()), + ProgramId::from(random::()), + ProgramId::from(random::()), + random_payload(), + random(), + details, + ), + context, + } + } + + #[test] + fn migration_to_v3_works() { + new_test_ext().execute_with(|| { + StorageVersion::new(2).put::(); + + let waitlist = up_to(32, || { + ( + ProgramId::from(random::()), + MessageId::from(random::()), + random_dispatch(false), + Interval { + start: random(), + finish: random(), + }, + ) + }) + .collect::>(); + + for (pid, mid, dispatch, interval) in waitlist.iter() { + v2::Waitlist::::insert(pid, mid, (dispatch, interval)); + } + + let dispatches = up_to(32, || { + ( + MessageId::from(random::()), + random_dispatch(false), + if random() { + Some(MessageId::from(random::())) + } else { + None + }, + ) + }) + .collect::>(); + + for (mid, dispatch, next_mid) in dispatches.clone() { + v2::Dispatches::::insert( + mid, + LinkedNode { + next: next_mid, + value: dispatch, + }, + ); + } + + let dispatch_stash = up_to(32, || { + ( + MessageId::from(random::()), + random_dispatch(true), + Interval { + start: random(), + finish: random(), + }, + ) + }) + .collect::>(); + + for (msg_id, dispatch, interval) in dispatch_stash.clone() { + v2::DispatchStash::::insert(msg_id, (dispatch.clone(), interval.clone())); + } + + let state = MigrateToV3::::pre_upgrade().unwrap(); + let weight = MigrateToV3::::on_runtime_upgrade(); + assert!(!weight.is_zero()); + MigrateToV3::::post_upgrade(state).unwrap(); + + assert_eq!(StorageVersion::get::(), 3); + + for dispatch in waitlist { + assert_eq!( + Waitlist::::get(dispatch.0, dispatch.1) + .expect("Waitlist failed to migrate"), + (dispatch.2.into(), dispatch.3) + ); + } + + for dispatch in dispatches { + let node = + Dispatches::::get(dispatch.0).expect("Dispatches failed to migrate"); + assert_eq!(node.value, dispatch.1.into()); + assert_eq!(node.next, dispatch.2); + } + + for dispatch in dispatch_stash { + assert_eq!( + DispatchStash::::get(dispatch.0) + .expect("DispatchStash failed to migrate"), + (dispatch.1.into(), dispatch.2) + ); + } + }); + } +} diff --git a/pallets/gear-messenger/src/mock.rs b/pallets/gear-messenger/src/mock.rs index 8e9f4c74f51..b5ef233284e 100644 --- a/pallets/gear-messenger/src/mock.rs +++ b/pallets/gear-messenger/src/mock.rs @@ -22,6 +22,7 @@ use crate as pallet_gear_messenger; use frame_support::{ construct_runtime, parameter_types, traits::{OnFinalize, OnInitialize}, + weights::constants::RocksDbWeight, }; use frame_system::{self as system, pallet_prelude::BlockNumberFor}; use primitive_types::H256; @@ -47,7 +48,7 @@ construct_runtime!( } ); -common::impl_pallet_system!(Test, DbWeight = (), BlockWeights = ()); +common::impl_pallet_system!(Test, DbWeight = RocksDbWeight, BlockWeights = ()); common::impl_pallet_balances!(Test); pallet_gear_gas::impl_config!(Test); pallet_gear_messenger::impl_config!(Test); diff --git a/pallets/gear/src/internal.rs b/pallets/gear/src/internal.rs index 3fac932b3b1..774bb0d7a35 100644 --- a/pallets/gear/src/internal.rs +++ b/pallets/gear/src/internal.rs @@ -653,7 +653,7 @@ where }; // Adding message into the stash. - DispatchStashOf::::insert(message_id, (dispatch.into_stored(), delay_interval)); + DispatchStashOf::::insert(message_id, (dispatch.into_stored_delayed(), delay_interval)); let task = if to_user { ScheduledTask::SendUserMessage { diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 79e9c29adb5..e3f1c79b826 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -210,6 +210,7 @@ pub mod pallet { MailboxSecondKey = MessageId, MailboxedMessage = UserStoredMessage, QueuedDispatch = StoredDispatch, + DelayedDispatch = StoredDelayedDispatch, WaitlistFirstKey = ProgramId, WaitlistSecondKey = MessageId, WaitlistedMessage = StoredDispatch, diff --git a/pallets/gear/src/manager/task.rs b/pallets/gear/src/manager/task.rs index 5093d6efc09..3cbd828af01 100644 --- a/pallets/gear/src/manager/task.rs +++ b/pallets/gear/src/manager/task.rs @@ -222,7 +222,7 @@ where // Charging locked gas for holding in dispatch stash. Pallet::::charge_for_hold(dispatch.id(), hold_interval, StorageType::DispatchStash); - QueueOf::::queue(dispatch) + QueueOf::::queue(dispatch.into()) .unwrap_or_else(|e| unreachable!("Message queue corrupted! {:?}", e)); let gas = ::WeightInfo::tasks_send_dispatch().ref_time(); diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 1a1e5478cf6..c89a8cc22a6 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -1361,9 +1361,7 @@ fn reply_deposit_gstd_async() { }); } -// TODO (#2763): resolve panic caused by "duplicate" wake in message A #[test] -#[should_panic] fn pseudo_duplicate_wake() { use demo_constructor::{Calls, Scheme}; diff --git a/runtime/vara/src/migrations.rs b/runtime/vara/src/migrations.rs index 10fb1fc6d5e..a019076e806 100644 --- a/runtime/vara/src/migrations.rs +++ b/runtime/vara/src/migrations.rs @@ -99,4 +99,6 @@ pub type Migrations = ( pallet_im_online::migration::v1::Migration, // v1050 pallet_gear_program::migrations::MigrateToV3, + // not yet executed + pallet_gear_messenger::migrations::MigrateToV3, );