diff --git a/Cargo.lock b/Cargo.lock index 1c77136f0e2..801fd7fecfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6753,6 +6753,7 @@ dependencies = [ "indexmap 2.2.6", "indicatif", "log", + "nonempty", "proptest", "rand 0.8.5", "thiserror", diff --git a/utils/runtime-fuzzer/src/generator/upload_program.rs b/utils/runtime-fuzzer/src/generator/upload_program.rs index 22321055be7..090c2c4652c 100644 --- a/utils/runtime-fuzzer/src/generator/upload_program.rs +++ b/utils/runtime-fuzzer/src/generator/upload_program.rs @@ -150,7 +150,8 @@ fn config( actor_kind: actor_kind.clone(), range: EXISTENTIAL_DEPOSIT..=max_value, }) - .with_ptr_rule(PtrParamAllowedValues::ReservationId); + .with_ptr_rule(PtrParamAllowedValues::ReservationId) + .with_ptr_rule(PtrParamAllowedValues::WaitedMessageId); if let Some(code_ids) = code_ids { params_config = params_config.with_ptr_rule(PtrParamAllowedValues::CodeIdsWithValue { diff --git a/utils/wasm-gen/Cargo.toml b/utils/wasm-gen/Cargo.toml index 0f45e1b7ecb..42bb0558f86 100644 --- a/utils/wasm-gen/Cargo.toml +++ b/utils/wasm-gen/Cargo.toml @@ -17,6 +17,7 @@ gear-core.workspace = true wasmparser.workspace = true thiserror.workspace = true indexmap.workspace = true +nonempty.workspace = true [dev-dependencies] rand = { workspace = true, features = ["small_rng"] } diff --git a/utils/wasm-gen/src/config/syscalls/param.rs b/utils/wasm-gen/src/config/syscalls/param.rs index 1df9d71a270..bd64ed521bc 100644 --- a/utils/wasm-gen/src/config/syscalls/param.rs +++ b/utils/wasm-gen/src/config/syscalls/param.rs @@ -97,6 +97,7 @@ impl SyscallsParamsConfig { range, }) .with_ptr_rule(PtrParamAllowedValues::ReservationId) + .with_ptr_rule(PtrParamAllowedValues::WaitedMessageId) } /// Set rules for a regular syscall param. @@ -127,6 +128,7 @@ impl SyscallsParamsConfig { } PtrParamAllowedValues::ReservationId => Ptr::Hash(HashType::ReservationId), PtrParamAllowedValues::CodeIdsWithValue { .. } => Ptr::HashWithValue(HashType::CodeId), + PtrParamAllowedValues::WaitedMessageId => Ptr::Hash(HashType::MessageId), }; self.ptr.insert(ptr, allowed_values); @@ -272,6 +274,8 @@ pub enum PtrParamAllowedValues { code_ids: NonEmpty, range: RangeInclusive, }, + /// Variant of `Ptr::Hash` pointer type, where hash is waited message id. + WaitedMessageId, } /// Actor kind, which is actually a syscall destination choice. diff --git a/utils/wasm-gen/src/generator/syscalls.rs b/utils/wasm-gen/src/generator/syscalls.rs index 4b0417f588d..e5048a989b9 100644 --- a/utils/wasm-gen/src/generator/syscalls.rs +++ b/utils/wasm-gen/src/generator/syscalls.rs @@ -214,6 +214,9 @@ impl InvocableSyscall { SyscallName::SendCommitWGas, ], SyscallName::ReplyDeposit => &[SyscallName::SendInput, SyscallName::ReplyDeposit], + // Enable `MessageId` syscall import if `Wake` syscall is enabled. + // This is done to provide a syscall `Wake` with a correct message id. + SyscallName::Wake => &[SyscallName::MessageId], _ => return None, }) } diff --git a/utils/wasm-gen/src/generator/syscalls/invocator.rs b/utils/wasm-gen/src/generator/syscalls/invocator.rs index 2ef24c0939a..5589b979576 100644 --- a/utils/wasm-gen/src/generator/syscalls/invocator.rs +++ b/utils/wasm-gen/src/generator/syscalls/invocator.rs @@ -354,6 +354,8 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { match invocable { Loose(Wait | WaitFor | WaitUpTo) => { + self.store_waited_message_id(&mut instructions); + if let Some(waiting_probability) = self.config.waiting_probability() { self.limit_infinite_waits(&mut instructions, waiting_probability.get()); } @@ -674,6 +676,12 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { ret_instr } + PtrParamAllowedValues::WaitedMessageId => { + // Loads waited message id on previous `Wait`-like syscall. + // Check `SyscallsInvocator::store_waited_message_id` method for implementation details. + let memory_layout = MemoryLayout::from(self.memory_size_bytes()); + vec![Instruction::I32Const(memory_layout.waited_message_id_ptr)] + } }; Ok(ParamInstructions(ret)) @@ -823,6 +831,29 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { } } + fn store_waited_message_id(&self, instructions: &mut Vec) { + let Some(gr_message_id_indexes_handle) = self + .syscalls_imports + .get(&InvocableSyscall::Loose(SyscallName::MessageId)) + .map(|&(_, call_indexes_handle)| call_indexes_handle as u32) + else { + // We automatically enable the `message_id` syscall import if the `wait` syscall is enabled in the config. + // If not, then we don't need to store the message ID. + return; + }; + + let memory_layout = MemoryLayout::from(self.memory_size_bytes()); + let start_offset = memory_layout.waited_message_id_ptr; + + let message_id_call = vec![ + // call `gsys::gr_message_id` storing message id at `start_offset` pointer. + Instruction::I32Const(start_offset), + Instruction::Call(gr_message_id_indexes_handle), + ]; + + instructions.splice(0..0, message_id_call); + } + /// Patches instructions of wait-syscalls to prevent deadlocks. fn limit_infinite_waits(&self, instructions: &mut Vec, waiting_probability: u32) { let MemoryLayout { diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index ec265941515..b832876633b 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -41,11 +41,14 @@ use gear_wasm_instrument::{ gas_metering::CustomConstantCostRules, parity_wasm::{self, elements::Module}, }; +use nonempty::nonempty; use proptest::prelude::*; use rand::{rngs::SmallRng, RngCore, SeedableRng}; use std::num::{NonZeroU32, NonZeroUsize}; const UNSTRUCTURED_SIZE: usize = 1_000_000; +const WASM_PAGE_SIZE: u32 = 64 * 1024; +const INITIAL_PAGES: u32 = 1; type Ext = gear_core_processor::Ext; @@ -234,11 +237,15 @@ fn test_avoid_waits_works() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - 0, - 0, + ExecuteParams { + // This test supposed to check if waiting probability works correctly, + // so we have to set `init_called flag` to make wait probability code reachable. + // And the second, we have to set waiting probability counter to non-zero value, + // because the wasm check looks like this `if *wait_called_ptr % waiting_probability == 0 { orig_wait_syscall(); }` + initial_memory_write: nonempty![set_init_called_flag(), set_wait_called_counter(1)] + .into(), + ..Default::default() + }, ); assert_eq!( @@ -247,6 +254,120 @@ fn test_avoid_waits_works() { ); } +#[test] +fn test_wait_stores_message_id() { + gear_utils::init_default_logger(); + + const EXPECTED_MSG_ID: u64 = 12345678; + + let mut rng = SmallRng::seed_from_u64(123); + let mut buf = vec![0; UNSTRUCTURED_SIZE]; + rng.fill_bytes(&mut buf); + let mut unstructured = Unstructured::new(&buf); + + let params_config = SyscallsParamsConfig::new().with_default_regular_config(); + + let mut injection_types = SyscallsInjectionTypes::all_never(); + injection_types.set(InvocableSyscall::Loose(SyscallName::Wait), 1, 1); + // Only for test, syscall `message_id` import added automatically when `wake` syscall is enabled in config. + injection_types.enable_syscall_import(InvocableSyscall::Loose(SyscallName::MessageId)); + + let syscalls_config_with_waiting_probabilty = + SyscallsConfigBuilder::new(injection_types.clone()) + .with_params_config(params_config.clone()) + .with_waiting_probability(NonZeroU32::new(4).unwrap()) + .build(); + + let syscalls_config_wo_waiting_probabilty = SyscallsConfigBuilder::new(injection_types) + .with_params_config(params_config) + .build(); + + // Test both configs: + for syscalls_config in [ + syscalls_config_with_waiting_probabilty, + syscalls_config_wo_waiting_probabilty, + ] { + let BackendReport { + termination_reason, + store, + memory, + .. + } = execute_wasm_with_custom_configs( + &mut unstructured, + syscalls_config, + ExecuteParams { + initial_memory_write: nonempty![set_init_called_flag(), set_wait_called_counter(0)] + .into(), + message_id: EXPECTED_MSG_ID, + ..Default::default() + }, + ); + + // It's Ok, message id is 32 bytes in size, but we use u64 for testing purposes. + let mut message_id = [0u8; 8]; + let waited_message_id_ptr = + MemoryLayout::from(WASM_PAGE_SIZE * INITIAL_PAGES).waited_message_id_ptr; + memory + .read(&store, waited_message_id_ptr as u32, &mut message_id) + .unwrap(); + assert_eq!(u64::from_le_bytes(message_id), EXPECTED_MSG_ID); + + assert!(matches!( + termination_reason, + TerminationReason::Actor(ActorTerminationReason::Wait(..)) + )); + } +} + +#[test] +fn test_wake_uses_stored_message_id() { + gear_utils::init_default_logger(); + + const EXPECTED_MSG_ID: u64 = 12345678; + + let mut rng = SmallRng::seed_from_u64(123); + let mut buf = vec![0; UNSTRUCTURED_SIZE]; + rng.fill_bytes(&mut buf); + let mut unstructured = Unstructured::new(&buf); + + let params_config = SyscallsParamsConfig::new() + .with_default_regular_config() + .with_ptr_rule(PtrParamAllowedValues::WaitedMessageId); + + let mut injection_types = SyscallsInjectionTypes::all_never(); + injection_types.set(InvocableSyscall::Loose(SyscallName::Wake), 1, 1); + let syscalls_config = SyscallsConfigBuilder::new(injection_types) + .with_params_config(params_config) + .build(); + + let BackendReport { + termination_reason, + mut store, + memory, + ext, + } = execute_wasm_with_custom_configs( + &mut unstructured, + syscalls_config, + ExecuteParams { + initial_memory_write: nonempty![set_waited_message_id(EXPECTED_MSG_ID)].into(), + ..Default::default() + }, + ); + + let info = ext.into_ext_info(&mut store, &memory).unwrap(); + let msg_id = info.awakening.first().unwrap().0; + let msg_id_bytes = msg_id.into_bytes(); + assert_eq!( + u64::from_le_bytes(msg_id_bytes[..8].try_into().unwrap()), + EXPECTED_MSG_ID + ); + + assert_eq!( + termination_reason, + TerminationReason::Actor(ActorTerminationReason::Success) + ); +} + #[test] fn test_source_as_address_param() { gear_utils::init_default_logger(); @@ -267,15 +388,8 @@ fn test_source_as_address_param() { .with_error_processing_config(ErrorProcessingConfig::All) .build(); - let backend_report = execute_wasm_with_custom_configs( - &mut unstructured, - syscalls_config, - None, - 1024, - false, - 0, - 0, - ); + let backend_report = + execute_wasm_with_custom_configs(&mut unstructured, syscalls_config, Default::default()); assert_eq!( backend_report.termination_reason, @@ -307,15 +421,8 @@ fn test_existing_address_as_address_param() { .with_error_processing_config(ErrorProcessingConfig::All) .build(); - let backend_report = execute_wasm_with_custom_configs( - &mut unstructured, - syscalls_config, - None, - 1024, - false, - 0, - 0, - ); + let backend_report = + execute_wasm_with_custom_configs(&mut unstructured, syscalls_config, Default::default()); assert_eq!( backend_report.termination_reason, @@ -366,11 +473,10 @@ fn test_msg_value_ptr() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - INITIAL_BALANCE, - 0, + ExecuteParams { + value: INITIAL_BALANCE, + ..Default::default() + }, ); assert_eq!( @@ -434,11 +540,10 @@ fn test_msg_value_ptr_dest() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - INITIAL_BALANCE, - 0, + ExecuteParams { + value: INITIAL_BALANCE, + ..Default::default() + }, ); assert_eq!( @@ -505,15 +610,8 @@ fn test_send_init_with_send() { .with_keeping_insertion_order(true) .build(); - let backend_report = execute_wasm_with_custom_configs( - &mut unstructured, - syscalls_config, - None, - 1024, - false, - 0, - 0, - ); + let backend_report = + execute_wasm_with_custom_configs(&mut unstructured, syscalls_config, Default::default()); assert_eq!( backend_report.termination_reason, @@ -546,11 +644,10 @@ fn test_reservation_id_with_value_ptr() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - 0, - 250_000_000_000, + ExecuteParams { + gas: 250_000_000_000, + ..Default::default() + }, ); assert_eq!( @@ -594,11 +691,10 @@ fn test_reservation_id_with_actor_id_and_value_ptr() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - 0, - 250_000_000_000, + ExecuteParams { + gas: 250_000_000_000, + ..Default::default() + }, ); assert_eq!( @@ -632,11 +728,10 @@ fn test_reservation_id_ptr() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - 0, - 250_000_000_000, + ExecuteParams { + gas: 250_000_000_000, + ..Default::default() + }, ); assert_eq!( @@ -683,11 +778,10 @@ fn test_code_id_with_value_ptr() { let backend_report = execute_wasm_with_custom_configs( &mut unstructured, syscalls_config, - None, - 1024, - false, - INITIAL_BALANCE, - 0, + ExecuteParams { + value: INITIAL_BALANCE, + ..Default::default() + }, ); assert_eq!( @@ -739,11 +833,12 @@ fn error_processing_works_for_fallible_syscalls() { .clone() .with_error_processing_config(ErrorProcessingConfig::All) .build(), - initial_memory_write.clone(), - 0, - true, - 0, - 0, + ExecuteParams { + initial_memory_write: initial_memory_write.clone(), + outgoing_limit: 0, + imitate_reply: true, + ..Default::default() + }, ) .termination_reason; @@ -758,11 +853,12 @@ fn error_processing_works_for_fallible_syscalls() { let termination_reason = execute_wasm_with_custom_configs( &mut unstructured2, syscalls_config_builder.build(), - initial_memory_write.clone(), - 0, - true, - 0, - 0, + ExecuteParams { + initial_memory_write: initial_memory_write.clone(), + outgoing_limit: 0, + imitate_reply: true, + ..Default::default() + }, ) .termination_reason; @@ -810,11 +906,7 @@ fn precise_syscalls_works() { .with_precise_syscalls_config(PreciseSyscallsConfig::new(3..=3, 3..=3)) .with_error_processing_config(ErrorProcessingConfig::All) .build(), - None, - 1024, - false, - 0, - 0, + Default::default(), ) .termination_reason; @@ -835,24 +927,72 @@ struct MemoryWrite { fn get_params_for_syscall_to_fail( _syscall: InvocableSyscall, -) -> (SyscallsParamsConfig, Option) { +) -> (SyscallsParamsConfig, Option>) { ( SyscallsParamsConfig::const_regular_params(i32::MAX as i64), None, ) } -fn execute_wasm_with_custom_configs( - unstructured: &mut Unstructured, - syscalls_config: SyscallsConfig, - initial_memory_write: Option, +fn set_init_called_flag() -> MemoryWrite { + let mem_layout = MemoryLayout::from(WASM_PAGE_SIZE * INITIAL_PAGES); + let offset = mem_layout.init_called_ptr as u32; + let content = 0x01u32.to_le_bytes().to_vec(); + + MemoryWrite { offset, content } +} + +fn set_wait_called_counter(counter: u8) -> MemoryWrite { + let mem_layout = MemoryLayout::from(WASM_PAGE_SIZE * INITIAL_PAGES); + let offset = mem_layout.wait_called_ptr as u32; + let content = vec![counter]; + + MemoryWrite { offset, content } +} + +fn set_waited_message_id(message_id: u64) -> MemoryWrite { + let mem_layout = MemoryLayout::from(WASM_PAGE_SIZE * INITIAL_PAGES); + let offset = mem_layout.waited_message_id_ptr as u32; + let content = message_id.to_le_bytes().to_vec(); + + MemoryWrite { offset, content } +} + +struct ExecuteParams { + initial_memory_write: Option>, outgoing_limit: u32, imitate_reply: bool, value: u128, gas: u64, + message_id: u64, +} + +impl Default for ExecuteParams { + fn default() -> Self { + Self { + initial_memory_write: None, + outgoing_limit: 1024, + imitate_reply: false, + value: 0, + gas: 0, + message_id: 0, + } + } +} + +fn execute_wasm_with_custom_configs( + unstructured: &mut Unstructured, + syscalls_config: SyscallsConfig, + ExecuteParams { + initial_memory_write, + outgoing_limit, + imitate_reply, + value, + gas, + message_id, + }: ExecuteParams, ) -> BackendReport { const PROGRAM_STORAGE_PREFIX: [u8; 32] = *b"execute_wasm_with_custom_configs"; - const INITIAL_PAGES: u16 = 1; gear_lazy_pages::init( LazyPagesVersion::Version1, @@ -864,7 +1004,7 @@ fn execute_wasm_with_custom_configs( let gear_config = ( GearWasmGeneratorConfigBuilder::new() .with_memory_config(MemoryPagesConfig { - initial_size: INITIAL_PAGES as u32, + initial_size: INITIAL_PAGES, ..MemoryPagesConfig::default() }) .with_syscalls_config(syscalls_config) @@ -894,7 +1034,7 @@ fn execute_wasm_with_custom_configs( let program_id = ProgramId::generate_from_user(code_id, b""); let incoming_message = IncomingMessage::new( - Default::default(), + message_id.into(), message_sender(), Default::default(), Default::default(), @@ -927,7 +1067,7 @@ fn execute_wasm_with_custom_configs( code.code(), DispatchKind::Init, vec![DispatchKind::Init].into_iter().collect(), - INITIAL_PAGES.into(), + (INITIAL_PAGES as u16).into(), ) .expect("Failed to create environment"); @@ -946,9 +1086,11 @@ fn execute_wasm_with_custom_configs( Default::default(), ); - if let Some(mem_write) = initial_memory_write { - mem.write(ctx, mem_write.offset, &mem_write.content) - .expect("Failed to write to memory"); + if let Some(mem_writes) = initial_memory_write { + for mem_write in mem_writes { + mem.write(ctx, mem_write.offset, &mem_write.content) + .expect("Failed to write to memory"); + } }; }) .expect("Failed to execute WASM module") diff --git a/utils/wasm-gen/src/wasm.rs b/utils/wasm-gen/src/wasm.rs index beff927b510..d8df5eb933b 100644 --- a/utils/wasm-gen/src/wasm.rs +++ b/utils/wasm-gen/src/wasm.rs @@ -270,6 +270,7 @@ def_memory_layout! { reservation_temp2_ptr: u32, reservation_flags_ptr: u32, reservation_array_ptr: [Hash; MemoryLayout::AMOUNT_OF_RESERVATIONS as _], + waited_message_id_ptr: Hash, } }