diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd96858a6fe..5f277af5fbc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -147,8 +147,6 @@ jobs: - name: "Test: Try runtime migrations" run: | - export RUST_LOG=remote-ext=debug,runtime=debug - echo "---------- Downloading try-runtime CLI ----------" curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.4/try-runtime-x86_64-unknown-linux-musl -o try-runtime chmod +x ./try-runtime @@ -159,7 +157,7 @@ jobs: time ./try-runtime --runtime ./target/${{ matrix.profiles.name }}/wbuild/vara-runtime/vara_runtime.wasm on-runtime-upgrade --checks=all --no-weight-warnings --disable-spec-version-check live --uri ws://rpc-private.vara-network.io:9944 sleep 5 env: - RUST_LOG: info,remote-ext=debug,runtime=debug + RUST_LOG: info,pallet=debug - name: "Build: Production binaries" if: ${{ inputs.production && matrix.profiles.name == 'release' }} diff --git a/Cargo.lock b/Cargo.lock index a7179dc3c6d..d0cf5e99c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11236,6 +11236,7 @@ dependencies = [ name = "pallet-gear-program" version = "1.4.2" dependencies = [ + "env_logger", "frame-support", "frame-system", "gear-common", diff --git a/common/src/benchmarking.rs b/common/src/benchmarking.rs index 42a4a1ea103..fb740587e0c 100644 --- a/common/src/benchmarking.rs +++ b/common/src/benchmarking.rs @@ -128,8 +128,7 @@ pub fn set_program( ProgramStorage::add_program( program_id, ActiveProgram { - allocations: Default::default(), - pages_with_data: Default::default(), + allocations_tree_len: 0, code_hash: CodeId::generate(&code).into_origin(), code_exports: Default::default(), static_pages, diff --git a/common/src/program_storage.rs b/common/src/program_storage.rs index d871048f9df..c1b968aacb8 100644 --- a/common/src/program_storage.rs +++ b/common/src/program_storage.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use gear_core::pages::{numerated::tree::IntervalsTree, WasmPage}; + use super::*; use crate::storage::{MapStorage, TripleMapStorage}; use core::fmt::Debug; @@ -56,11 +58,13 @@ pub trait ProgramStorage { Key3 = GearPage, Value = PageBuf, >; + type AllocationsMap: MapStorage>; /// Attempt to remove all items from all the associated maps. fn reset() { Self::ProgramMap::clear(); Self::MemoryPageMap::clear(); + Self::AllocationsMap::clear(); } /// Store a program to be associated with the given key `program_id` from the map. @@ -102,6 +106,46 @@ pub trait ProgramStorage { }) } + fn remove_data_for_pages( + program_id: ProgramId, + memory_infix: MemoryInfix, + pages: impl Iterator, + ) { + for page in pages { + Self::remove_program_page_data(program_id, memory_infix, page); + } + } + + fn allocations(program_id: ProgramId) -> Option> { + Self::AllocationsMap::get(&program_id) + } + + fn set_allocations(program_id: ProgramId, allocations: IntervalsTree) { + Self::update_active_program(program_id, |program| { + program.allocations_tree_len = u32::try_from(allocations.intervals_amount()) + .unwrap_or_else(|err| { + // This panic is impossible because page numbers are u32. + unreachable!("allocations tree length is too big to fit into u32: {err}") + }); + }) + .unwrap_or_else(|err| { + // set_allocations must be called only for active programs. + unreachable!("Failed to update program allocations: {err:?}") + }); + Self::AllocationsMap::insert(program_id, allocations); + } + + fn clear_allocations(program_id: ProgramId) { + Self::AllocationsMap::remove(program_id); + } + + fn memory_infix(program_id: ProgramId) -> Option { + match Self::ProgramMap::get(&program_id) { + Some(Program::Active(program)) => Some(program.memory_infix), + _ => None, + } + } + /// Update the program under the given key `program_id` only if the /// stored program is an active one. fn update_program_if_active( @@ -124,20 +168,12 @@ pub trait ProgramStorage { Ok(result) } - /// Return program data for each page from `pages`. - fn get_program_data_for_pages( + /// Return data buffer for each memory page, which has data. + fn get_program_pages_data( 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, &memory_infix, &page) - .ok_or(Self::InternalError::cannot_find_page_data())?; - pages_data.insert(page, data); - } - - Ok(pages_data) + Ok(Self::MemoryPageMap::iter_prefix(&program_id, &memory_infix).collect()) } /// Store a memory page buffer to be associated with the given keys `program_id`, `memory_infix` and `page` from the map. @@ -160,7 +196,7 @@ pub trait ProgramStorage { } /// Remove all memory page buffers under the given keys `program_id` and `memory_infix`. - fn remove_program_pages(program_id: ProgramId, memory_infix: MemoryInfix) { + fn clear_program_memory(program_id: ProgramId, memory_infix: MemoryInfix) { Self::MemoryPageMap::clear_prefix(program_id, memory_infix); } diff --git a/common/src/scheduler/task.rs b/common/src/scheduler/task.rs index 1e563c7845b..199d3b0a2eb 100644 --- a/common/src/scheduler/task.rs +++ b/common/src/scheduler/task.rs @@ -108,7 +108,6 @@ impl ScheduledTask { RemoveGasReservation(program_id, reservation_id) => { handler.remove_gas_reservation(program_id, reservation_id) } - #[allow(deprecated)] RemoveResumeSession(session_id) => handler.remove_resume_session(session_id), } diff --git a/common/src/storage/primitives/triple_map.rs b/common/src/storage/primitives/triple_map.rs index 5f9a6ce4c2d..f583d0fe133 100644 --- a/common/src/storage/primitives/triple_map.rs +++ b/common/src/storage/primitives/triple_map.rs @@ -81,6 +81,11 @@ pub trait TripleMapStorage { /// Remove items from the map matching a `key1`/`key2` prefix. fn clear_prefix(key1: Self::Key1, key2: Self::Key2); + + fn iter_prefix( + key1: &Self::Key1, + key2: &Self::Key2, + ) -> impl Iterator; } /// Creates new type with specified name and key1-key2-key3-value types and @@ -150,6 +155,13 @@ macro_rules! wrap_storage_triple_map { fn clear_prefix(key1: Self::Key1, key2: Self::Key2) { let _ = $storage::::clear_prefix((key1, key2), u32::MAX, None); } + + fn iter_prefix( + key1: &Self::Key1, + key2: &Self::Key2, + ) -> impl Iterator { + $storage::::iter_prefix((key1, key2)) + } } }; } diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index 3eaa9856459..2a57e6f92f0 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -561,31 +561,3 @@ pub(crate) struct WasmExecutionContext { /// Size of the memory block. pub memory_size: WasmPagesAmount, } - -/// Struct with dispatch and counters charged for program data. -#[derive(Debug)] -pub struct PrechargedDispatch { - dispatch: IncomingDispatch, - gas: GasCounter, - allowance: GasAllowanceCounter, -} - -impl PrechargedDispatch { - /// Create new instance from parts. - pub(crate) fn from_parts( - dispatch: IncomingDispatch, - gas: GasCounter, - allowance: GasAllowanceCounter, - ) -> Self { - Self { - dispatch, - gas, - allowance, - } - } - - /// Decompose the instance into parts. - pub fn into_parts(self) -> (IncomingDispatch, GasCounter, GasAllowanceCounter) { - (self.dispatch, self.gas, self.allowance) - } -} diff --git a/core-processor/src/configs.rs b/core-processor/src/configs.rs index cc0cdf1b751..d7000f5a873 100644 --- a/core-processor/src/configs.rs +++ b/core-processor/src/configs.rs @@ -98,6 +98,8 @@ pub struct ProcessCosts { pub instrumentation_per_byte: CostOf, /// Module instantiation costs. pub instantiation_costs: InstantiationCosts, + /// Load program allocations cost per interval. + pub load_allocations_per_interval: CostOf, } /// Execution settings for handling messages. diff --git a/core-processor/src/context.rs b/core-processor/src/context.rs index 41c73eb56e1..b9f13fdaa60 100644 --- a/core-processor/src/context.rs +++ b/core-processor/src/context.rs @@ -29,6 +29,17 @@ use gear_core::{ reservation::GasReserver, }; +/// Struct with dispatch and counters charged for program data. +#[derive(Debug)] +pub struct ContextChargedForProgram { + pub(crate) dispatch: IncomingDispatch, + pub(crate) destination_id: ProgramId, + pub(crate) gas_counter: GasCounter, + pub(crate) gas_allowance_counter: GasAllowanceCounter, +} + +pub struct ContextChargedForAllocations(pub(crate) ContextChargedForProgram); + pub(crate) struct ContextData { pub(crate) gas_counter: GasCounter, pub(crate) gas_allowance_counter: GasAllowanceCounter, diff --git a/core-processor/src/lib.rs b/core-processor/src/lib.rs index 1f414d24b09..f347e36c6e8 100644 --- a/core-processor/src/lib.rs +++ b/core-processor/src/lib.rs @@ -45,8 +45,9 @@ pub use ext::{ }; pub use handler::handle_journal; pub use precharge::{ - precharge_for_code, precharge_for_code_length, precharge_for_instrumentation, - precharge_for_module_instantiation, precharge_for_program, SuccessfulDispatchResultKind, + precharge_for_allocations, precharge_for_code, precharge_for_code_length, + precharge_for_instrumentation, precharge_for_module_instantiation, precharge_for_program, + SuccessfulDispatchResultKind, }; pub use processing::{ process, process_execution_error, process_non_executable, process_reinstrumentation_error, diff --git a/core-processor/src/precharge.rs b/core-processor/src/precharge.rs index 62fd4110274..28ca5d074ea 100644 --- a/core-processor/src/precharge.rs +++ b/core-processor/src/precharge.rs @@ -15,13 +15,11 @@ // along with this program. If not, see . use crate::{ - common::{ - ActorExecutionErrorReplyReason, DispatchResult, ExecutableActorData, JournalNote, - PrechargedDispatch, - }, + common::{ActorExecutionErrorReplyReason, DispatchResult, ExecutableActorData, JournalNote}, configs::{BlockConfig, ProcessCosts}, context::{ - ContextChargedForCodeLength, ContextChargedForMemory, ContextData, SystemReservationContext, + ContextChargedForAllocations, ContextChargedForCodeLength, ContextChargedForMemory, + ContextChargedForProgram, ContextData, SystemReservationContext, }, processing::{process_allowance_exceed, process_execution_error, process_success}, ContextChargedForCode, ContextChargedForInstrumentation, @@ -56,6 +54,9 @@ pub enum PreChargeGasOperation { /// Instrument Wasm module. #[display(fmt = "instrument Wasm module")] ModuleInstrumentation, + /// Obtain program allocations. + #[display(fmt = "obtain program allocations")] + Allocations, } #[derive(Debug, Eq, PartialEq)] @@ -163,7 +164,7 @@ impl<'a> GasPrecharger<'a> { } } -/// Possible variants of the `DispatchResult` if the latter contains value. +/// Possible variants of the [`DispatchResult`] if the latter contains value. #[allow(missing_docs)] #[derive(Debug)] pub enum SuccessfulDispatchResultKind { @@ -181,7 +182,7 @@ pub fn precharge_for_program( gas_allowance: u64, dispatch: IncomingDispatch, destination_id: ProgramId, -) -> PrechargeResult { +) -> PrechargeResult { let mut gas_counter = GasCounter::new(dispatch.gas_limit()); let mut gas_allowance_counter = GasAllowanceCounter::new(gas_allowance); let mut charger = GasPrecharger::new( @@ -191,11 +192,12 @@ pub fn precharge_for_program( ); match charger.charge_gas_for_program_data() { - Ok(()) => Ok(PrechargedDispatch::from_parts( + Ok(()) => Ok(ContextChargedForProgram { dispatch, + destination_id, gas_counter, gas_allowance_counter, - )), + }), Err(PrechargeError::BlockGasExceeded) => { let gas_burned = gas_counter.burned(); Err(process_allowance_exceed( @@ -218,6 +220,52 @@ pub fn precharge_for_program( } } +/// Precharge for allocations obtaining from storage. +pub fn precharge_for_allocations( + block_config: &BlockConfig, + mut context: ContextChargedForProgram, + allocations_tree_len: u32, +) -> PrechargeResult { + let mut charger = GasPrecharger::new( + &mut context.gas_counter, + &mut context.gas_allowance_counter, + &block_config.costs, + ); + + if allocations_tree_len == 0 { + return Ok(ContextChargedForAllocations(context)); + } + + let amount = block_config + .costs + .load_allocations_per_interval + .cost_for(allocations_tree_len) + .saturating_add(block_config.costs.read.cost_for_one()); + + match charger.charge_gas(PreChargeGasOperation::Allocations, amount) { + Ok(()) => Ok(ContextChargedForAllocations(context)), + Err(PrechargeError::BlockGasExceeded) => { + let gas_burned = context.gas_counter.burned(); + Err(process_allowance_exceed( + context.dispatch, + context.destination_id, + gas_burned, + )) + } + Err(PrechargeError::GasExceeded(op)) => { + let gas_burned = context.gas_counter.burned(); + let system_reservation_ctx = SystemReservationContext::from_dispatch(&context.dispatch); + Err(process_execution_error( + context.dispatch, + context.destination_id, + gas_burned, + system_reservation_ctx, + ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), + )) + } + } +} + /// Charge a message for fetching the actual length of the binary code /// from a storage. The updated value of binary code length /// should be kept in standalone storage. The caller has to call this @@ -228,11 +276,15 @@ pub fn precharge_for_program( /// - if a required dispatch method is exported. pub fn precharge_for_code_length( block_config: &BlockConfig, - dispatch: PrechargedDispatch, - destination_id: ProgramId, + context: ContextChargedForAllocations, actor_data: ExecutableActorData, ) -> PrechargeResult { - let (dispatch, mut gas_counter, mut gas_allowance_counter) = dispatch.into_parts(); + let ContextChargedForProgram { + dispatch, + destination_id, + mut gas_counter, + mut gas_allowance_counter, + } = context.0; if !actor_data.code_exports.contains(&dispatch.kind()) { return Err(process_success( diff --git a/core-processor/src/processing.rs b/core-processor/src/processing.rs index 19b6ab89cd3..b5572e0e3a8 100644 --- a/core-processor/src/processing.rs +++ b/core-processor/src/processing.rs @@ -19,8 +19,7 @@ use crate::{ common::{ ActorExecutionErrorReplyReason, DispatchOutcome, DispatchResult, DispatchResultKind, - ExecutionError, JournalNote, PrechargedDispatch, SystemExecutionError, - WasmExecutionContext, + ExecutionError, JournalNote, SystemExecutionError, WasmExecutionContext, }, configs::{BlockConfig, ExecutionSettings}, context::*, @@ -345,11 +344,14 @@ pub fn process_reinstrumentation_error( } /// Helper function for journal creation in message no execution case. -pub fn process_non_executable( - context: PrechargedDispatch, - destination_id: ProgramId, -) -> Vec { - let (dispatch, gas_counter, _) = context.into_parts(); +pub fn process_non_executable(context: ContextChargedForProgram) -> Vec { + let ContextChargedForProgram { + dispatch, + gas_counter, + destination_id, + .. + } = context; + let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); process_error( diff --git a/core/src/code/utils.rs b/core/src/code/utils.rs index bf01bb28e1f..193081fb61f 100644 --- a/core/src/code/utils.rs +++ b/core/src/code/utils.rs @@ -36,7 +36,7 @@ use gear_wasm_instrument::{ use wasmparser::Payload; /// Defines maximal permitted count of memory pages. -pub const MAX_WASM_PAGES_AMOUNT: u16 = 512; +pub const MAX_WASM_PAGES_AMOUNT: u16 = u16::MAX / 2 + 1; // 2GB /// Reference type size in bytes. pub(crate) const REF_TYPE_SIZE: u32 = 4; diff --git a/core/src/memory.rs b/core/src/memory.rs index feee39473e4..f4f04f4a1e8 100644 --- a/core/src/memory.rs +++ b/core/src/memory.rs @@ -98,8 +98,16 @@ impl Debug for MemoryInterval { } } +/// Error in attempt to make wrong size page buffer. +#[derive(Debug, Default, PartialEq, Eq, Clone, TypeInfo, derive_more::Display)] +#[display( + fmt = "Trying to make wrong size page buffer, must be {:#x}", + GearPage::SIZE +)] +pub struct IntoPageBufError; + /// Alias for inner type of page buffer. -pub type PageBufInner = LimitedVec; +pub type PageBufInner = LimitedVec; /// Buffer for gear page data. #[derive(Clone, PartialEq, Eq, TypeInfo)] diff --git a/core/src/program.rs b/core/src/program.rs index 079f8614eed..4f714854ab2 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -21,7 +21,7 @@ use crate::{ ids::{MessageId, ProgramId}, message::DispatchKind, - pages::{numerated::tree::IntervalsTree, GearPage, WasmPage, WasmPagesAmount}, + pages::WasmPagesAmount, reservation::GasReservationMap, }; use alloc::collections::BTreeSet; @@ -91,10 +91,8 @@ impl core::convert::TryFrom> /// Active program in storage. #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] pub struct ActiveProgram { - /// Set of wasm pages, that were allocated by the program. - pub allocations: IntervalsTree, - /// Set of gear pages, that have data in storage. - pub pages_with_data: IntervalsTree, + /// Continuous intervals amount in program allocations. + pub allocations_tree_len: u32, /// Infix of memory pages storage (is used for memory wake after pausing) pub memory_infix: MemoryInfix, /// Gas reservation map. diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index f865d3c4a64..5bf9b914d3b 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -205,7 +205,7 @@ pub fn process_next_message>( let dispatch = IncomingDispatch::new(kind, incoming_message, context); - let precharged_dispatch = match core_processor::precharge_for_program( + let context = match core_processor::precharge_for_program( &block_config, 1_000_000_000_000, dispatch, @@ -217,12 +217,22 @@ pub fn process_next_message>( let code = instrumented_code.expect("Instrumented code must be provided if program is active"); + // TODO: support normal allocations len #4068 let allocations = active_state.allocations_hash.with_hash_or_default(|hash| { ri.storage() .read_allocations(hash) .expect("Cannot get allocations") }); + let context = match core_processor::precharge_for_allocations( + &block_config, + context, + allocations.intervals_amount() as u32, + ) { + Ok(context) => context, + Err(journal) => return journal, + }; + let gas_reservation_map = active_state .gas_reservation_map_hash .with_hash_or_default(|hash| { @@ -245,15 +255,11 @@ pub fn process_next_message>( memory_infix: active_state.memory_infix, }; - let context = match core_processor::precharge_for_code_length( - &block_config, - precharged_dispatch, - program_id, - actor_data, - ) { - Ok(context) => context, - Err(journal) => return journal, - }; + let context = + match core_processor::precharge_for_code_length(&block_config, context, actor_data) { + Ok(context) => context, + Err(journal) => return journal, + }; let context = ContextChargedForCode::from(context); let context = ContextChargedForInstrumentation::from(context); diff --git a/gclient/src/api/calls.rs b/gclient/src/api/calls.rs index 21688115d34..aa206be1be5 100644 --- a/gclient/src/api/calls.rs +++ b/gclient/src/api/calls.rs @@ -18,6 +18,7 @@ use super::{GearApi, Result}; use crate::{api::storage::account_id::IntoAccountId32, utils, Error}; +use anyhow::anyhow; use gear_core::{ gas::LockId, ids::*, @@ -390,7 +391,11 @@ impl GearApi { let src_program_pages = self .0 .api() - .gpages_at(src_program_id, &src_program, src_block_hash) + .gpages_at( + src_program_id, + Some(src_program.memory_infix.0), + src_block_hash, + ) .await?; let src_program_reserved_gas_node_ids: Vec = src_program @@ -574,6 +579,52 @@ impl GearApi { Ok(dest_program_id) } + /// Get all pages with their data for program at specified block. + pub async fn get_program_pages_data_at( + &self, + program_id: ProgramId, + block_hash: Option, + ) -> Result> { + let pages_data = self.0.api().gpages_at(program_id, None, block_hash).await?; + + let mut res = BTreeMap::new(); + for (page, data) in pages_data.into_iter() { + res.insert( + GearPage::try_from(page).map_err(|err| anyhow!("{err}"))?, + PageBuf::from_inner(data.try_into().map_err(|err| anyhow!("{err}"))?), + ); + } + + Ok(res) + } + + /// Get specified pages with their data for program at specified block. + pub async fn get_program_specified_pages_data_at( + &self, + program_id: ProgramId, + pages: impl Iterator, + block_hash: Option, + ) -> Result> { + let pages_data = self + .0 + .api() + .specified_gpages_at(program_id, None, pages.map(Into::into), block_hash) + .await?; + + let mut res = BTreeMap::new(); + for (page, data) in pages_data.into_iter() { + res.insert( + GearPage::try_from(page).map_err(|err| anyhow::Error::msg(err.to_string()))?, + PageBuf::from_inner( + data.try_into() + .map_err(|_| anyhow::Error::msg("incorrect page data size"))?, + ), + ); + } + + Ok(res) + } + /// Save program (identified by `program_id`) memory dump to the file for /// further restoring in gclient/gtest. Program memory dumped at the /// time of `block_hash` if presented or the most recent block. @@ -592,7 +643,7 @@ impl GearApi { let program_pages = self .0 .api() - .gpages_at(program_id, &program, block_hash) + .gpages_at(program_id, Some(program.memory_infix.0), block_hash) .await? .into_iter() .filter_map(|(page_number, page_data)| { diff --git a/gclient/tests/memory_dump.rs b/gclient/tests/memory_dump.rs index d8c2c0a3919..861967f9dba 100644 --- a/gclient/tests/memory_dump.rs +++ b/gclient/tests/memory_dump.rs @@ -16,9 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::{collections::BTreeSet, ops::Deref}; + use demo_custom::{InitMessage, WASM_BINARY}; use gclient::{EventListener, EventProcessor, GearApi, Result}; -use gear_core::ids::ProgramId; +use gear_core::{ids::ProgramId, pages::GearPage}; use parity_scale_codec::Encode; async fn charge_10( @@ -118,3 +120,90 @@ async fn memory_dump() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn memory_download() -> Result<()> { + // Create API instance + let api = GearApi::dev_from_path("../target/release/gear").await?; + // Subscribe to events + let mut listener = api.subscribe().await?; + // Check that blocks are still running + assert!(listener.blocks_running().await?); + + let wat = r#" + (module + (import "env" "memory" (memory 512)) + (export "init" (func $init)) + (func $init + (local $counter i32) + + (loop + (i32.store + (i32.mul (local.get $counter) (i32.const 0x8000)) + (i32.const 0x42) + ) + + (i32.add (local.get $counter) (i32.const 1)) + local.tee $counter + + i32.const 1000 + i32.lt_u + br_if 0 + ) + ) + ) + "#; + + let wasm = wat::parse_str(wat).unwrap(); + + // Upload and init the program + let (message_id, program_id, _hash) = api + .upload_program_bytes( + wasm, + gclient::now_micros().to_le_bytes(), + Vec::new(), + 200_000_000_000, + 0, + ) + .await?; + + assert!(listener.message_processed(message_id).await?.succeed()); + + let timer_start = gclient::now_micros(); + let pages = api.get_program_pages_data_at(program_id, None).await?; + let timer_end = gclient::now_micros(); + + println!( + "Storage prefix iteration memory download took: {} ms", + (timer_end - timer_start) / 1000 + ); + + let mut accessed_pages = BTreeSet::new(); + let mut expected_data = [0u8; 0x4000]; + expected_data[0] = 0x42; + for (page, data) in pages { + accessed_pages.insert(page); + assert_eq!(data.deref(), expected_data.as_slice()); + } + + assert_eq!( + accessed_pages, + (0..1000) + .map(|p| p * 2) + .map(Into::into) + .collect::>() + ); + + let timer_start = gclient::now_micros(); + let _pages = api + .get_program_specified_pages_data_at(program_id, accessed_pages.into_iter(), None) + .await?; + let timer_end = gclient::now_micros(); + + println!( + "Memory page by page download took: {} ms", + (timer_end - timer_start) / 1000 + ); + + Ok(()) +} diff --git a/gclient/tests/upload.rs b/gclient/tests/upload.rs index 5f3c23a0e0a..6729d648d5d 100644 --- a/gclient/tests/upload.rs +++ b/gclient/tests/upload.rs @@ -22,6 +22,7 @@ use std::time::Duration; use demo_wat::WatExample; use gclient::{errors, Error, EventProcessor, GearApi}; +use gear_core::{code::MAX_WASM_PAGES_AMOUNT, pages::WasmPage}; async fn upload_programs_and_check( api: &GearApi, @@ -77,6 +78,8 @@ async fn harmless_upload() -> anyhow::Result<()> { WatExample::InfRecursion, WatExample::ReadAccess, WatExample::ReadWriteAccess, + WatExample::from_wat(use_big_memory_wat()) + .expect("Cannot create wat example for big memory test"), ]; let codes = examples.into_iter().map(|e| e.code()).collect(); @@ -238,3 +241,29 @@ async fn test_upload_failed() -> anyhow::Result<()> { Ok(()) } + +fn use_big_memory_wat() -> String { + let last_4_bytes_offset = WasmPage::from(MAX_WASM_PAGES_AMOUNT).offset() - 4; + let middle_4_bytes_offset = WasmPage::from(MAX_WASM_PAGES_AMOUNT / 2).offset(); + + format!( + r#" + (module + (import "env" "memory" (memory 0)) + (import "env" "alloc" (func $alloc (param i32) (result i32))) + (export "init" (func $init)) + (func $init + (drop (call $alloc (i32.const {MAX_WASM_PAGES_AMOUNT}))) + + ;; access last 4 bytes + (i32.store (i32.const {last_4_bytes_offset}) (i32.const 0x42)) + + ;; access first 4 bytes + (i32.store (i32.const 0) (i32.const 0x42)) + + ;; access 4 bytes in the middle + (i32.store (i32.const {middle_4_bytes_offset}) (i32.const 0x42)) + ) + )"# + ) +} diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 31b0980d22e..4edc4c06d65 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -693,8 +693,13 @@ pub mod runtime_types { pub mod memory { use super::runtime_types; #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] + pub struct IntoPageBufError; + #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct PageBuf( - pub runtime_types::gear_core::buffer::LimitedVec<::core::primitive::u8, ()>, + pub runtime_types::gear_core::buffer::LimitedVec< + ::core::primitive::u8, + runtime_types::gear_core::memory::IntoPageBufError, + >, ); } pub mod message { @@ -879,12 +884,7 @@ pub mod runtime_types { use super::runtime_types; #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct ActiveProgram<_0> { - pub allocations: runtime_types::numerated::tree::IntervalsTree< - runtime_types::gear_core::pages::Page2, - >, - pub pages_with_data: runtime_types::numerated::tree::IntervalsTree< - runtime_types::gear_core::pages::Page, - >, + pub allocations_tree_len: ::core::primitive::u32, pub memory_infix: runtime_types::gear_core::program::MemoryInfix, pub gas_reservation_map: ::subxt::utils::KeyedVec< runtime_types::gprimitives::ReservationId, @@ -2497,6 +2497,7 @@ pub mod runtime_types { pub code_instrumentation_cost: runtime_types::sp_weights::weight_v2::Weight, pub code_instrumentation_byte_cost: runtime_types::sp_weights::weight_v2::Weight, + pub load_allocations_weight: runtime_types::sp_weights::weight_v2::Weight, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct SyscallWeights { @@ -8992,6 +8993,7 @@ pub mod storage { CodeLenStorage, OriginalCodeStorage, MetadataStorage, + AllocationsStorage, ProgramStorage, MemoryPages, } @@ -9003,6 +9005,7 @@ pub mod storage { Self::CodeLenStorage => "CodeLenStorage", Self::OriginalCodeStorage => "OriginalCodeStorage", Self::MetadataStorage => "MetadataStorage", + Self::AllocationsStorage => "AllocationsStorage", Self::ProgramStorage => "ProgramStorage", Self::MemoryPages => "MemoryPages", } diff --git a/gsdk/src/storage.rs b/gsdk/src/storage.rs index d14e79707bb..4c5ff19cde3 100644 --- a/gsdk/src/storage.rs +++ b/gsdk/src/storage.rs @@ -60,7 +60,7 @@ impl Api { /// /// # You may not need this. /// - /// Read the docs of [`Api`] to checkout the wrappred storage queries, + /// Read the docs of [`Api`] to checkout the wrapped storage queries, /// we need this function only when we want to execute a query which /// has not been wrapped in `gsdk`. /// @@ -112,9 +112,8 @@ impl Api { } /// Get program pages from program id. - pub async fn program_pages(&self, pid: ProgramId) -> Result { - let program = self.gprog(pid).await?; - self.gpages(pid, &program).await + pub async fn program_pages(&self, program_id: ProgramId) -> Result { + self.gpages(program_id, None).await } } @@ -316,22 +315,61 @@ impl Api { pub async fn gpages_at( &self, program_id: ProgramId, - program: &ActiveProgram, + memory_infix: Option, block_hash: Option, ) -> Result { let mut pages = HashMap::new(); - for page in program - .pages_with_data - .inner - .iter() - .flat_map(|(start, end)| start.0..=end.0) - { + let memory_infix = match memory_infix { + Some(infix) => infix, + None => self.gprog_at(program_id, block_hash).await?.memory_infix.0, + }; + + let pages_storage_address = Self::storage( + GearProgramStorage::MemoryPages, + vec![ + Value::from_bytes(program_id), + Value::u128(memory_infix as u128), + ], + ); + + let mut pages_stream = self + .get_storage(block_hash) + .await? + .iter(pages_storage_address) + .await?; + + while let Some(Ok((encoded_key, encoded_value))) = pages_stream.next().await { + let (_, page) = <([u8; 68], u32)>::decode(&mut encoded_key.as_slice())?; + let data = encoded_value.encoded().to_vec(); + pages.insert(page, data); + } + + Ok(pages) + } + + /// Get pages of active program at specified block. + #[storage_fetch] + pub async fn specified_gpages_at( + &self, + program_id: ProgramId, + memory_infix: Option, + page_numbers: impl Iterator, + block_hash: Option, + ) -> Result { + let mut pages = HashMap::new(); + + let memory_infix = match memory_infix { + Some(infix) => infix, + None => self.gprog_at(program_id, block_hash).await?.memory_infix.0, + }; + + for page in page_numbers { let addr = Self::storage( GearProgramStorage::MemoryPages, vec![ Value::from_bytes(program_id), - Value::u128(program.memory_infix.0 as u128), + Value::u128(memory_infix as u128), Value::u128(page as u128), ], ); @@ -345,6 +383,7 @@ impl Api { .fetch_raw(lookup_bytes) .await? .ok_or_else(|| Error::PageNotFound(page, program_id.as_ref().encode_hex()))?; + pages.insert(page, encoded_page); } diff --git a/gsdk/tests/rpc.rs b/gsdk/tests/rpc.rs index 941f614bbdf..b51cef00736 100644 --- a/gsdk/tests/rpc.rs +++ b/gsdk/tests/rpc.rs @@ -525,18 +525,15 @@ async fn query_program_counters( let mut count_memory_page = 0u64; let mut count_program = 0u64; let mut count_active_program = 0u64; - while let Some(Ok((_key, value))) = iter.next().await { + while let Some(Ok((key, value))) = iter.next().await { let program = Program::::decode(&mut value.encoded())?; count_program += 1; - if let Program::Active(p) = program { + let program_id = ProgramId::decode(&mut key.as_slice())?; + + if let Program::Active(_) = program { count_active_program += 1; - count_memory_page += p - .pages_with_data - .inner - .iter() - .flat_map(|(start, end)| start.0..=end.0) - .count() as u64; + count_memory_page += signer.api().gpages(program_id, None).await?.len() as u64; } } diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index 4c42b9b5312..f0a1f3799f5 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -507,6 +507,8 @@ pub mod constants { /* Execution-related constants */ // TODO: use proper weights of instantiation and instrumentation (#3509). + /// Cost of loading allocations per interval. + pub const LOAD_ALLOCATIONS_PER_INTERVAL: Gas = 20_000; /// Maximal amount of reservations program may have. pub const MAX_RESERVATIONS: u64 = 256; /// Cost of wasm module code section instantiation before execution per byte diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 85e3174d981..121dee6f859 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -23,13 +23,13 @@ use crate::{ mailbox::MailboxManager, program::{Gas, WasmProgram}, Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, - GAS_ALLOWANCE, INITIAL_RANDOM_SEED, MAILBOX_THRESHOLD, MAX_RESERVATIONS, - MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, - MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, - MODULE_INSTRUMENTATION_BYTE_COST, MODULE_INSTRUMENTATION_COST, - MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, - READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, RESERVE_FOR, VALUE_PER_GAS, WAITLIST_COST, - WRITE_COST, + GAS_ALLOWANCE, INITIAL_RANDOM_SEED, LOAD_ALLOCATIONS_PER_INTERVAL, MAILBOX_THRESHOLD, + MAX_RESERVATIONS, MODULE_CODE_SECTION_INSTANTIATION_BYTE_COST, + MODULE_DATA_SECTION_INSTANTIATION_BYTE_COST, MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST, + MODULE_GLOBAL_SECTION_INSTANTIATION_BYTE_COST, MODULE_INSTRUMENTATION_BYTE_COST, + MODULE_INSTRUMENTATION_COST, MODULE_TABLE_SECTION_INSTANTIATION_BYTE_COST, + MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST, READ_COST, READ_PER_BYTE_COST, RESERVATION_COST, + RESERVE_FOR, VALUE_PER_GAS, WAITLIST_COST, WRITE_COST, }; use core_processor::{ common::*, @@ -884,6 +884,7 @@ impl ExtManager { element_section_per_byte: MODULE_ELEMENT_SECTION_INSTANTIATION_BYTE_COST.into(), type_section_per_byte: MODULE_TYPE_SECTION_INSTANTIATION_BYTE_COST.into(), }, + load_allocations_per_interval: LOAD_ALLOCATIONS_PER_INTERVAL.into(), }, existential_deposit: EXISTENTIAL_DEPOSIT, mailbox_threshold: MAILBOX_THRESHOLD, @@ -893,7 +894,7 @@ impl ExtManager { outgoing_bytes_limit: OUTGOING_BYTES_LIMIT, }; - let precharged_dispatch = match core_processor::precharge_for_program( + let context = match core_processor::precharge_for_program( &block_config, self.gas_allowance.0, dispatch.into_incoming(gas_limit), @@ -907,16 +908,15 @@ impl ExtManager { }; let Some((actor_data, code)) = data else { - let journal = core_processor::process_non_executable(precharged_dispatch, dest); + let journal = core_processor::process_non_executable(context); core_processor::handle_journal(journal, self); return; }; - let context = match core_processor::precharge_for_code_length( + let context = match core_processor::precharge_for_allocations( &block_config, - precharged_dispatch, - dest, - actor_data, + context, + actor_data.allocations.intervals_amount() as u32, ) { Ok(c) => c, Err(journal) => { @@ -925,6 +925,15 @@ impl ExtManager { } }; + let context = + match core_processor::precharge_for_code_length(&block_config, context, actor_data) { + Ok(c) => c, + Err(journal) => { + core_processor::handle_journal(journal, self); + return; + } + }; + let context = ContextChargedForCode::from(context); let context = ContextChargedForInstrumentation::from(context); let context = match core_processor::precharge_for_module_instantiation( diff --git a/pallets/gear-debug/src/lib.rs b/pallets/gear-debug/src/lib.rs index 949cba6644c..2ad8e7f5ecb 100644 --- a/pallets/gear-debug/src/lib.rs +++ b/pallets/gear-debug/src/lib.rs @@ -217,12 +217,8 @@ pub mod pallet { Some(code) => code.static_pages(), None => 0.into(), }; - let persistent_pages = T::ProgramStorage::get_program_data_for_pages( - id, - active.memory_infix, - active.pages_with_data.points_iter(), - ) - .unwrap(); + let persistent_pages = + T::ProgramStorage::get_program_pages_data(id, active.memory_infix).unwrap(); ProgramDetails { id, diff --git a/pallets/gear-program/Cargo.toml b/pallets/gear-program/Cargo.toml index 3946f0b00e5..c10a081a704 100644 --- a/pallets/gear-program/Cargo.toml +++ b/pallets/gear-program/Cargo.toml @@ -44,6 +44,7 @@ pallet-gear-gas = { workspace = true, features = ["std"] } pallet-gear-scheduler = { workspace = true, features = ["std"] } pallet-treasury = { workspace = true, features = ["std"] } wabt.workspace = true +env_logger.workspace = true [features] default = ['std'] diff --git a/pallets/gear-program/src/lib.rs b/pallets/gear-program/src/lib.rs index fd87cd25357..fd613c7fb4e 100644 --- a/pallets/gear-program/src/lib.rs +++ b/pallets/gear-program/src/lib.rs @@ -158,14 +158,13 @@ pub mod pallet { code::InstrumentedCode, ids::{CodeId, ProgramId}, memory::PageBuf, - pages::GearPage, + pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, program::{MemoryInfix, Program}, }; - use sp_runtime::DispatchError; /// The current storage version. - pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(9); + pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(10); #[pallet::config] pub trait Config: frame_system::Config { @@ -257,6 +256,18 @@ pub mod pallet { value: CodeMetadata ); + #[pallet::storage] + #[pallet::unbounded] + pub(crate) type AllocationsStorage = + StorageMap<_, Identity, ProgramId, IntervalsTree>; + + common::wrap_storage_map!( + storage: AllocationsStorage, + name: AllocationsStorageWrap, + key: ProgramId, + value: IntervalsTree + ); + #[pallet::storage] #[pallet::unbounded] pub(crate) type ProgramStorage = @@ -303,6 +314,7 @@ pub mod pallet { type BlockNumber = BlockNumberFor; type AccountId = T::AccountId; + type AllocationsMap = AllocationsStorageWrap; type ProgramMap = ProgramStorageWrap; type MemoryPageMap = MemoryPageStorageWrap; diff --git a/pallets/gear-program/src/migrations/add_section_sizes.rs b/pallets/gear-program/src/migrations/add_section_sizes.rs index c850166e05c..5e258e1d889 100644 --- a/pallets/gear-program/src/migrations/add_section_sizes.rs +++ b/pallets/gear-program/src/migrations/add_section_sizes.rs @@ -36,7 +36,7 @@ use gear_core::code::{InstantiatedSectionSizes, InstrumentedCode}; const MIGRATE_FROM_VERSION: u16 = 8; const MIGRATE_TO_VERSION: u16 = 9; -const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 9; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 10; pub struct AddSectionSizesMigration(PhantomData); diff --git a/pallets/gear-program/src/migrations/allocations.rs b/pallets/gear-program/src/migrations/allocations.rs new file mode 100644 index 00000000000..e64a0289fd1 --- /dev/null +++ b/pallets/gear-program/src/migrations/allocations.rs @@ -0,0 +1,293 @@ +// This file is part of Gear. + +// Copyright (C) 2023-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{AllocationsStorage, Config, Pallet, ProgramStorage}; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use gear_core::program::{ActiveProgram, Program}; +use sp_runtime::SaturatedConversion; +use sp_std::marker::PhantomData; + +#[cfg(feature = "try-runtime")] +use { + frame_support::ensure, + sp_runtime::{ + codec::{Decode, Encode}, + TryRuntimeError, + }, + sp_std::vec::Vec, +}; + +const MIGRATE_FROM_VERSION: u16 = 9; +const MIGRATE_TO_VERSION: u16 = 10; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 10; + +pub struct MigrateAllocations(PhantomData); + +impl OnRuntimeUpgrade for MigrateAllocations { + fn on_runtime_upgrade() -> Weight { + let onchain = Pallet::::on_chain_storage_version(); + + // 1 read for on chain storage version + let mut weight = T::DbWeight::get().reads(1); + + if onchain == MIGRATE_FROM_VERSION { + let current = Pallet::::current_storage_version(); + if current != ALLOWED_CURRENT_STORAGE_VERSION { + log::error!("❌ Migration is not allowed for current storage version {current:?}."); + return weight; + } + + let update_to = StorageVersion::new(MIGRATE_TO_VERSION); + log::info!("🚚 Running migration from {onchain:?} to {update_to:?}, current storage version is {current:?}."); + + ProgramStorage::::translate(|id, program: v9::Program>| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + Some(match program { + v9::Program::Active(p) => { + let allocations_tree_len = + p.allocations.intervals_amount().saturated_into(); + AllocationsStorage::::insert(id, p.allocations); + + // NOTE: p.pages_with_data is removed from the program + + Program::Active(ActiveProgram { + allocations_tree_len, + memory_infix: p.memory_infix, + gas_reservation_map: p.gas_reservation_map, + code_hash: p.code_hash, + code_exports: p.code_exports, + static_pages: p.static_pages.into(), + state: p.state, + expiration_block: p.expiration_block, + }) + } + v9::Program::Exited(id) => Program::Exited(id), + v9::Program::Terminated(id) => Program::Terminated(id), + }) + }); + + update_to.put::>(); + + log::info!("✅ Successfully migrates storage"); + } else { + log::info!("🟠 Migration requires onchain version {MIGRATE_FROM_VERSION}, so was skipped for {onchain:?}"); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + let res = if onchain == MIGRATE_FROM_VERSION { + ensure!( + current == ALLOWED_CURRENT_STORAGE_VERSION, + "Current storage version is not allowed for migration, check migration code in order to allow it." + ); + + Some(v9::ProgramStorage::::iter().count() as u64) + } else { + None + }; + + Ok(res.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + if let Some(old_count) = Option::::decode(&mut state.as_ref()) + .map_err(|_| "`pre_upgrade` provided an invalid state")? + { + let count = ProgramStorage::::iter().count() as u64; + ensure!( + old_count == count, + "incorrect count of programs after migration: old {} != new {}", + ); + ensure!( + Pallet::::on_chain_storage_version() == MIGRATE_TO_VERSION, + "incorrect storage version after migration" + ); + } + + Ok(()) + } +} + +mod v9 { + use gear_core::{ + ids::ProgramId, + message::DispatchKind, + pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, + program::{MemoryInfix, ProgramState}, + reservation::GasReservationMap, + }; + use primitive_types::H256; + use sp_runtime::{ + codec::{self, Decode, Encode}, + scale_info::{self, TypeInfo}, + traits::Saturating, + }; + use sp_std::{collections::btree_set::BTreeSet, prelude::*}; + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub struct ActiveProgram { + pub allocations: IntervalsTree, + pub pages_with_data: IntervalsTree, + pub memory_infix: MemoryInfix, + 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), + } + + #[cfg(feature = "try-runtime")] + use { + crate::{Config, Pallet}, + frame_support::{ + storage::types::StorageMap, + traits::{PalletInfo, StorageInstance}, + Identity, + }, + sp_std::marker::PhantomData, + }; + + #[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>, + >; +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use common::GearPage; + use frame_support::traits::StorageVersion; + use frame_system::pallet_prelude::BlockNumberFor; + use gear_core::{ids::ProgramId, pages::WasmPage, program::ProgramState}; + use sp_runtime::traits::Zero; + + #[test] + fn migration_works() { + env_logger::init(); + + new_test_ext().execute_with(|| { + StorageVersion::new(MIGRATE_FROM_VERSION).put::(); + + // add active program + let active_program_id = ProgramId::from(1u64); + let program = v9::Program::>::Active(v9::ActiveProgram { + allocations: [1u16, 2, 3, 4, 5, 101, 102] + .into_iter() + .map(WasmPage::from) + .collect(), + pages_with_data: [4u16, 5, 6, 7, 8, 400, 401] + .into_iter() + .map(GearPage::from) + .collect(), + gas_reservation_map: Default::default(), + code_hash: Default::default(), + code_exports: Default::default(), + static_pages: 1.into(), + state: ProgramState::Initialized, + expiration_block: 100, + memory_infix: Default::default(), + }); + v9::ProgramStorage::::insert(active_program_id, program); + + // add exited program + let program = v9::Program::>::Exited(active_program_id); + let program_id = ProgramId::from(2u64); + v9::ProgramStorage::::insert(program_id, program); + + // add terminated program + let program = v9::Program::>::Terminated(program_id); + let program_id = ProgramId::from(3u64); + v9::ProgramStorage::::insert(program_id, program); + + let state = MigrateAllocations::::pre_upgrade().unwrap(); + let w = MigrateAllocations::::on_runtime_upgrade(); + assert!(!w.is_zero()); + MigrateAllocations::::post_upgrade(state).unwrap(); + + let allocations = AllocationsStorage::::get(active_program_id).unwrap(); + assert_eq!( + allocations.to_vec(), + [ + WasmPage::from(1)..=WasmPage::from(5), + WasmPage::from(101)..=WasmPage::from(102) + ] + ); + + let Some(Program::Active(program)) = ProgramStorage::::get(active_program_id) + else { + panic!("Program must be active"); + }; + + assert_eq!(program.allocations_tree_len, 2); + + assert_eq!( + ProgramStorage::::get(ProgramId::from(2u64)).unwrap(), + Program::Exited(active_program_id) + ); + assert_eq!( + ProgramStorage::::get(ProgramId::from(3u64)).unwrap(), + Program::Terminated(ProgramId::from(2u64)) + ); + + assert_eq!(StorageVersion::get::(), MIGRATE_TO_VERSION); + }) + } +} diff --git a/pallets/gear-program/src/migrations/mod.rs b/pallets/gear-program/src/migrations/mod.rs index b9e4841c121..e58d61d163a 100644 --- a/pallets/gear-program/src/migrations/mod.rs +++ b/pallets/gear-program/src/migrations/mod.rs @@ -17,3 +17,4 @@ // along with this program. If not, see . pub mod add_section_sizes; +pub mod allocations; diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index e1ddef4a603..fba654734f9 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -663,6 +663,17 @@ benchmarks! { Gear::::reinstrument_code(code_id, &schedule).expect("Re-instrumentation failed"); } + load_allocations_per_interval { + let a in 0 .. u16::MAX as u32 / 2; + let allocations = (0..a).map(|p| WasmPage::from(p as u16 * 2 + 1)); + let program_id = benchmarking::account::("program", 0, 100).cast(); + let code = benchmarking::generate_wasm(16.into()).unwrap(); + benchmarking::set_program::, _>(program_id, code, 1.into()); + ProgramStorageOf::::set_allocations(program_id, allocations.collect()); + }: { + let _ = ProgramStorageOf::::allocations(program_id).unwrap(); + } + alloc { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index d2d5d18c1f6..d03d1aec8e0 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -202,10 +202,7 @@ where let instance = Program::::new(module.into(), vec![])?; let program_id = ProgramId::from_origin(instance.addr); - ProgramStorageOf::::update_active_program(program_id, |program| { - program.allocations = allocations.collect(); - }) - .expect("Program must be active"); + ProgramStorageOf::::set_allocations(program_id, allocations.collect()); utils::prepare_exec::( instance.caller.into_origin(), diff --git a/pallets/gear/src/benchmarking/utils.rs b/pallets/gear/src/benchmarking/utils.rs index 68a4fe44b74..9e5709747e3 100644 --- a/pallets/gear/src/benchmarking/utils.rs +++ b/pallets/gear/src/benchmarking/utils.rs @@ -22,14 +22,15 @@ use super::Exec; use crate::{ builtin::BuiltinDispatcherFactory, manager::{CodeInfo, ExtManager, HandleKind}, - Config, LazyPagesInterface, LazyPagesRuntimeInterface, MailboxOf, Pallet as Gear, + Config, CurrencyOf, LazyPagesInterface, LazyPagesRuntimeInterface, MailboxOf, Pallet as Gear, ProgramStorageOf, QueueOf, }; -use common::{storage::*, CodeStorage, Origin, ProgramStorage}; +use common::{storage::*, CodeStorage, Origin, Program, ProgramStorage}; use core_processor::{ - configs::BlockConfig, ContextChargedForCode, ContextChargedForInstrumentation, + common::ExecutableActorData, configs::BlockConfig, ContextChargedForCode, + ContextChargedForInstrumentation, }; -use frame_support::traits::Get; +use frame_support::traits::{Currency, Get}; use gear_core::{ code::{Code, CodeAndId}, ids::{prelude::*, CodeId, MessageId, ProgramId}, @@ -207,9 +208,6 @@ where }; let actor_id = queued_dispatch.destination(); - let actor = ext_manager - .get_actor(actor_id) - .ok_or("Program not found in the storage")?; let pallet_config = Gear::::block_config(); let block_config = BlockConfig { @@ -219,7 +217,7 @@ where ..pallet_config }; - let precharged_dispatch = core_processor::precharge_for_program( + let context = core_processor::precharge_for_program( &block_config, config.gas_allowance, queued_dispatch.into_incoming(config.gas_limit), @@ -227,14 +225,32 @@ where ) .map_err(|_| "core_processor::precharge_for_program failed")?; - let balance = actor.balance; - let context = core_processor::precharge_for_code_length( + let active = match ProgramStorageOf::::get_program(actor_id) { + Some(Program::Active(active)) => active, + _ => return Err("Program not found"), + }; + let balance = CurrencyOf::::free_balance(&actor_id.cast()).unique_saturated_into(); + + let context = core_processor::precharge_for_allocations( &block_config, - precharged_dispatch, - actor_id, - actor.executable_data, + context, + active.allocations_tree_len, ) - .map_err(|_| "core_processor::precharge_for_code failed")?; + .map_err(|_| "core_processor::precharge_for_allocations failed")?; + + let allocations = ProgramStorageOf::::allocations(actor_id).unwrap_or_default(); + + let actor_data = ExecutableActorData { + allocations, + code_id: active.code_hash.cast(), + code_exports: active.code_exports, + static_pages: active.static_pages, + gas_reservation_map: active.gas_reservation_map, + memory_infix: active.memory_infix, + }; + + let context = core_processor::precharge_for_code_length(&block_config, context, actor_data) + .map_err(|_| "core_processor::precharge_for_code failed")?; let code = T::CodeStorage::get_code(context.actor_data().code_id).ok_or("Program code not found")?; diff --git a/pallets/gear/src/manager/journal.rs b/pallets/gear/src/manager/journal.rs index 99b32bdf84f..6f365cf5c28 100644 --- a/pallets/gear/src/manager/journal.rs +++ b/pallets/gear/src/manager/journal.rs @@ -373,62 +373,39 @@ where ) { self.state_changes.insert(program_id); - ProgramStorageOf::::update_active_program(program_id, |p| { - for (page, data) in pages_data { - log::trace!("{:?} has been write accessed, update it in storage", page); - - ProgramStorageOf::::set_program_page_data( - program_id, - p.memory_infix, - page, - data, - ); - p.pages_with_data.insert(page); - } - }) - .unwrap_or_else(|e| { + // TODO: pass `memory_infix` as argument #4025 + let memory_infix = ProgramStorageOf::::memory_infix(program_id).unwrap_or_else(|| { // Guaranteed to be called on existing active program - let err_msg = format!( - "JournalHandler::update_pages_data: failed to update program. \ - Program - {program_id}. Got error: {e:?}" - ); + let err_msg = + format!("JournalHandler::update_pages_data: program is not active {program_id}"); log::error!("{err_msg}"); unreachable!("{err_msg}"); }); + + for (page, data) in pages_data { + ProgramStorageOf::::set_program_page_data(program_id, memory_infix, page, data); + } } fn update_allocations(&mut self, program_id: ProgramId, allocations: IntervalsTree) { - ProgramStorageOf::::update_active_program(program_id, |p| { - for page in p - .allocations - .difference(&allocations) - .flat_map(|i| i.iter()) - .flat_map(|p| p.to_iter()) - { - // TODO: do not use `contains` #3879 - if p.pages_with_data.contains(page) { - p.pages_with_data.remove(page); - ProgramStorageOf::::remove_program_page_data( - program_id, - p.memory_infix, - page, - ); - } - } - - p.allocations = allocations; - }) - .unwrap_or_else(|e| { + // TODO: pass `memory_infix` as argument #4025 + let memory_infix = ProgramStorageOf::::memory_infix(program_id).unwrap_or_else(|| { // Guaranteed to be called on existing active program - let err_msg = format!( - "JournalHandler::update_allocations: failed to update program. \ - Program - {program_id}. Got error: {e:?}" - ); + let err_msg = + format!("JournalHandler::update_allocations: program is not active {program_id}."); log::error!("{err_msg}"); unreachable!("{err_msg}"); }); + + let old_allocations = ProgramStorageOf::::allocations(program_id).unwrap_or_default(); + let remove_pages = old_allocations + .difference(&allocations) + .flat_map(|i| i.iter()) + .flat_map(|i| i.to_iter()); + ProgramStorageOf::::remove_data_for_pages(program_id, memory_infix, remove_pages); + ProgramStorageOf::::set_allocations(program_id, allocations.clone()); } fn send_value(&mut self, from: ProgramId, to: Option, value: u128) { diff --git a/pallets/gear/src/manager/mod.rs b/pallets/gear/src/manager/mod.rs index 9444ea2344c..90093cf4238 100644 --- a/pallets/gear/src/manager/mod.rs +++ b/pallets/gear/src/manager/mod.rs @@ -63,7 +63,6 @@ use common::{ CodeStorage, Origin, ProgramStorage, ReservableTree, }; use core::{fmt, mem}; -use core_processor::common::{Actor, ExecutableActorData}; use frame_support::traits::{Currency, ExistenceRequirement, LockableCurrency}; use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ @@ -78,7 +77,7 @@ use primitive_types::H256; use scale_info::TypeInfo; use sp_runtime::{ codec::{Decode, Encode}, - traits::{UniqueSaturatedInto, Zero}, + traits::Zero, }; use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, @@ -207,33 +206,6 @@ where !self.check_program_id(id) } - /// NOTE: By calling this function we can't differ whether `None` returned, because - /// program with `id` doesn't exist or it's terminated - pub fn get_actor(&self, id: ProgramId) -> Option { - let active: ActiveProgram<_> = ProgramStorageOf::::get_program(id)?.try_into().ok()?; - let code_id = active.code_hash.cast(); - - let balance = as fungible::Inspect<_>>::reducible_balance( - &id.cast(), - Preservation::Expendable, - Fortitude::Polite, - ) - .unique_saturated_into(); - - Some(Actor { - balance, - destination_program: id, - executable_data: ExecutableActorData { - allocations: active.allocations.clone(), - code_id, - code_exports: active.code_exports, - static_pages: active.static_pages, - gas_reservation_map: active.gas_reservation_map, - memory_infix: active.memory_infix, - }, - }) - } - pub fn set_program( &self, program_id: ProgramId, @@ -252,8 +224,7 @@ where // An empty program has been just constructed: it contains no mem allocations. let program = ActiveProgram { - allocations: Default::default(), - pages_with_data: Default::default(), + allocations_tree_len: 0, code_hash: code_info.id, code_exports: code_info.exports.clone(), static_pages: code_info.static_pages, @@ -380,8 +351,6 @@ where ) { Self::remove_gas_reservation_map(program_id, mem::take(&mut program.gas_reservation_map)); - ProgramStorageOf::::remove_program_pages(program_id, program.memory_infix); - let program_account = program_id.cast(); let value_destination = value_destination.cast(); diff --git a/pallets/gear/src/queue.rs b/pallets/gear/src/queue.rs index b2aa4333b1a..9a1560260d0 100644 --- a/pallets/gear/src/queue.rs +++ b/pallets/gear/src/queue.rs @@ -46,7 +46,7 @@ where // To start executing a message resources of a destination program should be // fetched from the storage. // The first step is to get program data so charge gas for the operation. - let precharged_dispatch = match core_processor::precharge_for_program( + let context = match core_processor::precharge_for_program( block_config, GasAllowanceOf::::get(), dispatch.into_incoming(gas_limit), @@ -58,8 +58,8 @@ where let Some(Program::Active(program)) = ProgramStorageOf::::get_program(destination_id) else { - log::trace!("Message is sent to non-active program {destination_id:?}"); - return core_processor::process_non_executable(precharged_dispatch, destination_id); + log::trace!("Message {dispatch_id} is sent to non-active program {destination_id}"); + return core_processor::process_non_executable(context); }; if program.state == ProgramState::Initialized && dispatch_kind == DispatchKind::Init { @@ -92,11 +92,29 @@ where unreachable!("{err_msg}"); } - return core_processor::process_non_executable(precharged_dispatch, destination_id); + return core_processor::process_non_executable(context); } + let context = match core_processor::precharge_for_allocations( + block_config, + context, + program.allocations_tree_len, + ) { + Ok(context) => context, + Err(journal) => return journal, + }; + + let allocations = (program.allocations_tree_len != 0).then(|| { + ProgramStorageOf::::allocations(destination_id).unwrap_or_else(|| { + unreachable!( + "`allocations_tree_len` {} is not zero, so program {destination_id:?} must have allocations", + program.allocations_tree_len, + ) + }) + }).unwrap_or_default(); + let actor_data = ExecutableActorData { - allocations: program.allocations, + allocations, code_id: program.code_hash.cast(), code_exports: program.code_exports, static_pages: program.static_pages, @@ -106,15 +124,11 @@ where // The second step is to load instrumented binary code of the program but // first its correct length should be obtained. - let context = match core_processor::precharge_for_code_length( - block_config, - precharged_dispatch, - destination_id, - actor_data, - ) { - Ok(context) => context, - Err(journal) => return journal, - }; + let context = + match core_processor::precharge_for_code_length(block_config, context, actor_data) { + Ok(context) => context, + Err(journal) => return journal, + }; // Load correct code length value. let code_id = context.actor_data().code_id; diff --git a/pallets/gear/src/runtime_api.rs b/pallets/gear/src/runtime_api.rs index 806198fcb7e..fd59ecac582 100644 --- a/pallets/gear/src/runtime_api.rs +++ b/pallets/gear/src/runtime_api.rs @@ -540,9 +540,11 @@ where .map_err(|e| format!("Code {code_id:?} failed reinstrumentation: {e:?}"))?; } + let allocations = ProgramStorageOf::::allocations(program_id).unwrap_or_default(); + Ok(CodeWithMemoryData { instrumented_code: code, - allocations: program.allocations, + allocations, memory_infix: program.memory_infix, }) } diff --git a/pallets/gear/src/schedule.rs b/pallets/gear/src/schedule.rs index 41f2190a469..13edb1d16e1 100644 --- a/pallets/gear/src/schedule.rs +++ b/pallets/gear/src/schedule.rs @@ -140,6 +140,9 @@ pub struct Schedule { /// WASM code instrumentation per-byte cost. pub code_instrumentation_byte_cost: Weight, + + /// Load allocations weight. + pub load_allocations_weight: Weight, } /// Describes the upper limits on various metrics. @@ -762,6 +765,7 @@ impl Default for Schedule { }, code_instrumentation_cost: cost_zero(W::::reinstrument_per_kb), code_instrumentation_byte_cost: cost_byte(W::::reinstrument_per_kb), + load_allocations_weight: cost(W::::load_allocations_per_interval), } } } @@ -1216,6 +1220,7 @@ impl Schedule { .ref_time() .into(), }, + load_allocations_per_interval: self.load_allocations_weight.ref_time().into(), } } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 7928a97bb8f..970b9810961 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -33,8 +33,8 @@ use crate::{ Limits, MailboxOf, ProgramStorageOf, QueueOf, Schedule, TaskPoolOf, WaitlistOf, }; use common::{ - event::*, scheduler::*, storage::*, CodeStorage, GasTree, LockId, LockableTree, Origin as _, - ProgramStorage, ReservableTree, + event::*, scheduler::*, storage::*, CodeStorage, GasTree, GearPage, LockId, LockableTree, + Origin as _, Program, ProgramStorage, ReservableTree, }; use core_processor::common::ActorExecutionErrorReplyReason; use demo_constructor::{Calls, Scheme}; @@ -47,15 +47,18 @@ use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ code::{ self, Code, CodeAndId, CodeError, ExportError, InstantiatedSectionSizes, - InstrumentedCodeAndId, + InstrumentedCodeAndId, MAX_WASM_PAGES_AMOUNT, }, ids::{prelude::*, CodeId, MessageId, ProgramId}, message::{ ContextSettings, DispatchKind, IncomingDispatch, IncomingMessage, MessageContext, Payload, ReplyInfo, StoredDispatch, UserStoredMessage, }, - pages::WasmPage, - program::{ActiveProgram, Program}, + pages::{ + numerated::{self, tree::IntervalsTree}, + WasmPage, + }, + program::ActiveProgram, }; use gear_core_backend::error::{ TrapExplanation, UnrecoverableExecutionError, UnrecoverableExtError, UnrecoverableWaitError, @@ -15409,12 +15412,9 @@ fn allocate_in_init_free_in_handle() { run_to_next_block(None); - let Some(Program::Active(program)) = ProgramStorageOf::::get_program(program_id) - else { - panic!("program must be active") - }; + let allocations = ProgramStorageOf::::allocations(program_id).unwrap_or_default(); assert_eq!( - program.allocations, + allocations, [WasmPage::from(static_pages)].into_iter().collect() ); @@ -15430,11 +15430,8 @@ fn allocate_in_init_free_in_handle() { run_to_next_block(None); - let Some(Program::Active(program)) = ProgramStorageOf::::get_program(program_id) - else { - panic!("program must be active") - }; - assert_eq!(program.allocations, Default::default()); + let allocations = ProgramStorageOf::::allocations(program_id).unwrap_or_default(); + assert_eq!(allocations, Default::default()); }); } @@ -15668,6 +15665,125 @@ fn test_gasless_steal_gas_for_wait() { }) } +#[test] +fn use_big_memory() { + let last_4_bytes_offset = WasmPage::from(MAX_WASM_PAGES_AMOUNT).offset() - 4; + let middle_4_bytes_offset = WasmPage::from(MAX_WASM_PAGES_AMOUNT / 2).offset(); + let last_page_number = MAX_WASM_PAGES_AMOUNT.checked_sub(1).unwrap(); + + let wat = format!( + r#" + (module + (import "env" "memory" (memory 0)) + (import "env" "alloc" (func $alloc (param i32) (result i32))) + (import "env" "free_range" (func $free_range (param i32) (param i32) (result i32))) + (export "init" (func $init)) + (export "handle" (func $handle)) + (func $init + (drop (call $alloc (i32.const {MAX_WASM_PAGES_AMOUNT}))) + + ;; access last 4 bytes + (i32.store (i32.const {last_4_bytes_offset}) (i32.const 0x42)) + + ;; access first 4 bytes + (i32.store (i32.const 0) (i32.const 0x42)) + + ;; access 4 bytes in the middle + (i32.store (i32.const {middle_4_bytes_offset}) (i32.const 0x42)) + ) + (func $handle + (drop (call $free_range (i32.const 0) (i32.const {last_page_number}))) + ) + )"# + ); + + init_logger(); + new_test_ext().execute_with(|| { + Gear::upload_program( + RuntimeOrigin::signed(USER_1), + ProgramCodeKind::Custom(wat.as_str()).to_bytes(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + true, + ) + .unwrap(); + + let program_id = get_last_program_id(); + + run_to_next_block(None); + assert_last_dequeued(1); + + let expected_allocations: IntervalsTree = + [numerated::interval::Interval::try_from( + WasmPage::from(0)..WasmPage::from(MAX_WASM_PAGES_AMOUNT), + ) + .unwrap()] + .into_iter() + .collect(); + + assert_eq!( + ProgramStorageOf::::allocations(program_id), + Some(expected_allocations), + ); + + let program = ProgramStorageOf::::get_program(program_id).expect("Program not found"); + let Program::Active(program) = program else { + panic!("Program is not active"); + }; + + assert_eq!(program.allocations_tree_len, 1); + + let pages_with_data = + as ProgramStorage>::MemoryPageMap::iter_prefix( + &program_id, + &program.memory_infix, + ) + .map(|(page, buf)| { + assert_eq!(buf.iter().copied().sum::(), 0x42); + page + }) + .collect::>(); + + assert_eq!( + pages_with_data, + vec![ + GearPage::from_offset(0), + GearPage::from_offset(middle_4_bytes_offset), + GearPage::from_offset(last_4_bytes_offset) + ] + ); + + Gear::send_message( + RuntimeOrigin::signed(USER_1), + program_id, + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + true, + ) + .unwrap(); + + run_to_next_block(None); + assert_last_dequeued(1); + + assert_eq!( + ProgramStorageOf::::allocations(program_id), + Some(Default::default()), + ); + + assert_eq!( + as ProgramStorage>::MemoryPageMap::iter_prefix( + &program_id, + &program.memory_infix, + ) + .count(), + 0 + ); + }); +} + pub(crate) mod utils { #![allow(unused)] diff --git a/pallets/gear/src/weights.rs b/pallets/gear/src/weights.rs index c2093910eb9..9c26b329465 100644 --- a/pallets/gear/src/weights.rs +++ b/pallets/gear/src/weights.rs @@ -66,6 +66,7 @@ pub trait WeightInfo { fn send_reply(p: u32, ) -> Weight; fn claim_value_to_inheritor(d: u32, ) -> Weight; fn reinstrument_per_kb(c: u32, ) -> Weight; + fn load_allocations_per_interval(a: u32, ) -> Weight; fn alloc(r: u32, ) -> Weight; fn mem_grow(r: u32, ) -> Weight; fn mem_grow_per_page(p: u32, ) -> Weight; @@ -545,6 +546,18 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1024).saturating_mul(e.into())) } + /// The range of component `a` is `[0, 32767]`. + fn load_allocations_per_interval(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101 + a * (8 ±0)` + // Estimated: `3566 + a * (8 ±0)` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(15_735_686, 3566) + // Standard Error: 47 + .saturating_add(Weight::from_parts(19_766, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(a.into())) + } /// The range of component `r` is `[0, 20]`. fn alloc(r: u32, ) -> Weight { // Proof Size summary in bytes: @@ -2472,6 +2485,18 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1024).saturating_mul(e.into())) } + /// The range of component `a` is `[0, 32767]`. + fn load_allocations_per_interval(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101 + a * (8 ±0)` + // Estimated: `3566 + a * (8 ±0)` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(15_735_686, 3566) + // Standard Error: 47 + .saturating_add(Weight::from_parts(19_766, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(a.into())) + } /// The range of component `r` is `[0, 20]`. fn alloc(r: u32, ) -> Weight { // Proof Size summary in bytes: diff --git a/runtime/vara/src/migrations.rs b/runtime/vara/src/migrations.rs index de25967021e..9b7aa1ddb0c 100644 --- a/runtime/vara/src/migrations.rs +++ b/runtime/vara/src/migrations.rs @@ -25,6 +25,8 @@ pub type Migrations = ( // substrate v1.4.0 staking::MigrateToV14, pallet_grandpa::migrations::MigrateV4ToV5, + // move allocations to a separate storage item and remove pages_with_data field from program + pallet_gear_program::migrations::allocations::MigrateAllocations, ); mod staking { diff --git a/runtime/vara/src/weights/pallet_gear.rs b/runtime/vara/src/weights/pallet_gear.rs index d52de99f525..a90d62c7504 100644 --- a/runtime/vara/src/weights/pallet_gear.rs +++ b/runtime/vara/src/weights/pallet_gear.rs @@ -66,6 +66,7 @@ pub trait WeightInfo { fn send_reply(p: u32, ) -> Weight; fn claim_value_to_inheritor(d: u32, ) -> Weight; fn reinstrument_per_kb(c: u32, ) -> Weight; + fn load_allocations_per_interval(a: u32, ) -> Weight; fn alloc(r: u32, ) -> Weight; fn mem_grow(r: u32, ) -> Weight; fn mem_grow_per_page(p: u32, ) -> Weight; @@ -545,6 +546,18 @@ impl pallet_gear::WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1024).saturating_mul(e.into())) } + /// The range of component `a` is `[0, 32767]`. + fn load_allocations_per_interval(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101 + a * (8 ±0)` + // Estimated: `3566 + a * (8 ±0)` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(15_735_686, 3566) + // Standard Error: 47 + .saturating_add(Weight::from_parts(19_776, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(a.into())) + } /// The range of component `r` is `[0, 20]`. fn alloc(r: u32, ) -> Weight { // Proof Size summary in bytes: @@ -2472,6 +2485,18 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1024).saturating_mul(e.into())) } + /// The range of component `a` is `[0, 32767]`. + fn load_allocations_per_interval(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101 + a * (8 ±0)` + // Estimated: `3566 + a * (8 ±0)` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(15_735_686, 3566) + // Standard Error: 47 + .saturating_add(Weight::from_parts(19_776, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(a.into())) + } /// The range of component `r` is `[0, 20]`. fn alloc(r: u32, ) -> Weight { // Proof Size summary in bytes: