diff --git a/common/src/benchmarking.rs b/common/src/benchmarking.rs index 53826b3c7d9..a8042a9456d 100644 --- a/common/src/benchmarking.rs +++ b/common/src/benchmarking.rs @@ -18,7 +18,10 @@ use super::*; -use gear_core::pages::{PageNumber, PageU32Size, WasmPage}; +use gear_core::{ + pages::{PageNumber, PageU32Size, WasmPage}, + program::MemoryInfix, +}; use gear_wasm_instrument::parity_wasm::{self, elements::*}; use sp_io::hashing::blake2_256; use sp_runtime::traits::Zero; @@ -150,22 +153,22 @@ pub fn set_program< .collect(); let pages_with_data = persistent_pages_data.keys().copied().collect(); + let memory_infix = MemoryInfix::new(1u32); + let program = ActiveProgram { + allocations, + pages_with_data, + code_hash: code_id, + code_exports: Default::default(), + static_pages, + state: ProgramState::Initialized, + gas_reservation_map: GasReservationMap::default(), + expiration_block: Zero::zero(), + memory_infix, + }; for (page, page_buf) in persistent_pages_data { - ProgramStorage::set_program_page_data(program_id, page, page_buf); + ProgramStorage::set_program_page_data(program_id, memory_infix, page, page_buf); } - ProgramStorage::add_program( - program_id, - ActiveProgram { - allocations, - pages_with_data, - code_hash: code_id, - code_exports: Default::default(), - static_pages, - state: ProgramState::Initialized, - gas_reservation_map: GasReservationMap::default(), - expiration_block: Zero::zero(), - }, - ) - .expect("benchmarking; program duplicates should not exist"); + ProgramStorage::add_program(program_id, program) + .expect("benchmarking; program duplicates should not exist"); } diff --git a/common/src/lib.rs b/common/src/lib.rs index 42fa44e8cb2..6f295e18115 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -56,6 +56,7 @@ use gear_core::{ memory::PageBuf, message::DispatchKind, pages::{GearPage, WasmPage}, + program::MemoryInfix, reservation::GasReservationMap, }; use primitive_types::H256; @@ -275,6 +276,7 @@ pub struct ActiveProgram { pub allocations: BTreeSet, /// Set of gear pages numbers, which has data in storage. pub pages_with_data: BTreeSet, + pub memory_infix: MemoryInfix, pub gas_reservation_map: GasReservationMap, pub code_hash: H256, pub code_exports: BTreeSet, diff --git a/common/src/paused_program_storage.rs b/common/src/paused_program_storage.rs index 8ff2ba57ef3..86d87f5928d 100644 --- a/common/src/paused_program_storage.rs +++ b/common/src/paused_program_storage.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use super::{program_storage::MemoryMap, *}; -use crate::storage::{AppendMapStorage, MapStorage, ValueStorage}; +use crate::storage::{MapStorage, ValueStorage}; use gear_core::{ code::MAX_WASM_PAGE_COUNT, pages::{GEAR_PAGE_SIZE, WASM_PAGE_SIZE}, @@ -27,7 +27,7 @@ use sp_io::hashing; const SPLIT_COUNT: u16 = (WASM_PAGE_SIZE / GEAR_PAGE_SIZE) as u16 * MAX_WASM_PAGE_COUNT / 2; -pub type SessionId = u128; +pub type SessionId = u32; // The entity helps to calculate hash of program's data and memory pages. // Its structure designed that way to avoid memory allocation of more than MAX_POSSIBLE_ALLOCATION bytes. @@ -78,6 +78,7 @@ pub struct ResumeSession { user: AccountId, program_id: ProgramId, allocations: BTreeSet, + pages_with_data: BTreeSet, code_hash: CodeId, end_block: BlockNumber, } @@ -100,11 +101,6 @@ pub trait PausedProgramStorage: super::ProgramStorage { Key = SessionId, Value = ResumeSession, >; - type SessionMemoryPages: AppendMapStorage< - (GearPage, PageBuf), - SessionId, - Vec<(GearPage, PageBuf)>, - >; /// Attempt to remove all items from all the associated maps. fn reset() { @@ -136,6 +132,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { let memory_pages = match Self::get_program_data_for_pages( program_id, + program.memory_infix, program.pages_with_data.iter(), ) { Ok(memory_pages) => memory_pages, @@ -147,7 +144,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { }; Self::waiting_init_remove(program_id); - Self::remove_program_pages(program_id); + Self::remove_program_pages(program_id, program.memory_infix); Ok((program, memory_pages)) })?; @@ -192,6 +189,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { user, program_id, allocations, + pages_with_data: Default::default(), code_hash, end_block, }); @@ -211,6 +209,8 @@ pub trait PausedProgramStorage: super::ProgramStorage { user: Self::AccountId, memory_pages: Vec<(GearPage, PageBuf)>, ) -> Result<(), Self::Error> { + // TODO: #3447 additional check + Self::ResumeSessions::mutate(session_id, |maybe_session| { let session = match maybe_session.as_mut() { Some(s) if s.user == user => s, @@ -219,8 +219,14 @@ pub trait PausedProgramStorage: super::ProgramStorage { }; session.page_count += memory_pages.len() as u32; - for page in memory_pages { - Self::SessionMemoryPages::append(session_id, page); + for (page, page_buf) in memory_pages { + session.pages_with_data.insert(page); + Self::set_program_page_data( + session.program_id, + MemoryInfix::new(session_id), + page, + page_buf, + ); } Ok(()) @@ -248,7 +254,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { }; // it means that the program has been already resumed within another session - Self::SessionMemoryPages::remove(session_id); + Self::remove_program_pages(session.program_id, MemoryInfix::new(session_id)); *maybe_session = None; return Ok(result); @@ -263,10 +269,12 @@ pub trait PausedProgramStorage: super::ProgramStorage { return Err(Self::InternalError::program_code_not_found().into()); } - let memory_pages: MemoryMap = Self::SessionMemoryPages::get(&session_id) - .unwrap_or_default() - .into_iter() - .collect(); + let memory_pages = Self::get_program_data_for_pages( + session.program_id, + MemoryInfix::new(session_id), + session.pages_with_data.iter(), + ) + .unwrap_or_default(); let code_hash = session.code_hash.into_origin(); let item = Item::from((session.allocations.clone(), code_hash, memory_pages)); if item.hash() != hash { @@ -293,6 +301,7 @@ pub trait PausedProgramStorage: super::ProgramStorage { static_pages: code.static_pages(), state: ProgramState::Initialized, expiration_block, + memory_infix: MemoryInfix::new(session_id), }; let program_id = session.program_id; @@ -304,15 +313,6 @@ pub trait PausedProgramStorage: super::ProgramStorage { // wipe all uploaded data out *maybe_session = None; Self::PausedProgramMap::remove(program_id); - Self::SessionMemoryPages::remove(session_id); - - // set program memory pages - for (page, page_buf) in memory_pages { - Self::set_program_page_data(program_id, page, page_buf); - } - for (page, page_buf) in remaining_pages { - Self::set_program_page_data(program_id, page, page_buf); - } // and finally start the program Self::ProgramMap::insert(program_id, Program::Active(program)); @@ -324,8 +324,8 @@ pub trait PausedProgramStorage: super::ProgramStorage { /// Remove all data created by a call to `resume_session_init`. fn remove_resume_session(session_id: SessionId) -> Result<(), Self::Error> { Self::ResumeSessions::mutate(session_id, |maybe_session| match maybe_session.take() { - Some(_) => { - Self::SessionMemoryPages::remove(session_id); + Some(session) => { + Self::remove_program_pages(session.program_id, MemoryInfix::new(session_id)); Ok(()) } diff --git a/common/src/program_storage.rs b/common/src/program_storage.rs index 98c4279bbf3..55908aa1e10 100644 --- a/common/src/program_storage.rs +++ b/common/src/program_storage.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use super::*; -use crate::storage::{AppendMapStorage, DoubleMapStorage, MapStorage}; +use crate::storage::{AppendMapStorage, MapStorage, TripleMapStorage}; use core::fmt::Debug; /// Trait for ProgramStorage errors. @@ -62,7 +62,12 @@ pub trait ProgramStorage { type AccountId: Eq + PartialEq; type ProgramMap: MapStorage>; - type MemoryPageMap: DoubleMapStorage; + type MemoryPageMap: TripleMapStorage< + Key1 = ProgramId, + Key2 = MemoryInfix, + Key3 = GearPage, + Value = PageBuf, + >; type WaitingInitMap: AppendMapStorage>; /// Attempt to remove all items from all the associated maps. @@ -136,11 +141,12 @@ pub trait ProgramStorage { /// Return program data for each page from `pages`. fn get_program_data_for_pages<'a>( program_id: ProgramId, + memory_infix: MemoryInfix, pages: impl Iterator, ) -> Result { let mut pages_data = BTreeMap::new(); for page in pages { - let data = Self::MemoryPageMap::get(&program_id, page) + let data = Self::MemoryPageMap::get(&program_id, &memory_infix, page) .ok_or(Self::InternalError::cannot_find_page_data())?; pages_data.insert(*page, data); } @@ -148,19 +154,28 @@ pub trait ProgramStorage { Ok(pages_data) } - /// Store a memory page buffer to be associated with the given keys `program_id` and `page` from the map. - fn set_program_page_data(program_id: ProgramId, page: GearPage, page_buf: PageBuf) { - Self::MemoryPageMap::insert(program_id, page, page_buf); + /// Store a memory page buffer to be associated with the given keys `program_id`, `memory_infix` and `page` from the map. + fn set_program_page_data( + program_id: ProgramId, + memory_infix: MemoryInfix, + page: GearPage, + page_buf: PageBuf, + ) { + Self::MemoryPageMap::insert(program_id, memory_infix, page, page_buf); } - /// Remove a memory page buffer under the given keys `program_id` and `page`. - fn remove_program_page_data(program_id: ProgramId, page_num: GearPage) { - Self::MemoryPageMap::remove(program_id, page_num); + /// Remove a memory page buffer under the given keys `program_id`, `memory_infix` and `page`. + fn remove_program_page_data( + program_id: ProgramId, + memory_infix: MemoryInfix, + page_num: GearPage, + ) { + Self::MemoryPageMap::remove(program_id, memory_infix, page_num); } - /// Remove all memory page buffers under the given key `program_id`. - fn remove_program_pages(program_id: ProgramId) { - Self::MemoryPageMap::clear_prefix(program_id); + /// Remove all memory page buffers under the given keys `program_id` and `memory_infix`. + fn remove_program_pages(program_id: ProgramId, memory_infix: MemoryInfix) { + Self::MemoryPageMap::clear_prefix(program_id, memory_infix); } /// Final full prefix that prefixes all keys of memory pages. diff --git a/common/src/storage/primitives/mod.rs b/common/src/storage/primitives/mod.rs index fae953eb7c4..e5f63a8d48c 100644 --- a/common/src/storage/primitives/mod.rs +++ b/common/src/storage/primitives/mod.rs @@ -29,6 +29,7 @@ mod double_map; mod iterable; mod key; mod map; +mod triple_map; mod value; // Public exports from primitive modules. @@ -41,6 +42,7 @@ pub use iterable::{ }; pub use key::{KeyFor, MailboxKeyGen, QueueKeyGen, WaitlistKeyGen}; pub use map::{AppendMapStorage, MapStorage}; +pub use triple_map::TripleMapStorage; pub use value::ValueStorage; use frame_support::{ diff --git a/common/src/storage/primitives/triple_map.rs b/common/src/storage/primitives/triple_map.rs new file mode 100644 index 00000000000..5f9a6ce4c2d --- /dev/null +++ b/common/src/storage/primitives/triple_map.rs @@ -0,0 +1,155 @@ +// This file is part of Gear. + +// Copyright (C) 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 . + +//! Module for map with three keys storing primitive. +//! +//! This primitive defines interface of interaction +//! with globally stored triple-key map (Key1 -> Key2 -> Key3 -> Value). + +/// Represents logic of managing globally stored +/// triple-key map for more complicated logic. +/// +/// In fact, represents custom implementation/wrapper +/// around of Substrate's `StorageNMap` with `OptionQuery`. +pub trait TripleMapStorage { + /// Map's first key type. + type Key1; + /// Map's second key type. + type Key2; + /// Map's third key type. + type Key3; + /// Map's stored value type. + type Value; + + /// Returns bool, defining does map contain value under given keys. + fn contains_keys(key1: &Self::Key1, key2: &Self::Key2, key3: &Self::Key3) -> bool; + + /// Gets value stored under given keys, if present. + fn get(key1: &Self::Key1, key2: &Self::Key2, key3: &Self::Key3) -> Option; + + /// Inserts value with given keys. + fn insert(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3, value: Self::Value); + + /// Mutates value by `Option` reference, which stored (or not + /// in `None` case) under given keys with given function. + /// + /// May return generic type value. + fn mutate) -> R>( + key1: Self::Key1, + key2: Self::Key2, + key3: Self::Key3, + f: F, + ) -> R; + + /// Works the same as `Self::mutate`, but triggers if value present. + fn mutate_exists R>( + key1: Self::Key1, + key2: Self::Key2, + key3: Self::Key3, + f: F, + ) -> Option { + Self::mutate(key1, key2, key3, |opt_val| opt_val.as_mut().map(f)) + } + + /// Mutates all stored values with given convert function. + fn mutate_values Self::Value>(f: F); + + /// Removes value stored under the given keys. + fn remove(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3); + + /// Removes all values. + fn clear(); + + /// Gets value stored under given keys, if present, + /// and removes it from storage. + fn take(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3) -> Option; + + /// Remove items from the map matching a `key1`/`key2` prefix. + fn clear_prefix(key1: Self::Key1, key2: Self::Key2); +} + +/// Creates new type with specified name and key1-key2-key3-value types and +/// implements `TripleMapStorage` for it based on specified storage, +/// which is a `Substrate`'s `StorageNMap`. +/// +/// The macro main purpose is to follow newtype pattern +/// and avoid `Substrate` dependencies in `gear_common`. +/// +/// Requires `PhantomData` be in scope: from `std`, `core` or `sp_std`. +/// +/// Requires `Config` be in scope of the crate root where it called. +#[allow(clippy::crate_in_macro_def)] +#[macro_export] +macro_rules! wrap_storage_triple_map { + (storage: $storage: ident, name: $name: ident, + key1: $key1: ty, + key2: $key2: ty, + key3: $key3: ty, + value: $val: ty) => { + pub struct $name(PhantomData); + + impl TripleMapStorage for $name { + type Key1 = $key1; + type Key2 = $key2; + type Key3 = $key3; + type Value = $val; + + fn contains_keys(key1: &Self::Key1, key2: &Self::Key2, key3: &Self::Key3) -> bool { + $storage::::contains_key((key1, key2, key3)) + } + + fn get(key1: &Self::Key1, key2: &Self::Key2, key3: &Self::Key3) -> Option { + $storage::::get((key1, key2, key3)) + } + + fn insert(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3, value: Self::Value) { + $storage::::insert((key1, key2, key3), value) + } + + fn mutate) -> R>( + key1: Self::Key1, + key2: Self::Key2, + key3: Self::Key3, + f: F, + ) -> R { + $storage::::mutate((key1, key2, key3), f) + } + + fn mutate_values Self::Value>(mut f: F) { + let f = |v| Some(f(v)); + $storage::::translate_values(f) + } + + fn remove(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3) { + $storage::::remove((key1, key2, key3)) + } + + fn clear() { + let _ = $storage::::clear(u32::MAX, None); + } + + fn take(key1: Self::Key1, key2: Self::Key2, key3: Self::Key3) -> Option { + $storage::::take((key1, key2, key3)) + } + + fn clear_prefix(key1: Self::Key1, key2: Self::Key2) { + let _ = $storage::::clear_prefix((key1, key2), u32::MAX, None); + } + } + }; +} diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index 748293b0013..26db8af94c5 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -36,7 +36,7 @@ use gear_core::{ ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, StoredDispatch, }, pages::{GearPage, WasmPage}, - program::Program, + program::{MemoryInfix, Program}, reservation::{GasReservationMap, GasReserver}, }; use gear_core_backend::{ @@ -524,6 +524,8 @@ pub struct Actor { pub struct ExecutableActorData { /// Set of dynamic wasm page numbers, which are allocated by the program. pub allocations: BTreeSet, + /// The infix of memory pages in a storage. + pub memory_infix: MemoryInfix, /// Set of gear pages numbers, which has data in storage. pub pages_with_data: BTreeSet, /// Id of the program code. diff --git a/core-processor/src/context.rs b/core-processor/src/context.rs index 0946b1d35d1..e6136b35eab 100644 --- a/core-processor/src/context.rs +++ b/core-processor/src/context.rs @@ -130,6 +130,7 @@ impl From<(ContextChargedForMemory, InstrumentedCode, u128)> for ProcessExecutio let program = Program::from_parts( destination_id, + actor_data.memory_infix, code, actor_data.allocations, actor_data.initialized, diff --git a/core-processor/src/executor.rs b/core-processor/src/executor.rs index 804d0420bdb..35069e56a95 100644 --- a/core-processor/src/executor.rs +++ b/core-processor/src/executor.rs @@ -42,7 +42,7 @@ use gear_core::{ WasmEntryPoint, }, pages::{PageU32Size, WasmPage}, - program::Program, + program::{MemoryInfix, Program}, reservation::GasReserver, }; use gear_core_backend::{ @@ -102,9 +102,11 @@ fn check_memory( } /// Writes initial pages data to memory and prepare memory for execution. +#[allow(clippy::too_many_arguments)] fn prepare_memory( mem: &mut EnvMem, program_id: ProgramId, + memory_infix: MemoryInfix, static_pages: WasmPage, stack_end: Option, globals_config: GlobalsAccessConfig, @@ -131,6 +133,7 @@ fn prepare_memory( ProcessorExt::lazy_pages_init_for_program( mem, program_id, + memory_infix, stack_end, globals_config, lazy_pages_weights, @@ -243,6 +246,7 @@ where prepare_memory::>( memory, program_id, + program.memory_infix(), static_pages, stack_end, globals_config, @@ -349,7 +353,7 @@ pub fn execute_for_reply( function: EP, instrumented_code: InstrumentedCode, allocations: Option>, - program_id: Option, + program_info: Option<(ProgramId, MemoryInfix)>, payload: Vec, gas_limit: u64, block_info: BlockInfo, @@ -362,7 +366,8 @@ where ::UnrecoverableError: BackendSyscallError, EP: WasmEntryPoint, { - let program = Program::new(program_id.unwrap_or_default(), instrumented_code); + let (program_id, memory_infix) = program_info.unwrap_or_default(); + let program = Program::new(program_id, memory_infix, instrumented_code); let static_pages = program.static_pages(); let allocations = allocations.unwrap_or_else(|| program.allocations().clone()); @@ -439,7 +444,8 @@ where env.execute(|memory, stack_end, globals_config| { prepare_memory::>( memory, - program.id(), + program_id, + memory_infix, static_pages, stack_end, globals_config, diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index f46aada1bec..fed8880e141 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -41,6 +41,7 @@ use gear_core::{ MessageContext, Packet, ReplyPacket, }, pages::{GearPage, PageU32Size, WasmPage}, + program::MemoryInfix, reservation::GasReserver, }; use gear_core_backend::{ @@ -189,6 +190,7 @@ pub trait ProcessorExternalities { fn lazy_pages_init_for_program( mem: &mut impl Memory, prog_id: ProgramId, + memory_infix: MemoryInfix, stack_end: Option, globals_config: GlobalsAccessConfig, lazy_pages_weights: LazyPagesWeights, @@ -439,6 +441,7 @@ impl ProcessorExternalities for Ext { fn lazy_pages_init_for_program( mem: &mut impl Memory, prog_id: ProgramId, + memory_infix: MemoryInfix, stack_end: Option, globals_config: GlobalsAccessConfig, lazy_pages_weights: LazyPagesWeights, @@ -446,6 +449,7 @@ impl ProcessorExternalities for Ext { gear_lazy_pages_interface::init_for_program( mem, prog_id, + memory_infix, stack_end, globals_config, lazy_pages_weights, diff --git a/core/src/program.rs b/core/src/program.rs index 3ead4b37d98..900534e5825 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -20,12 +20,32 @@ use crate::{code::InstrumentedCode, ids::ProgramId, pages::WasmPage}; use alloc::collections::BTreeSet; -use scale_info::scale::{Decode, Encode}; +use scale_info::{ + scale::{Decode, Encode}, + TypeInfo, +}; + +/// Struct defines infix of memory pages storage. +#[derive(Clone, Copy, Debug, Default, Decode, Encode, PartialEq, Eq, TypeInfo)] +pub struct MemoryInfix(u32); + +impl MemoryInfix { + /// Constructing function from u32 number. + pub const fn new(value: u32) -> Self { + Self(value) + } + + /// Return inner u32 value. + pub fn inner(&self) -> u32 { + self.0 + } +} /// Program. #[derive(Clone, Debug, Decode, Encode)] pub struct Program { id: ProgramId, + memory_infix: MemoryInfix, code: InstrumentedCode, /// Wasm pages allocated by program. allocations: BTreeSet, @@ -35,9 +55,10 @@ pub struct Program { impl Program { /// New program with specific `id` and `code`. - pub fn new(id: ProgramId, code: InstrumentedCode) -> Self { + pub fn new(id: ProgramId, memory_infix: MemoryInfix, code: InstrumentedCode) -> Self { Program { id, + memory_infix, code, allocations: Default::default(), is_initialized: false, @@ -47,12 +68,14 @@ impl Program { /// New program from stored data pub fn from_parts( id: ProgramId, + memory_infix: MemoryInfix, code: InstrumentedCode, allocations: BTreeSet, is_initialized: bool, ) -> Self { Self { id, + memory_infix, code, allocations, is_initialized, @@ -74,6 +97,11 @@ impl Program { self.id } + /// Get the [`MemoryInfix`] of this program. + pub fn memory_infix(&self) -> MemoryInfix { + self.memory_infix + } + /// Get initial memory size for this program. pub fn static_pages(&self) -> WasmPage { self.code.static_pages() @@ -156,7 +184,7 @@ mod tests { let code = Code::try_new(binary, 1, |_| ConstantCostRules::default(), None).unwrap(); let (code, _) = code.into_parts(); - let program = Program::new(ProgramId::from(1), code); + let program = Program::new(ProgramId::from(1), Default::default(), code); // 2 static pages assert_eq!(program.static_pages(), 2.into()); diff --git a/gclient/src/api/calls.rs b/gclient/src/api/calls.rs index c31533e4901..ca4bb1d4fc1 100644 --- a/gclient/src/api/calls.rs +++ b/gclient/src/api/calls.rs @@ -508,7 +508,11 @@ impl GearApi { dest_node_api .0 .storage - .set_gpages(dest_program_id, &src_program_pages) + .set_gpages( + dest_program_id, + src_program.memory_infix.0, + &src_program_pages, + ) .await?; src_program.expiration_block = dest_node_api.last_block_number().await?; @@ -604,7 +608,12 @@ impl GearApi { ) .await?; - self.0.storage.set_gpages(program_id, &pages).await?; + let program = self.0.api().gprog_at(program_id, None).await?; + + self.0 + .storage + .set_gpages(program_id, program.memory_infix.0, &pages) + .await?; Ok(()) } diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 185a2e6ab4f..2024928555f 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -594,6 +594,7 @@ pub mod runtime_types { pub user: _0, pub program_id: runtime_types::gear_core::ids::ProgramId, pub allocations: ::std::vec::Vec, + pub pages_with_data: ::std::vec::Vec, pub code_hash: runtime_types::gear_core::ids::CodeId, pub end_block: _1, } @@ -637,7 +638,7 @@ pub mod runtime_types { runtime_types::gear_core::ids::ReservationId, ), #[codec(index = 9)] - RemoveResumeSession(::core::primitive::u128), + RemoveResumeSession(::core::primitive::u32), } } } @@ -671,6 +672,7 @@ pub mod runtime_types { pub struct ActiveProgram<_0> { pub allocations: ::std::vec::Vec, pub pages_with_data: ::std::vec::Vec, + pub memory_infix: runtime_types::gear_core::program::MemoryInfix, pub gas_reservation_map: ::subxt::utils::KeyedVec< runtime_types::gear_core::ids::ReservationId, runtime_types::gear_core::reservation::GasReservationSlot, @@ -925,6 +927,17 @@ pub mod runtime_types { )] pub struct Percent(pub ::core::primitive::u32); } + pub mod program { + use super::runtime_types; + #[derive( + ::subxt::ext::codec::CompactAs, + Debug, + crate::gp::Decode, + crate::gp::DecodeAsType, + crate::gp::Encode, + )] + pub struct MemoryInfix(pub ::core::primitive::u32); + } pub mod reservation { use super::runtime_types; #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] @@ -2574,7 +2587,7 @@ pub mod runtime_types { #[doc = "- `session_id`: id of the resume session."] #[doc = "- `memory_pages`: program memory (or its part) before it was paused."] resume_session_push { - session_id: ::core::primitive::u128, + session_id: ::core::primitive::u32, memory_pages: ::std::vec::Vec<( runtime_types::gear_core::pages::GearPage, runtime_types::gear_core::memory::PageBuf, @@ -2589,7 +2602,7 @@ pub mod runtime_types { #[doc = "- `session_id`: id of the resume session."] #[doc = "- `block_count`: the specified period of rent."] resume_session_commit { - session_id: ::core::primitive::u128, + session_id: ::core::primitive::u32, block_count: ::core::primitive::u32, }, } @@ -2751,7 +2764,7 @@ pub mod runtime_types { #[codec(index = 9)] #[doc = "Program resume session has been started."] ProgramResumeSessionStarted { - session_id: ::core::primitive::u128, + session_id: ::core::primitive::u32, account_id: ::subxt::utils::AccountId32, program_id: runtime_types::gear_core::ids::ProgramId, session_end_block: ::core::primitive::u32, @@ -10292,12 +10305,11 @@ pub mod storage { OriginalCodeStorage, MetadataStorage, ProgramStorage, - MemoryPageStorage, + MemoryPages, WaitingInitStorage, PausedProgramStorage, ResumeSessionsNonce, ResumeSessions, - SessionMemoryPages, } impl StorageInfo for GearProgramStorage { const PALLET: &'static str = "GearProgram"; @@ -10308,12 +10320,11 @@ pub mod storage { Self::OriginalCodeStorage => "OriginalCodeStorage", Self::MetadataStorage => "MetadataStorage", Self::ProgramStorage => "ProgramStorage", - Self::MemoryPageStorage => "MemoryPageStorage", + Self::MemoryPages => "MemoryPages", Self::WaitingInitStorage => "WaitingInitStorage", Self::PausedProgramStorage => "PausedProgramStorage", Self::ResumeSessionsNonce => "ResumeSessionsNonce", Self::ResumeSessions => "ResumeSessions", - Self::SessionMemoryPages => "SessionMemoryPages", } } } diff --git a/gsdk/src/signer/storage.rs b/gsdk/src/signer/storage.rs index 6854c2b1afb..dd3f9b7a03c 100644 --- a/gsdk/src/signer/storage.rs +++ b/gsdk/src/signer/storage.rs @@ -138,14 +138,16 @@ impl SignerStorage { pub async fn set_gpages( &self, program_id: ProgramId, + memory_infix: u32, program_pages: &GearPages, ) -> EventsResult { let mut program_pages_to_set = Vec::with_capacity(program_pages.len()); for program_page in program_pages { let addr = Api::storage( - GearProgramStorage::MemoryPageStorage, + GearProgramStorage::MemoryPages, vec![ subxt::dynamic::Value::from_bytes(program_id), + subxt::dynamic::Value::u128(memory_infix as u128), subxt::dynamic::Value::u128(*program_page.0 as u128), ], ); diff --git a/gsdk/src/storage.rs b/gsdk/src/storage.rs index 96dd567a2c7..5642a050db0 100644 --- a/gsdk/src/storage.rs +++ b/gsdk/src/storage.rs @@ -319,8 +319,12 @@ impl Api { for page in &program.pages_with_data { let addr = Self::storage( - GearProgramStorage::MemoryPageStorage, - vec![Value::from_bytes(program_id), Value::u128(page.0 as u128)], + GearProgramStorage::MemoryPages, + vec![ + Value::from_bytes(program_id), + Value::u128(program.memory_infix.0 as u128), + Value::u128(page.0 as u128), + ], ); let metadata = self.metadata(); diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 2a28d025abc..018a1b5bbcf 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -39,7 +39,7 @@ use gear_core::{ StoredMessage, }, pages::{GearPage, PageU32Size, WasmPage}, - program::Program as CoreProgram, + program::{MemoryInfix, Program as CoreProgram}, reservation::{GasReservationMap, GasReserver}, }; use gear_core_errors::{ErrorReplyReason, SignalCode, SimpleExecutionError}; @@ -100,12 +100,21 @@ impl TestActor { matches!(self, TestActor::Uninitialized(..)) } - fn get_pages_data_mut(&mut self) -> Option<&mut BTreeMap> { + fn get_pages_data_mut(&mut self) -> Option<(MemoryInfix, &mut BTreeMap)> { match self { - TestActor::Initialized(Program::Genuine { pages_data, .. }) - | TestActor::Uninitialized(_, Some(Program::Genuine { pages_data, .. })) => { - Some(pages_data) - } + TestActor::Initialized(Program::Genuine { + pages_data, + program, + .. + }) + | TestActor::Uninitialized( + _, + Some(Program::Genuine { + pages_data, + program, + .. + }), + ) => Some((program.memory_infix(), pages_data)), _ => None, } } @@ -156,6 +165,7 @@ impl TestActor { initialized: program.is_initialized(), pages_with_data: pages_data.keys().copied().collect(), gas_reservation_map, + memory_infix: program.memory_infix(), }, program, )) @@ -332,13 +342,18 @@ impl ExtManager { externalities.execute_with(|| f(self)) } - fn update_storage_pages(program_id: ProgramId, memory_pages: &BTreeMap) { + fn update_storage_pages( + program_id: ProgramId, + memory_infix: MemoryInfix, + memory_pages: &BTreeMap, + ) { // write pages into storage so lazy-pages can access them for (page, buf) in memory_pages { let page_no: u32 = (*page).into(); let prefix = [ System::PAGE_STORAGE_PREFIX.as_slice(), program_id.into_bytes().as_slice(), + memory_infix.inner().to_le_bytes().as_slice(), page_no.to_le_bytes().as_slice(), ] .concat(); @@ -471,7 +486,7 @@ impl ExtManager { String::from("state"), program.code().clone(), Some(program.allocations().clone()), - Some(*program_id), + Some((*program_id, program.memory_infix())), payload, u64::MAX, self.block_info, @@ -609,12 +624,22 @@ impl ExtManager { TestActor::Dormant | TestActor::User => panic!("Actor {program_id} isn't a program"), }; - match program { - Program::Genuine { pages_data, .. } => *pages_data = memory_pages.clone(), + let memory_infix = match program { + Program::Genuine { + pages_data, + program, + .. + } => { + *pages_data = memory_pages.clone(); + + program.memory_infix() + } Program::Mock(_) => panic!("Can't read memory of mock program"), - } + }; - self.with_externalities(|_this| Self::update_storage_pages(*program_id, &memory_pages)) + self.with_externalities(|_this| { + Self::update_storage_pages(*program_id, memory_infix, &memory_pages) + }) } #[track_caller] @@ -1019,8 +1044,8 @@ impl JournalHandler for ExtManager { .get_mut(&program_id) .expect("Can't find existing program"); - if let Some(actor_pages_data) = actor.get_pages_data_mut() { - Self::update_storage_pages(program_id, &pages_data); + if let Some((memory_infix, actor_pages_data)) = actor.get_pages_data_mut() { + Self::update_storage_pages(program_id, memory_infix, &pages_data); actor_pages_data.append(&mut pages_data); } else { @@ -1101,7 +1126,7 @@ impl JournalHandler for ExtManager { let code_and_id: InstrumentedCodeAndId = CodeAndId::from_parts_unchecked(code, code_id).into(); let (code, code_id) = code_and_id.into_parts(); - let candidate = CoreProgram::new(candidate_id, code); + let candidate = CoreProgram::new(candidate_id, Default::default(), code); self.store_new_actor( candidate_id, Program::new(candidate, code_id, Default::default(), Default::default()), diff --git a/gtest/src/program.rs b/gtest/src/program.rs index c2cd0e63057..73104089fb9 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -443,7 +443,7 @@ impl<'a> Program<'a> { } let program_id = id.clone().into().0; - let program = CoreProgram::new(program_id, code); + let program = CoreProgram::new(program_id, Default::default(), code); Self::program_with_id( system, diff --git a/lazy-pages/interface/src/lib.rs b/lazy-pages/interface/src/lib.rs index 9aab7a86f27..7865a92fc3e 100644 --- a/lazy-pages/interface/src/lib.rs +++ b/lazy-pages/interface/src/lib.rs @@ -29,6 +29,7 @@ use gear_core::{ ids::ProgramId, memory::{HostPointer, Memory, MemoryInterval}, pages::{GearPage, PageNumber, PageU32Size, WasmPage}, + program::MemoryInfix, str::LimitedStr, }; use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesWeights, ProcessAccessError, Status}; @@ -60,6 +61,7 @@ pub fn try_to_enable_lazy_pages(prefix: [u8; 32]) -> bool { pub fn init_for_program( mem: &mut impl Memory, program_id: ProgramId, + memory_infix: MemoryInfix, stack_end: Option, globals_config: GlobalsAccessConfig, weights: LazyPagesWeights, @@ -80,7 +82,12 @@ pub fn init_for_program( wasm_mem_addr: mem.get_buffer_host_addr(), wasm_mem_size: mem.size().raw(), stack_end: stack_end.map(|p| p.raw()), - program_id: <[u8; 32]>::from(program_id.into_origin()).into(), + program_key: { + let program_id = <[u8; 32]>::from(program_id.into_origin()); + let memory_infix = memory_infix.inner().to_le_bytes(); + + [&program_id[..], &memory_infix[..]].concat() + }, globals_config, weights, }; diff --git a/lazy-pages/src/lib.rs b/lazy-pages/src/lib.rs index 7b384255a06..304b826fa9d 100644 --- a/lazy-pages/src/lib.rs +++ b/lazy-pages/src/lib.rs @@ -110,7 +110,7 @@ pub fn initialize_for_program( wasm_mem_addr: Option, wasm_mem_size: u32, stack_end: Option, - program_id: Vec, + program_key: Vec, globals_config: Option, weights: Vec, ) -> Result<(), Error> { @@ -158,7 +158,7 @@ pub fn initialize_for_program( runtime_ctx .pages_storage_prefix .iter() - .chain(program_id.iter()) + .chain(program_key.iter()) .copied() .collect(), ), diff --git a/pallets/gear-debug/src/lib.rs b/pallets/gear-debug/src/lib.rs index 5f0425e6a60..7e699474be1 100644 --- a/pallets/gear-debug/src/lib.rs +++ b/pallets/gear-debug/src/lib.rs @@ -218,6 +218,7 @@ pub mod pallet { }; let persistent_pages = T::ProgramStorage::get_program_data_for_pages( id, + active.memory_infix, active.pages_with_data.iter(), ) .unwrap(); diff --git a/pallets/gear-program/src/lib.rs b/pallets/gear-program/src/lib.rs index b06ac001038..bb3fd47b935 100644 --- a/pallets/gear-program/src/lib.rs +++ b/pallets/gear-program/src/lib.rs @@ -133,6 +133,8 @@ use sp_std::{convert::TryInto, prelude::*}; pub use pallet::*; +pub mod migrations; + #[cfg(test)] mod mock; @@ -146,7 +148,10 @@ pub mod pallet { CodeMetadata, Program, }; use frame_support::{ - dispatch::EncodeLike, pallet_prelude::*, storage::PrefixIterator, traits::StorageVersion, + dispatch::EncodeLike, + pallet_prelude::*, + storage::{Key, PrefixIterator}, + traits::StorageVersion, StoragePrefixedMap, }; use frame_system::pallet_prelude::*; @@ -155,12 +160,13 @@ pub mod pallet { ids::{CodeId, MessageId, ProgramId}, memory::PageBuf, pages::GearPage, + program::MemoryInfix, }; use primitive_types::H256; use sp_runtime::DispatchError; /// The current storage version. - pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(3); #[pallet::config] pub trait Config: frame_system::Config { @@ -286,14 +292,22 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] - pub(crate) type MemoryPageStorage = - StorageDoubleMap<_, Identity, ProgramId, Identity, GearPage, PageBuf>; + pub(crate) type MemoryPages = StorageNMap< + _, + ( + Key, + Key, + Key, + ), + PageBuf, + >; - common::wrap_storage_double_map!( - storage: MemoryPageStorage, + common::wrap_storage_triple_map!( + storage: MemoryPages, name: MemoryPageStorageWrap, key1: ProgramId, - key2: GearPage, + key2: MemoryInfix, + key3: GearPage, value: PageBuf ); @@ -345,18 +359,6 @@ pub mod pallet { value: ResumeSession<::AccountId, BlockNumberFor> ); - #[pallet::storage] - #[pallet::unbounded] - pub(crate) type SessionMemoryPages = - StorageMap<_, Identity, SessionId, Vec<(GearPage, PageBuf)>>; - - common::wrap_storage_map!( - storage: SessionMemoryPages, - name: SessionMemoryPagesWrap, - key: SessionId, - value: Vec<(GearPage, PageBuf)> - ); - impl common::CodeStorage for pallet::Pallet { type InstrumentedCodeStorage = CodeStorageWrap; type InstrumentedLenStorage = CodeLenStorageWrap; @@ -375,7 +377,7 @@ pub mod pallet { type WaitingInitMap = WaitingInitStorageWrap; fn pages_final_prefix() -> [u8; 32] { - MemoryPageStorage::::final_prefix() + MemoryPages::::final_prefix() } } @@ -384,7 +386,6 @@ pub mod pallet { type CodeStorage = Self; type NonceStorage = ResumeSessionsNonceWrap; type ResumeSessions = ResumeSessionsWrap; - type SessionMemoryPages = SessionMemoryPagesWrap; } impl IterableMap<(ProgramId, Program>)> for pallet::Pallet { @@ -411,16 +412,4 @@ pub mod pallet { WaitingInitStorage::::append(key, item); } } - - impl AppendMapStorage<(GearPage, PageBuf), SessionId, Vec<(GearPage, PageBuf)>> - for SessionMemoryPagesWrap - { - fn append(key: EncodeLikeKey, item: EncodeLikeItem) - where - EncodeLikeKey: EncodeLike, - EncodeLikeItem: EncodeLike<(GearPage, PageBuf)>, - { - SessionMemoryPages::::append(key, item); - } - } } diff --git a/pallets/gear-program/src/migrations.rs b/pallets/gear-program/src/migrations.rs new file mode 100644 index 00000000000..f24db6df7fd --- /dev/null +++ b/pallets/gear-program/src/migrations.rs @@ -0,0 +1,307 @@ +// 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, MemoryPages, Pallet, PausedProgramStorage, ProgramStorage, ResumeSessions}; +use common::Program; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use gear_core::program::MemoryInfix; +use sp_std::marker::PhantomData; + +#[cfg(feature = "try-runtime")] +use { + frame_support::codec::{Decode, Encode}, + sp_std::vec::Vec, +}; + +const MEMORY_INFIX: MemoryInfix = MemoryInfix::new(0); + +pub struct MigrateToV3(PhantomData); + +impl OnRuntimeUpgrade for MigrateToV3 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + assert!(v2::SessionMemoryPages::::iter().next().is_none()); + assert!(ResumeSessions::::iter().next().is_none()); + assert!(PausedProgramStorage::::iter().next().is_none()); + + let count = v2::ProgramStorage::::iter().fold(0u64, |count, (program_id, program)| { + match program { + v2::Program::Terminated(_) | v2::Program::Exited(_) => { + assert!(v2::MemoryPageStorage::::iter_key_prefix(program_id) + .next() + .is_none()); + } + v2::Program::Active(_) => (), + } + + count + 1 + }); + + Ok(count.encode()) + } + + 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 { + ProgramStorage::::translate( + |program_id, program: v2::Program>| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + Some(match program { + v2::Program::Active(p) => { + for (page, data) in v2::MemoryPageStorage::::drain_prefix(program_id) + { + weight = + weight.saturating_add(T::DbWeight::get().reads_writes(0, 2)); + + MemoryPages::::insert((program_id, MEMORY_INFIX, page), data); + } + + Program::Active(common::ActiveProgram { + allocations: p.allocations, + pages_with_data: p.pages_with_data, + gas_reservation_map: p.gas_reservation_map, + code_hash: p.code_hash, + code_exports: p.code_exports, + static_pages: p.static_pages, + state: p.state, + expiration_block: p.expiration_block, + memory_infix: MEMORY_INFIX, + }) + } + v2::Program::Exited(id) => Program::Exited(id), + v2::Program::Terminated(id) => Program::Terminated(id), + }) + }, + ); + + if v2::SessionMemoryPages::::iter().next().is_some() { + log::error!("v2::SessionMemoryPages is not empty"); + } + + if ResumeSessions::::iter().next().is_some() { + log::error!("ResumeSessions is not empty"); + } + + if PausedProgramStorage::::iter().next().is_some() { + log::error!("PausedProgramStorage is not empty"); + } + + 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 post_upgrade(state: Vec) -> Result<(), &'static str> { + // Check that everything decoded fine. + let count = ProgramStorage::::iter_keys().fold(0u64, |i, k| { + let Ok(program) = ProgramStorage::::try_get(k) else { + unreachable!("Cannot decode v3 Program"); + }; + + if let Program::Active(p) = program { + assert_eq!(p.memory_infix, MEMORY_INFIX); + + for page in p.pages_with_data.iter() { + assert!(MemoryPages::::contains_key((k, p.memory_infix, page))); + } + } + + i + 1 + }); + + let old_count: u64 = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + assert_eq!(count, old_count); + + assert!(v2::MemoryPageStorage::::iter().next().is_none()); + + Ok(()) + } +} + +mod v2 { + use crate::{Config, Pallet}; + use common::ProgramState; + use frame_support::{ + codec::{self, Decode, Encode}, + scale_info::{self, TypeInfo}, + storage::types::{StorageDoubleMap, StorageMap}, + traits::{PalletInfo, StorageInstance}, + Identity, + }; + use gear_core::{ + ids::ProgramId, + memory::PageBuf, + message::DispatchKind, + pages::{GearPage, WasmPage}, + reservation::GasReservationMap, + }; + use primitive_types::H256; + use sp_runtime::traits::Saturating; + use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, prelude::*}; + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub struct ActiveProgram { + pub allocations: BTreeSet, + pub pages_with_data: BTreeSet, + pub gas_reservation_map: GasReservationMap, + pub code_hash: H256, + pub code_exports: BTreeSet, + pub static_pages: WasmPage, + pub state: ProgramState, + pub expiration_block: BlockNumber, + } + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub enum Program { + Active(ActiveProgram), + Exited(ProgramId), + Terminated(ProgramId), + } + + pub struct MemoryPagesPrefix(PhantomData); + + impl StorageInstance for MemoryPagesPrefix { + const STORAGE_PREFIX: &'static str = "MemoryPageStorage"; + + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + } + + pub type MemoryPageStorage = + StorageDoubleMap, Identity, ProgramId, Identity, GearPage, PageBuf>; + + #[cfg(feature = "try-runtime")] + pub struct ProgramStoragePrefix(PhantomData); + + #[cfg(feature = "try-runtime")] + impl StorageInstance for ProgramStoragePrefix { + const STORAGE_PREFIX: &'static str = "ProgramStorage"; + + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + } + + #[cfg(feature = "try-runtime")] + pub type ProgramStorage = StorageMap< + ProgramStoragePrefix, + Identity, + ProgramId, + Program>, + >; + + pub struct SessionMemoryPagesPrefix(PhantomData); + + impl StorageInstance for SessionMemoryPagesPrefix { + const STORAGE_PREFIX: &'static str = "SessionMemoryPages"; + + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + } + + pub type SessionMemoryPages = + StorageMap, Identity, u128, Vec<(GearPage, PageBuf)>>; +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use common::ProgramState; + use frame_support::pallet_prelude::StorageVersion; + use frame_system::pallet_prelude::BlockNumberFor; + use gear_core::{ids::ProgramId, memory::PageBuf, pages::GearPage}; + use sp_runtime::traits::Zero; + + #[test] + fn migration_to_v3_works() { + new_test_ext().execute_with(|| { + StorageVersion::new(2).put::(); + + // add active program + let program_id = ProgramId::from(1u64); + let page = GearPage::from(0); + v2::MemoryPageStorage::::insert(program_id, page, { + let mut page = PageBuf::new_zeroed(); + page[0] = 1; + + page + }); + let program = v2::Program::>::Active(v2::ActiveProgram { + allocations: Default::default(), + pages_with_data: [page].into(), + gas_reservation_map: Default::default(), + code_hash: Default::default(), + code_exports: Default::default(), + static_pages: 13.into(), + state: ProgramState::Initialized, + expiration_block: 100, + }); + v2::ProgramStorage::::insert(program_id, program); + + // add exited program + let program = v2::Program::>::Exited(program_id); + let program_id = ProgramId::from(2u64); + v2::ProgramStorage::::insert(program_id, program); + + // add terminated program + let program = v2::Program::>::Terminated(program_id); + let program_id = ProgramId::from(3u64); + v2::ProgramStorage::::insert(program_id, program); + + let state = MigrateToV3::::pre_upgrade().unwrap(); + let w = MigrateToV3::::on_runtime_upgrade(); + assert!(!w.is_zero()); + MigrateToV3::::post_upgrade(state).unwrap(); + + assert_eq!(StorageVersion::get::(), 3); + }) + } +} diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index c69605bb86c..cc866af80d1 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -594,12 +594,12 @@ benchmarks! { page }; - for i in 0 .. c { - ProgramStorageOf::::set_program_page_data(program_id, GearPage::from(i as u16), memory_page.clone()); - } - let program: ActiveProgram<_> = ProgramStorageOf::::update_active_program(program_id, |program| { - program.pages_with_data = BTreeSet::from_iter((0..c).map(|i| GearPage::from(i as u16))); + for i in 0 .. c { + let page = GearPage::from(i as u16); + ProgramStorageOf::::set_program_page_data(program_id, program.memory_infix, page, memory_page.clone()); + program.pages_with_data.insert(page); + } let wasm_pages = (c as usize * GEAR_PAGE_SIZE) / WASM_PAGE_SIZE; program.allocations = BTreeSet::from_iter((0..wasm_pages).map(|i| WasmPage::from(i as u16))); diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index dfee0f6647a..48e6869b26e 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -1531,6 +1531,7 @@ where { ProgramStorageOf::::set_program_page_data( program_id, + exec.context.program().memory_infix(), page, PageBuf::from_inner(PageBufInner::filled_with(1)), ); diff --git a/pallets/gear/src/benchmarking/tasks.rs b/pallets/gear/src/benchmarking/tasks.rs index 88d6b2fb1cb..a7987fb9412 100644 --- a/pallets/gear/src/benchmarking/tasks.rs +++ b/pallets/gear/src/benchmarking/tasks.rs @@ -77,16 +77,17 @@ where page }; - for i in 0..c { - ProgramStorageOf::::set_program_page_data( - program_id, - GearPage::from(i as u16), - memory_page.clone(), - ); - } - ProgramStorageOf::::update_active_program(program_id, |program| { - program.pages_with_data = BTreeSet::from_iter((0..c).map(|i| GearPage::from(i as u16))); + for i in 0..c { + let page = GearPage::from(i as u16); + ProgramStorageOf::::set_program_page_data( + program_id, + program.memory_infix, + page, + memory_page.clone(), + ); + program.pages_with_data.insert(page); + } let wasm_pages = (c as usize * GEAR_PAGE_SIZE) / WASM_PAGE_SIZE; program.allocations = diff --git a/pallets/gear/src/benchmarking/tests/lazy_pages.rs b/pallets/gear/src/benchmarking/tests/lazy_pages.rs index de869b20aef..631a7e70adb 100644 --- a/pallets/gear/src/benchmarking/tests/lazy_pages.rs +++ b/pallets/gear/src/benchmarking/tests/lazy_pages.rs @@ -277,7 +277,12 @@ where .map(|_| GearPage::new(rng.gen_range(0..size_gear.raw())).unwrap()) { page_sets.add_page_with_data(page); - ProgramStorageOf::::set_program_page_data(program_id, page, PageBuf::new_zeroed()); + ProgramStorageOf::::set_program_page_data( + program_id, + Default::default(), + page, + PageBuf::new_zeroed(), + ); } // execute program with random page costs diff --git a/pallets/gear/src/manager/journal.rs b/pallets/gear/src/manager/journal.rs index dbd89248c1d..79a60a0f110 100644 --- a/pallets/gear/src/manager/journal.rs +++ b/pallets/gear/src/manager/journal.rs @@ -176,10 +176,18 @@ where let _ = TaskPoolOf::::delete(bn, ScheduledTask::PauseProgram(id_exited)); match p { - Program::Active(program) => Self::remove_gas_reservation_map( - id_exited, - core::mem::take(&mut program.gas_reservation_map), - ), + Program::Active(program) => { + Self::remove_gas_reservation_map( + id_exited, + core::mem::take(&mut program.gas_reservation_map), + ); + + Self::clean_inactive_program( + id_exited, + program.memory_infix, + value_destination, + ); + } _ => unreachable!("Action executed only for active program"), } @@ -188,8 +196,6 @@ where .unwrap_or_else(|e| { unreachable!("`exit` can be called only from active program: {:?}", e); }); - - Self::clean_inactive_program(id_exited, value_destination); } fn message_consumed(&mut self, message_id: MessageId) { @@ -336,7 +342,12 @@ where for (page, data) in pages_data { log::trace!("{:?} has been write accessed, update it in storage", page); - ProgramStorageOf::::set_program_page_data(program_id, page, data); + ProgramStorageOf::::set_program_page_data( + program_id, + p.memory_infix, + page, + data, + ); p.pages_with_data.insert(page); } }) @@ -353,7 +364,7 @@ where let removed_pages = p.allocations.difference(&allocations); for page in removed_pages.flat_map(|page| page.to_pages_iter()) { if p.pages_with_data.remove(&page) { - ProgramStorageOf::::remove_program_page_data(program_id, page); + ProgramStorageOf::::remove_program_page_data(program_id, p.memory_infix, page); } } diff --git a/pallets/gear/src/manager/mod.rs b/pallets/gear/src/manager/mod.rs index 8d25b8cf21a..4973d70a7f6 100644 --- a/pallets/gear/src/manager/mod.rs +++ b/pallets/gear/src/manager/mod.rs @@ -74,6 +74,7 @@ use gear_core::{ ids::{CodeId, MessageId, ProgramId, ReservationId}, message::{DispatchKind, SignalMessage}, pages::WasmPage, + program::MemoryInfix, reservation::GasReservationSlot, }; use primitive_types::H256; @@ -239,6 +240,7 @@ where initialized: matches!(active.state, ProgramState::Initialized), pages_with_data: active.pages_with_data, gas_reservation_map: active.gas_reservation_map, + memory_infix: active.memory_infix, }), }) } @@ -269,6 +271,7 @@ where state: common::ProgramState::Uninitialized { message_id }, gas_reservation_map: Default::default(), expiration_block, + memory_infix: Default::default(), }; ProgramStorageOf::::add_program(program_id, program) @@ -367,8 +370,12 @@ where } /// Removes memory pages of the program and transfers program balance to the `value_destination`. - fn clean_inactive_program(program_id: ProgramId, value_destination: ProgramId) { - ProgramStorageOf::::remove_program_pages(program_id); + fn clean_inactive_program( + program_id: ProgramId, + memory_infix: MemoryInfix, + value_destination: ProgramId, + ) { + ProgramStorageOf::::remove_program_pages(program_id, memory_infix); let program_account = &::from_origin(program_id.into_origin()); let balance = CurrencyOf::::free_balance(program_account); @@ -412,10 +419,14 @@ where let _ = TaskPoolOf::::delete(bn, ScheduledTask::PauseProgram(program_id)); match p { - Program::Active(program) => Self::remove_gas_reservation_map( - program_id, - core::mem::take(&mut program.gas_reservation_map), - ), + Program::Active(program) => { + Self::remove_gas_reservation_map( + program_id, + core::mem::take(&mut program.gas_reservation_map), + ); + + Self::clean_inactive_program(program_id, program.memory_infix, origin); + } _ if executed => unreachable!("Action executed only for active program"), _ => (), } @@ -434,8 +445,6 @@ where } }); - Self::clean_inactive_program(program_id, origin); - Pallet::::deposit_event(Event::ProgramChanged { id: program_id, change: ProgramChangeKind::Terminated, diff --git a/pallets/gear/src/manager/task.rs b/pallets/gear/src/manager/task.rs index 4f0026ad372..02d12383a28 100644 --- a/pallets/gear/src/manager/task.rs +++ b/pallets/gear/src/manager/task.rs @@ -180,10 +180,14 @@ where // set program status to Terminated ProgramStorageOf::::update_program_if_active(program_id, |p, _bn| { match p { - Program::Active(program) => Self::remove_gas_reservation_map( - program_id, - core::mem::take(&mut program.gas_reservation_map), - ), + Program::Active(program) => { + Self::remove_gas_reservation_map( + program_id, + core::mem::take(&mut program.gas_reservation_map), + ); + + Self::clean_inactive_program(program_id, program.memory_infix, origin); + } _ => unreachable!("Action executed only for active program"), } @@ -195,8 +199,6 @@ where ); }); - Self::clean_inactive_program(program_id, origin); - Pallet::::deposit_event(Event::ProgramChanged { id: program_id, change: ProgramChangeKind::Terminated, diff --git a/pallets/gear/src/queue.rs b/pallets/gear/src/queue.rs index c4de95c7f11..5cb06f554c7 100644 --- a/pallets/gear/src/queue.rs +++ b/pallets/gear/src/queue.rs @@ -302,6 +302,7 @@ where initialized: matches!(program.state, ProgramState::Initialized), pages_with_data: program.pages_with_data, gas_reservation_map: program.gas_reservation_map, + memory_infix: program.memory_infix, })) } } diff --git a/pallets/gear/src/runtime_api.rs b/pallets/gear/src/runtime_api.rs index 689cfe1b3f7..6accfc8c9a4 100644 --- a/pallets/gear/src/runtime_api.rs +++ b/pallets/gear/src/runtime_api.rs @@ -21,7 +21,7 @@ use crate::queue::{ActorResult, QueueStep}; use common::ActiveProgram; use core::convert::TryFrom; use core_processor::common::PrechargedDispatch; -use gear_core::{code::TryNewCodeConfig, pages::WasmPage}; +use gear_core::{code::TryNewCodeConfig, pages::WasmPage, program::MemoryInfix}; use gear_wasm_instrument::syscalls::SysCallName; // Multiplier 6 was experimentally found as median value for performance, @@ -31,6 +31,7 @@ pub(crate) const RUNTIME_API_BLOCK_LIMITS_COUNT: u64 = 6; pub(crate) struct CodeWithMemoryData { pub instrumented_code: InstrumentedCode, pub allocations: BTreeSet, + pub memory_infix: MemoryInfix, } impl Pallet @@ -278,11 +279,10 @@ where let instrumented_code = T::CodeStorage::get_code(code_id) .ok_or_else(|| String::from("Failed to get code for given program id"))?; - let allocations = program.allocations; - Ok(CodeWithMemoryData { instrumented_code, - allocations, + allocations: program.allocations, + memory_infix: program.memory_infix, }) } @@ -360,6 +360,7 @@ where let CodeWithMemoryData { instrumented_code, allocations, + memory_infix, } = Self::code_with_memory(program_id)?; let block_info = BlockInfo { @@ -377,7 +378,7 @@ where String::from("state"), instrumented_code, Some(allocations), - Some(program_id), + Some((program_id, memory_infix)), payload, gas_allowance, block_info, @@ -395,6 +396,7 @@ where let CodeWithMemoryData { instrumented_code, allocations, + memory_infix, } = Self::code_with_memory(program_id)?; let block_info = BlockInfo { @@ -412,7 +414,7 @@ where String::from("metahash"), instrumented_code, Some(allocations), - Some(program_id), + Some((program_id, memory_infix)), Default::default(), gas_allowance, block_info, diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 518e0c52b2d..af5b3271e35 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -6497,6 +6497,7 @@ fn resume_session_push_works() { let memory_pages = ProgramStorageOf::::get_program_data_for_pages( program_id, + program.memory_infix, program.pages_with_data.iter(), ) .unwrap(); @@ -6563,6 +6564,7 @@ fn resume_session_push_works() { assert!(ProgramStorageOf::::resume_session_page_count(&session_id).is_none()); assert!(ProgramStorageOf::::get_program_data_for_pages( program_id, + program.memory_infix, program.pages_with_data.iter(), ) .is_err()); @@ -6613,6 +6615,7 @@ fn resume_program_works() { let memory_pages = ProgramStorageOf::::get_program_data_for_pages( program_id, + program.memory_infix, program.pages_with_data.iter(), ) .unwrap(); @@ -6624,7 +6627,19 @@ fn resume_program_works() { assert!(ProgramStorageOf::::paused_program_exists(&program_id)); - let block_count = ResumeMinimalPeriodOf::::get(); + let old_nonce = as PausedProgramStorage>::NonceStorage::get(); + // start a session to bump nonce + assert_ok!(Gear::resume_session_init( + RuntimeOrigin::signed(USER_1), + program_id, + program.allocations.clone(), + CodeId::from_origin(program.code_hash), + )); + assert_ne!( + old_nonce, + as PausedProgramStorage>::NonceStorage::get() + ); + assert_ok!(Gear::resume_session_init( RuntimeOrigin::signed(USER_3), program_id, @@ -6651,6 +6666,7 @@ fn resume_program_works() { memory_pages.into_iter().collect() )); + let block_count = ResumeMinimalPeriodOf::::get(); // access to finish session by another user is denied assert_err!( Gear::resume_session_commit(RuntimeOrigin::signed(USER_1), session_id, block_count), @@ -6948,6 +6964,7 @@ fn uninitialized_program_terminates_on_pause() { assert_err!( ProgramStorageOf::::get_program_data_for_pages( program_id, + program.memory_infix, Some(*page).iter() ), pallet_gear_program::Error::::CannotFindDataForPage diff --git a/runtime-interface/src/lib.rs b/runtime-interface/src/lib.rs index fecc627ef0a..85cf8d6b139 100644 --- a/runtime-interface/src/lib.rs +++ b/runtime-interface/src/lib.rs @@ -56,8 +56,9 @@ pub struct LazyPagesProgramContext { pub wasm_mem_size: u32, /// Wasm program stack end page. pub stack_end: Option, - /// Wasm program id. - pub program_id: Vec, + /// The field contains prefix to a program's memory pages, i.e. + /// `program_id` + `memory_infix`. + pub program_key: Vec, /// Globals config to access globals inside lazy-pages. pub globals_config: GlobalsAccessConfig, /// Lazy-pages access weights. @@ -190,7 +191,7 @@ pub trait GearRI { wasm_mem_addr, ctx.wasm_mem_size, ctx.stack_end, - ctx.program_id, + ctx.program_key, Some(ctx.globals_config), ctx.weights, ) diff --git a/runtime/vara/src/migrations.rs b/runtime/vara/src/migrations.rs index 248916f1fad..73efbb2158b 100644 --- a/runtime/vara/src/migrations.rs +++ b/runtime/vara/src/migrations.rs @@ -19,4 +19,4 @@ #[allow(unused)] use crate::*; -pub type Migrations = (); +pub type Migrations = (pallet_gear_program::migrations::MigrateToV3,); diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index 0cc7ce34c37..1d4ff88eccf 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -392,6 +392,7 @@ fn execute_wasm_with_custom_configs( gear_core_processor::Ext::lazy_pages_init_for_program( mem, program_id, + Default::default(), Some(mem.size()), globals_config, Default::default(),