diff --git a/common/src/benchmarking.rs b/common/src/benchmarking.rs index fb740587e0c..223733bdc97 100644 --- a/common/src/benchmarking.rs +++ b/common/src/benchmarking.rs @@ -20,7 +20,7 @@ use super::*; use gear_core::{ ids::prelude::*, - pages::{WasmPage, WasmPagesAmount}, + pages::WasmPage, program::{MemoryInfix, ProgramState}, reservation::GasReservationMap, }; @@ -117,11 +117,8 @@ pub fn generate_wasm(num_pages: WasmPage) -> Result, &'static str> { Ok(code) } -pub fn set_program( - program_id: ProgramId, - code: Vec, - static_pages: WasmPagesAmount, -) where +pub fn set_program(program_id: ProgramId, code: Vec) +where ProgramStorage: super::ProgramStorage, BlockNumber: Zero + Copy + Saturating, { @@ -129,9 +126,7 @@ pub fn set_program( program_id, ActiveProgram { allocations_tree_len: 0, - code_hash: CodeId::generate(&code).into_origin(), - code_exports: Default::default(), - static_pages, + code_id: CodeId::generate(&code), state: ProgramState::Initialized, gas_reservation_map: GasReservationMap::default(), expiration_block: Zero::zero(), diff --git a/common/src/code_storage.rs b/common/src/code_storage.rs index cac295e88eb..036d759fc28 100644 --- a/common/src/code_storage.rs +++ b/common/src/code_storage.rs @@ -18,7 +18,7 @@ use super::*; use crate::storage::MapStorage; -use gear_core::code::{CodeAndId, InstrumentedCode, InstrumentedCodeAndId}; +use gear_core::code::{CodeAndId, CodeMetadata, InstrumentedCode, InstrumentedCodeAndMetadata}; #[derive(Clone, Copy, Debug)] pub enum Error { @@ -29,44 +29,52 @@ pub enum Error { /// Trait to work with program binary codes in a storage. pub trait CodeStorage { type InstrumentedCodeStorage: MapStorage; - type InstrumentedLenStorage: MapStorage; type OriginalCodeStorage: MapStorage>; - type MetadataStorage: MapStorage; + type CodeMetadataStorage: MapStorage; /// Attempt to remove all items from all the associated maps. fn reset() { - Self::MetadataStorage::clear(); + Self::CodeMetadataStorage::clear(); Self::OriginalCodeStorage::clear(); - Self::InstrumentedLenStorage::clear(); Self::InstrumentedCodeStorage::clear(); } - fn add_code(code_and_id: CodeAndId, metadata: CodeMetadata) -> Result<(), Error> { + /// Add the code to the storage. + fn add_code(code_and_id: CodeAndId) -> Result<(), Error> { let (code, code_id) = code_and_id.into_parts(); - let (code, original_code) = code.into_parts(); + let (original_code, instrumented_code, code_metadata) = code.into_parts(); Self::InstrumentedCodeStorage::mutate(code_id, |maybe| { if maybe.is_some() { return Err(CodeStorageError::DuplicateItem); } - Self::InstrumentedLenStorage::insert(code_id, code.code().len() as u32); Self::OriginalCodeStorage::insert(code_id, original_code); - Self::MetadataStorage::insert(code_id, metadata); + Self::CodeMetadataStorage::insert(code_id, code_metadata); - *maybe = Some(code); + *maybe = Some(instrumented_code); Ok(()) }) } - /// Update the corresponding code in the storage. - fn update_code(code_and_id: InstrumentedCodeAndId) { - let (code, code_id) = code_and_id.into_parts(); + /// Update the corresponding code and metadata in the storage. + fn update_instrumented_code_and_metadata( + code_id: CodeId, + instrumented_code_and_metadata: InstrumentedCodeAndMetadata, + ) { + Self::InstrumentedCodeStorage::insert( + code_id, + instrumented_code_and_metadata.instrumented_code, + ); + Self::CodeMetadataStorage::insert(code_id, instrumented_code_and_metadata.metadata); + } - Self::InstrumentedLenStorage::insert(code_id, code.code().len() as u32); - Self::InstrumentedCodeStorage::insert(code_id, code); + /// Update the corresponding metadata in the storage. + fn update_code_metadata(code_id: CodeId, metadata: CodeMetadata) { + Self::CodeMetadataStorage::insert(code_id, metadata); } + /// Returns true if the code associated with given id exists. fn exists(code_id: CodeId) -> bool { Self::InstrumentedCodeStorage::contains_key(&code_id) } @@ -80,28 +88,23 @@ pub trait CodeStorage { return false; } - Self::InstrumentedLenStorage::remove(code_id); Self::OriginalCodeStorage::remove(code_id); - Self::MetadataStorage::remove(code_id); + Self::CodeMetadataStorage::remove(code_id); *maybe = None; true }) } - fn get_code(code_id: CodeId) -> Option { + fn get_instrumented_code(code_id: CodeId) -> Option { Self::InstrumentedCodeStorage::get(&code_id) } - fn get_code_len(code_id: CodeId) -> Option { - Self::InstrumentedLenStorage::get(&code_id) - } - fn get_original_code(code_id: CodeId) -> Option> { Self::OriginalCodeStorage::get(&code_id) } - fn get_metadata(code_id: CodeId) -> Option { - Self::MetadataStorage::get(&code_id) + fn get_code_metadata(code_id: CodeId) -> Option { + Self::CodeMetadataStorage::get(&code_id) } } diff --git a/common/src/lib.rs b/common/src/lib.rs index f2e49093bdd..0b8e65cf2b7 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -231,24 +231,6 @@ pub trait BlockLimiter { type GasAllowance: storage::Limiter; } -#[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] -#[codec(crate = codec)] -#[scale_info(crate = scale_info)] -pub struct CodeMetadata { - pub author: H256, - #[codec(compact)] - pub block_number: u32, -} - -impl CodeMetadata { - pub fn new(author: H256, block_number: u32) -> Self { - CodeMetadata { - author, - block_number, - } - } -} - /// A trait whose purpose is to extract the `Call` variant of an extrinsic pub trait ExtractCall { fn extract_call(&self) -> Call; diff --git a/core-processor/src/common.rs b/core-processor/src/common.rs index 2a57e6f92f0..c1d757decf5 100644 --- a/core-processor/src/common.rs +++ b/core-processor/src/common.rs @@ -20,19 +20,13 @@ use crate::{context::SystemReservationContext, precharge::PreChargeGasOperation}; use actor_system_error::actor_system_error; -use alloc::{ - collections::{BTreeMap, BTreeSet}, - string::String, - vec::Vec, -}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, gas::{GasAllowanceCounter, GasAmount, GasCounter}, ids::{CodeId, MessageId, ProgramId, ReservationId}, memory::{MemoryError, MemorySetupError, PageBuf}, - message::{ - ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, StoredDispatch, - }, + message::{ContextStore, Dispatch, IncomingDispatch, MessageWaitedType, StoredDispatch}, pages::{numerated::tree::IntervalsTree, GearPage, WasmPage, WasmPagesAmount}, program::MemoryInfix, reservation::{GasReservationMap, GasReserver}, @@ -57,6 +51,15 @@ pub enum DispatchResultKind { GasAllowanceExceed, } +/// Possible variants of the [`DispatchResult`] if the latter contains value. +#[allow(missing_docs)] +#[derive(Debug)] +pub enum SuccessfulDispatchResultKind { + Exit(ProgramId), + Wait(Option, MessageWaitedType), + Success, +} + /// Result of the specific dispatch. pub struct DispatchResult { /// Kind of the dispatch. @@ -524,16 +527,19 @@ pub struct ExecutableActorData { pub allocations: IntervalsTree, /// The infix of memory pages in a storage. pub memory_infix: MemoryInfix, - /// Id of the program code. - pub code_id: CodeId, - /// Exported functions by the program code. - pub code_exports: BTreeSet, - /// Count of static memory pages. - pub static_pages: WasmPagesAmount, /// Gas reservation map. pub gas_reservation_map: GasReservationMap, } +/// Executable allocations data. +#[derive(Clone, Debug)] +pub struct ExecutableAllocationsData { + /// Amount of reservations can exist for 1 program. + pub max_reservations: u64, + /// Size of wasm memory buffer which must be created in execution environment + pub memory_size: WasmPagesAmount, +} + /// Program. #[derive(Clone, Debug)] pub(crate) struct Program { @@ -542,7 +548,9 @@ pub(crate) struct Program { /// Memory infix. pub memory_infix: MemoryInfix, /// Instrumented code. - pub code: InstrumentedCode, + pub instrumented_code: InstrumentedCode, + /// Code metadata. + pub code_metadata: CodeMetadata, /// Allocations. pub allocations: IntervalsTree, } diff --git a/core-processor/src/context.rs b/core-processor/src/context.rs index 8cb1c4a5136..bda46c8ed39 100644 --- a/core-processor/src/context.rs +++ b/core-processor/src/context.rs @@ -18,9 +18,12 @@ //! Module contains context-structures for processing. -use crate::common::{ExecutableActorData, Program}; +use crate::{ + common::Program, + precharge::{ContextCharged, ForModuleInstantiation}, +}; use gear_core::{ - code::InstrumentedCode, + code::InstrumentedCodeAndMetadata, gas::{GasAllowanceCounter, GasCounter}, ids::ProgramId, message::IncomingDispatch, @@ -29,88 +32,6 @@ 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, -} - -impl ContextChargedForProgram { - /// Unwraps into inner data. - #[cfg(feature = "gtest")] - pub fn into_inner(self) -> (IncomingDispatch, ProgramId, GasCounter) { - (self.dispatch, self.destination_id, self.gas_counter) - } -} - -pub struct ContextChargedForAllocations(pub(crate) ContextChargedForProgram); - -pub(crate) struct ContextData { - pub(crate) gas_counter: GasCounter, - pub(crate) gas_allowance_counter: GasAllowanceCounter, - pub(crate) dispatch: IncomingDispatch, - pub(crate) destination_id: ProgramId, - pub(crate) actor_data: ExecutableActorData, -} - -pub struct ContextChargedForCodeLength { - pub(crate) data: ContextData, -} - -impl ContextChargedForCodeLength { - /// Returns reference to the ExecutableActorData. - pub fn actor_data(&self) -> &ExecutableActorData { - &self.data.actor_data - } -} - -/// The instance returned by `precharge_for_code`. -/// Existence of the instance means that corresponding counters were -/// successfully charged for fetching the binary code from storage. -pub struct ContextChargedForCode { - pub(crate) data: ContextData, -} - -impl From for ContextChargedForCode { - fn from(context: ContextChargedForCodeLength) -> Self { - Self { data: context.data } - } -} - -/// The instance returned by `precharge_for_instrumentation`. -/// Existence of the instance means that corresponding counters were -/// successfully charged for reinstrumentation of the code. -pub struct ContextChargedForInstrumentation { - pub(crate) data: ContextData, -} - -impl From for ContextChargedForInstrumentation { - fn from(context: ContextChargedForCode) -> Self { - Self { data: context.data } - } -} - -pub struct ContextChargedForMemory { - pub(crate) data: ContextData, - pub(crate) max_reservations: u64, - pub(crate) memory_size: WasmPagesAmount, -} - -impl ContextChargedForMemory { - /// Returns reference to the ExecutableActorData. - pub fn actor_data(&self) -> &ExecutableActorData { - &self.data.actor_data - } - - /// Returns reference to the GasCounter. - pub fn gas_counter(&self) -> &GasCounter { - &self.data.gas_counter - } -} - /// Checked parameters for message execution across processing runs. pub struct ProcessExecutionContext { pub(crate) gas_counter: GasCounter, @@ -123,44 +44,35 @@ pub struct ProcessExecutionContext { } impl ProcessExecutionContext { - /// Returns program id. - pub fn program_id(&self) -> ProgramId { - self.program.id - } - - /// Returns memory infix. - pub fn memory_infix(&self) -> MemoryInfix { - self.program.memory_infix - } -} - -impl From<(ContextChargedForMemory, InstrumentedCode, u128)> for ProcessExecutionContext { - fn from(args: (ContextChargedForMemory, InstrumentedCode, u128)) -> Self { - let (context, code, balance) = args; - - let ContextChargedForMemory { - data: - ContextData { - gas_counter, - gas_allowance_counter, - dispatch, - destination_id, - actor_data, - }, - max_reservations, - memory_size, - } = context; + /// Creates a new instance of the process execution context. + pub fn new( + context: ContextCharged, + instrumented_code_and_metadata: InstrumentedCodeAndMetadata, + balance: u128, + ) -> Self { + let ( + destination_id, + dispatch, + gas_counter, + gas_allowance_counter, + actor_data, + allocations_data, + ) = context.into_final_parts(); let program = Program { id: destination_id, memory_infix: actor_data.memory_infix, - code, + instrumented_code: instrumented_code_and_metadata.instrumented_code, + code_metadata: instrumented_code_and_metadata.metadata, allocations: actor_data.allocations, }; // Must be created once per taken from the queue dispatch by program. - let gas_reserver = - GasReserver::new(&dispatch, actor_data.gas_reservation_map, max_reservations); + let gas_reserver = GasReserver::new( + &dispatch, + actor_data.gas_reservation_map, + allocations_data.max_reservations, + ); Self { gas_counter, @@ -169,9 +81,19 @@ impl From<(ContextChargedForMemory, InstrumentedCode, u128)> for ProcessExecutio dispatch, balance, program, - memory_size, + memory_size: allocations_data.memory_size, } } + + /// Returns program id. + pub fn program_id(&self) -> ProgramId { + self.program.id + } + + /// Returns memory infix. + pub fn memory_infix(&self) -> MemoryInfix { + self.program.memory_infix + } } /// System reservation context. diff --git a/core-processor/src/executor.rs b/core-processor/src/executor.rs index 4b0e6e0ba72..05dde6796e0 100644 --- a/core-processor/src/executor.rs +++ b/core-processor/src/executor.rs @@ -26,7 +26,7 @@ use crate::{ }; use alloc::{format, string::String, vec::Vec}; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, env::Externalities, gas::{GasAllowanceCounter, GasCounter, ValueCounter}, ids::ProgramId, @@ -83,8 +83,8 @@ where let allocations_context = AllocationsContext::try_new( memory_size, program.allocations, - program.code.static_pages(), - program.code.stack_end(), + program.code_metadata.static_pages(), + program.code_metadata.stack_end(), settings.max_pages, ) .map_err(SystemExecutionError::from)?; @@ -143,9 +143,9 @@ where let execute = || { let env = Environment::new( ext, - program.code.code(), + program.instrumented_code.bytes(), kind, - program.code.exports().clone(), + program.code_metadata.exports().clone(), memory_size, )?; env.execute(|ctx, memory, globals_config| { @@ -154,7 +154,7 @@ where memory, program.id, program.memory_infix, - program.code.stack_end(), + program.code_metadata.stack_end(), globals_config, settings.lazy_pages_costs, ) @@ -259,6 +259,7 @@ where pub fn execute_for_reply( function: EP, instrumented_code: InstrumentedCode, + code_metadata: CodeMetadata, allocations: Option>, program_info: Option<(ProgramId, MemoryInfix)>, payload: Vec, @@ -277,10 +278,11 @@ where let program = Program { id: program_id, memory_infix, - code: instrumented_code, + instrumented_code, + code_metadata, allocations: allocations.unwrap_or_default(), }; - let static_pages = program.code.static_pages(); + let static_pages = program.code_metadata.static_pages(); let memory_size = program .allocations .end() @@ -316,7 +318,7 @@ where memory_size, program.allocations, static_pages, - program.code.stack_end(), + program.code_metadata.stack_end(), 512.into(), ) .map_err(|e| format!("Failed to create alloc ctx: {e:?}"))?, @@ -342,9 +344,9 @@ where let execute = || { let env = Environment::new( ext, - program.code.code(), + program.instrumented_code.bytes(), function, - program.code.exports().clone(), + program.code_metadata.exports().clone(), memory_size, )?; env.execute(|ctx, memory, globals_config| { @@ -353,7 +355,7 @@ where memory, program_id, program.memory_infix, - program.code.stack_end(), + program.code_metadata.stack_end(), globals_config, Default::default(), ) diff --git a/core-processor/src/lib.rs b/core-processor/src/lib.rs index 8b6d97a1e93..c21a01f2065 100644 --- a/core-processor/src/lib.rs +++ b/core-processor/src/lib.rs @@ -31,26 +31,19 @@ mod context; mod executor; mod ext; mod handler; -mod precharge; +pub mod precharge; mod processing; -pub use context::{ - ContextChargedForCode, ContextChargedForInstrumentation, ContextChargedForProgram, - ProcessExecutionContext, SystemReservationContext, -}; +pub use context::{ProcessExecutionContext, SystemReservationContext}; pub use ext::{ AllocExtError, Ext, FallibleExtError, ProcessorContext, ProcessorExternalities, UnrecoverableExtError, }; pub use handler::handle_journal; -pub use precharge::{ - precharge_for_allocations, precharge_for_code, precharge_for_code_length, - precharge_for_instrumentation, precharge_for_module_instantiation, precharge_for_program, - SuccessfulDispatchResultKind, -}; +pub use precharge::*; pub use processing::{ - process, process_execution_error, process_non_executable, process_reinstrumentation_error, - process_success, + process, process_code_metadata_error, process_execution_error, process_non_executable, + process_reinstrumentation_error, process_success, }; /// Informational functions for core-processor and executor. diff --git a/core-processor/src/precharge.rs b/core-processor/src/precharge.rs index 75aba729ed8..9fa341ad160 100644 --- a/core-processor/src/precharge.rs +++ b/core-processor/src/precharge.rs @@ -14,23 +14,24 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! Precharge module. + use crate::{ - common::{ActorExecutionErrorReplyReason, DispatchResult, ExecutableActorData, JournalNote}, - configs::BlockConfig, - context::{ - ContextChargedForAllocations, ContextChargedForCodeLength, ContextChargedForMemory, - ContextChargedForProgram, ContextData, SystemReservationContext, + common::{ + ActorExecutionErrorReplyReason, ExecutableActorData, ExecutableAllocationsData, JournalNote, }, - processing::{process_allowance_exceed, process_execution_error, process_success}, - ContextChargedForCode, ContextChargedForInstrumentation, + configs::BlockConfig, + context::SystemReservationContext, + processing::{process_allowance_exceed, process_execution_error}, }; use alloc::vec::Vec; +use core::marker::PhantomData; use gear_core::{ - code::{InstantiatedSectionSizes, SectionName}, + code::{CodeMetadata, InstantiatedSectionSizes, SectionName}, costs::{BytesAmount, ProcessCosts}, gas::{ChargeResult, GasAllowanceCounter, GasCounter}, ids::ProgramId, - message::{IncomingDispatch, MessageWaitedType}, + message::IncomingDispatch, }; /// Operation related to gas charging. @@ -42,12 +43,15 @@ pub enum PreChargeGasOperation { /// Handle program data. #[display(fmt = "handle program data")] ProgramData, - /// Obtain code length. - #[display(fmt = "obtain program code length")] - ProgramCodeLen, - /// Handle program code. - #[display(fmt = "handle program code")] - ProgramCode, + /// Obtain code metadata. + #[display(fmt = "obtain code metadata")] + CodeMetadata, + /// Obtain original code + #[display(fmt = "obtain original code")] + OriginalCode, + /// Obtain instrumented code + #[display(fmt = "obtain instrumented code")] + InstrumentedCode, /// Instantiate the type section of the Wasm module. #[display(fmt = "instantiate {_0} of Wasm module")] ModuleInstantiation(SectionName), @@ -59,424 +63,348 @@ pub enum PreChargeGasOperation { Allocations, } -#[derive(Debug, Eq, PartialEq)] -enum PrechargeError { - BlockGasExceeded, - GasExceeded(PreChargeGasOperation), -} +/// Defines result variants of the precharge functions. +pub type PrechargeResult = Result>; + +/// ZST for the context that charged nothing. +pub struct ForNothing; + +/// ZST for the context that charged for program data. +pub struct ForProgram; + +/// ZST for the context that charged for code metadata. +pub struct ForCodeMetadata; -struct GasPrecharger<'a> { - counter: &'a mut GasCounter, - allowance_counter: &'a mut GasAllowanceCounter, - costs: &'a ProcessCosts, +/// ZST for the context that charged for original code. +pub struct ForOriginalCode; + +/// ZST for the context that charged for instrumented code. +pub struct ForInstrumentedCode; + +/// ZST for the context that charged for allocations. +pub struct ForAllocations; + +/// ZST for the context that charged for module instantiation. +pub struct ForModuleInstantiation; + +/// Context charged gas for the program execution. +pub struct ContextCharged { + destination_id: ProgramId, + dispatch: IncomingDispatch, + gas_counter: GasCounter, + gas_allowance_counter: GasAllowanceCounter, + actor_data: Option, + allocations_data: Option, + + _phantom: PhantomData, } -impl<'a> GasPrecharger<'a> { +impl ContextCharged { + /// Creates a new empty instance of the context charged for the program execution. pub fn new( - counter: &'a mut GasCounter, - allowance_counter: &'a mut GasAllowanceCounter, - costs: &'a ProcessCosts, - ) -> Self { + destination_id: ProgramId, + dispatch: IncomingDispatch, + gas_allowance: u64, + ) -> ContextCharged { + let gas_counter = GasCounter::new(dispatch.gas_limit()); + let gas_allowance_counter = GasAllowanceCounter::new(gas_allowance); + Self { - counter, - allowance_counter, - costs, + destination_id, + dispatch, + gas_counter, + gas_allowance_counter, + actor_data: None, + allocations_data: None, + _phantom: PhantomData, } } +} - fn charge_gas( - &mut self, +impl ContextCharged { + /// Splits the context into parts. + pub fn into_parts(self) -> (ProgramId, IncomingDispatch, GasCounter, GasAllowanceCounter) { + ( + self.destination_id, + self.dispatch, + self.gas_counter, + self.gas_allowance_counter, + ) + } + + /// Gas already burned + pub fn gas_burned(&self) -> u64 { + self.gas_counter.burned() + } + + /// Gas left + pub fn gas_left(&self) -> u64 { + self.gas_counter.left() + } + + /// Charges gas for the operation. + fn charge_gas( + mut self, operation: PreChargeGasOperation, amount: u64, - ) -> Result<(), PrechargeError> { - if self.allowance_counter.charge_if_enough(amount) != ChargeResult::Enough { - return Err(PrechargeError::BlockGasExceeded); + ) -> PrechargeResult> { + if self.gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough { + let gas_burned = self.gas_counter.burned(); + + return Err(process_allowance_exceed( + self.dispatch, + self.destination_id, + gas_burned, + )); } - if self.counter.charge_if_enough(amount) != ChargeResult::Enough { - return Err(PrechargeError::GasExceeded(operation)); + + if self.gas_counter.charge_if_enough(amount) != ChargeResult::Enough { + let gas_burned = self.gas_counter.burned(); + let system_reservation_ctx = SystemReservationContext::from_dispatch(&self.dispatch); + + return Err(process_execution_error( + self.dispatch, + self.destination_id, + gas_burned, + system_reservation_ctx, + ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(operation), + )); } - Ok(()) + Ok(ContextCharged { + destination_id: self.destination_id, + dispatch: self.dispatch, + gas_counter: self.gas_counter, + gas_allowance_counter: self.gas_allowance_counter, + actor_data: self.actor_data, + allocations_data: self.allocations_data, + _phantom: PhantomData, + }) } +} - pub fn charge_gas_for_program_data(&mut self) -> Result<(), PrechargeError> { +impl ContextCharged { + /// Charges gas for getting the program data. + pub fn charge_for_program( + self, + block_config: &BlockConfig, + ) -> PrechargeResult> { self.charge_gas( PreChargeGasOperation::ProgramData, - self.costs.read.cost_for_one(), + block_config.costs.read.cost_for_one(), ) } +} - pub fn charge_gas_for_program_code_len(&mut self) -> Result<(), PrechargeError> { +impl ContextCharged { + /// Charges gas for getting the code metadata. + pub fn charge_for_code_metadata( + self, + block_config: &BlockConfig, + ) -> PrechargeResult> { self.charge_gas( - PreChargeGasOperation::ProgramCodeLen, - self.costs.read.cost_for_one(), + PreChargeGasOperation::CodeMetadata, + block_config.costs.read.cost_for_one(), ) } +} - pub fn charge_gas_for_program_code( - &mut self, - code_len: BytesAmount, - ) -> Result<(), PrechargeError> { +impl ContextCharged { + /// Charges gas for getting the original code. + pub fn charge_for_original_code( + self, + block_config: &BlockConfig, + code_len_bytes: u32, + ) -> PrechargeResult> { self.charge_gas( - PreChargeGasOperation::ProgramCode, - self.costs + PreChargeGasOperation::OriginalCode, + block_config + .costs .read - .cost_for_with_bytes(self.costs.read_per_byte, code_len), + .cost_for_with_bytes(block_config.costs.read_per_byte, code_len_bytes.into()), ) } - pub fn charge_gas_for_section_instantiation( - &mut self, - section_name: SectionName, - section_len: BytesAmount, - ) -> Result<(), PrechargeError> { - let instantiation_costs = &self.costs.instantiation_costs; - - let cost_per_byte = match section_name { - SectionName::Function => &instantiation_costs.code_section_per_byte, - SectionName::Data => &instantiation_costs.data_section_per_byte, - SectionName::Global => &instantiation_costs.global_section_per_byte, - SectionName::Table => &instantiation_costs.table_section_per_byte, - SectionName::Element => &instantiation_costs.element_section_per_byte, - SectionName::Type => &instantiation_costs.type_section_per_byte, - _ => { - // TODO: change this to a system error in future - unimplemented!("Wrong {section_name:?} for section instantiation") - } - }; - + /// Charges gas for getting the instrumented code. + pub fn charge_for_instrumented_code( + self, + block_config: &BlockConfig, + code_len_bytes: u32, + ) -> PrechargeResult> { self.charge_gas( - PreChargeGasOperation::ModuleInstantiation(section_name), - cost_per_byte.cost_for(section_len), + PreChargeGasOperation::InstrumentedCode, + block_config + .costs + .read + .cost_for_with_bytes(block_config.costs.read_per_byte, code_len_bytes.into()), ) } +} - pub fn charge_gas_for_instrumentation( - &mut self, - original_code_len_bytes: BytesAmount, - ) -> Result<(), PrechargeError> { +impl ContextCharged { + /// Charges gas for code instrumentation. + pub fn charge_for_instrumentation( + self, + block_config: &BlockConfig, + original_code_len_bytes: u32, + ) -> PrechargeResult> { self.charge_gas( PreChargeGasOperation::ModuleInstrumentation, - self.costs - .instrumentation - .cost_for_with_bytes(self.costs.instrumentation_per_byte, original_code_len_bytes), + block_config.costs.instrumentation.cost_for_with_bytes( + block_config.costs.instrumentation_per_byte, + original_code_len_bytes.into(), + ), ) } } -/// Possible variants of the [`DispatchResult`] if the latter contains value. -#[allow(missing_docs)] -#[derive(Debug)] -pub enum SuccessfulDispatchResultKind { - Exit(ProgramId), - Wait(Option, MessageWaitedType), - Success, -} - -/// Defines result variants of the precharge functions. -pub type PrechargeResult = Result>; - -/// Charge a message for program data beforehand. -pub fn precharge_for_program( - block_config: &BlockConfig, - gas_allowance: u64, - dispatch: IncomingDispatch, - destination_id: ProgramId, -) -> PrechargeResult { - let mut gas_counter = GasCounter::new(dispatch.gas_limit()); - let mut gas_allowance_counter = GasAllowanceCounter::new(gas_allowance); - let mut charger = GasPrecharger::new( - &mut gas_counter, - &mut gas_allowance_counter, - &block_config.costs, - ); - - match charger.charge_gas_for_program_data() { - Ok(()) => Ok(ContextChargedForProgram { - dispatch, - destination_id, - gas_counter, - gas_allowance_counter, - }), - Err(PrechargeError::BlockGasExceeded) => { - let gas_burned = gas_counter.burned(); - Err(process_allowance_exceed( - dispatch, - destination_id, - gas_burned, - )) - } - Err(PrechargeError::GasExceeded(op)) => { - let gas_burned = gas_counter.burned(); - let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); - Err(process_execution_error( - dispatch, - destination_id, - gas_burned, - system_reservation_ctx, - ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), - )) - } - } -} - -/// 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 -/// function to charge gas-counters accordingly before fetching the value. -/// -/// The function also performs several additional checks: -/// - if an actor is executable -/// - if a required dispatch method is exported. -pub fn precharge_for_code_length( - block_config: &BlockConfig, - context: ContextChargedForAllocations, - actor_data: ExecutableActorData, -) -> PrechargeResult { - 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( - SuccessfulDispatchResultKind::Success, - DispatchResult::success(dispatch, destination_id, gas_counter.to_amount()), - )); - } - - let mut charger = GasPrecharger::new( - &mut gas_counter, - &mut gas_allowance_counter, - &block_config.costs, - ); - match charger.charge_gas_for_program_code_len() { - Ok(()) => Ok(ContextChargedForCodeLength { - data: ContextData { - gas_counter, - gas_allowance_counter, - dispatch, - destination_id, - actor_data, - }, - }), - Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed( - dispatch, - destination_id, - gas_counter.burned(), - )), - Err(PrechargeError::GasExceeded(op)) => { - let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); - Err(process_execution_error( - dispatch, - destination_id, - gas_counter.burned(), - system_reservation_ctx, - ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), - )) +impl ContextCharged { + /// Charges gas for allocations. + pub fn charge_for_allocations( + self, + block_config: &BlockConfig, + allocations_tree_len: u32, + ) -> PrechargeResult> { + if allocations_tree_len == 0 { + return Ok(ContextCharged { + destination_id: self.destination_id, + dispatch: self.dispatch, + gas_counter: self.gas_counter, + gas_allowance_counter: self.gas_allowance_counter, + actor_data: self.actor_data, + allocations_data: self.allocations_data, + _phantom: PhantomData, + }); } - } -} -/// Charge a message for the program binary code beforehand. -pub fn precharge_for_code( - block_config: &BlockConfig, - mut context: ContextChargedForCodeLength, - code_len_bytes: u32, -) -> PrechargeResult { - let mut charger = GasPrecharger::new( - &mut context.data.gas_counter, - &mut context.data.gas_allowance_counter, - &block_config.costs, - ); - - match charger.charge_gas_for_program_code(code_len_bytes.into()) { - Ok(()) => Ok(context.into()), - Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - )), - Err(PrechargeError::GasExceeded(op)) => { - let system_reservation_ctx = - SystemReservationContext::from_dispatch(&context.data.dispatch); - Err(process_execution_error( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - system_reservation_ctx, - ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), - )) - } - } -} + let amount = block_config + .costs + .load_allocations_per_interval + .cost_for(allocations_tree_len) + .saturating_add(block_config.costs.read.cost_for_one()); -/// Charge a message for instrumentation of the binary code beforehand. -pub fn precharge_for_instrumentation( - block_config: &BlockConfig, - mut context: ContextChargedForCode, - original_code_len_bytes: u32, -) -> PrechargeResult { - let mut charger = GasPrecharger::new( - &mut context.data.gas_counter, - &mut context.data.gas_allowance_counter, - &block_config.costs, - ); - - match charger.charge_gas_for_instrumentation(original_code_len_bytes.into()) { - Ok(()) => Ok(context.into()), - Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - )), - Err(PrechargeError::GasExceeded(op)) => { - let system_reservation_ctx = - SystemReservationContext::from_dispatch(&context.data.dispatch); - Err(process_execution_error( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - system_reservation_ctx, - ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), - )) - } + self.charge_gas(PreChargeGasOperation::Allocations, amount) } } -/// Charge a message for program memory and module instantiation beforehand. -pub fn precharge_for_module_instantiation( - block_config: &BlockConfig, - mut context: ContextChargedForInstrumentation, - section_sizes: &InstantiatedSectionSizes, -) -> PrechargeResult { - let ContextChargedForInstrumentation { - data: - ContextData { - gas_counter, - gas_allowance_counter, - actor_data, - .. - }, - .. - } = &mut context; - - let mut f = || { - let mut charger = - GasPrecharger::new(gas_counter, gas_allowance_counter, &block_config.costs); - +impl ContextCharged { + /// Charges gas for module instantiation. + pub fn charge_for_module_instantiation( + mut self, + block_config: &BlockConfig, + actor_data: ExecutableActorData, + section_sizes: &InstantiatedSectionSizes, + code_metadata: &CodeMetadata, + ) -> PrechargeResult> { // Calculates size of wasm memory buffer which must be created in execution environment let memory_size = if let Some(page) = actor_data.allocations.end() { page.inc() } else { - actor_data.static_pages + code_metadata.static_pages() }; - charger.charge_gas_for_section_instantiation( + let allocations_data = ExecutableAllocationsData { + max_reservations: block_config.max_reservations, + memory_size, + }; + + self.actor_data = Some(actor_data); + self.allocations_data = Some(allocations_data); + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Function, - section_sizes.code_section.into(), + section_sizes.code_section().into(), )?; - charger.charge_gas_for_section_instantiation( + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Data, - section_sizes.data_section.into(), + section_sizes.data_section().into(), )?; - charger.charge_gas_for_section_instantiation( + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Global, - section_sizes.global_section.into(), + section_sizes.global_section().into(), )?; - charger.charge_gas_for_section_instantiation( + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Table, - section_sizes.table_section.into(), + section_sizes.table_section().into(), )?; - charger.charge_gas_for_section_instantiation( + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Element, - section_sizes.element_section.into(), + section_sizes.element_section().into(), )?; - charger.charge_gas_for_section_instantiation( + + self = self.charge_gas_for_section_instantiation( + &block_config.costs, SectionName::Type, - section_sizes.type_section.into(), + section_sizes.type_section().into(), )?; - Ok(memory_size) - }; - - match f() { - Ok(memory_size) => { - log::trace!("Charged for module instantiation and memory pages. Size: {memory_size:?}"); - Ok(ContextChargedForMemory { - data: context.data, - max_reservations: block_config.max_reservations, - memory_size, - }) - } - Err(err) => { - log::trace!("Failed to charge for module instantiation or memory pages: {err:?}"); - match err { - PrechargeError::BlockGasExceeded => Err(process_allowance_exceed( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - )), - PrechargeError::GasExceeded(op) => { - let system_reservation_ctx = - SystemReservationContext::from_dispatch(&context.data.dispatch); - Err(process_execution_error( - context.data.dispatch, - context.data.destination_id, - context.data.gas_counter.burned(), - system_reservation_ctx, - ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op), - )) - } + Ok(ContextCharged { + destination_id: self.destination_id, + dispatch: self.dispatch, + gas_counter: self.gas_counter, + gas_allowance_counter: self.gas_allowance_counter, + actor_data: self.actor_data, + allocations_data: self.allocations_data, + _phantom: PhantomData, + }) + } + + /// Helper function to charge gas for section instantiation. + fn charge_gas_for_section_instantiation( + self, + costs: &ProcessCosts, + section_name: SectionName, + section_len: BytesAmount, + ) -> PrechargeResult> { + let instantiation_costs = &costs.instantiation_costs; + + let cost_per_byte = match section_name { + SectionName::Function => &instantiation_costs.code_section_per_byte, + SectionName::Data => &instantiation_costs.data_section_per_byte, + SectionName::Global => &instantiation_costs.global_section_per_byte, + SectionName::Table => &instantiation_costs.table_section_per_byte, + SectionName::Element => &instantiation_costs.element_section_per_byte, + SectionName::Type => &instantiation_costs.type_section_per_byte, + _ => { + unimplemented!("Wrong {section_name:?} for section instantiation") } - } + }; + + self.charge_gas( + PreChargeGasOperation::ModuleInstantiation(section_name), + cost_per_byte.cost_for(section_len), + ) + } +} + +impl ContextCharged { + /// Converts the context into the final parts. + pub fn into_final_parts( + self, + ) -> ( + ProgramId, + IncomingDispatch, + GasCounter, + GasAllowanceCounter, + ExecutableActorData, + ExecutableAllocationsData, + ) { + ( + self.destination_id, + self.dispatch, + self.gas_counter, + self.gas_allowance_counter, + self.actor_data.unwrap(), + self.allocations_data.unwrap(), + ) } } diff --git a/core-processor/src/processing.rs b/core-processor/src/processing.rs index 7011082f2d6..160cdc680ee 100644 --- a/core-processor/src/processing.rs +++ b/core-processor/src/processing.rs @@ -19,13 +19,14 @@ use crate::{ common::{ ActorExecutionErrorReplyReason, DispatchOutcome, DispatchResult, DispatchResultKind, - ExecutionError, JournalNote, SystemExecutionError, WasmExecutionContext, + ExecutionError, JournalNote, SuccessfulDispatchResultKind, SystemExecutionError, + WasmExecutionContext, }, configs::{BlockConfig, ExecutionSettings}, context::*, executor, ext::ProcessorExternalities, - precharge::SuccessfulDispatchResultKind, + ContextCharged, ForCodeMetadata, ForInstrumentedCode, ForProgram, }; use alloc::{ format, @@ -57,7 +58,7 @@ where RunFallibleError: From, ::UnrecoverableError: BackendSyscallError, { - use crate::precharge::SuccessfulDispatchResultKind::*; + use crate::common::SuccessfulDispatchResultKind::*; let BlockConfig { block_info, @@ -203,6 +204,8 @@ enum ProcessErrorCase { NonExecutable, /// Error is considered as an execution failure. ExecutionFailed(ActorExecutionErrorReplyReason), + /// Message is executable, but it's execution failed due to code metadata verification. + MetadataVerificationFailed, /// Message is executable, but it's execution failed due to re-instrumentation. ReinstrumentationFailed, } @@ -217,6 +220,10 @@ impl ProcessErrorCase { ProcessErrorCase::ExecutionFailed(reason) => { (reason.as_simple().into(), reason.to_string()) } + ProcessErrorCase::MetadataVerificationFailed => { + let err = ErrorReplyReason::ReinstrumentationFailure; + (err, err.to_string()) + } ProcessErrorCase::ReinstrumentationFailed => { let err = ErrorReplyReason::ReinstrumentationFailure; (err, err.to_string()) @@ -316,7 +323,9 @@ fn process_error( } let outcome = match case { - ProcessErrorCase::ExecutionFailed { .. } | ProcessErrorCase::ReinstrumentationFailed => { + ProcessErrorCase::ExecutionFailed { .. } + | ProcessErrorCase::ReinstrumentationFailed + | ProcessErrorCase::MetadataVerificationFailed => { let (_, err_payload) = case.to_reason_and_payload(); match dispatch.kind() { DispatchKind::Init => DispatchOutcome::InitFailure { @@ -362,30 +371,41 @@ pub fn process_execution_error( /// Helper function for journal creation in case of re-instrumentation error. pub fn process_reinstrumentation_error( - context: ContextChargedForInstrumentation, + context: ContextCharged, ) -> Vec { - let dispatch = context.data.dispatch; - let program_id = context.data.destination_id; - let gas_burned = context.data.gas_counter.burned(); + let (destination_id, dispatch, gas_counter, _) = context.into_parts(); + + let gas_burned = gas_counter.burned(); let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); process_error( dispatch, - program_id, + destination_id, gas_burned, system_reservation_ctx, ProcessErrorCase::ReinstrumentationFailed, ) } -/// Helper function for journal creation in message no execution case. -pub fn process_non_executable(context: ContextChargedForProgram) -> Vec { - let ContextChargedForProgram { +/// Helper function for journal creation in case of metadata verification error. +pub fn process_code_metadata_error(context: ContextCharged) -> Vec { + let (destination_id, dispatch, gas_counter, _) = context.into_parts(); + + let gas_burned = gas_counter.burned(); + let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); + + process_error( dispatch, - gas_counter, destination_id, - .. - } = context; + gas_burned, + system_reservation_ctx, + ProcessErrorCase::MetadataVerificationFailed, + ) +} + +/// Helper function for journal creation in message no execution case. +pub fn process_non_executable(context: ContextCharged) -> Vec { + let (destination_id, dispatch, gas_counter, _) = context.into_parts(); let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch); @@ -403,7 +423,7 @@ pub fn process_success( kind: SuccessfulDispatchResultKind, dispatch_result: DispatchResult, ) -> Vec { - use crate::precharge::SuccessfulDispatchResultKind::*; + use crate::common::SuccessfulDispatchResultKind::*; let DispatchResult { dispatch, diff --git a/core/src/code/config.rs b/core/src/code/config.rs new file mode 100644 index 00000000000..9ee9877e7ba --- /dev/null +++ b/core/src/code/config.rs @@ -0,0 +1,60 @@ +/// Configuration for `Code::try_new_mock_`. +/// By default all checks enabled. +pub struct TryNewCodeConfig { + /// Instrumentation version + pub version: u32, + /// Stack height limit + pub stack_height: Option, + /// Limit of data section amount + pub data_segments_amount_limit: Option, + /// Limit on the number of tables. + pub table_number_limit: Option, + /// Export `STACK_HEIGHT_EXPORT_NAME` global + pub export_stack_height: bool, + /// Check exports (wasm contains init or handle exports) + pub check_exports: bool, + /// Check imports (check that all imports are valid syscalls with correct signature) + pub check_imports: bool, + /// Check and canonize stack end + pub check_and_canonize_stack_end: bool, + /// Check mutable global exports + pub check_mut_global_exports: bool, + /// Check start section (not allowed for programs) + pub check_start_section: bool, + /// Check data section + pub check_data_section: bool, + /// Check table section + pub check_table_section: bool, + /// Make wasmparser validation + pub make_validation: bool, +} + +impl TryNewCodeConfig { + /// New default config without exports checks. + pub fn with_no_exports_check() -> Self { + Self { + check_exports: false, + ..Default::default() + } + } +} + +impl Default for TryNewCodeConfig { + fn default() -> Self { + Self { + version: 1, + stack_height: None, + data_segments_amount_limit: None, + table_number_limit: None, + export_stack_height: false, + check_exports: true, + check_imports: true, + check_and_canonize_stack_end: true, + check_mut_global_exports: true, + check_start_section: true, + check_data_section: true, + check_table_section: true, + make_validation: true, + } + } +} diff --git a/core/src/code/instrumented.rs b/core/src/code/instrumented.rs index 9c4d2f01d43..b1f5aa4cd71 100644 --- a/core/src/code/instrumented.rs +++ b/core/src/code/instrumented.rs @@ -18,13 +18,8 @@ //! Module for instrumented code. -use crate::{ - code::{Code, CodeAndId}, - ids::CodeId, - message::DispatchKind, - pages::{WasmPage, WasmPagesAmount}, -}; -use alloc::{collections::BTreeSet, vec::Vec}; +use crate::code::Code; +use alloc::vec::Vec; use scale_info::{ scale::{Decode, Encode}, TypeInfo, @@ -36,145 +31,107 @@ use scale_info::{ #[derive(Clone, Debug, PartialEq, Eq, Decode, Encode, TypeInfo)] pub struct InstantiatedSectionSizes { /// Code section size in bytes. - pub code_section: u32, + code_section: u32, /// Data section size in bytes based on the number of heuristic memory pages /// used during data section instantiation (see `GENERIC_OS_PAGE_SIZE`). - pub data_section: u32, + data_section: u32, /// Global section size in bytes. - pub global_section: u32, + global_section: u32, /// Table section size in bytes. - pub table_section: u32, + table_section: u32, /// Element section size in bytes. - pub element_section: u32, + element_section: u32, /// Type section size in bytes. - pub type_section: u32, + type_section: u32, } impl InstantiatedSectionSizes { - /// Returns an empty instance of the section sizes. - pub const EMPTY: Self = Self { - code_section: 0, - data_section: 0, - global_section: 0, - table_section: 0, - element_section: 0, - type_section: 0, - }; -} - -/// The newtype contains the instrumented code and the corresponding id (hash). -#[derive(Clone, Debug, Decode, Encode, TypeInfo)] -pub struct InstrumentedCode { - pub(crate) code: Vec, - pub(crate) original_code_len: u32, - pub(crate) exports: BTreeSet, - pub(crate) static_pages: WasmPagesAmount, - pub(crate) stack_end: Option, - pub(crate) instantiated_section_sizes: InstantiatedSectionSizes, - pub(crate) version: u32, -} - -impl InstrumentedCode { - /// Creates a new instance of the instrumented code. - /// - /// # Safety - /// The caller must ensure that the `code` is a valid wasm binary, - /// and other parameters are valid and suitable for the wasm binary. - pub unsafe fn new_unchecked( - code: Vec, - original_code_len: u32, - exports: BTreeSet, - static_pages: WasmPagesAmount, - stack_end: Option, - instantiated_section_sizes: InstantiatedSectionSizes, - version: u32, + /// Creates a new instance of the section sizes. + pub fn new( + code_section: u32, + data_section: u32, + global_section: u32, + table_section: u32, + element_section: u32, + type_section: u32, ) -> Self { Self { - code, - original_code_len, - exports, - static_pages, - stack_end, - instantiated_section_sizes, - version, + code_section, + data_section, + global_section, + table_section, + element_section, + type_section, } } - /// Returns reference to the instrumented binary code. - pub fn code(&self) -> &[u8] { - &self.code - } - - /// Returns the length of the original binary code. - pub fn original_code_len(&self) -> u32 { - self.original_code_len - } - - /// Returns instruction weights version. - pub fn instruction_weights_version(&self) -> u32 { - self.version + /// Returns the code section size in bytes. + pub fn code_section(&self) -> u32 { + self.code_section } - /// Returns wasm module exports. - pub fn exports(&self) -> &BTreeSet { - &self.exports + /// Returns the data section size in bytes. + pub fn data_section(&self) -> u32 { + self.data_section } - /// Returns initial memory size from memory import. - pub fn static_pages(&self) -> WasmPagesAmount { - self.static_pages + /// Returns the global section size in bytes. + pub fn global_section(&self) -> u32 { + self.global_section } - /// Returns stack end page if exists. - pub fn stack_end(&self) -> Option { - self.stack_end - } - - /// Returns instantiated section sizes used for charging during module instantiation. - pub fn instantiated_section_sizes(&self) -> &InstantiatedSectionSizes { - &self.instantiated_section_sizes + /// Returns the table section size in bytes. + pub fn table_section(&self) -> u32 { + self.table_section } - /// Consumes the instance and returns the instrumented code. - pub fn into_code(self) -> Vec { - self.code + /// Returns the element section size in bytes. + pub fn element_section(&self) -> u32 { + self.element_section } -} -impl From for InstrumentedCode { - fn from(code: Code) -> Self { - code.into_parts().0 + /// Returns the type section size in bytes. + pub fn type_section(&self) -> u32 { + self.type_section } } /// The newtype contains the instrumented code and the corresponding id (hash). -#[derive(Clone, Debug)] -pub struct InstrumentedCodeAndId { - code: InstrumentedCode, - code_id: CodeId, +#[derive(Clone, Debug, Decode, Encode, TypeInfo, PartialEq, Eq)] +pub struct InstrumentedCode { + /// Code instrumented with the latest schedule. + bytes: Vec, + /// Instantiated section sizes used for charging during module instantiation. + instantiated_section_sizes: InstantiatedSectionSizes, } -impl InstrumentedCodeAndId { - /// Returns reference to the instrumented code. - pub fn code(&self) -> &InstrumentedCode { - &self.code +impl InstrumentedCode { + /// Creates a new instance of the instrumented code. + pub fn new(bytes: Vec, instantiated_section_sizes: InstantiatedSectionSizes) -> Self { + Self { + bytes, + instantiated_section_sizes, + } + } + + /// Returns reference to the instrumented binary code. + pub fn bytes(&self) -> &[u8] { + &self.bytes } - /// Returns corresponding id (hash) for the code. - pub fn code_id(&self) -> CodeId { - self.code_id + /// Returns instantiated section sizes used for charging during module instantiation. + pub fn instantiated_section_sizes(&self) -> &InstantiatedSectionSizes { + &self.instantiated_section_sizes } /// Consumes the instance and returns the instrumented code. - pub fn into_parts(self) -> (InstrumentedCode, CodeId) { - (self.code, self.code_id) + pub fn into_code(self) -> Vec { + self.bytes } } -impl From for InstrumentedCodeAndId { - fn from(code_and_id: CodeAndId) -> Self { - let (code, code_id) = code_and_id.into_parts(); - let (code, _) = code.into_parts(); - Self { code, code_id } +impl From for InstrumentedCode { + fn from(code: Code) -> Self { + code.into_parts().1 } } diff --git a/core/src/code/metadata.rs b/core/src/code/metadata.rs new file mode 100644 index 00000000000..f3269f040a0 --- /dev/null +++ b/core/src/code/metadata.rs @@ -0,0 +1,106 @@ +use scale_info::{ + scale::{Decode, Encode}, + TypeInfo, +}; + +use crate::{ + message::DispatchKind, + pages::{WasmPage, WasmPagesAmount}, +}; + +use alloc::collections::BTreeSet; + +/// Status of the instrumentation. +#[derive(Clone, Copy, Debug, Decode, Encode, TypeInfo, PartialEq, Eq)] +pub enum InstrumentationStatus { + /// Code is instrumented. + Instrumented(u32), + /// Failed to instrument code. + InstrumentationFailed(u32), +} + +/// Metadata for the code. +#[derive(Clone, Debug, Decode, Encode, TypeInfo, PartialEq, Eq)] +pub struct CodeMetadata { + /// Original code length. + original_code_len: u32, + /// Instrumented code length. + instrumented_code_len: u32, + /// Exports of the wasm module. + exports: BTreeSet, + // Static pages count from memory import. + static_pages: WasmPagesAmount, + /// Stack end page. + stack_end: Option, + /// Instrumentation status, contains version of the instructions. + instrumentation_status: InstrumentationStatus, +} + +impl CodeMetadata { + /// Creates a new instance of the code metadata. + pub fn new( + original_code_len: u32, + instrumented_code_len: u32, + exports: BTreeSet, + static_pages: WasmPagesAmount, + stack_end: Option, + instrumentation_status: InstrumentationStatus, + ) -> Self { + Self { + original_code_len, + instrumented_code_len, + exports, + static_pages, + stack_end, + instrumentation_status, + } + } + + /// Converts the metadata into the failed instrumentation state. + pub fn into_failed_instrumentation(self, instruction_weights_version: u32) -> Self { + Self { + instrumentation_status: InstrumentationStatus::InstrumentationFailed( + instruction_weights_version, + ), + ..self + } + } + + /// Returns the original code length. + pub fn original_code_len(&self) -> u32 { + self.original_code_len + } + + /// Returns the instrumented code length. + pub fn instrumented_code_len(&self) -> u32 { + self.instrumented_code_len + } + + /// Returns the code exports. + pub fn exports(&self) -> &BTreeSet { + &self.exports + } + + /// Returns the static pages count from memory import. + pub fn static_pages(&self) -> WasmPagesAmount { + self.static_pages + } + + /// Returns the stack end page. + pub fn stack_end(&self) -> Option { + self.stack_end + } + + /// Returns the instrumentation status. + pub fn instrumentation_status(&self) -> InstrumentationStatus { + self.instrumentation_status + } + + /// Returns the version of the instructions. + pub fn instruction_weights_version(&self) -> u32 { + match self.instrumentation_status { + InstrumentationStatus::Instrumented(version) => version, + InstrumentationStatus::InstrumentationFailed(version) => version, + } + } +} diff --git a/core/src/code/mod.rs b/core/src/code/mod.rs index bf824602f5b..e04cb9c54a7 100644 --- a/core/src/code/mod.rs +++ b/core/src/code/mod.rs @@ -21,21 +21,23 @@ use crate::{ gas_metering::{CustomConstantCostRules, Rules}, ids::{prelude::*, CodeId}, - message::DispatchKind, - pages::{WasmPage, WasmPagesAmount}, }; -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::vec::Vec; use gear_wasm_instrument::{ parity_wasm::{self, elements::Module}, InstrumentationBuilder, }; +mod config; mod errors; mod instrumented; +mod metadata; mod utils; +pub use config::*; pub use errors::*; pub use instrumented::*; +pub use metadata::*; pub use utils::{ALLOWED_EXPORTS, MAX_WASM_PAGES_AMOUNT, REQUIRED_EXPORTS}; use utils::CodeTypeSectionSizes; @@ -43,84 +45,12 @@ use utils::CodeTypeSectionSizes; /// Generic OS page size. Approximated to 4KB as a most common value. const GENERIC_OS_PAGE_SIZE: u32 = 4096; -/// Contains instrumented binary code of a program and initial memory size from memory import. +/// Contains original and instrumented binary code of a program. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Code { - /// Code instrumented with the latest schedule. - code: Vec, - /// The uninstrumented, original version of the code. original_code: Vec, - /// Exports of the wasm module. - exports: BTreeSet, - /// Static pages count from memory import. - static_pages: WasmPagesAmount, - /// Stack end page. - stack_end: Option, - /// Instruction weights version. - instruction_weights_version: u32, - /// Instantiated section sizes used for charging during module instantiation. - instantiated_section_sizes: InstantiatedSectionSizes, -} - -/// Configuration for `Code::try_new_mock_`. -/// By default all checks enabled. -pub struct TryNewCodeConfig { - /// Instrumentation version - pub version: u32, - /// Stack height limit - pub stack_height: Option, - /// Limit of data section amount - pub data_segments_amount_limit: Option, - /// Limit on the number of tables. - pub table_number_limit: Option, - /// Export `STACK_HEIGHT_EXPORT_NAME` global - pub export_stack_height: bool, - /// Check exports (wasm contains init or handle exports) - pub check_exports: bool, - /// Check imports (check that all imports are valid syscalls with correct signature) - pub check_imports: bool, - /// Check and canonize stack end - pub check_and_canonize_stack_end: bool, - /// Check mutable global exports - pub check_mut_global_exports: bool, - /// Check start section (not allowed for programs) - pub check_start_section: bool, - /// Check data section - pub check_data_section: bool, - /// Check table section - pub check_table_section: bool, - /// Make wasmparser validation - pub make_validation: bool, -} - -impl Default for TryNewCodeConfig { - fn default() -> Self { - Self { - version: 1, - stack_height: None, - data_segments_amount_limit: None, - table_number_limit: None, - export_stack_height: false, - check_exports: true, - check_imports: true, - check_and_canonize_stack_end: true, - check_mut_global_exports: true, - check_start_section: true, - check_data_section: true, - check_table_section: true, - make_validation: true, - } - } -} - -impl TryNewCodeConfig { - /// New default config without exports checks. - pub fn new_no_exports_check() -> Self { - Self { - check_exports: false, - ..Default::default() - } - } + instrumented_code: InstrumentedCode, + metadata: CodeMetadata, } impl Code { @@ -184,6 +114,7 @@ impl Code { if let Some(get_gas_rules) = get_gas_rules { instrumentation_builder.with_gas_limiter(get_gas_rules); } + module = instrumentation_builder.instrument(module)?; // Use instrumented module to get section sizes. @@ -200,21 +131,30 @@ impl Code { type_section, } = utils::get_code_type_sections_sizes(&code)?; - Ok(Self { - code, - original_code, - exports, + let instantiated_section_sizes = InstantiatedSectionSizes::new( + code_section, + data_section_size, + global_section_size, + table_section_size, + element_section_size, + type_section, + ); + + let instrumented_code = InstrumentedCode::new(code, instantiated_section_sizes); + + let metadata = CodeMetadata::new( + original_code.len() as u32, + instrumented_code.bytes().len() as u32, + exports.clone(), static_pages, stack_end, - instruction_weights_version: config.version, - instantiated_section_sizes: InstantiatedSectionSizes { - code_section, - data_section: data_section_size, - global_section: global_section_size, - table_section: table_section_size, - element_section: element_section_size, - type_section, - }, + InstrumentationStatus::Instrumented(config.version), + ); + + Ok(Self { + original_code, + instrumented_code, + metadata, }) } @@ -348,41 +288,27 @@ impl Code { &self.original_code } - /// Returns reference to the instrumented binary code. - pub fn code(&self) -> &[u8] { - &self.code - } - - /// Returns wasm module exports. - pub fn exports(&self) -> &BTreeSet { - &self.exports + /// Returns the instrumented code. + pub fn instrumented_code(&self) -> &InstrumentedCode { + &self.instrumented_code } - /// Returns instruction weights version. - pub fn instruction_weights_version(&self) -> u32 { - self.instruction_weights_version + /// Returns the code metadata. + pub fn metadata(&self) -> &CodeMetadata { + &self.metadata } - /// Returns initial memory size from memory import. - pub fn static_pages(&self) -> WasmPagesAmount { - self.static_pages + /// Consumes this instance and returns the instrumented and raw binary codes. + pub fn into_parts(self) -> (Vec, InstrumentedCode, CodeMetadata) { + (self.original_code, self.instrumented_code, self.metadata) } - /// Consumes this instance and returns the instrumented and raw binary codes. - pub fn into_parts(self) -> (InstrumentedCode, Vec) { - let original_code_len = self.original_code.len() as u32; - ( - InstrumentedCode { - code: self.code, - original_code_len, - exports: self.exports, - static_pages: self.static_pages, - stack_end: self.stack_end, - instantiated_section_sizes: self.instantiated_section_sizes, - version: self.instruction_weights_version, - }, - self.original_code, - ) + /// Consumes this instance and returns the instrumented code and metadata struct. + pub fn into_instrumented_code_and_metadata(self) -> InstrumentedCodeAndMetadata { + InstrumentedCodeAndMetadata { + instrumented_code: self.instrumented_code, + metadata: self.metadata, + } } } @@ -430,6 +356,32 @@ impl CodeAndId { } } +/// The newtype contains the InstrumentedCode instance and the corresponding metadata. +#[derive(Clone, Debug)] +pub struct InstrumentedCodeAndMetadata { + /// Instrumented code. + pub instrumented_code: InstrumentedCode, + /// Code metadata. + pub metadata: CodeMetadata, +} + +impl InstrumentedCodeAndMetadata { + /// Decomposes this instance into parts. + pub fn into_parts(self) -> (InstrumentedCode, CodeMetadata) { + (self.instrumented_code, self.metadata) + } +} + +impl From for InstrumentedCodeAndMetadata { + fn from(code: Code) -> Self { + let (_, instrumented_code, metadata) = code.into_parts(); + Self { + instrumented_code, + metadata, + } + } +} + #[cfg(test)] mod tests { use crate::{ @@ -718,7 +670,7 @@ mod tests { ); let code = try_new_code_from_wat(wat.as_str(), None).expect("Must be ok"); - assert_eq!(code.stack_end, Some(1.into())); + assert_eq!(code.metadata().stack_end(), Some(1.into())); } #[test] @@ -891,8 +843,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE, ); @@ -910,8 +863,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE * 2, ); @@ -929,8 +883,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE * 2, ); @@ -948,8 +903,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), 0, ); @@ -967,8 +923,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE, ); @@ -988,8 +945,9 @@ mod tests { assert_eq!( try_new_code_from_wat(&wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE * 2, ); @@ -1011,8 +969,9 @@ mod tests { assert_eq!( try_new_code_from_wat(&wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE * 4, ); @@ -1034,8 +993,9 @@ mod tests { assert_eq!( try_new_code_from_wat(&wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .data_section, + .instrumented_code() + .instantiated_section_sizes() + .data_section(), GENERIC_OS_PAGE_SIZE * 4, ); } @@ -1060,8 +1020,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .code_section, + .instrumented_code() + .instantiated_section_sizes() + .code_section(), INSTRUMENTATION_CODE_SIZE + 11, ); } @@ -1084,8 +1045,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .global_section, + .instrumented_code() + .instantiated_section_sizes() + .global_section(), (INSTRUMENTATION_GLOBALS_SIZE + size_of::() * 2 + size_of::()) as u32, ); } @@ -1107,16 +1069,18 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .table_section, + .instrumented_code() + .instantiated_section_sizes() + .table_section(), 40 * REF_TYPE_SIZE, ); assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .element_section, + .instrumented_code() + .instantiated_section_sizes() + .element_section(), 0, ); } @@ -1136,16 +1100,18 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .table_section, + .instrumented_code() + .instantiated_section_sizes() + .table_section(), 10 * REF_TYPE_SIZE, ); assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .element_section, + .instrumented_code() + .instantiated_section_sizes() + .element_section(), REF_TYPE_SIZE * 4, ); } @@ -1165,8 +1131,9 @@ mod tests { assert_eq!( try_new_code_from_wat(wat, Some(1024)) .unwrap() - .instantiated_section_sizes - .type_section, + .instrumented_code() + .instantiated_section_sizes() + .type_section(), 50, ); } diff --git a/core/src/program.rs b/core/src/program.rs index 4812cec8bb6..c4cdc0a3bda 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -20,12 +20,9 @@ use crate::{ ids::{MessageId, ProgramId}, - message::DispatchKind, - pages::WasmPagesAmount, reservation::GasReservationMap, }; -use alloc::collections::BTreeSet; -use primitive_types::H256; +use gprimitives::CodeId; use scale_info::{ scale::{Decode, Encode}, TypeInfo, @@ -98,11 +95,7 @@ pub struct ActiveProgram { /// Gas reservation map. pub gas_reservation_map: GasReservationMap, /// Code hash of the program. - pub code_hash: H256, - /// Set of supported dispatch kinds. - pub code_exports: BTreeSet, - /// Amount of static pages. - pub static_pages: WasmPagesAmount, + pub code_id: CodeId, /// Initialization state of the program. pub state: ProgramState, /// Block number when the program will be expired. diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index 3d1c2c2e95e..7eaa0597a71 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -24,7 +24,7 @@ use alloc::{ vec::Vec, }; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, ids::{ActorId, CodeId, ProgramId}, }; use gprimitives::{MessageId, H256}; @@ -119,6 +119,9 @@ pub trait CodesStorage: Send + Sync { fn instrumented_code(&self, runtime_id: u32, code_id: CodeId) -> Option; fn set_instrumented_code(&self, runtime_id: u32, code_id: CodeId, code: InstrumentedCode); + fn code_metadata(&self, code_id: CodeId) -> Option; + fn set_code_metadata(&self, code_id: CodeId, code_metadata: CodeMetadata); + fn code_blob_tx(&self, code_id: CodeId) -> Option; fn set_code_blob_tx(&self, code_id: CodeId, blob_tx_hash: H256); diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index da82d71f0d2..2d253e93b32 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -31,7 +31,7 @@ use ethexe_runtime_common::state::{ Allocations, DispatchStash, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, Waitlist, }; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, ids::{ActorId, CodeId, ProgramId}, memory::PageBuf, message::Payload, @@ -46,17 +46,18 @@ const LOG_TARGET: &str = "ethexe-db"; enum KeyPrefix { ProgramToCodeId = 0, InstrumentedCode = 1, - BlockStartProgramStates = 2, - BlockEndProgramStates = 3, - BlockEvents = 4, - BlockOutcome = 5, - BlockSmallMeta = 6, - CodeUpload = 7, - LatestValidBlock = 8, - BlockHeader = 9, - CodeValid = 10, - BlockStartSchedule = 11, - BlockEndSchedule = 12, + CodeMetadata = 2, + BlockStartProgramStates = 3, + BlockEndProgramStates = 4, + BlockEvents = 5, + BlockOutcome = 6, + BlockSmallMeta = 7, + CodeUpload = 8, + LatestValidBlock = 9, + BlockHeader = 10, + CodeValid = 11, + BlockStartSchedule = 12, + BlockEndSchedule = 13, } impl KeyPrefix { @@ -376,6 +377,22 @@ impl CodesStorage for Database { ); } + fn code_metadata(&self, code_id: CodeId) -> Option { + self.kv + .get(&KeyPrefix::CodeMetadata.one(code_id)) + .map(|data| { + CodeMetadata::decode(&mut data.as_slice()) + .expect("Failed to decode data into `CodeMetadata`") + }) + } + + fn set_code_metadata(&self, code_id: CodeId, code_metadata: CodeMetadata) { + self.kv.put( + &KeyPrefix::CodeMetadata.one(code_id), + code_metadata.encode(), + ); + } + fn code_blob_tx(&self, code_id: CodeId) -> Option { self.kv .get(&KeyPrefix::CodeUpload.one(code_id)) diff --git a/ethexe/processor/src/handling/mod.rs b/ethexe/processor/src/handling/mod.rs index 46e9cd7927d..d6f73232e03 100644 --- a/ethexe/processor/src/handling/mod.rs +++ b/ethexe/processor/src/handling/mod.rs @@ -83,18 +83,20 @@ impl Processor { let original_code = original_code.as_ref(); - let Some(instrumented_code) = executor.instrument(original_code)? else { + let Some((instrumented_code, code_metadata)) = executor.instrument(original_code)? else { return Ok(None); }; let code_id = self.db.set_original_code(original_code); self.db.set_instrumented_code( - instrumented_code.instruction_weights_version(), + code_metadata.instruction_weights_version(), code_id, instrumented_code, ); + self.db.set_code_metadata(code_id, code_metadata); + Ok(Some(code_id)) } } diff --git a/ethexe/processor/src/handling/run.rs b/ethexe/processor/src/handling/run.rs index 774e30b30c9..607ee91fc1f 100644 --- a/ethexe/processor/src/handling/run.rs +++ b/ethexe/processor/src/handling/run.rs @@ -121,9 +121,17 @@ async fn run_task(db: Database, executor: &mut InstanceWrapper, task: Task) { let code_id = db.program_code_id(program_id).expect("Code ID must be set"); let instrumented_code = db.instrumented_code(ethexe_runtime::VERSION, code_id); + let code_metadata = db.code_metadata(code_id); let journal = executor - .run(db, program_id, code_id, state_hash, instrumented_code) + .run( + db, + program_id, + code_id, + state_hash, + instrumented_code, + code_metadata, + ) .expect("Some error occurs while running program in instance"); result_sender.send(journal).unwrap(); diff --git a/ethexe/processor/src/host/mod.rs b/ethexe/processor/src/host/mod.rs index 822b7b9498e..265755aa92e 100644 --- a/ethexe/processor/src/host/mod.rs +++ b/ethexe/processor/src/host/mod.rs @@ -18,7 +18,10 @@ use anyhow::{anyhow, Result}; use core_processor::common::JournalNote; -use gear_core::{code::InstrumentedCode, ids::ProgramId}; +use gear_core::{ + code::{CodeMetadata, InstrumentedCode}, + ids::ProgramId, +}; use gprimitives::{CodeId, H256}; use parity_scale_codec::{Decode, Encode}; use sp_allocator::{AllocationStats, FreeingBumpHeapAllocator}; @@ -119,7 +122,7 @@ impl InstanceWrapper { pub fn instrument( &mut self, original_code: impl AsRef<[u8]>, - ) -> Result> { + ) -> Result> { self.call("instrument_code", original_code) } @@ -130,6 +133,7 @@ impl InstanceWrapper { original_code_id: CodeId, state_hash: H256, maybe_instrumented_code: Option, + maybe_code_metadata: Option, ) -> Result> { let chain_head = self.chain_head.expect("chain head must be set before run"); threads::set(db, chain_head, state_hash); @@ -139,6 +143,7 @@ impl InstanceWrapper { original_code_id, state_hash, maybe_instrumented_code, + maybe_code_metadata, ); self.call("run", arg.encode()) diff --git a/ethexe/processor/src/tests.rs b/ethexe/processor/src/tests.rs index b2afb513d00..6491bf4db3f 100644 --- a/ethexe/processor/src/tests.rs +++ b/ethexe/processor/src/tests.rs @@ -183,6 +183,8 @@ fn handle_new_code_valid() { .instrumented_code(ethexe_runtime::VERSION, code_id) .is_none()); + assert!(processor.db.code_metadata(code_id).is_none()); + let calculated_id = processor .handle_new_code(original_code.clone()) .expect("failed to call runtime api") @@ -197,15 +199,25 @@ fn handle_new_code_valid() { .expect("failed to read original code"), original_code ); + assert!( processor .db .instrumented_code(ethexe_runtime::VERSION, code_id) .expect("failed to read original code") - .code() + .bytes() .len() > original_code_len ); + + assert_eq!( + processor + .db + .code_metadata(code_id) + .expect("failed to read code metadata") + .original_code_len(), + original_code_len as u32 + ); } #[test] @@ -226,6 +238,8 @@ fn handle_new_code_invalid() { .instrumented_code(ethexe_runtime::VERSION, code_id) .is_none()); + assert!(processor.db.code_metadata(code_id).is_none()); + assert!(processor .handle_new_code(original_code.clone()) .expect("failed to call runtime api") @@ -236,6 +250,8 @@ fn handle_new_code_invalid() { .db .instrumented_code(ethexe_runtime::VERSION, code_id) .is_none()); + + assert!(processor.db.code_metadata(code_id).is_none()); } #[test] diff --git a/ethexe/runtime/common/src/lib.rs b/ethexe/runtime/common/src/lib.rs index 085f8f8351e..9a05c6ccec5 100644 --- a/ethexe/runtime/common/src/lib.rs +++ b/ethexe/runtime/common/src/lib.rs @@ -32,10 +32,10 @@ use core::{marker::PhantomData, mem::swap}; use core_processor::{ common::{ExecutableActorData, JournalNote}, configs::{BlockConfig, SyscallName}, - ContextChargedForCode, ContextChargedForInstrumentation, Ext, ProcessExecutionContext, + ContextCharged, Ext, ProcessExecutionContext, }; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, ids::ProgramId, message::{DispatchKind, IncomingDispatch, IncomingMessage, Value}, pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, @@ -52,6 +52,7 @@ use state::{ }; pub use core_processor::configs::BlockInfo; +use gear_core::code::InstrumentedCodeAndMetadata; pub use journal::Handler as JournalHandler; pub use schedule::Handler as ScheduleHandler; pub use transitions::{InBlockTransitions, NonFinalTransition}; @@ -109,6 +110,7 @@ pub fn process_next_message( program_id: ProgramId, program_state: ProgramState, instrumented_code: Option, + code_metadata: Option, code_id: CodeId, ri: &RI, ) -> Vec @@ -217,17 +219,26 @@ where let dispatch = IncomingDispatch::new(kind, incoming_message, context); - let context = match core_processor::precharge_for_program( - &block_config, - 1_000_000_000_000, - dispatch, - program_id, - ) { - Ok(dispatch) => dispatch, + let context = ContextCharged::new(program_id, dispatch, 1_000_000_000_000); + + let context = match context.charge_for_program(&block_config) { + Ok(context) => context, + Err(journal) => return journal, + }; + + let context = match context.charge_for_code_metadata(&block_config) { + Ok(context) => context, Err(journal) => return journal, }; let code = instrumented_code.expect("Instrumented code must be provided if program is active"); + let code_metadata = code_metadata.expect("Code metadata must be provided if program is active"); + + let context = + match context.charge_for_instrumented_code(&block_config, code.bytes().len() as u32) { + Ok(context) => context, + Err(journal) => return journal, + }; // TODO: support normal allocations len #4068 let allocations = active_state.allocations_hash.with_hash_or_default(|hash| { @@ -236,47 +247,43 @@ where .expect("Cannot get allocations") }); - let context = match core_processor::precharge_for_allocations( - &block_config, - context, - allocations.intervals_amount() as u32, - ) { + let context = match context + .charge_for_allocations(&block_config, allocations.intervals_amount() as u32) + { Ok(context) => context, Err(journal) => return journal, }; - let pages_map = active_state.pages_hash.with_hash_or_default(|hash| { - ri.storage() - .read_pages(hash) - .expect("Cannot get memory pages") - }); let actor_data = ExecutableActorData { allocations, - code_id, - code_exports: code.exports().clone(), - static_pages: code.static_pages(), gas_reservation_map: Default::default(), // TODO (gear_v2): deprecate it. memory_infix: active_state.memory_infix, }; - 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); - let context = match core_processor::precharge_for_module_instantiation( + let context = match context.charge_for_module_instantiation( &block_config, - context, + actor_data, code.instantiated_section_sizes(), + &code_metadata, ) { Ok(context) => context, Err(journal) => return journal, }; - let execution_context = ProcessExecutionContext::from((context, code, program_state.balance)); + let pages_map = active_state.pages_hash.with_hash_or_default(|hash| { + ri.storage() + .read_pages(hash) + .expect("Cannot get memory pages") + }); + + let execution_context = ProcessExecutionContext::new( + context, + InstrumentedCodeAndMetadata { + instrumented_code: code, + metadata: code_metadata, + }, + program_state.balance, + ); let random_data = ri.random_data(); diff --git a/ethexe/runtime/src/wasm/api/instrument.rs b/ethexe/runtime/src/wasm/api/instrument.rs index 02648293d2c..e45cfb63dd1 100644 --- a/ethexe/runtime/src/wasm/api/instrument.rs +++ b/ethexe/runtime/src/wasm/api/instrument.rs @@ -18,12 +18,12 @@ use alloc::vec::Vec; use gear_core::{ - code::{Code, CodeError, InstrumentedCode}, + code::{Code, CodeError, CodeMetadata, InstrumentedCode}, gas_metering::Schedule, }; // TODO: impl Codec for CodeError, so could be thrown to host via memory. -pub fn instrument_code(original_code: Vec) -> Option { +pub fn instrument_code(original_code: Vec) -> Option<(InstrumentedCode, CodeMetadata)> { log::debug!("Runtime::instrument_code(..)"); let schedule = Schedule::default(); @@ -33,7 +33,7 @@ pub fn instrument_code(original_code: Vec) -> Option { return None; } - let instrumented = Code::try_new( + let code = Code::try_new( original_code, // TODO: should we update it on each upgrade (?); crate::VERSION, @@ -42,17 +42,18 @@ pub fn instrument_code(original_code: Vec) -> Option { schedule.limits.data_segments_amount.into(), schedule.limits.table_number.into(), ) - .map(InstrumentedCode::from) .map_err(|e: CodeError| { log::debug!("Failed to validate or instrument code: {e:?}"); e }) .ok()?; - if instrumented.code().len() > schedule.limits.code_len as usize { + let (_, instrumented, metadata) = code.into_parts(); + + if instrumented.bytes().len() > schedule.limits.code_len as usize { log::debug!("Instrumented code exceeds size limit!"); return None; } - Some(instrumented) + Some((instrumented, metadata)) } diff --git a/ethexe/runtime/src/wasm/api/mod.rs b/ethexe/runtime/src/wasm/api/mod.rs index 109a11dea10..8f430fb10b6 100644 --- a/ethexe/runtime/src/wasm/api/mod.rs +++ b/ethexe/runtime/src/wasm/api/mod.rs @@ -43,7 +43,7 @@ extern "C" fn run(arg_ptr: i32, arg_len: i32) -> i64 { #[cfg_attr(not(target_arch = "wasm32"), allow(unused))] fn _run(arg_ptr: i32, arg_len: i32) -> i64 { - let (program_id, original_code_id, state_root, maybe_instrumented_code) = + let (program_id, original_code_id, state_root, maybe_instrumented_code, maybe_code_metadata) = Decode::decode(&mut get_slice(arg_ptr, arg_len)).unwrap(); let res = run::run( @@ -51,6 +51,7 @@ fn _run(arg_ptr: i32, arg_len: i32) -> i64 { original_code_id, state_root, maybe_instrumented_code, + maybe_code_metadata, ); return_val(res) diff --git a/ethexe/runtime/src/wasm/api/run.rs b/ethexe/runtime/src/wasm/api/run.rs index d8ee7cafb8a..3e5f41212fa 100644 --- a/ethexe/runtime/src/wasm/api/run.rs +++ b/ethexe/runtime/src/wasm/api/run.rs @@ -23,7 +23,10 @@ use crate::wasm::{ use alloc::vec::Vec; use core_processor::{common::JournalNote, configs::BlockInfo}; use ethexe_runtime_common::{process_next_message, state::Storage, RuntimeInterface}; -use gear_core::{code::InstrumentedCode, ids::ProgramId}; +use gear_core::{ + code::{CodeMetadata, InstrumentedCode}, + ids::ProgramId, +}; use gprimitives::{CodeId, H256}; pub fn run( @@ -31,6 +34,7 @@ pub fn run( original_code_id: CodeId, state_root: H256, maybe_instrumented_code: Option, + code_metadata: Option, ) -> Vec { log::debug!("You're calling 'run(..)'"); @@ -50,6 +54,7 @@ pub fn run( program_id, program_state, maybe_instrumented_code, + code_metadata, original_code_id, &ri, ); diff --git a/gcli/src/meta/mod.rs b/gcli/src/meta/mod.rs index a2d69c7845f..5d0083e4a08 100644 --- a/gcli/src/meta/mod.rs +++ b/gcli/src/meta/mod.rs @@ -23,7 +23,7 @@ mod registry; mod tests; use crate::result::{Error, Result}; -use gear_core::code::{Code, CodeAndId, InstrumentedCodeAndId, TryNewCodeConfig}; +use gear_core::code::{Code, TryNewCodeConfig}; use gmeta::{MetadataRepr, MetawasmData, TypesRepr}; use registry::LocalRegistry as _; use scale_info::{scale::Decode, PortableRegistry}; @@ -110,10 +110,11 @@ impl Meta { let code = Code::try_new_mock_const_or_no_rules( wasm.to_vec(), true, - TryNewCodeConfig::new_no_exports_check(), + TryNewCodeConfig::with_no_exports_check(), )?; - let (code, _) = InstrumentedCodeAndId::from(CodeAndId::new(code)).into_parts(); - let result = executor::call_metadata(code.code())?; + + let instrumented_code = code.into_parts().1; + let result = executor::call_metadata(instrumented_code.bytes())?; Ok(Self::Wasm(MetawasmData::decode(&mut result.as_ref())?)) } diff --git a/gcli/tests/cmd/upload.rs b/gcli/tests/cmd/upload.rs index 611c04959cb..8f8eef6602b 100644 --- a/gcli/tests/cmd/upload.rs +++ b/gcli/tests/cmd/upload.rs @@ -33,7 +33,11 @@ async fn test_command_upload_works() -> Result<()> { .signer("//Alice", None)?; let code_id = CodeId::generate(demo_new_meta::WASM_BINARY); assert!( - signer.api().code_storage(code_id).await.is_err(), + signer + .api() + .instrumented_code_storage(code_id) + .await + .is_err(), "code should not exist" ); @@ -47,7 +51,11 @@ async fn test_command_upload_works() -> Result<()> { output.stderr ); assert!( - signer.api().code_storage(code_id).await.is_ok(), + signer + .api() + .instrumented_code_storage(code_id) + .await + .is_ok(), "code should exist" ); diff --git a/gclient/src/api/calls.rs b/gclient/src/api/calls.rs index cca3fd36ae5..fdedfdb3012 100644 --- a/gclient/src/api/calls.rs +++ b/gclient/src/api/calls.rs @@ -420,18 +420,18 @@ impl GearApi { } } - let src_code_id = src_program.code_hash.0.into(); + let src_code_id = src_program.code_id.0.into(); - let src_code_len = self + let src_instrumented_code = self .0 .api() - .code_len_storage_at(src_code_id, src_block_hash) + .instrumented_code_storage_at(src_code_id, src_block_hash) .await?; - let src_code = self + let src_code_metadata = self .0 .api() - .code_storage_at(src_code_id, src_block_hash) + .code_metadata_storage_at(src_code_id, src_block_hash) .await?; // Apply data to the target program @@ -458,13 +458,13 @@ impl GearApi { dest_node_api .0 .storage - .set_code_storage(src_code_id, &src_code) + .set_instrumented_code_storage(src_code_id, &src_instrumented_code) .await?; dest_node_api .0 .storage - .set_code_len_storage(src_code_id, src_code_len) + .set_code_metadata_storage(src_code_id, &src_code_metadata) .await?; dest_node_api diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 4d3c64a6c0b..11749c3432d 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -596,12 +596,6 @@ pub mod runtime_types { } } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] - pub struct CodeMetadata { - pub author: ::subxt::ext::subxt_core::utils::H256, - #[codec(compact)] - pub block_number: ::core::primitive::u32, - } - #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub enum GasMultiplier<_0, _1> { #[codec(index = 0)] ValuePerGas(_0), @@ -638,17 +632,36 @@ pub mod runtime_types { Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, )] pub struct InstrumentedCode { - pub code: ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>, + pub bytes: ::subxt::ext::subxt_core::alloc::vec::Vec<::core::primitive::u8>, + pub instantiated_section_sizes: + runtime_types::gear_core::code::instrumented::InstantiatedSectionSizes, + } + } + pub mod metadata { + use super::runtime_types; + #[derive( + Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, + )] + pub struct CodeMetadata { pub original_code_len: ::core::primitive::u32, + pub instrumented_code_len: ::core::primitive::u32, pub exports: ::subxt::ext::subxt_core::alloc::vec::Vec< runtime_types::gear_core::message::DispatchKind, >, pub static_pages: runtime_types::gear_core::pages::PagesAmount, pub stack_end: ::core::option::Option, - pub instantiated_section_sizes: - runtime_types::gear_core::code::instrumented::InstantiatedSectionSizes, - pub version: ::core::primitive::u32, + pub instrumentation_status: + runtime_types::gear_core::code::metadata::InstrumentationStatus, + } + #[derive( + Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, + )] + pub enum InstrumentationStatus { + #[codec(index = 0)] + Instrumented(::core::primitive::u32), + #[codec(index = 1)] + InstrumentationFailed(::core::primitive::u32), } } } @@ -846,11 +859,7 @@ pub mod runtime_types { runtime_types::gprimitives::ReservationId, runtime_types::gear_core::reservation::GasReservationSlot, >, - pub code_hash: ::subxt::ext::subxt_core::utils::H256, - pub code_exports: ::subxt::ext::subxt_core::alloc::vec::Vec< - runtime_types::gear_core::message::DispatchKind, - >, - pub static_pages: runtime_types::gear_core::pages::PagesAmount, + pub code_id: runtime_types::gprimitives::CodeId, pub state: runtime_types::gear_core::program::ProgramState, pub expiration_block: _0, } @@ -2757,7 +2766,7 @@ pub mod runtime_types { runtime_types::gear_core::pages::Page, runtime_types::gear_core::memory::PageBuf, >, - pub code_hash: ::subxt::ext::subxt_core::utils::H256, + pub code_hash: runtime_types::gprimitives::CodeId, } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub enum ProgramState { @@ -9359,10 +9368,9 @@ pub mod storage { } #[doc = "Storage of pallet `GearProgram`."] pub enum GearProgramStorage { - CodeStorage, - CodeLenStorage, + InstrumentedCodeStorage, OriginalCodeStorage, - MetadataStorage, + CodeMetadataStorage, AllocationsStorage, ProgramStorage, MemoryPages, @@ -9371,10 +9379,9 @@ pub mod storage { const PALLET: &'static str = "GearProgram"; fn storage_name(&self) -> &'static str { match self { - Self::CodeStorage => "CodeStorage", - Self::CodeLenStorage => "CodeLenStorage", + Self::InstrumentedCodeStorage => "InstrumentedCodeStorage", Self::OriginalCodeStorage => "OriginalCodeStorage", - Self::MetadataStorage => "MetadataStorage", + Self::CodeMetadataStorage => "CodeMetadataStorage", Self::AllocationsStorage => "AllocationsStorage", Self::ProgramStorage => "ProgramStorage", Self::MemoryPages => "MemoryPages", diff --git a/gsdk/src/signer/storage.rs b/gsdk/src/signer/storage.rs index 3811d7cc224..550668548df 100644 --- a/gsdk/src/signer/storage.rs +++ b/gsdk/src/signer/storage.rs @@ -22,7 +22,7 @@ use crate::{ runtime_types::{ frame_system::pallet::Call, gear_core::{ - code::instrumented::InstrumentedCode, + code::{instrumented::InstrumentedCode, metadata::CodeMetadata}, program::{ActiveProgram, Program}, }, pallet_gear_bank::pallet::BankAccount, @@ -117,22 +117,30 @@ impl SignerStorage { // pallet-gear-program impl SignerStorage { - /// Writes `InstrumentedCode` length into storage at `CodeId` - pub async fn set_code_len_storage(&self, code_id: CodeId, code_len: u32) -> EventsResult { + /// Writes `InstrumentedCode` into storage at `CodeId` + pub async fn set_instrumented_code_storage( + &self, + code_id: CodeId, + code: &InstrumentedCode, + ) -> EventsResult { let addr = Api::storage( - GearProgramStorage::CodeLenStorage, + GearProgramStorage::InstrumentedCodeStorage, vec![Value::from_bytes(code_id)], ); - self.set_storage(&[(addr, code_len)]).await + self.set_storage(&[(addr, code)]).await } - /// Writes `InstrumentedCode` into storage at `CodeId` - pub async fn set_code_storage(&self, code_id: CodeId, code: &InstrumentedCode) -> EventsResult { + /// Writes `CodeMetadata` into storage at `CodeId` + pub async fn set_code_metadata_storage( + &self, + code_id: CodeId, + code_metadata: &CodeMetadata, + ) -> EventsResult { let addr = Api::storage( - GearProgramStorage::CodeStorage, + GearProgramStorage::CodeMetadataStorage, vec![Value::from_bytes(code_id)], ); - self.set_storage(&[(addr, code)]).await + self.set_storage(&[(addr, code_metadata)]).await } /// Writes `GearPages` into storage at `program_id` diff --git a/gsdk/src/storage.rs b/gsdk/src/storage.rs index 4096bdc8d0c..280228d4f05 100644 --- a/gsdk/src/storage.rs +++ b/gsdk/src/storage.rs @@ -23,7 +23,7 @@ use crate::{ frame_system::{AccountInfo, EventRecord}, gear_common::storage::primitives::Interval, gear_core::{ - code::instrumented::InstrumentedCode, + code::{instrumented::InstrumentedCode, metadata::CodeMetadata}, message::user::UserStoredMessage, program::{ActiveProgram, Program}, }, @@ -258,29 +258,29 @@ impl Api { self.fetch_storage_at(&addr, block_hash).await } - /// Get `InstrumentedCode` by its `CodeId at specified block. + /// Get `InstrumentedCode` by its `CodeId` at specified block. #[storage_fetch] - pub async fn code_storage_at( + pub async fn instrumented_code_storage_at( &self, code_id: CodeId, block_hash: Option, ) -> Result { let addr = Self::storage( - GearProgramStorage::CodeStorage, + GearProgramStorage::InstrumentedCodeStorage, vec![Value::from_bytes(code_id)], ); self.fetch_storage_at(&addr, block_hash).await } - /// Get `InstrumentedCode` length by its `CodeId` at specified block. + /// Get `CodeMetadata` by its `CodeId` at specified block. #[storage_fetch] - pub async fn code_len_storage_at( + pub async fn code_metadata_storage_at( &self, code_id: CodeId, block_hash: Option, - ) -> Result { + ) -> Result { let addr = Self::storage( - GearProgramStorage::CodeLenStorage, + GearProgramStorage::CodeMetadataStorage, vec![Value::from_bytes(code_id)], ); self.fetch_storage_at(&addr, block_hash).await diff --git a/gsdk/tests/rpc.rs b/gsdk/tests/rpc.rs index 8ae224973ce..8ea8bf10374 100644 --- a/gsdk/tests/rpc.rs +++ b/gsdk/tests/rpc.rs @@ -329,7 +329,7 @@ async fn test_original_code_storage() -> Result<()> { let block_hash = rpc.latest_finalized_block_ref().await?.hash(); let code = signer .api() - .original_code_storage_at(program.code_hash.0.into(), Some(block_hash)) + .original_code_storage_at(program.code_id.0.into(), Some(block_hash)) .await?; assert_eq!( diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 04bd511ae53..b28076aa7ab 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -47,7 +47,7 @@ use crate::{ use core_processor::{ common::*, configs::{BlockConfig, TESTS_MAX_PAGES_NUMBER}, - ContextChargedForInstrumentation, ContextChargedForProgram, Ext, + Ext, }; use gear_common::{ auxiliary::{ @@ -60,7 +60,7 @@ use gear_common::{ LockId, Origin, }; use gear_core::{ - code::{Code, CodeAndId, InstrumentedCode, InstrumentedCodeAndId, TryNewCodeConfig}, + code::{Code, CodeAndId, InstrumentedCode, TryNewCodeConfig}, gas_metering::{DbWeights, RentWeights, Schedule}, ids::{prelude::*, CodeId, MessageId, ProgramId, ReservationId}, memory::PageBuf, diff --git a/gtest/src/manager/block_exec.rs b/gtest/src/manager/block_exec.rs index 07361be7360..7ab904a75ee 100644 --- a/gtest/src/manager/block_exec.rs +++ b/gtest/src/manager/block_exec.rs @@ -16,8 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use core_processor::SuccessfulDispatchResultKind; -use gear_core::{gas::GasCounter, str::LimitedStr}; +use core_processor::{ + common::SuccessfulDispatchResultKind, ContextCharged, ForProgram, ProcessExecutionContext, +}; +use gear_core::{ + code::{CodeMetadata, InstrumentedCodeAndMetadata}, + gas::GasCounter, + str::LimitedStr, +}; use task::get_maximum_task_gas; use super::*; @@ -285,13 +291,14 @@ impl ExtManager { let balance = Accounts::reducible_balance(destination_id); - let context = match core_processor::precharge_for_program( - block_config, - self.gas_allowance.0, - dispatch.into_incoming(gas_limit), + let context = ContextCharged::new( destination_id, - ) { - Ok(dispatch) => dispatch, + dispatch.into_incoming(gas_limit), + self.gas_allowance.0, + ); + + let context = match context.charge_for_program(block_config) { + Ok(context) => context, Err(journal) => { core_processor::handle_journal(journal, self); return; @@ -301,8 +308,8 @@ impl ExtManager { enum Exec { Notes(Vec), ExecutableActor( - (ExecutableActorData, InstrumentedCode), - ContextChargedForProgram, + (ExecutableActorData, InstrumentedCode, CodeMetadata), + ContextCharged, ), } @@ -370,10 +377,11 @@ impl ExtManager { let journal = match exec { Exec::Notes(journal) => journal, - Exec::ExecutableActor((actor_data, instrumented_code), context) => self + Exec::ExecutableActor((actor_data, instrumented_code, code_metadata), context) => self .process_executable_actor( actor_data, instrumented_code, + code_metadata, block_config, context, balance, @@ -387,57 +395,51 @@ impl ExtManager { &self, actor_data: ExecutableActorData, instrumented_code: InstrumentedCode, + code_metadata: CodeMetadata, block_config: &BlockConfig, - context: ContextChargedForProgram, + context: ContextCharged, balance: Value, ) -> Vec { - let context = match core_processor::precharge_for_allocations( + let context = match context.charge_for_code_metadata(block_config) { + Ok(context) => context, + Err(journal) => return journal, + }; + + let context = match context + .charge_for_instrumented_code(block_config, instrumented_code.bytes().len() as u32) + { + Ok(context) => context, + Err(journal) => return journal, + }; + + let context = match context.charge_for_allocations( block_config, - context, actor_data.allocations.intervals_amount() as u32, ) { Ok(context) => context, - Err(journal) => { - return journal; - } + 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 code_id = context.actor_data().code_id; - let code_len_bytes = self - .read_code(code_id) - .map(|code| code.len().try_into().expect("too big code len")) - .unwrap_or_else(|| unreachable!("can't find code for the existing code id {code_id}")); - let context = - match core_processor::precharge_for_code(block_config, context, code_len_bytes) { - Ok(context) => context, - Err(journal) => { - return journal; - } - }; - - let context = match core_processor::precharge_for_module_instantiation( + let context = match context.charge_for_module_instantiation( block_config, - // No re-instrumentation - ContextChargedForInstrumentation::from(context), + actor_data, instrumented_code.instantiated_section_sizes(), + &code_metadata, ) { Ok(context) => context, - Err(journal) => { - return journal; - } + Err(journal) => return journal, }; core_processor::process::>( block_config, - (context, instrumented_code, balance).into(), + ProcessExecutionContext::new( + context, + InstrumentedCodeAndMetadata { + instrumented_code, + metadata: code_metadata, + }, + balance, + ), self.random_data.clone(), ) .unwrap_or_else(|e| unreachable!("core-processor logic violated: {}", e)) @@ -446,14 +448,14 @@ impl ExtManager { fn process_mock( &self, mock: &mut Box, - context: ContextChargedForProgram, + context: ContextCharged, ) -> Vec { enum Mocked { Reply(Option>), Signal, } - let (dispatch, program_id, gas_counter) = context.into_inner(); + let (program_id, dispatch, gas_counter, _) = context.into_parts(); let payload = dispatch.payload_bytes().to_vec(); let response = match dispatch.kind() { diff --git a/gtest/src/manager/journal.rs b/gtest/src/manager/journal.rs index 07eefcda2ac..f1b92d82a00 100644 --- a/gtest/src/manager/journal.rs +++ b/gtest/src/manager/journal.rs @@ -296,13 +296,13 @@ impl JournalHandler for ExtManager { if let Some(code) = self.opt_binaries.get(&code_id).cloned() { for (init_message_id, candidate_id) in candidates { if !Actors::contains_key(candidate_id) { - let (instrumented, _) = + let (_, instrumented_code_and_metadata) = ProgramBuilder::build_instrumented_code_and_id(code.clone()); self.store_new_actor( candidate_id, Program::Genuine(GenuineProgram { - code: instrumented, - code_id, + code: instrumented_code_and_metadata.instrumented_code, + code_metadata: instrumented_code_and_metadata.metadata, allocations: Default::default(), pages_data: Default::default(), gas_reservation_map: Default::default(), diff --git a/gtest/src/manager/memory.rs b/gtest/src/manager/memory.rs index 643d6d82404..5ebad3e50df 100644 --- a/gtest/src/manager/memory.rs +++ b/gtest/src/manager/memory.rs @@ -34,10 +34,11 @@ impl ExtManager { } })?; - if let Some((data, code)) = executable_actor_data { + if let Some((data, code, code_metadata)) = executable_actor_data { core_processor::informational::execute_for_reply::, _>( String::from("state"), code, + code_metadata, Some(data.allocations), Some((*program_id, Default::default())), payload, @@ -67,13 +68,12 @@ impl ExtManager { let mapping_code = Code::try_new_mock_const_or_no_rules( wasm, true, - TryNewCodeConfig::new_no_exports_check(), + TryNewCodeConfig::with_no_exports_check(), ) .map_err(|_| TestError::Instrumentation)?; - let mapping_code = InstrumentedCodeAndId::from(CodeAndId::new(mapping_code)) - .into_parts() - .0; + let (_, mapping_code, code_metadata) = + CodeAndId::new(mapping_code).into_parts().0.into_parts(); let mut mapping_code_payload = args.unwrap_or_default(); mapping_code_payload.append(&mut self.read_state_bytes(payload, program_id)?); @@ -81,6 +81,7 @@ impl ExtManager { core_processor::informational::execute_for_reply::, _>( String::from(fn_name), mapping_code, + code_metadata, None, None, mapping_code_payload, diff --git a/gtest/src/program.rs b/gtest/src/program.rs index c0478087f98..a5d55035c42 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -26,7 +26,7 @@ use crate::{ }; use codec::{Codec, Decode, Encode}; use gear_core::{ - code::{Code, CodeAndId, InstrumentedCode, InstrumentedCodeAndId}, + code::{Code, CodeAndId, InstrumentedCodeAndMetadata}, gas_metering::Schedule, ids::{prelude::*, CodeId, MessageId, ProgramId}, message::{Dispatch, DispatchKind, Message}, @@ -331,7 +331,8 @@ impl ProgramBuilder { .id .unwrap_or_else(|| system.0.borrow_mut().free_id_nonce().into()); - let (code, code_id) = Self::build_instrumented_code_and_id(self.code.clone()); + let (code_id, instrumented_code_and_metadata) = + Self::build_instrumented_code_and_id(self.code.clone()); system.0.borrow_mut().store_new_code(code_id, self.code); if let Some(metadata) = self.meta { @@ -346,8 +347,8 @@ impl ProgramBuilder { system, id, InnerProgram::Genuine(GenuineProgram { - code, - code_id, + code: instrumented_code_and_metadata.instrumented_code, + code_metadata: instrumented_code_and_metadata.metadata, allocations: Default::default(), pages_data: Default::default(), gas_reservation_map: Default::default(), @@ -357,7 +358,7 @@ impl ProgramBuilder { pub(crate) fn build_instrumented_code_and_id( original_code: Vec, - ) -> (InstrumentedCode, CodeId) { + ) -> (CodeId, InstrumentedCodeAndMetadata) { let schedule = Schedule::default(); let code = Code::try_new( original_code, @@ -369,7 +370,9 @@ impl ProgramBuilder { ) .expect("Failed to create Program from provided code"); - InstrumentedCodeAndId::from(CodeAndId::new(code)).into_parts() + let (code, code_id) = CodeAndId::new(code).into_parts(); + + (code_id, code.into()) } } diff --git a/gtest/src/state/actors.rs b/gtest/src/state/actors.rs index a95bc0b712e..5f40258cffe 100644 --- a/gtest/src/state/actors.rs +++ b/gtest/src/state/actors.rs @@ -21,9 +21,9 @@ use std::{cell::RefCell, collections::BTreeMap, fmt}; use core_processor::common::ExecutableActorData; -use gear_common::{CodeId, GearPage, MessageId, PageBuf, ProgramId}; +use gear_common::{GearPage, MessageId, PageBuf, ProgramId}; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, pages::{numerated::tree::IntervalsTree, WasmPage}, reservation::GasReservationMap, }; @@ -200,7 +200,7 @@ impl TestActor { // Gets a new executable actor derived from the inner program. pub(crate) fn get_executable_actor_data( &self, - ) -> Option<(ExecutableActorData, InstrumentedCode)> { + ) -> Option<(ExecutableActorData, InstrumentedCode, CodeMetadata)> { self.genuine_program() .map(GenuineProgram::executable_actor_data) } @@ -208,25 +208,25 @@ impl TestActor { #[derive(Debug, Clone)] pub(crate) struct GenuineProgram { - pub code_id: CodeId, pub code: InstrumentedCode, + pub code_metadata: CodeMetadata, pub allocations: IntervalsTree, pub pages_data: BTreeMap, pub gas_reservation_map: GasReservationMap, } impl GenuineProgram { - pub(crate) fn executable_actor_data(&self) -> (ExecutableActorData, InstrumentedCode) { + pub(crate) fn executable_actor_data( + &self, + ) -> (ExecutableActorData, InstrumentedCode, CodeMetadata) { ( ExecutableActorData { allocations: self.allocations.clone(), - code_id: self.code_id, - code_exports: self.code.exports().clone(), - static_pages: self.code.static_pages(), gas_reservation_map: self.gas_reservation_map.clone(), memory_infix: Default::default(), }, self.code.clone(), + self.code_metadata.clone(), ) } } diff --git a/pallets/gear-builtin/src/lib.rs b/pallets/gear-builtin/src/lib.rs index 9e2fc0c61f3..aaadda747ab 100644 --- a/pallets/gear-builtin/src/lib.rs +++ b/pallets/gear-builtin/src/lib.rs @@ -55,9 +55,11 @@ use alloc::{ }; use core::marker::PhantomData; use core_processor::{ - common::{ActorExecutionErrorReplyReason, DispatchResult, JournalNote, TrapExplanation}, - process_execution_error, process_success, SuccessfulDispatchResultKind, - SystemReservationContext, + common::{ + ActorExecutionErrorReplyReason, DispatchResult, JournalNote, SuccessfulDispatchResultKind, + TrapExplanation, + }, + process_execution_error, process_success, SystemReservationContext, }; use frame_support::dispatch::extract_actual_weight; use gear_core::{ diff --git a/pallets/gear-debug/src/lib.rs b/pallets/gear-debug/src/lib.rs index a2133c0fdbc..0001c728e94 100644 --- a/pallets/gear-debug/src/lib.rs +++ b/pallets/gear-debug/src/lib.rs @@ -37,7 +37,7 @@ mod tests; #[frame_support::pallet] pub mod pallet { use super::*; - use common::{self, storage::*, CodeStorage, Origin, ProgramStorage}; + use common::{self, storage::*, CodeId, CodeStorage, Origin, ProgramStorage}; use core::fmt; use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; @@ -98,7 +98,7 @@ pub mod pallet { pub struct ProgramInfo { pub static_pages: WasmPagesAmount, pub persistent_pages: BTreeMap, - pub code_hash: H256, + pub code_hash: CodeId, } impl fmt::Debug for ProgramInfo { @@ -222,8 +222,8 @@ pub mod pallet { } } }; - let static_pages = match T::CodeStorage::get_code(active.code_hash.cast()) { - Some(code) => code.static_pages(), + let static_pages = match T::CodeStorage::get_code_metadata(active.code_id) { + Some(code_metadata) => code_metadata.static_pages(), None => 0.into(), }; let persistent_pages = @@ -235,7 +235,7 @@ pub mod pallet { ProgramState::Active(ProgramInfo { static_pages, persistent_pages, - code_hash: active.code_hash, + code_hash: active.code_id, }) }, } diff --git a/pallets/gear-debug/src/tests/mod.rs b/pallets/gear-debug/src/tests/mod.rs index d30de8332bf..21383a6a97e 100644 --- a/pallets/gear-debug/src/tests/mod.rs +++ b/pallets/gear-debug/src/tests/mod.rs @@ -59,10 +59,10 @@ fn vec() { let code_id = CodeId::generate(WASM_BINARY); - let code = ::CodeStorage::get_code(code_id) - .expect("code should be in the storage"); + let code_metadata = ::CodeStorage::get_code_metadata(code_id) + .expect("code metadata should be in the storage"); - let static_pages = code.static_pages(); + let static_pages = code_metadata.static_pages(); assert_ok!(Gear::send_message( RuntimeOrigin::signed(1), @@ -90,7 +90,7 @@ fn vec() { let crate::ProgramState::Active(ref program_info) = program_details.state else { panic!("Inactive program") }; - assert_eq!(program_info.code_hash, code_id.into_origin()); + assert_eq!(program_info.code_hash, code_id); let pages = program_info .persistent_pages @@ -169,7 +169,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_1), + code_hash: utils::h256_code_hash(&code_1).into(), }), }], }) @@ -200,7 +200,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_2), + code_hash: utils::h256_code_hash(&code_2).into(), }), }, crate::ProgramDetails { @@ -208,7 +208,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_1), + code_hash: utils::h256_code_hash(&code_1).into(), }), }, ], @@ -278,7 +278,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_2), + code_hash: utils::h256_code_hash(&code_2).into(), }), }, crate::ProgramDetails { @@ -286,7 +286,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_1), + code_hash: utils::h256_code_hash(&code_1).into(), }), }, ], @@ -307,7 +307,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_2), + code_hash: utils::h256_code_hash(&code_2).into(), }), }, crate::ProgramDetails { @@ -315,7 +315,7 @@ fn debug_mode_works() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: Default::default(), - code_hash: utils::h256_code_hash(&code_1), + code_hash: utils::h256_code_hash(&code_1).into(), }), }, ], @@ -549,7 +549,7 @@ fn check_not_allocated_pages() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages: 0.into(), persistent_pages: persistent_pages.clone(), - code_hash: h256_code_hash(&code), + code_hash: h256_code_hash(&code).into(), }), }], }) @@ -580,7 +580,7 @@ fn check_not_allocated_pages() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages: 0.into(), persistent_pages: persistent_pages.clone(), - code_hash: h256_code_hash(&code), + code_hash: h256_code_hash(&code).into(), }), }], }) @@ -783,7 +783,7 @@ fn check_changed_pages_in_storage() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages: persistent_pages.clone(), - code_hash: h256_code_hash(&code), + code_hash: h256_code_hash(&code).into(), }), }], }) @@ -820,7 +820,7 @@ fn check_changed_pages_in_storage() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages, persistent_pages, - code_hash: h256_code_hash(&code), + code_hash: h256_code_hash(&code).into(), }), }], }) @@ -906,7 +906,7 @@ fn check_gear_stack_end() { state: crate::ProgramState::Active(crate::ProgramInfo { static_pages: 4.into(), persistent_pages, - code_hash: utils::h256_code_hash(&code), + code_hash: utils::h256_code_hash(&code).into(), }), }], }) diff --git a/pallets/gear-program/src/lib.rs b/pallets/gear-program/src/lib.rs index 23a7cfdc6f5..f62d0250218 100644 --- a/pallets/gear-program/src/lib.rs +++ b/pallets/gear-program/src/lib.rs @@ -147,7 +147,7 @@ pub mod pallet_tests; #[frame_support::pallet] pub mod pallet { use super::*; - use common::{scheduler::*, storage::*, CodeMetadata}; + use common::{scheduler::*, storage::*}; use frame_support::{ pallet_prelude::*, storage::{Key, PrefixIterator}, @@ -156,7 +156,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use gear_core::{ - code::InstrumentedCode, + code::{CodeMetadata, InstrumentedCode}, ids::{CodeId, ProgramId}, memory::PageBuf, pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, @@ -166,7 +166,7 @@ pub mod pallet { use sp_runtime::DispatchError; /// The current storage version. - pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(10); + pub(crate) const PROGRAM_STORAGE_VERSION: StorageVersion = StorageVersion::new(13); #[pallet::config] pub trait Config: frame_system::Config { @@ -217,25 +217,16 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] - pub(crate) type CodeStorage = StorageMap<_, Identity, CodeId, InstrumentedCode>; + pub(crate) type InstrumentedCodeStorage = + StorageMap<_, Identity, CodeId, InstrumentedCode>; common::wrap_storage_map!( - storage: CodeStorage, - name: CodeStorageWrap, + storage: InstrumentedCodeStorage, + name: InstrumentedCodeStorageWrap, key: CodeId, value: InstrumentedCode ); - #[pallet::storage] - pub(crate) type CodeLenStorage = StorageMap<_, Identity, CodeId, u32>; - - common::wrap_storage_map!( - storage: CodeLenStorage, - name: CodeLenStorageWrap, - key: CodeId, - value: u32 - ); - #[pallet::storage] #[pallet::unbounded] pub(crate) type OriginalCodeStorage = StorageMap<_, Identity, CodeId, Vec>; @@ -249,11 +240,11 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] - pub(crate) type MetadataStorage = StorageMap<_, Identity, CodeId, CodeMetadata>; + pub(crate) type CodeMetadataStorage = StorageMap<_, Identity, CodeId, CodeMetadata>; common::wrap_storage_map!( - storage: MetadataStorage, - name: MetadataStorageWrap, + storage: CodeMetadataStorage, + name: CodeMetadataStorageWrap, key: CodeId, value: CodeMetadata ); @@ -304,10 +295,9 @@ pub mod pallet { ); impl common::CodeStorage for pallet::Pallet { - type InstrumentedCodeStorage = CodeStorageWrap; - type InstrumentedLenStorage = CodeLenStorageWrap; - type MetadataStorage = MetadataStorageWrap; + type InstrumentedCodeStorage = InstrumentedCodeStorageWrap; type OriginalCodeStorage = OriginalCodeStorageWrap; + type CodeMetadataStorage = CodeMetadataStorageWrap; } impl common::ProgramStorage for pallet::Pallet { diff --git a/pallets/gear-program/src/migrations/add_section_sizes.rs b/pallets/gear-program/src/migrations/add_section_sizes.rs deleted file mode 100644 index 5e258e1d889..00000000000 --- a/pallets/gear-program/src/migrations/add_section_sizes.rs +++ /dev/null @@ -1,246 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2024 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use frame_support::{ - traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, - weights::Weight, -}; -use sp_std::marker::PhantomData; -#[cfg(feature = "try-runtime")] -use { - frame_support::ensure, - sp_runtime::{ - codec::{Decode, Encode}, - TryRuntimeError, - }, - sp_std::vec::Vec, -}; - -use crate::{CodeStorage, Config, Pallet}; -use gear_core::code::{InstantiatedSectionSizes, InstrumentedCode}; - -const MIGRATE_FROM_VERSION: u16 = 8; -const MIGRATE_TO_VERSION: u16 = 9; -const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 10; - -pub struct AddSectionSizesMigration(PhantomData); - -impl OnRuntimeUpgrade for AddSectionSizesMigration { - fn on_runtime_upgrade() -> Weight { - let onchain = Pallet::::on_chain_storage_version(); - - // 1 read for onchain storage version - let mut weight = T::DbWeight::get().reads(1); - let mut counter = 0; - - 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:?}."); - - CodeStorage::::translate(|_, code: v8::InstrumentedCode| { - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - counter += 1; - - // The actual section sizes will be calculated on the next program re-instrumentation. - let section_sizes = InstantiatedSectionSizes::EMPTY; - - let code = unsafe { - InstrumentedCode::new_unchecked( - code.code, - code.original_code_len, - code.exports, - code.static_pages, - code.stack_end, - section_sizes, - code.version, - ) - }; - - Some(code) - }); - - // Put new storage version - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - - update_to.put::>(); - - log::info!("✅ Successfully migrated storage. {counter} codes have been migrated"); - } 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(v8::CodeStorage::::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 = CodeStorage::::iter_keys().count() as u64; - ensure!(old_count == count, "incorrect count of elements"); - } - - Ok(()) - } -} - -mod v8 { - use gear_core::{ - message::DispatchKind, - pages::{WasmPage, WasmPagesAmount}, - }; - use sp_runtime::{ - codec::{Decode, Encode}, - scale_info::TypeInfo, - }; - use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; - - #[derive(Clone, Debug, Decode, Encode, TypeInfo)] - pub struct InstrumentedCode { - pub code: Vec, - pub original_code_len: u32, - pub exports: BTreeSet, - pub static_pages: WasmPagesAmount, - pub stack_end: Option, - pub version: u32, - } - - #[cfg(feature = "try-runtime")] - use { - crate::{Config, Pallet}, - frame_support::{ - storage::types::StorageMap, - traits::{PalletInfo, StorageInstance}, - Identity, - }, - gear_core::ids::CodeId, - sp_std::marker::PhantomData, - }; - - #[cfg(feature = "try-runtime")] - pub struct CodeStoragePrefix(PhantomData); - - #[cfg(feature = "try-runtime")] - impl StorageInstance for CodeStoragePrefix { - const STORAGE_PREFIX: &'static str = "CodeStorage"; - - 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 CodeStorage = StorageMap, Identity, CodeId, InstrumentedCode>; -} - -#[cfg(test)] -#[cfg(feature = "try-runtime")] -mod test { - use super::*; - use crate::mock::*; - use frame_support::traits::StorageVersion; - use gear_core::{ids::CodeId, message::DispatchKind}; - use sp_runtime::traits::Zero; - - fn wat2wasm(s: &str) -> Vec { - wabt::Wat2Wasm::new().convert(s).unwrap().as_ref().to_vec() - } - - #[test] - fn add_section_sizes_works() { - new_test_ext().execute_with(|| { - StorageVersion::new(MIGRATE_FROM_VERSION).put::(); - - let wat = r#" - (module - (import "env" "memory" (memory 3)) - (data (i32.const 0x20000) "gear") - (data (i32.const 0x20001) "gear") - (type (;36;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32))) - (func $init) - (export "init" (func $init)) - (func $sum (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add - ) - (global (mut i32) (i32.const 0)) - (global (mut i32) (i32.const 0)) - (global (mut i64) (i64.const 0)) - (table 10 10 funcref) - (elem (i32.const 1) 0 0 0 0) - ) - "#; - - let code = v8::InstrumentedCode { - code: wat2wasm(wat), - original_code_len: 100, - exports: vec![DispatchKind::Init].into_iter().collect(), - static_pages: 1.into(), - stack_end: None, - version: 1, - }; - - v8::CodeStorage::::insert(CodeId::from(1u64), code.clone()); - - let state = AddSectionSizesMigration::::pre_upgrade().unwrap(); - let w = AddSectionSizesMigration::::on_runtime_upgrade(); - assert!(!w.is_zero()); - AddSectionSizesMigration::::post_upgrade(state).unwrap(); - - let new_code = CodeStorage::::get(CodeId::from(1u64)).unwrap(); - assert_eq!(new_code.code(), code.code.as_slice()); - assert_eq!(new_code.original_code_len(), code.original_code_len); - assert_eq!(new_code.exports(), &code.exports); - assert_eq!(new_code.static_pages(), code.static_pages); - assert_eq!(new_code.instruction_weights_version(), code.version); - assert_eq!(new_code.stack_end(), None); - - // The actual section sizes will be calculated on the program re-instrumentation - assert_eq!(new_code.instantiated_section_sizes(), &InstantiatedSectionSizes::EMPTY); - }); - } -} diff --git a/pallets/gear-program/src/migrations/mod.rs b/pallets/gear-program/src/migrations/mod.rs index e58d61d163a..5476dbd6a2c 100644 --- a/pallets/gear-program/src/migrations/mod.rs +++ b/pallets/gear-program/src/migrations/mod.rs @@ -16,5 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pub mod add_section_sizes; -pub mod allocations; +// pub mod add_section_sizes; +// pub mod allocations; + +pub mod v11_code_metadata_delete_migration; +pub mod v12_program_code_id_migration; +pub mod v13_split_instrumented_code_migration; diff --git a/pallets/gear-program/src/migrations/v11_code_metadata_delete_migration.rs b/pallets/gear-program/src/migrations/v11_code_metadata_delete_migration.rs new file mode 100644 index 00000000000..7c424ec580d --- /dev/null +++ b/pallets/gear-program/src/migrations/v11_code_metadata_delete_migration.rs @@ -0,0 +1,179 @@ +// 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::{Config, Pallet}; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; +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 = 10; +const MIGRATE_TO_VERSION: u16 = 11; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 13; + +pub struct MigrateRemoveCodeMetadata(PhantomData); + +impl OnRuntimeUpgrade for MigrateRemoveCodeMetadata { + fn on_runtime_upgrade() -> Weight { + // 1 read for onchain storage version + let mut weight = T::DbWeight::get().reads(1); + + let onchain = Pallet::::on_chain_storage_version(); + + 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:?}."); + + v10::MetadataStorageNonce::::kill(); + + // killing a storage: one write + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Put new storage version + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + update_to.put::>(); + + log::info!("✅ Successfully migrated 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(v10::MetadataStorage::::iter().count() as u64) + } else { + None + }; + + Ok(res.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + Option::::decode(&mut state.as_ref()) + .map_err(|_| "`pre_upgrade` provided an invalid state")?; + + Ok(()) + } +} + +mod v10 { + use primitive_types::H256; + use sp_runtime::{ + codec::{self, Decode, Encode}, + scale_info::{self, TypeInfo}, + }; + use sp_std::prelude::*; + + #[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] + #[codec(crate = codec)] + #[scale_info(crate = scale_info)] + pub struct CodeMetadata { + pub author: H256, + #[codec(compact)] + pub block_number: u32, + } + + use crate::{Config, Pallet}; + use frame_support::{ + storage::types::StorageValue, + traits::{PalletInfo, StorageInstance}, + }; + + use sp_std::marker::PhantomData; + + #[cfg(feature = "try-runtime")] + use { + frame_support::{storage::types::StorageMap, Identity}, + gear_core::ids::CodeId, + }; + + pub type MetadataStorageNonce = StorageValue, u32>; + + pub struct MetadataStoragePrefix(PhantomData); + + impl StorageInstance for MetadataStoragePrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + + const STORAGE_PREFIX: &'static str = "MetadataStorage"; + } + + #[cfg(feature = "try-runtime")] + pub type MetadataStorage = + StorageMap, Identity, CodeId, CodeMetadata>; +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use frame_support::traits::StorageVersion; + use sp_runtime::traits::Zero; + + #[test] + fn v11_metadata_into_attribution_migration_works() { + let _ = env_logger::try_init(); + + new_test_ext().execute_with(|| { + StorageVersion::new(MIGRATE_FROM_VERSION).put::(); + + let state = MigrateRemoveCodeMetadata::::pre_upgrade().unwrap(); + let w = MigrateRemoveCodeMetadata::::on_runtime_upgrade(); + assert!(!w.is_zero()); + MigrateRemoveCodeMetadata::::post_upgrade(state).unwrap(); + + assert_eq!(StorageVersion::get::(), MIGRATE_TO_VERSION); + }) + } +} diff --git a/pallets/gear-program/src/migrations/allocations.rs b/pallets/gear-program/src/migrations/v12_program_code_id_migration.rs similarity index 68% rename from pallets/gear-program/src/migrations/allocations.rs rename to pallets/gear-program/src/migrations/v12_program_code_id_migration.rs index 3fde976a1c5..e9acc314d00 100644 --- a/pallets/gear-program/src/migrations/allocations.rs +++ b/pallets/gear-program/src/migrations/v12_program_code_id_migration.rs @@ -16,14 +16,13 @@ // 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 crate::{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")] @@ -36,59 +35,60 @@ use { sp_std::vec::Vec, }; -const MIGRATE_FROM_VERSION: u16 = 9; -const MIGRATE_TO_VERSION: u16 = 10; -const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 10; +const MIGRATE_FROM_VERSION: u16 = 11; +const MIGRATE_TO_VERSION: u16 = 12; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 13; -pub struct MigrateAllocations(PhantomData); +pub struct MigrateProgramCodeHashToCodeId(PhantomData); -impl OnRuntimeUpgrade for MigrateAllocations { +impl OnRuntimeUpgrade for MigrateProgramCodeHashToCodeId { fn on_runtime_upgrade() -> Weight { - let onchain = Pallet::::on_chain_storage_version(); - - // 1 read for on chain storage version + // 1 read for onchain storage version let mut weight = T::DbWeight::get().reads(1); + let mut counter = 0; + + let onchain = Pallet::::on_chain_storage_version(); 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>| { + ProgramStorage::::translate(|_, program: v11::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 + counter += 1; + Some(match program { + v11::Program::Active(p) => { + // NOTE: p.code_exports and static_pages is removed from the program Program::Active(ActiveProgram { - allocations_tree_len, + allocations_tree_len: p.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(), + code_id: p.code_hash.into(), state: p.state, expiration_block: p.expiration_block, }) } - v9::Program::Exited(id) => Program::Exited(id), - v9::Program::Terminated(id) => Program::Terminated(id), + v11::Program::Exited(id) => Program::Exited(id), + v11::Program::Terminated(id) => Program::Terminated(id), }) }); + // Put new storage version + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + update_to.put::>(); - log::info!("✅ Successfully migrates storage"); + log::info!("✅ Successfully migrated storage. {counter} codes have been migrated"); } else { log::info!("🟠 Migration requires onchain version {MIGRATE_FROM_VERSION}, so was skipped for {onchain:?}"); } @@ -107,7 +107,7 @@ impl OnRuntimeUpgrade for MigrateAllocations { "Current storage version is not allowed for migration, check migration code in order to allow it." ); - Some(v9::ProgramStorage::::iter().count() as u64) + Some(v11::ProgramStorage::::iter().count() as u64) } else { None }; @@ -120,26 +120,19 @@ impl OnRuntimeUpgrade for MigrateAllocations { 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" - ); + let count = ProgramStorage::::iter_keys().count() as u64; + ensure!(old_count == count, "incorrect count of elements"); } Ok(()) } } -mod v9 { +mod v11 { use gear_core::{ ids::ProgramId, message::DispatchKind, - pages::{numerated::tree::IntervalsTree, GearPage, WasmPage}, + pages::WasmPage, program::{MemoryInfix, ProgramState}, reservation::GasReservationMap, }; @@ -155,8 +148,7 @@ mod v9 { #[codec(crate = codec)] #[scale_info(crate = scale_info)] pub struct ActiveProgram { - pub allocations: IntervalsTree, - pub pages_with_data: IntervalsTree, + pub allocations_tree_len: u32, pub memory_infix: MemoryInfix, pub gas_reservation_map: GasReservationMap, pub code_hash: H256, @@ -191,12 +183,12 @@ mod v9 { #[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!") } + + const STORAGE_PREFIX: &'static str = "ProgramStorage"; } #[cfg(feature = "try-runtime")] @@ -213,14 +205,16 @@ mod v9 { 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 gear_core::{ + ids::{CodeId, ProgramId}, + program::ProgramState, + }; + use primitive_types::H256; use sp_runtime::traits::Zero; #[test] - fn migration_works() { + fn v12_program_code_id_migration_works() { let _ = env_logger::try_init(); new_test_ext().execute_with(|| { @@ -228,48 +222,32 @@ mod test { // 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(), + let program = v11::Program::>::Active(v11::ActiveProgram { + allocations_tree_len: 2, gas_reservation_map: Default::default(), - code_hash: Default::default(), + code_hash: H256::from([1; 32]), 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); + v11::ProgramStorage::::insert(active_program_id, program.clone()); // add exited program - let program = v9::Program::>::Exited(active_program_id); + let program = v11::Program::>::Exited(active_program_id); let program_id = ProgramId::from(2u64); - v9::ProgramStorage::::insert(program_id, program); + v11::ProgramStorage::::insert(program_id, program); // add terminated program - let program = v9::Program::>::Terminated(program_id); + let program = v11::Program::>::Terminated(program_id); let program_id = ProgramId::from(3u64); - v9::ProgramStorage::::insert(program_id, program); + v11::ProgramStorage::::insert(program_id, program); - let state = MigrateAllocations::::pre_upgrade().unwrap(); - let w = MigrateAllocations::::on_runtime_upgrade(); + let state = MigrateProgramCodeHashToCodeId::::pre_upgrade().unwrap(); + let w = MigrateProgramCodeHashToCodeId::::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) - ] - ); + MigrateProgramCodeHashToCodeId::::post_upgrade(state).unwrap(); let Some(Program::Active(program)) = ProgramStorage::::get(active_program_id) else { @@ -277,6 +255,11 @@ mod test { }; assert_eq!(program.allocations_tree_len, 2); + assert_eq!(program.memory_infix, Default::default()); + assert_eq!(program.gas_reservation_map, Default::default()); + assert_eq!(program.code_id, CodeId::from(H256::from([1; 32]))); + assert_eq!(program.state, ProgramState::Initialized); + assert_eq!(program.expiration_block, 100); assert_eq!( ProgramStorage::::get(ProgramId::from(2u64)).unwrap(), diff --git a/pallets/gear-program/src/migrations/v13_split_instrumented_code_migration.rs b/pallets/gear-program/src/migrations/v13_split_instrumented_code_migration.rs new file mode 100644 index 00000000000..f14c5cd52ae --- /dev/null +++ b/pallets/gear-program/src/migrations/v13_split_instrumented_code_migration.rs @@ -0,0 +1,275 @@ +// 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::{CodeMetadataStorage, Config, InstrumentedCodeStorage, Pallet}; +use frame_support::{ + traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; +use gear_core::code::{CodeMetadata, InstrumentedCode}; +use sp_std::marker::PhantomData; + +use gear_core::code::InstrumentationStatus; +#[cfg(feature = "try-runtime")] +use { + frame_support::ensure, + sp_runtime::{ + codec::{Decode, Encode}, + TryRuntimeError, + }, + sp_std::vec::Vec, +}; + +const MIGRATE_FROM_VERSION: u16 = 12; +const MIGRATE_TO_VERSION: u16 = 13; +const ALLOWED_CURRENT_STORAGE_VERSION: u16 = 13; + +pub struct MigrateSplitInstrumentedCode(PhantomData); + +impl OnRuntimeUpgrade for MigrateSplitInstrumentedCode { + fn on_runtime_upgrade() -> Weight { + // 1 read for onchain storage version + let mut weight = T::DbWeight::get().reads(1); + let mut counter = 0; + + let onchain = Pallet::::on_chain_storage_version(); + + 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:?}."); + + v12::CodeStorage::::drain().for_each(|(code_id, instrumented_code)| { + // 1 read for instrumented code, 1 write for instrumented code and 1 write for code metadata + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + + let code_metadata = CodeMetadata::new( + instrumented_code.original_code_len, + instrumented_code.code.len() as u32, + instrumented_code.exports, + instrumented_code.static_pages, + instrumented_code.stack_end, + InstrumentationStatus::Instrumented(instrumented_code.version), + ); + + let instrumented_code = InstrumentedCode::new( + instrumented_code.code, + instrumented_code.instantiated_section_sizes, + ); + + InstrumentedCodeStorage::::insert(code_id, instrumented_code); + CodeMetadataStorage::::insert(code_id, code_metadata); + + counter += 1; + }); + + v12::CodeStorageNonce::::kill(); + // killing a storage: one write + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + v12::CodeLenStorageNonce::::kill(); + // killing a storage: one write + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Put new storage version + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + update_to.put::>(); + + log::info!("✅ Successfully migrated storage. {counter} codes have been migrated"); + } 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(v12::CodeStorage::::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_instrumented_code = InstrumentedCodeStorage::::iter_keys().count() as u64; + let count_code_metadata = CodeMetadataStorage::::iter_keys().count() as u64; + ensure!( + old_count == count_instrumented_code && old_count == count_code_metadata, + "incorrect count of elements" + ); + } + + Ok(()) + } +} + +mod v12 { + use gear_core::{ + code::InstantiatedSectionSizes, + message::DispatchKind, + pages::{WasmPage, WasmPagesAmount}, + }; + use sp_runtime::{ + codec::{self, Decode, Encode}, + scale_info::{self, TypeInfo}, + }; + 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 InstrumentedCode { + pub code: Vec, + pub original_code_len: u32, + pub exports: BTreeSet, + pub static_pages: WasmPagesAmount, + pub stack_end: Option, + pub instantiated_section_sizes: InstantiatedSectionSizes, + pub version: u32, + } + + use crate::{Config, Pallet}; + use frame_support::{ + storage::types::{StorageMap, StorageValue}, + traits::{PalletInfo, StorageInstance}, + Identity, + }; + use gear_core::ids::CodeId; + use sp_std::marker::PhantomData; + + pub type CodeStorageNonce = StorageValue, u32>; + + pub struct CodeStorageStoragePrefix(PhantomData); + + impl StorageInstance for CodeStorageStoragePrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + + const STORAGE_PREFIX: &'static str = "CodeStorage"; + } + + pub type CodeStorage = + StorageMap, Identity, CodeId, InstrumentedCode>; + + pub type CodeLenStorageNonce = StorageValue, u32>; + + pub struct CodeLenStoragePrefix(PhantomData); + + impl StorageInstance for CodeLenStoragePrefix { + fn pallet_prefix() -> &'static str { + <::PalletInfo as PalletInfo>::name::>() + .expect("No name found for the pallet in the runtime!") + } + + const STORAGE_PREFIX: &'static str = "CodeLenStorage"; + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use super::*; + use crate::mock::*; + use common::CodeId; + use frame_support::traits::StorageVersion; + use gear_core::{code::InstantiatedSectionSizes, pages::WasmPagesAmount}; + use sp_runtime::traits::Zero; + + #[test] + fn v13_split_instrumented_code_migration_works() { + let _ = env_logger::try_init(); + + new_test_ext().execute_with(|| { + StorageVersion::new(MIGRATE_FROM_VERSION).put::(); + + let code_id = CodeId::from(1u64); + + let section_sizes = InstantiatedSectionSizes::new(0, 0, 0, 0, 0, 0); + + let instrumented_code = v12::InstrumentedCode { + code: vec![1u8; 32], + original_code_len: 32, + exports: Default::default(), + static_pages: WasmPagesAmount::from(1u16), + stack_end: None, + instantiated_section_sizes: section_sizes, + version: 1, + }; + + v12::CodeStorage::::insert(code_id, instrumented_code.clone()); + + let state = MigrateSplitInstrumentedCode::::pre_upgrade().unwrap(); + let w = MigrateSplitInstrumentedCode::::on_runtime_upgrade(); + assert!(!w.is_zero()); + MigrateSplitInstrumentedCode::::post_upgrade(state).unwrap(); + + let code_metadata = CodeMetadataStorage::::get(code_id).unwrap(); + let new_instrumented_code = InstrumentedCodeStorage::::get(code_id).unwrap(); + + assert_eq!( + code_metadata.original_code_len(), + instrumented_code.original_code_len + ); + assert_eq!( + code_metadata.instrumented_code_len(), + instrumented_code.code.len() as u32 + ); + assert_eq!(code_metadata.exports(), &instrumented_code.exports); + assert_eq!(code_metadata.static_pages(), instrumented_code.static_pages); + assert_eq!(code_metadata.stack_end(), instrumented_code.stack_end); + assert_eq!( + code_metadata.instrumentation_status(), + InstrumentationStatus::Instrumented(instrumented_code.version) + ); + + assert_eq!(new_instrumented_code.bytes(), &instrumented_code.code); + assert_eq!( + new_instrumented_code.instantiated_section_sizes(), + &instrumented_code.instantiated_section_sizes + ); + + assert_eq!(StorageVersion::get::(), MIGRATE_TO_VERSION); + }) + } +} diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index f565967b93b..6cce6c4553a 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -65,7 +65,7 @@ use ::alloc::{collections::BTreeMap, vec}; use common::{ self, benchmarking, storage::{Counter, *}, - CodeMetadata, CodeStorage, GasTree, Origin, ProgramStorage, ReservableTree, + CodeStorage, GasTree, Origin, ProgramStorage, ReservableTree, }; use core_processor::{ common::{DispatchOutcome, JournalNote}, @@ -449,7 +449,7 @@ benchmarks! { let program_id = benchmarking::account::("program", 0, 100); let _ = CurrencyOf::::deposit_creating(&program_id, 100_000_000_000_000_u128.unique_saturated_into()); let code = benchmarking::generate_wasm(16.into()).unwrap(); - benchmarking::set_program::, _>(program_id.clone().cast(), code, 1.into()); + benchmarking::set_program::, _>(program_id.clone().cast(), code); let original_message_id = benchmarking::account::("message", 0, 100).cast(); let gas_limit = 50000; let value = 10000u32.into(); @@ -553,7 +553,7 @@ benchmarks! { let minimum_balance = CurrencyOf::::minimum_balance(); 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()); + benchmarking::set_program::, _>(program_id, code); let payload = vec![0_u8; p as usize]; init_block::(None); @@ -570,7 +570,7 @@ benchmarks! { let program_id = benchmarking::account::("program", 0, 100); let _ = CurrencyOf::::deposit_creating(&program_id, 100_000_000_000_000_u128.unique_saturated_into()); let code = benchmarking::generate_wasm(16.into()).unwrap(); - benchmarking::set_program::, _>(program_id.clone().cast(), code, 1.into()); + benchmarking::set_program::, _>(program_id.clone().cast(), code); let original_message_id = benchmarking::account::("message", 0, 100).cast(); let gas_limit = 50000; let value = (p % 2).into(); @@ -609,7 +609,7 @@ benchmarks! { programs.push(program_id.clone()); let _ = CurrencyOf::::deposit_creating(&program_id, minimum_balance); let program_id = program_id.cast(); - benchmarking::set_program::, _>(program_id, vec![], 1.into()); + benchmarking::set_program::, _>(program_id, vec![]); ProgramStorageOf::::update_program_if_active(program_id, |program, _bn| { if i % 2 == 0 { @@ -649,19 +649,15 @@ benchmarks! { let WasmModule { code, hash, .. } = WasmModule::::sized_table_section(max_table_size, Some(e * 1024)); let code = Code::try_new_mock_const_or_no_rules(code, false, Default::default()).unwrap(); let code_and_id = CodeAndId::new(code); - let code_id = code_and_id.code_id(); - let caller: T::AccountId = benchmarking::account("caller", 0, 0); - let metadata = { - let block_number = Pallet::::block_number().unique_saturated_into(); - CodeMetadata::new(caller.into_origin(), block_number) - }; + T::CodeStorage::add_code(code_and_id.clone()).unwrap(); - T::CodeStorage::add_code(code_and_id, metadata).unwrap(); + let (code, code_id) = code_and_id.into_parts(); + let (_, _, code_metadata) = code.into_parts(); let schedule = T::Schedule::get(); }: { - Gear::::reinstrument_code(code_id, &schedule).expect("Re-instrumentation failed"); + Gear::::reinstrument_code(code_id, code_metadata, &schedule).expect("Re-instrumentation failed"); } load_allocations_per_interval { @@ -669,7 +665,7 @@ benchmarks! { 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()); + benchmarking::set_program::, _>(program_id, code); ProgramStorageOf::::set_allocations(program_id, allocations.collect()); }: { let _ = ProgramStorageOf::::allocations(program_id).unwrap(); diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index d62ca565bbd..9d14a1614dd 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -31,9 +31,8 @@ use crate::{ MailboxOf, Pallet as Gear, ProgramStorageOf, }; use alloc::{vec, vec::Vec}; -use common::{benchmarking, storage::*, Origin, ProgramStorage}; +use common::{storage::*, Origin, ProgramStorage}; use core::marker::PhantomData; -use frame_system::RawOrigin; use gear_core::{ ids::{CodeId, MessageId, ProgramId, ReservationId}, memory::{PageBuf, PageBufInner}, @@ -1458,10 +1457,7 @@ where let repetitions = batches * API_BENCHMARK_BATCH_SIZE; let module = WasmModule::::dummy(); - let _ = Gear::::upload_code_raw( - RawOrigin::Signed(benchmarking::account("instantiator", 0, 0)).into(), - module.code, - ); + let _ = Gear::::upload_code_raw(module.code); let mut cid_value = [0; CID_VALUE_SIZE as usize]; cid_value[0..CID_SIZE as usize].copy_from_slice(module.hash.as_ref()); diff --git a/pallets/gear/src/benchmarking/utils.rs b/pallets/gear/src/benchmarking/utils.rs index 9e5709747e3..cc07de91759 100644 --- a/pallets/gear/src/benchmarking/utils.rs +++ b/pallets/gear/src/benchmarking/utils.rs @@ -21,18 +21,18 @@ use super::Exec; use crate::{ builtin::BuiltinDispatcherFactory, - manager::{CodeInfo, ExtManager, HandleKind}, + manager::{ExtManager, HandleKind}, Config, CurrencyOf, LazyPagesInterface, LazyPagesRuntimeInterface, MailboxOf, Pallet as Gear, ProgramStorageOf, QueueOf, }; use common::{storage::*, CodeStorage, Origin, Program, ProgramStorage}; use core_processor::{ - common::ExecutableActorData, configs::BlockConfig, ContextChargedForCode, - ContextChargedForInstrumentation, + common::ExecutableActorData, configs::BlockConfig, precharge::ContextCharged, + ProcessExecutionContext, }; use frame_support::traits::{Currency, Get}; use gear_core::{ - code::{Code, CodeAndId}, + code::{Code, CodeAndId, InstrumentedCodeAndMetadata}, ids::{prelude::*, CodeId, MessageId, ProgramId}, message::{Dispatch, DispatchKind, Message, ReplyDetails, SignalDetails}, pages::WasmPagesAmount, @@ -100,13 +100,13 @@ where .map_err(|_| "Code failed to load")?; let code_and_id = CodeAndId::new(code); - let code_info = CodeInfo::from_code_and_id(&code_and_id); + let code_id = code_and_id.code_id(); - let _ = Gear::::set_code_with_metadata(code_and_id, source); + let _ = Gear::::set_code(code_and_id); ext_manager.set_program( program_id, - &code_info, + code_id, root_message_id, DEFAULT_BLOCK_NUMBER.saturating_add(DEFAULT_INTERVAL).into(), ); @@ -127,12 +127,9 @@ where HandleKind::InitByHash(code_id) => { let program_id = ProgramId::generate_from_user(code_id, b"bench_salt"); - let code = T::CodeStorage::get_code(code_id).ok_or("Code not found in storage")?; - let code_info = CodeInfo::from_code(&code_id, &code); - ext_manager.set_program( program_id, - &code_info, + code_id, root_message_id, DEFAULT_BLOCK_NUMBER.saturating_add(DEFAULT_INTERVAL).into(), ); @@ -217,56 +214,71 @@ where ..pallet_config }; - let context = core_processor::precharge_for_program( - &block_config, - config.gas_allowance, - queued_dispatch.into_incoming(config.gas_limit), + let context = ContextCharged::new( actor_id, - ) - .map_err(|_| "core_processor::precharge_for_program failed")?; + queued_dispatch.into_incoming(config.gas_limit), + config.gas_allowance, + ); + + let context = context + .charge_for_program(&block_config) + .map_err(|_| "core_processor::precharge_for_program failed")?; 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, - context, - active.allocations_tree_len, - ) - .map_err(|_| "core_processor::precharge_for_allocations failed")?; + let balance: u128 = CurrencyOf::::free_balance(&actor_id.cast()).unique_saturated_into(); + + let context = context + .charge_for_code_metadata(&block_config) + .map_err(|_| "core_processor::precharge_for_code_metadata failed")?; + + let code_metadata = + T::CodeStorage::get_code_metadata(active.code_id).ok_or("Code metadata not found")?; + + let context = context + .charge_for_instrumented_code(&block_config, code_metadata.instrumented_code_len()) + .map_err(|_| "core_processor::precharge_for_instrumented_code failed")?; + + let code = + T::CodeStorage::get_instrumented_code(active.code_id).ok_or("Program code not found")?; + + let context = context + .charge_for_allocations(&block_config, active.allocations_tree_len) + .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")?; + let context = context + .charge_for_module_instantiation( + &block_config, + actor_data, + code.instantiated_section_sizes(), + &code_metadata, + ) + .map_err(|_| "core_processor::precharge_for_module_instantiation failed")?; - let context = ContextChargedForCode::from(context); - let context = core_processor::precharge_for_module_instantiation( - &block_config, - ContextChargedForInstrumentation::from(context), - code.instantiated_section_sizes(), - ) - .map_err(|_| "core_processor::precharge_for_module_instantiation failed")?; + let process_exec_context = ProcessExecutionContext::new( + context, + InstrumentedCodeAndMetadata { + instrumented_code: code, + metadata: code_metadata, + }, + balance, + ); Ok(Exec { ext_manager, block_config, - context: (context, code, balance).into(), + context: process_exec_context, random_data: (vec![0u8; 32], 0), }) } diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 8aaaebe0461..2b859830056 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -60,8 +60,8 @@ use alloc::{ string::{String, ToString}, }; use common::{ - self, event::*, gas_provider::GasNodeId, scheduler::*, storage::*, BlockLimiter, CodeMetadata, - CodeStorage, GasProvider, GasTree, Origin, Program, ProgramStorage, QueueRunner, + self, event::*, gas_provider::GasNodeId, scheduler::*, storage::*, BlockLimiter, CodeStorage, + GasProvider, GasTree, Origin, Program, ProgramStorage, QueueRunner, }; use core::{marker::PhantomData, num::NonZero}; use core_processor::{ @@ -85,7 +85,7 @@ use frame_system::{ Pallet as System, RawOrigin, }; use gear_core::{ - code::{Code, CodeAndId, CodeError, InstrumentedCode, InstrumentedCodeAndId}, + code::{Code, CodeAndId, CodeError, CodeMetadata, InstrumentationStatus, InstrumentedCode}, ids::{prelude::*, CodeId, MessageId, ProgramId, ReservationId}, message::*, percent::Percent, @@ -93,7 +93,7 @@ use gear_core::{ }; use gear_lazy_pages_common::LazyPagesInterface; use gear_lazy_pages_interface::LazyPagesRuntimeInterface; -use manager::{CodeInfo, QueuePostProcessingData}; +use manager::QueuePostProcessingData; use pallet_gear_voucher::{PrepaidCall, PrepaidCallsDispatcher, VoucherId, WeightInfo as _}; use primitive_types::H256; use sp_runtime::{ @@ -160,6 +160,7 @@ impl DebugInfo for () { #[frame_support::pallet] pub mod pallet { use super::*; + use gear_core::code::InstrumentedCodeAndMetadata; #[pallet::config] pub trait Config: @@ -579,10 +580,10 @@ pub mod pallet { })?; let code_and_id = CodeAndId::new(code); - let code_info = CodeInfo::from_code_and_id(&code_and_id); + let code_id = code_and_id.code_id(); let packet = InitPacket::new_from_user( - code_and_id.code_id(), + code_id, salt.try_into() .map_err(|err: PayloadSizeError| DispatchError::Other(err.into()))?, init_payload @@ -615,11 +616,9 @@ pub mod pallet { GearBank::::deposit_gas(&who, gas_limit, false)?; GearBank::::deposit_value(&who, value, false)?; - let origin = who.clone().into_origin(); - // By that call we follow the guarantee that we have in `Self::upload_code` - // if there's code in storage, there's also metadata for it. - if let Ok(code_id) = Self::set_code_with_metadata(code_and_id, origin) { + if let Ok(code_id) = Self::set_code(code_and_id) { // TODO: replace this temporary (`None`) value // for expiration block number with properly // calculated one (issues #646 and #969). @@ -629,12 +628,14 @@ pub mod pallet { }); } + let origin = who.clone().into_origin(); + let message_id = Self::next_message_id(origin); let block_number = Self::block_number(); ExtManager::::new(builtins).set_program( program_id, - &code_info, + code_id, message_id, block_number, ); @@ -664,16 +665,14 @@ pub mod pallet { /// Upload code to the chain without gas and stack limit injection. #[cfg(feature = "runtime-benchmarks")] - pub fn upload_code_raw(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - + pub fn upload_code_raw(code: Vec) -> DispatchResultWithPostInfo { let code = Code::try_new_mock_const_or_no_rules(code, false, Default::default()) .map_err(|e| { log::debug!("Code failed to load: {e:?}"); Error::::ProgramConstructionFailed })?; - let code_id = Self::set_code_with_metadata(CodeAndId::new(code), who.into_origin())?; + let code_id = Self::set_code(CodeAndId::new(code))?; // TODO: replace this temporary (`None`) value // for expiration block number with properly @@ -1083,26 +1082,14 @@ pub mod pallet { } } - /// Sets `code` and metadata, if code doesn't exist in storage. + /// Sets `code`, if code doesn't exist in storage. /// /// On success returns Blake256 hash of the `code`. If code already /// exists (*so, metadata exists as well*), returns unit `CodeAlreadyExists` error. - /// - /// # Note - /// Code existence in storage means that metadata is there too. - pub(crate) fn set_code_with_metadata( - code_and_id: CodeAndId, - who: H256, - ) -> Result> { + pub(crate) fn set_code(code_and_id: CodeAndId) -> Result> { let code_id = code_and_id.code_id(); - let metadata = { - let block_number = Self::block_number().unique_saturated_into(); - CodeMetadata::new(who, block_number) - }; - - T::CodeStorage::add_code(code_and_id, metadata) - .map_err(|_| Error::::CodeAlreadyExists)?; + T::CodeStorage::add_code(code_and_id).map_err(|_| Error::::CodeAlreadyExists)?; Ok(code_id) } @@ -1118,37 +1105,51 @@ pub mod pallet { /// test (`schedule::tests::instructions_backward_compatibility`) pub(crate) fn reinstrument_code( code_id: CodeId, + code_metadata: CodeMetadata, schedule: &Schedule, - ) -> Result { - debug_assert!(T::CodeStorage::get_code(code_id).is_some()); - + ) -> Result { // By the invariant set in CodeStorage trait, original code can't exist in storage // without the instrumented code let original_code = T::CodeStorage::get_original_code(code_id).unwrap_or_else(|| { let err_msg = format!( - "reinstrument_code: failed to get original code, while instrumented exists. \ - Code id - {code_id}" + "reinstrument_code: failed to get original code for the existing program. \ + Code id - '{code_id:?}'." ); log::error!("{err_msg}"); unreachable!("{err_msg}") }); - let code = Code::try_new( + let instrumented_code_and_metadata = match Code::try_new( original_code, schedule.instruction_weights.version, |module| schedule.rules(module), schedule.limits.stack_height, schedule.limits.data_segments_amount.into(), schedule.limits.table_number.into(), - )?; + ) { + Ok(code) => { + let instrumented_code_and_metadata = code.into_instrumented_code_and_metadata(); - let code_and_id = CodeAndId::from_parts_unchecked(code, code_id); - let code_and_id = InstrumentedCodeAndId::from(code_and_id); - T::CodeStorage::update_code(code_and_id.clone()); - let (code, _) = code_and_id.into_parts(); + T::CodeStorage::update_instrumented_code_and_metadata( + code_id, + instrumented_code_and_metadata.clone(), + ); + + instrumented_code_and_metadata + } + Err(e) => { + T::CodeStorage::update_code_metadata( + code_id, + code_metadata + .into_failed_instrumentation(schedule.instruction_weights.version), + ); - Ok(code) + return Err(e); + } + }; + + Ok(instrumented_code_and_metadata) } pub(crate) fn try_new_code(code: Vec) -> Result { @@ -1173,7 +1174,7 @@ pub mod pallet { })?; ensure!( - (code.code().len() as u32) <= schedule.limits.code_len, + (code.instrumented_code().bytes().len() as u32) <= schedule.limits.code_len, Error::::CodeTooLarge ); @@ -1229,7 +1230,7 @@ pub mod pallet { pub(crate) fn do_create_program( who: T::AccountId, packet: InitPacket, - code_info: CodeInfo, + code_id: CodeId, ) -> Result<(), DispatchError> { let origin = who.clone().into_origin(); @@ -1262,7 +1263,7 @@ pub mod pallet { WithdrawReasons::all(), ); - ext_manager.set_program(program_id, &code_info, message_id, block_number); + ext_manager.set_program(program_id, code_id, message_id, block_number); let program_event = Event::ProgramChanged { id: program_id, @@ -1331,9 +1332,9 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::upload_code((code.len() as u32) / 1024))] pub fn upload_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; + let _ = ensure_signed(origin)?; - Self::upload_code_impl(who, code) + Self::upload_code_impl(code) } /// Creates program initialization request (message), that is scheduled to be run in the same block. @@ -1401,10 +1402,11 @@ pub mod pallet { Self::check_gas_limit(gas_limit)?; let code_and_id = Self::try_new_code(code)?; - let code_info = CodeInfo::from_code_and_id(&code_and_id); + let code_id = code_and_id.code_id(); + let packet = Self::init_packet( who.clone(), - code_and_id.code_id(), + code_id, salt, init_payload, gas_limit, @@ -1412,11 +1414,10 @@ pub mod pallet { keep_alive, )?; - if !T::CodeStorage::exists(code_and_id.code_id()) { + if !T::CodeStorage::exists(code_id) { // By that call we follow the guarantee that we have in `Self::upload_code` - // if there's code in storage, there's also metadata for it. - let code_hash = - Self::set_code_with_metadata(code_and_id, who.clone().into_origin())?; + let code_hash = Self::set_code(code_and_id)?; // TODO: replace this temporary (`None`) value // for expiration block number with properly @@ -1427,7 +1428,7 @@ pub mod pallet { }); } - Self::do_create_program(who, packet, code_info)?; + Self::do_create_program(who, packet, code_id)?; Ok(().into()) } @@ -1462,7 +1463,8 @@ pub mod pallet { let who = ensure_signed(origin)?; // Check if code exists. - let code = T::CodeStorage::get_code(code_id).ok_or(Error::::CodeDoesntExist)?; + let _ = T::CodeStorage::get_instrumented_code(code_id) + .ok_or(Error::::CodeDoesntExist)?; // Check `gas_limit` Self::check_gas_limit(gas_limit)?; @@ -1478,7 +1480,7 @@ pub mod pallet { keep_alive, )?; - Self::do_create_program(who, packet, CodeInfo::from_code(&code_id, &code))?; + Self::do_create_program(who, packet, code_id)?; Ok(().into()) } @@ -1955,12 +1957,8 @@ pub mod pallet { } /// Underlying implementation of `GearPallet::upload_code`. - pub fn upload_code_impl( - origin: AccountIdOf, - code: Vec, - ) -> DispatchResultWithPostInfo { - let code_id = - Self::set_code_with_metadata(Self::try_new_code(code)?, origin.into_origin())?; + pub fn upload_code_impl(code: Vec) -> DispatchResultWithPostInfo { + let code_id = Self::set_code(Self::try_new_code(code)?)?; // TODO: replace this temporary (`None`) value // for expiration block number with properly @@ -2038,7 +2036,7 @@ pub mod pallet { keep_alive, Some(sponsor_id), ), - PrepaidCall::UploadCode { code } => Pallet::::upload_code_impl(account_id, code), + PrepaidCall::UploadCode { code } => Pallet::::upload_code_impl(code), PrepaidCall::DeclineVoucher => pallet_gear_voucher::Pallet::::decline( RawOrigin::Signed(account_id).into(), voucher_id, diff --git a/pallets/gear/src/manager/journal.rs b/pallets/gear/src/manager/journal.rs index 79a3bdeddad..88fbb8ffcbf 100644 --- a/pallets/gear/src/manager/journal.rs +++ b/pallets/gear/src/manager/journal.rs @@ -17,10 +17,9 @@ // along with this program. If not, see . use crate::{ - internal::HoldBoundBuilder, - manager::{CodeInfo, ExtManager}, - Config, CostsPerBlockOf, CurrencyOf, Event, GasAllowanceOf, GasHandlerOf, GasTree, GearBank, - Pallet, ProgramStorageOf, QueueOf, TaskPoolOf, WaitlistOf, EXISTENTIAL_DEPOSIT_LOCK_ID, + internal::HoldBoundBuilder, manager::ExtManager, Config, CostsPerBlockOf, CurrencyOf, Event, + GasAllowanceOf, GasHandlerOf, GasTree, GearBank, Pallet, ProgramStorageOf, QueueOf, TaskPoolOf, + WaitlistOf, EXISTENTIAL_DEPOSIT_LOCK_ID, }; use alloc::format; use common::{ @@ -431,8 +430,7 @@ where code_id: CodeId, candidates: Vec<(MessageId, ProgramId)>, ) { - if let Some(code) = T::CodeStorage::get_code(code_id) { - let code_info = CodeInfo::from_code(&code_id, &code); + if T::CodeStorage::exists(code_id) { for (init_message, candidate_id) in candidates { if !Pallet::::program_exists(self.builtins(), candidate_id) { let block_number = Pallet::::block_number(); @@ -465,7 +463,7 @@ where WithdrawReasons::all(), ); - self.set_program(candidate_id, &code_info, init_message, block_number); + self.set_program(candidate_id, code_id, init_message, block_number); Pallet::::deposit_event(Event::ProgramChanged { id: candidate_id, diff --git a/pallets/gear/src/manager/mod.rs b/pallets/gear/src/manager/mod.rs index 105168e42e6..f35dc4f4cba 100644 --- a/pallets/gear/src/manager/mod.rs +++ b/pallets/gear/src/manager/mod.rs @@ -67,15 +67,12 @@ use core::{fmt, mem}; use frame_support::traits::{Currency, ExistenceRequirement, LockableCurrency}; use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ - code::{CodeAndId, InstrumentedCode}, ids::{CodeId, MessageId, ProgramId, ReservationId}, - message::{DispatchKind, SignalMessage}, - pages::WasmPagesAmount, + message::SignalMessage, program::{ActiveProgram, Program, ProgramState}, reservation::GasReservationSlot, tasks::ScheduledTask, }; -use primitive_types::H256; use scale_info::TypeInfo; use sp_runtime::{ codec::{Decode, Encode}, @@ -108,31 +105,6 @@ impl fmt::Debug for HandleKind { } } -#[derive(Debug)] -pub struct CodeInfo { - id: H256, - exports: BTreeSet, - static_pages: WasmPagesAmount, -} - -impl CodeInfo { - pub fn from_code_and_id(code: &CodeAndId) -> Self { - Self { - id: code.code_id().into_origin(), - exports: code.code().exports().clone(), - static_pages: code.code().static_pages(), - } - } - - pub fn from_code(id: &CodeId, code: &InstrumentedCode) -> Self { - Self { - id: id.into_origin(), - exports: code.exports().clone(), - static_pages: code.static_pages(), - } - } -} - /// Journal handler implementation for `pallet_gear`. pub struct ExtManager { /// Ids checked that they are users. @@ -211,7 +183,7 @@ where pub fn set_program( &self, program_id: ProgramId, - code_info: &CodeInfo, + code_id: CodeId, message_id: MessageId, expiration_block: BlockNumberFor, ) { @@ -220,16 +192,14 @@ where // // Code can exist without program, but the latter can't exist without code. debug_assert!( - T::CodeStorage::exists(code_info.id.cast()), + T::CodeStorage::exists(code_id), "Program set must be called only when code exists", ); // An empty program has been just constructed: it contains no mem allocations. let program = ActiveProgram { allocations_tree_len: 0, - code_hash: code_info.id, - code_exports: code_info.exports.clone(), - static_pages: code_info.static_pages, + code_id, state: ProgramState::Uninitialized { message_id }, gas_reservation_map: Default::default(), expiration_block, diff --git a/pallets/gear/src/queue.rs b/pallets/gear/src/queue.rs index 9a1560260d0..43139a97f85 100644 --- a/pallets/gear/src/queue.rs +++ b/pallets/gear/src/queue.rs @@ -17,8 +17,11 @@ // along with this program. If not, see . use super::*; -use core_processor::ContextChargedForInstrumentation; -use gear_core::program::ProgramState; +use core_processor::{ + common::{DispatchResult, SuccessfulDispatchResultKind}, + ContextCharged, ProcessExecutionContext, +}; +use gear_core::{code::InstrumentedCodeAndMetadata, program::ProgramState}; pub(crate) struct QueueStep<'a, T: Config> { pub block_config: &'a BlockConfig, @@ -43,19 +46,22 @@ where let dispatch_id = dispatch.id(); let dispatch_kind = dispatch.kind(); + let context = ContextCharged::new( + destination_id, + dispatch.into_incoming(gas_limit), + GasAllowanceOf::::get(), + ); + // 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 context = match core_processor::precharge_for_program( - block_config, - GasAllowanceOf::::get(), - dispatch.into_incoming(gas_limit), - destination_id, - ) { - Ok(dispatch) => dispatch, + let context = match context.charge_for_program(block_config) { + Ok(context) => context, Err(journal) => return journal, }; + log::debug!("Gas burned after Program {:?}", context.gas_burned()); + let Some(Program::Active(program)) = ProgramStorageOf::::get_program(destination_id) else { log::trace!("Message {dispatch_id} is sent to non-active program {destination_id}"); @@ -95,47 +101,20 @@ where return core_processor::process_non_executable(context); } - let context = match core_processor::precharge_for_allocations( - block_config, - context, - program.allocations_tree_len, - ) { + // Adjust gas counters for fetching code metadata. + let context = match context.charge_for_code_metadata(block_config) { 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, - code_id: program.code_hash.cast(), - code_exports: program.code_exports, - static_pages: program.static_pages, - gas_reservation_map: program.gas_reservation_map, - memory_infix: program.memory_infix, - }; + log::debug!("Gas burned after CodeMetadata {:?}", context.gas_burned()); - // 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, context, actor_data) { - Ok(context) => context, - Err(journal) => return journal, - }; + let code_id = program.code_id; - // Load correct code length value. - let code_id = context.actor_data().code_id; - let code_len_bytes = T::CodeStorage::get_code_len(code_id).unwrap_or_else(|| { - // `Program` exists, so do code and code len. + // The second step is to load code metadata + let code_metadata = T::CodeStorage::get_code_metadata(code_id).unwrap_or_else(|| { let err_msg = format!( - "run_queue_step: failed to get code len for the existing program. \ + "run_queue_step: failed to get code metadata for the existing program. \ Program id -'{destination_id:?}', Code id - '{code_id:?}'." ); @@ -143,68 +122,150 @@ where unreachable!("{err_msg}"); }); - // Adjust gas counters for fetching instrumented binary code. - let context = - match core_processor::precharge_for_code(block_config, context, code_len_bytes) { - Ok(context) => context, - Err(journal) => return journal, - }; + if !code_metadata.exports().contains(&dispatch_kind) { + let (destination_id, dispatch, gas_counter, _) = context.into_parts(); - // Load instrumented binary code from storage. - let code = T::CodeStorage::get_code(code_id).unwrap_or_else(|| { - // `Program` exists, so do code and code len. - let err_msg = format!( - "run_queue_step: failed to get code for the existing program. \ - Program id -'{destination_id:?}', Code id - '{code_id:?}'." + return core_processor::process_success( + SuccessfulDispatchResultKind::Success, + DispatchResult::success(dispatch, destination_id, gas_counter.to_amount()), ); + } - log::error!("{err_msg}"); - unreachable!("{err_msg}"); - }); + let schedule = T::Schedule::get(); + + // Check if the code needs to be reinstrumented. + let needs_reinstrumentation = match code_metadata.instrumentation_status() { + InstrumentationStatus::Instrumented(weights_version) => { + weights_version != schedule.instruction_weights.version + } + InstrumentationStatus::InstrumentationFailed(weights_version) => { + if weights_version == schedule.instruction_weights.version { + log::debug!( + "Re-instrumentation already failed for program '{destination_id:?}' \ + with instructions weights version {weights_version}" + ); + + return core_processor::process_code_metadata_error(context); + } + + true + } + }; // Reinstrument the code if necessary. - let schedule = T::Schedule::get(); - let (code, context) = - if code.instruction_weights_version() == schedule.instruction_weights.version { - (code, ContextChargedForInstrumentation::from(context)) - } else { - log::debug!("Re-instrumenting code for program '{destination_id:?}'"); - - let context = match core_processor::precharge_for_instrumentation( - block_config, - context, - code.original_code_len(), - ) { - Ok(context) => context, - Err(journal) => return journal, - }; + let (instrumented_code, code_metadata, context) = if needs_reinstrumentation { + log::debug!("Re-instrumenting code for program '{destination_id:?}'"); - let code = match Pallet::::reinstrument_code(code_id, &schedule) { - Ok(code) => code, + let context = match context + .charge_for_original_code(block_config, code_metadata.original_code_len()) + { + Ok(code) => code, + Err(journal) => return journal, + }; + + log::debug!("Gas burned after Original Code {:?}", context.gas_burned()); + + let context = match context + .charge_for_instrumentation(block_config, code_metadata.original_code_len()) + { + Ok(code) => code, + Err(journal) => return journal, + }; + + log::debug!( + "Gas burned after Instrumentation {:?}", + context.gas_burned() + ); + + let instrumented_code_and_metadata = + match Pallet::::reinstrument_code(code_id, code_metadata, &schedule) { + Ok(code_and_metadata) => code_and_metadata, Err(e) => { log::debug!("Re-instrumentation error for code {code_id:?}: {e:?}"); return core_processor::process_reinstrumentation_error(context); } }; - (code, context) + ( + instrumented_code_and_metadata.instrumented_code, + instrumented_code_and_metadata.metadata, + context, + ) + } else { + // Adjust gas counters for fetching instrumented binary code. + let context = match context + .charge_for_instrumented_code(block_config, code_metadata.instrumented_code_len()) + { + Ok(context) => context, + Err(journal) => return journal, }; + log::debug!( + "Gas burned after Instrumented Code {:?}", + context.gas_burned() + ); + + let code = T::CodeStorage::get_instrumented_code(code_id).unwrap_or_else(|| { + // `Program` exists, so do code and code len. + let err_msg = format!( + "run_queue_step: failed to get code for the existing program. \ + Program id -'{destination_id:?}', Code id - '{code_id:?}'." + ); + + log::error!("{err_msg}"); + unreachable!("{err_msg}"); + }); + + (code, code_metadata, context) + }; + + let context = + match context.charge_for_allocations(block_config, program.allocations_tree_len) { + Ok(context) => context, + Err(journal) => return journal, + }; + + log::debug!("Gas burned after Allocations {:?}", context.gas_burned()); + + 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, + gas_reservation_map: program.gas_reservation_map, + memory_infix: program.memory_infix, + }; + // The last one thing is to load program memory. Adjust gas counters for memory pages. - let context = match core_processor::precharge_for_module_instantiation( + let context = match context.charge_for_module_instantiation( block_config, - context, - code.instantiated_section_sizes(), + actor_data, + instrumented_code.instantiated_section_sizes(), + &code_metadata, ) { Ok(context) => context, Err(journal) => return journal, }; + log::debug!( + "Gas burned after Module Instantiation {:?}", + context.gas_burned() + ); + + log::debug!("Gas left after Instantiation {:?}", context.gas_left()); + let (random, bn) = T::Randomness::random(dispatch_id.as_ref()); core_processor::process::( block_config, - (context, code, balance).into(), + ProcessExecutionContext::new + (context, InstrumentedCodeAndMetadata{instrumented_code, metadata: code_metadata}, balance), (random.encode(), bn.unique_saturated_into()), ) .unwrap_or_else(|e| { diff --git a/pallets/gear/src/runtime_api.rs b/pallets/gear/src/runtime_api.rs index fd59ecac582..f4a785a30b5 100644 --- a/pallets/gear/src/runtime_api.rs +++ b/pallets/gear/src/runtime_api.rs @@ -21,7 +21,7 @@ use crate::queue::QueueStep; use core::convert::TryFrom; use frame_support::{dispatch::RawOrigin, traits::PalletInfo}; use gear_core::{ - code::TryNewCodeConfig, + code::{InstrumentedCodeAndMetadata, TryNewCodeConfig}, message::ReplyInfo, pages::{numerated::tree::IntervalsTree, WasmPage}, program::{ActiveProgram, MemoryInfix}, @@ -36,6 +36,7 @@ pub(crate) const ALLOWANCE_LIMIT_ERR: &str = "Calculation gas limit exceeded. Us pub(crate) struct CodeWithMemoryData { pub instrumented_code: InstrumentedCode, + pub code_metadata: CodeMetadata, pub allocations: IntervalsTree, pub memory_infix: MemoryInfix, } @@ -398,18 +399,19 @@ where let code = Code::try_new_mock_with_rules( wasm, |module| schedule.rules(module), - TryNewCodeConfig::new_no_exports_check(), + TryNewCodeConfig::with_no_exports_check(), ) .map_err(|e| format!("Failed to construct program: {e:?}"))?; - if u32::try_from(code.code().len()).unwrap_or(u32::MAX) > schedule.limits.code_len { + if u32::try_from(code.instrumented_code().bytes().len()).unwrap_or(u32::MAX) + > schedule.limits.code_len + { return Err("Wasm after instrumentation too big".into()); } let code_and_id = CodeAndId::new(code); - let code_and_id = InstrumentedCodeAndId::from(code_and_id); - let instrumented_code = code_and_id.into_parts().0; + let (_, instrumented_code, code_metadata) = code_and_id.into_parts().0.into_parts(); let payload_arg = payload; let mut payload = argument.unwrap_or_default(); @@ -433,6 +435,7 @@ where core_processor::informational::execute_for_reply::( function.into(), instrumented_code, + code_metadata, None, None, payload, @@ -452,6 +455,7 @@ where let CodeWithMemoryData { instrumented_code, + code_metadata, allocations, memory_infix, } = Self::code_with_memory(program_id)?; @@ -470,6 +474,7 @@ where core_processor::informational::execute_for_reply::( String::from("state"), instrumented_code, + code_metadata, Some(allocations), Some((program_id, memory_infix)), payload, @@ -488,6 +493,7 @@ where let CodeWithMemoryData { instrumented_code, + code_metadata, allocations, memory_infix, } = Self::code_with_memory(program_id)?; @@ -506,6 +512,7 @@ where core_processor::informational::execute_for_reply::( String::from("metahash"), instrumented_code, + code_metadata, Some(allocations), Some((program_id, memory_infix)), Default::default(), @@ -525,25 +532,50 @@ where .try_into() .map_err(|e| format!("Get active program error: {e:?}"))?; - let code_id = program.code_hash.cast(); + let code_id = program.code_id.cast(); - // Load instrumented binary code from storage. - let mut code = T::CodeStorage::get_code(code_id).ok_or_else(|| { - format!("Program '{program_id:?}' exists so must do code '{code_id:?}'") - })?; + let code_metadata = T::CodeStorage::get_code_metadata(code_id) + .ok_or_else(|| format!("Code '{code_id:?}' not found for program '{program_id:?}'"))?; - // Reinstrument the code if necessary. let schedule = T::Schedule::get(); - if code.instruction_weights_version() != schedule.instruction_weights.version { - code = Pallet::::reinstrument_code(code_id, &schedule) - .map_err(|e| format!("Code {code_id:?} failed reinstrumentation: {e:?}"))?; - } + // Check if the code needs to be reinstrumented. + let needs_reinstrumentation = match code_metadata.instrumentation_status() { + InstrumentationStatus::Instrumented(weights_version) => { + weights_version != schedule.instruction_weights.version + } + InstrumentationStatus::InstrumentationFailed(weights_version) => { + if weights_version == schedule.instruction_weights.version { + return Err(format!( + "Re-instrumentation already failed for program '{program_id:?}' \ + with instructions weights version {weights_version}" + )); + } + + true + } + }; + + let instrumented_code_and_metadata = if needs_reinstrumentation { + Pallet::::reinstrument_code(code_id, code_metadata, &schedule) + .map_err(|e| format!("Code {code_id:?} failed reinstrumentation: {e:?}"))? + } else { + let instrumented_code = + T::CodeStorage::get_instrumented_code(code_id).ok_or_else(|| { + format!("Program '{program_id:?}' exists so must do code '{code_id:?}'") + })?; + + InstrumentedCodeAndMetadata { + instrumented_code, + metadata: code_metadata, + } + }; let allocations = ProgramStorageOf::::allocations(program_id).unwrap_or_default(); Ok(CodeWithMemoryData { - instrumented_code: code, + instrumented_code: instrumented_code_and_metadata.instrumented_code, + code_metadata: instrumented_code_and_metadata.metadata, allocations, memory_infix: program.memory_infix, }) diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 9d0f129d77b..a5f11d18cf4 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -19,7 +19,7 @@ use crate::{ builtin::BuiltinDispatcherFactory, internal::{HoldBound, HoldBoundBuilder, InheritorForError}, - manager::{CodeInfo, HandleKind}, + manager::HandleKind, mock::{ self, new_test_ext, run_for_blocks, run_to_block, run_to_block_maybe_with_queue, run_to_next_block, Balances, BlockNumber, DynamicSchedule, Gear, GearVoucher, @@ -46,8 +46,7 @@ use frame_support::{ use frame_system::pallet_prelude::BlockNumberFor; use gear_core::{ code::{ - self, Code, CodeAndId, CodeError, ExportError, InstantiatedSectionSizes, - InstrumentedCodeAndId, MAX_WASM_PAGES_AMOUNT, + self, Code, CodeError, ExportError, InstrumentedCodeAndMetadata, MAX_WASM_PAGES_AMOUNT, }, gas_metering::CustomConstantCostRules, ids::{prelude::*, CodeId, MessageId, ProgramId}, @@ -237,11 +236,21 @@ fn state_rpc_calls_trigger_reinstrumentation() { ) .expect("Failed to create dummy code"); - let code_and_id = - unsafe { CodeAndId::from_incompatible_parts(code, program.code_hash.cast()) }; - let code_and_id = InstrumentedCodeAndId::from(code_and_id); + let (_, instrumented_code, invalid_metadata) = code.into_parts(); - ::CodeStorage::update_code(code_and_id); + // Code metadata doesn't have to be completely wrong, just a version of instrumentation + let old_code_metadata = + ::CodeStorage::get_code_metadata(program.code_id).unwrap(); + let code_metadata = old_code_metadata + .into_failed_instrumentation(invalid_metadata.instruction_weights_version()); + + ::CodeStorage::update_instrumented_code_and_metadata( + program.code_id, + InstrumentedCodeAndMetadata { + instrumented_code, + metadata: code_metadata, + }, + ); /* ends here */ assert_ok!(Gear::read_metahash_impl(program_id, None)); @@ -2513,10 +2522,15 @@ fn delayed_program_creation_no_code() { ); let read_program_from_storage_fee = gas_price(DbWeightOf::::get().reads(1).ref_time()); + let read_code_metadata_from_storage_fee = + gas_price(DbWeightOf::::get().reads(1).ref_time()); assert_eq!( Balances::free_balance(USER_1), - free_balance + reserved_balance - delay_holding_fee - 2 * read_program_from_storage_fee + free_balance + reserved_balance + - delay_holding_fee + - 2 * read_program_from_storage_fee + - read_code_metadata_from_storage_fee ); assert!(GearBank::::account_total(&USER_1).is_zero()); }) @@ -5028,7 +5042,7 @@ fn test_code_submission_pass() { code.clone() )); - let saved_code = ::CodeStorage::get_code(code_id); + let saved_code = ::CodeStorage::get_instrumented_code(code_id); let schedule = ::Schedule::get(); let code = Code::try_new( @@ -5040,11 +5054,10 @@ fn test_code_submission_pass() { schedule.limits.table_number.into(), ) .expect("Error creating Code"); - assert_eq!(saved_code.unwrap().code(), code.code()); - - let expected_meta = Some(common::CodeMetadata::new(USER_1.into_origin(), 1)); - let actual_meta = ::CodeStorage::get_metadata(code_id); - assert_eq!(expected_meta, actual_meta); + assert_eq!( + saved_code.unwrap().bytes(), + code.instrumented_code().bytes() + ); // TODO: replace this temporary (`None`) value // for expiration block number with properly @@ -5125,7 +5138,6 @@ fn test_code_is_not_reset_within_program_submission() { init_logger(); new_test_ext().execute_with(|| { let code = ProgramCodeKind::Default.to_bytes(); - let code_id = CodeId::generate(&code); // First submit code assert_ok!(Gear::upload_code( @@ -5133,8 +5145,6 @@ fn test_code_is_not_reset_within_program_submission() { code.clone() )); let expected_code_saved_events = 1; - let expected_meta = ::CodeStorage::get_metadata(code_id); - assert!(expected_meta.is_some()); // Submit program from another origin. Should not change meta or code. assert_ok!(Gear::upload_program( @@ -5146,7 +5156,7 @@ fn test_code_is_not_reset_within_program_submission() { 0, false, )); - let actual_meta = ::CodeStorage::get_metadata(code_id); + let actual_code_saved_events = System::events() .iter() .filter(|e| { @@ -5160,7 +5170,6 @@ fn test_code_is_not_reset_within_program_submission() { }) .count(); - assert_eq!(expected_meta, actual_meta); assert_eq!(expected_code_saved_events, actual_code_saved_events); }) } @@ -6455,54 +6464,47 @@ fn terminated_locking_funds() { let schedule = Schedule::::default(); let code_id = get_last_code_id(); - let code = ::CodeStorage::get_code(code_id) + let code = ::CodeStorage::get_instrumented_code(code_id) .expect("code should be in the storage"); - let code_length = code.code().len() as u64; + let code_length = code.bytes().len() as u64; let system_reservation = demo_init_fail_sender::system_reserve(); let reply_duration = demo_init_fail_sender::reply_duration(); let read_cost = DbWeightOf::::get().reads(1).ref_time(); let gas_for_module_instantiation = { - let InstantiatedSectionSizes { - code_section: code_section_bytes, - data_section: data_section_bytes, - global_section: global_section_bytes, - table_section: table_section_bytes, - element_section: element_section_bytes, - type_section: type_section_bytes, - } = *code.instantiated_section_sizes(); + let instantiated_section_sizes = code.instantiated_section_sizes(); let instantiation_weights = schedule.instantiation_weights; let mut gas_for_code_instantiation = instantiation_weights .code_section_per_byte .ref_time() - .saturating_mul(code_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.code_section() as u64); gas_for_code_instantiation += instantiation_weights .data_section_per_byte .ref_time() - .saturating_mul(data_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.data_section() as u64); gas_for_code_instantiation += instantiation_weights .global_section_per_byte .ref_time() - .saturating_mul(global_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.global_section() as u64); gas_for_code_instantiation += instantiation_weights .table_section_per_byte .ref_time() - .saturating_mul(table_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.table_section() as u64); gas_for_code_instantiation += instantiation_weights .element_section_per_byte .ref_time() - .saturating_mul(element_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.element_section() as u64); gas_for_code_instantiation += instantiation_weights .type_section_per_byte .ref_time() - .saturating_mul(type_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.type_section() as u64); gas_for_code_instantiation }; @@ -6811,8 +6813,6 @@ fn test_sequence_inheritor_of() { demo_ping::WASM_BINARY.to_vec(), )); let code_id = get_last_code_id(); - let code = ::CodeStorage::get_code(code_id).unwrap(); - let code_info = CodeInfo::from_code(&code_id, &code); let message_id = MessageId::from(1); @@ -6820,12 +6820,7 @@ fn test_sequence_inheritor_of() { let mut programs = vec![]; for i in 1000..1100 { let program_id = ProgramId::from(i); - manager.set_program( - program_id, - &code_info, - message_id, - 1.unique_saturated_into(), - ); + manager.set_program(program_id, code_id, message_id, 1.unique_saturated_into()); ProgramStorageOf::::update_program_if_active(program_id, |program, _bn| { let inheritor = programs.last().copied().unwrap_or_else(|| USER_1.into()); @@ -6905,8 +6900,6 @@ fn test_cyclic_inheritor_of() { demo_ping::WASM_BINARY.to_vec(), )); let code_id = get_last_code_id(); - let code = ::CodeStorage::get_code(code_id).unwrap(); - let code_info = CodeInfo::from_code(&code_id, &code); let message_id = MessageId::from(1); @@ -6914,12 +6907,7 @@ fn test_cyclic_inheritor_of() { let mut cyclic_programs = vec![]; for i in 2000..2100 { let program_id = ProgramId::from(i); - manager.set_program( - program_id, - &code_info, - message_id, - 1.unique_saturated_into(), - ); + manager.set_program(program_id, code_id, message_id, 1.unique_saturated_into()); ProgramStorageOf::::update_program_if_active(program_id, |program, _bn| { let inheritor = cyclic_programs @@ -8004,17 +7992,17 @@ fn gas_spent_precalculated() { let code_id = ProgramStorageOf::::get_program(pid) .and_then(|program| ActiveProgram::try_from(program).ok()) .expect("program must exist") - .code_hash + .code_id .cast(); - ::CodeStorage::get_code(code_id).unwrap() + ::CodeStorage::get_instrumented_code(code_id).unwrap() }; let get_gas_charged_for_code = |pid| { let schedule = ::Schedule::get(); let read_cost = DbWeightOf::::get().reads(1).ref_time(); let instrumented_prog = get_program_code(pid); - let code_len = instrumented_prog.code().len() as u64; + let code_len = instrumented_prog.bytes().len() as u64; let gas_for_code_read = schedule .db_weights .read_per_byte @@ -8022,52 +8010,45 @@ fn gas_spent_precalculated() { .saturating_mul(code_len) .saturating_add(read_cost); - let InstantiatedSectionSizes { - code_section: code_section_bytes, - data_section: data_section_bytes, - global_section: global_section_bytes, - table_section: table_section_bytes, - element_section: element_section_bytes, - type_section: type_section_bytes, - } = *instrumented_prog.instantiated_section_sizes(); + let instantiated_section_sizes = instrumented_prog.instantiated_section_sizes(); let instantiation_weights = schedule.instantiation_weights; let mut gas_for_code_instantiation = instantiation_weights .code_section_per_byte .ref_time() - .saturating_mul(code_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.code_section() as u64); gas_for_code_instantiation += instantiation_weights .data_section_per_byte .ref_time() - .saturating_mul(data_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.data_section() as u64); gas_for_code_instantiation += instantiation_weights .global_section_per_byte .ref_time() - .saturating_mul(global_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.global_section() as u64); gas_for_code_instantiation += instantiation_weights .table_section_per_byte .ref_time() - .saturating_mul(table_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.table_section() as u64); gas_for_code_instantiation += instantiation_weights .element_section_per_byte .ref_time() - .saturating_mul(element_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.element_section() as u64); gas_for_code_instantiation += instantiation_weights .type_section_per_byte .ref_time() - .saturating_mul(type_section_bytes as u64); + .saturating_mul(instantiated_section_sizes.type_section() as u64); gas_for_code_read + gas_for_code_instantiation }; let instrumented_code = get_program_code(pid); - let module = parity_wasm::deserialize_buffer::(instrumented_code.code()) + let module = parity_wasm::deserialize_buffer::(instrumented_code.bytes()) .expect("invalid wasm bytes"); let (handle_export_func_body, gas_charge_func_body) = module @@ -10215,8 +10196,9 @@ fn missing_functions_are_not_executed() { .expect("calculate_gas_info failed"); let program_cost = DbWeightOf::::get().reads(1).ref_time(); + let metadata_cost = DbWeightOf::::get().reads(1).ref_time(); // there is no execution so the values should be equal - assert_eq!(min_limit, program_cost); + assert_eq!(min_limit, program_cost + metadata_cost); run_to_next_block(None); @@ -10224,7 +10206,7 @@ fn missing_functions_are_not_executed() { // no execution is performed at all and hence user was not charged for program execution. assert_eq!( balance_before, - Balances::free_balance(USER_1) + gas_price(program_cost) + ed + Balances::free_balance(USER_1) + gas_price(program_cost + metadata_cost) + ed ); // this value is actually a constant in the wat. @@ -10259,7 +10241,7 @@ fn missing_functions_are_not_executed() { ) .expect("calculate_gas_info failed"); - assert_eq!(min_limit, program_cost); + assert_eq!(min_limit, program_cost + metadata_cost); let balance_before = Balances::free_balance(USER_1); let reply_value = 1_500; @@ -10276,7 +10258,7 @@ fn missing_functions_are_not_executed() { assert_eq!( balance_before - reply_value + locked_value, - Balances::free_balance(USER_1) + gas_price(program_cost) + Balances::free_balance(USER_1) + gas_price(program_cost + metadata_cost) ); }); } @@ -10407,9 +10389,9 @@ fn test_reinstrumentation_works() { // check old version let _reset_guard = DynamicSchedule::mutate(|schedule| { - let code = ::CodeStorage::get_code(code_id).unwrap(); + let code_metadata = ::CodeStorage::get_code_metadata(code_id).unwrap(); assert_eq!( - code.instruction_weights_version(), + code_metadata.instruction_weights_version(), schedule.instruction_weights.version ); @@ -10428,8 +10410,8 @@ fn test_reinstrumentation_works() { run_to_block(3, None); // check new version - let code = ::CodeStorage::get_code(code_id).unwrap(); - assert_eq!(code.instruction_weights_version(), 0xdeadbeef); + let code_metadata = ::CodeStorage::get_code_metadata(code_id).unwrap(); + assert_eq!(code_metadata.instruction_weights_version(), 0xdeadbeef); assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), @@ -10443,8 +10425,8 @@ fn test_reinstrumentation_works() { run_to_block(4, None); // check new version stands still - let code = ::CodeStorage::get_code(code_id).unwrap(); - assert_eq!(code.instruction_weights_version(), 0xdeadbeef); + let code_metadata = ::CodeStorage::get_code_metadata(code_id).unwrap(); + assert_eq!(code_metadata.instruction_weights_version(), 0xdeadbeef); }) } @@ -10457,7 +10439,8 @@ fn test_reinstrumentation_failure() { run_to_block(2, None); - let mut old_version = 0; + let new_weights_version = 0xdeadbeef; + let _reset_guard = DynamicSchedule::mutate(|schedule| { // Insert new original code to cause re-instrumentation failure. let wasm = ProgramCodeKind::Custom("(module)").to_bytes(); @@ -10465,8 +10448,7 @@ fn test_reinstrumentation_failure() { code_id, wasm, ); - old_version = schedule.instruction_weights.version; - schedule.instruction_weights.version = 0xdeadbeef; + schedule.instruction_weights.version = new_weights_version; }); assert_ok!(Gear::send_message( @@ -10486,9 +10468,12 @@ fn test_reinstrumentation_failure() { let program = ProgramStorageOf::::get_program(pid).unwrap(); assert!(program.is_active()); - // After message processing the code must have the old instrumentation version. - let code = ::CodeStorage::get_code(code_id).unwrap(); - assert_eq!(code.instruction_weights_version(), old_version); + // After message processing the code must have the new instrumentation version. + let code_metadata = ::CodeStorage::get_code_metadata(code_id).unwrap(); + assert_eq!( + code_metadata.instruction_weights_version(), + new_weights_version + ); // Error reply must be returned with the reason of re-instrumentation failure. assert_failed(mid, ErrorReplyReason::ReinstrumentationFailure); @@ -10503,7 +10488,8 @@ fn test_init_reinstrumentation_failure() { let code_id = CodeId::generate(&ProgramCodeKind::Default.to_bytes()); let pid = upload_program_default(USER_1, ProgramCodeKind::Default).unwrap(); - let mut old_version = 0; + let new_weights_version = 0xdeadbeef; + let _reset_guard = DynamicSchedule::mutate(|schedule| { // Insert new original code to cause init re-instrumentation failure. let wasm = ProgramCodeKind::Custom("(module)").to_bytes(); @@ -10511,8 +10497,7 @@ fn test_init_reinstrumentation_failure() { code_id, wasm, ); - old_version = schedule.instruction_weights.version; - schedule.instruction_weights.version = 0xdeadbeef; + schedule.instruction_weights.version = new_weights_version; }); let mid = get_last_message_id(); @@ -10523,9 +10508,12 @@ fn test_init_reinstrumentation_failure() { let program = ProgramStorageOf::::get_program(pid).unwrap(); assert!(program.is_terminated()); - // After message processing the code must have the old instrumentation version. - let code = ::CodeStorage::get_code(code_id).unwrap(); - assert_eq!(code.instruction_weights_version(), old_version); + // After message processing the code must have the new instrumentation version. + let code_metadata = ::CodeStorage::get_code_metadata(code_id).unwrap(); + assert_eq!( + code_metadata.instruction_weights_version(), + new_weights_version + ); // Error reply must be returned with the reason of re-instrumentation failure. assert_failed(mid, ErrorReplyReason::ReinstrumentationFailure); @@ -16151,6 +16139,10 @@ pub(crate) mod utils { #[track_caller] pub(super) fn assert_total_dequeued(expected: u32) { + System::events().iter().for_each(|e| { + log::debug!("Event: {:?}", e); + }); + let actual_dequeued: u32 = System::events() .iter() .filter_map(|e| { @@ -16212,7 +16204,7 @@ pub(crate) mod utils { ProgramStorageOf::::get_program(prog_id) .and_then(|program| ActiveProgram::try_from(program).ok()) .expect("program must exist") - .code_hash, + .code_id, generate_code_hash(&expected_code).into(), "can invoke send to mailbox only from `ProgramCodeKind::OutgoingWithValueInHandle` program" ); @@ -16223,7 +16215,7 @@ pub(crate) mod utils { #[track_caller] pub(super) fn increase_prog_balance_for_mailbox_test(sender: AccountId, program_id: ProgramId) { - let expected_code_hash: H256 = generate_code_hash( + let expected_code_hash: CodeId = generate_code_hash( ProgramCodeKind::OutgoingWithValueInHandle .to_bytes() .as_slice(), @@ -16231,7 +16223,7 @@ pub(crate) mod utils { .into(); let actual_code_hash = ProgramStorageOf::::get_program(program_id) .and_then(|program| ActiveProgram::try_from(program).ok()) - .map(|prog| prog.code_hash) + .map(|prog| prog.code_id) .expect("invalid program address for the test"); assert_eq!( expected_code_hash, actual_code_hash, diff --git a/runtime/vara/src/migrations.rs b/runtime/vara/src/migrations.rs index 9b7aa1ddb0c..77ba942144a 100644 --- a/runtime/vara/src/migrations.rs +++ b/runtime/vara/src/migrations.rs @@ -20,13 +20,15 @@ use crate::*; /// All migrations that will run on the next runtime upgrade. pub type Migrations = ( - // migration for added section sizes - pallet_gear_program::migrations::add_section_sizes::AddSectionSizesMigration, // 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, + // move metadata into attribution + pallet_gear_program::migrations::v11_code_metadata_delete_migration::MigrateRemoveCodeMetadata, + // migrate program code hash to code id and remove code_exports and static_pages + pallet_gear_program::migrations::v12_program_code_id_migration::MigrateProgramCodeHashToCodeId, + // split instrumented code into separate storage items + pallet_gear_program::migrations::v13_split_instrumented_code_migration::MigrateSplitInstrumentedCode, ); mod staking { diff --git a/utils/calc-stack-height/src/main.rs b/utils/calc-stack-height/src/main.rs index b35018f6dc5..6af0a85e63a 100644 --- a/utils/calc-stack-height/src/main.rs +++ b/utils/calc-stack-height/src/main.rs @@ -47,7 +47,7 @@ fn main() -> anyhow::Result<()> { let compiler = Singlepass::default(); let mut store = Store::new(compiler); - let module = Module::new(&store, code.code())?; + let module = Module::new(&store, code.instrumented_code().bytes())?; let mut imports = Imports::new(); let mut exports = Exports::new(); @@ -112,7 +112,7 @@ fn main() -> anyhow::Result<()> { ) .map_err(|e| anyhow!("{e}"))?; - let module = Module::new(&store, code.code())?; + let module = Module::new(&store, code.instrumented_code().bytes())?; let instance = Instance::new(&mut store, &module, &imports)?; let init = instance.exports.get_function("init")?; let err = init.call(&mut store, &[]).unwrap_err(); diff --git a/utils/wasm-builder/src/code_validator.rs b/utils/wasm-builder/src/code_validator.rs index 984a3ba7483..0439768f670 100644 --- a/utils/wasm-builder/src/code_validator.rs +++ b/utils/wasm-builder/src/code_validator.rs @@ -347,7 +347,7 @@ impl CodeValidator { match Code::try_new_mock_with_rules( self.code, |_| CustomConstantCostRules::default(), - TryNewCodeConfig::new_no_exports_check(), + TryNewCodeConfig::with_no_exports_check(), ) { Err(code_error) => Err(CodeErrorWithContext::from((self.module, code_error)))?, _ => Ok(()), diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index 28c38c5083f..e5cb604a767 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -1066,7 +1066,7 @@ fn execute_wasm_with_custom_configs( let ext = Ext::new(processor_context); let env = Environment::new( ext, - code.code(), + code.instrumented_code().bytes(), DispatchKind::Init, vec![DispatchKind::Init].into_iter().collect(), (INITIAL_PAGES as u16).into(),