Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions core/processor/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,30 @@ impl ProcessorContext {
}
}

/// Message execution result info
#[derive(Debug)]
pub struct ExtInfo {
/// Gas amount left after execution.
pub gas_amount: GasAmount,
/// Gas reserver with updated reservations after execution.
pub gas_reserver: GasReserver,
/// System reservation context with current and previous reservations.
pub system_reservation_context: SystemReservationContext,
/// Whether allocations were changed during execution and final state of allocations if they were changed.
pub allocations: Option<IntervalsTree<WasmPage>>,
/// Data of accessed pages during execution.
pub pages_data: BTreeMap<GearPage, PageBuf>,
/// List of generated dispatches with their delay and optional reservation id.
pub generated_dispatches: Vec<(Dispatch, u32, Option<ReservationId>)>,
/// List of wakened messages with their id and delay until awakening.
pub awakening: Vec<(MessageId, u32)>,
/// List of reply deposits with message id and amount.
pub reply_deposits: Vec<(MessageId, u64)>,
/// Programs to create data.
pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ActorId)>>,
/// Executed message context store after execution.
pub context_store: ContextStore,
/// Whether reply was sent during execution.
pub reply_sent: bool,
}

Expand Down Expand Up @@ -1225,7 +1237,7 @@ impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
if let Some(reimbursement) = reimburse {
let current_gas_amount = self.gas_amount();

// Basically amount of the reseravtion and the cost for the hold duration.
// Basically amount of the reservation and the cost for the hold duration.
let reimbursement_amount = self.cost_for_reservation(amount, reimbursement.duration());
self.context
.gas_counter
Expand Down Expand Up @@ -2324,7 +2336,7 @@ mod tests {
.build(),
);

// Check all the reseravations are in "existing" state.
// Check all the reservations are in "existing" state.
assert!(
ext.context
.gas_reserver
Expand Down
2 changes: 1 addition & 1 deletion core/processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ mod processing;

pub use context::{ProcessExecutionContext, SystemReservationContext};
pub use ext::{
AllocExtError, Ext, FallibleExtError, ProcessorContext, ProcessorExternalities,
AllocExtError, Ext, ExtInfo, FallibleExtError, ProcessorContext, ProcessorExternalities,
UnrecoverableExtError,
};
pub use handler::handle_journal;
Expand Down
172 changes: 170 additions & 2 deletions ethexe/processor/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::*;
use anyhow::{Result, anyhow};
use ethexe_common::{
DEFAULT_BLOCK_GAS_LIMIT, OUTGOING_MESSAGES_SOFT_LIMIT, PROGRAM_MODIFICATIONS_SOFT_LIMIT,
SimpleBlockData,
ScheduledTask, SimpleBlockData,
db::*,
events::{
BlockRequestEvent, MirrorRequestEvent, RouterRequestEvent,
Expand All @@ -29,7 +29,7 @@ use ethexe_common::{
},
mock::*,
};
use ethexe_runtime_common::{RUNTIME_ID, state::MessageQueue};
use ethexe_runtime_common::{RUNTIME_ID, WAIT_UP_TO_SAFE_DURATION, state::MessageQueue};
use gear_core::{
ids::prelude::CodeIdExt,
message::{ErrorReplyReason, ReplyCode, SuccessReplyReason},
Expand Down Expand Up @@ -133,6 +133,47 @@ mod utils {
salt: H256::random().0.to_vec().try_into().unwrap(),
}
}

pub async fn simple_init_test(code: impl AsRef<[u8]>) -> InBlockTransitions {
let (mut processor, chain, [code_id]) = setup_test_env_and_load_codes([code.as_ref()]);
let block1 = chain.blocks[1].to_simple();

let mut handler = setup_handler(processor.db.clone(), block1);
let actor_id = ActorId::from(0x10000);
handler
.handle_router_event(RouterRequestEvent::ProgramCreated(ProgramCreatedEvent {
actor_id,
code_id,
}))
.expect("failed to create new program");
handler
.handle_mirror_event(
actor_id,
MirrorRequestEvent::ExecutableBalanceTopUpRequested(
ExecutableBalanceTopUpRequestedEvent {
value: 350_000_000_000,
},
),
)
.expect("failed to top up balance");
handler
.handle_mirror_event(
actor_id,
MirrorRequestEvent::MessageQueueingRequested(MessageQueueingRequestedEvent {
id: MessageId::from(1),
source: ActorId::from(10),
payload: vec![],
value: 0,
call_reply: false,
}),
)
.expect("failed to queue message");

processor
.process_queues(handler.transitions, block1, DEFAULT_BLOCK_GAS_LIMIT, None)
.await
.unwrap()
}
}

#[tokio::test(flavor = "multi_thread")]
Expand Down Expand Up @@ -1382,3 +1423,130 @@ async fn insufficient_executable_balance_still_charged() {
let exec_balance_after = handler.program_state(actor_id).executable_balance;
assert!(exec_balance_after < INSUFFICIENT_EXECUTABLE_BALANCE);
}

#[tokio::test(flavor = "multi_thread")]
async fn call_gr_wait_is_forbidden() {
init_logger();

let wat = r#"
(module
(import "env" "memory" (memory 0))
(import "env" "gr_wait" (func $wait))
(export "init" (func $init))
(func $init call $wait)
)
"#;

let transitions = simple_init_test(wat_to_wasm(wat).1).await;
let reply_code = transitions.current_messages()[0]
.1
.reply_details
.expect("must be reply")
.to_reply_code();
assert_eq!(
reply_code,
ReplyCode::Error(ErrorReplyReason::Execution(
SimpleExecutionError::BackendError
)),
"Forbidden syscall should return backend error"
);
}

#[tokio::test(flavor = "multi_thread")]
async fn call_wake_with_delay_is_unsupported() {
init_logger();

let get_wat = |delay: u32| {
format!(
r#"
(module
(import "env" "memory" (memory 1))
(import "env" "gr_wake" (func $wake (param i32 i32 i32)))
(export "init" (func $init))
(func $init
(call $wake (i32.const 0x0) (i32.const {delay}) (i32.const 0x0))
(if (i32.eqz (i32.load (i32.const 0x0)))
(then nop)
(else unreachable)
)
)
)
"#
)
};

// with delay != 0
let wat = get_wat(10);
let transitions = simple_init_test(wat_to_wasm(&wat).1).await;
let reply_code = transitions.current_messages()[0]
.1
.reply_details
.expect("must be reply")
.to_reply_code();
assert_eq!(
reply_code,
ReplyCode::Error(ErrorReplyReason::Execution(
SimpleExecutionError::UnreachableInstruction
)),
"Calling gr_wake with non-zero delay should lead to unreachable instruction"
);

// with delay == 0
let wat = get_wat(0);
let transitions = simple_init_test(wat_to_wasm(&wat).1).await;
let reply_code = transitions.current_messages()[0]
.1
.reply_details
.expect("must be reply")
.to_reply_code();
assert_eq!(reply_code, ReplyCode::Success(SuccessReplyReason::Auto));
}

#[tokio::test(flavor = "multi_thread")]
async fn call_wait_up_to_with_huge_duration() {
init_logger();

let get_wat = |duration: u32| {
format!(
r#"
(module
(import "env" "memory" (memory 0))
(import "env" "gr_wait_up_to" (func $wait_up_to (param i32)))
(export "init" (func $init))
(func $init
(call $wait_up_to (i32.const {duration}))
)
)
"#
)
};

// Huge duration
let wat = get_wat(0xFFFFFFFF);
let transitions = simple_init_test(wat_to_wasm(&wat).1).await;
let block_height = transitions.block_height();
let FinalizedBlockTransitions { schedule, .. } = transitions.finalize();
let (block, tasks) = schedule.into_iter().next().unwrap();
assert_eq!(
block,
block_height + WAIT_UP_TO_SAFE_DURATION,
"Duration should be capped to WAIT_UP_TO_SAFE_DURATION"
);
let task = tasks.into_iter().next().unwrap();
assert!(matches!(task, ScheduledTask::WakeMessage(_, _)));

// Normal duration
let duration = WAIT_UP_TO_SAFE_DURATION + 20;
let wat = get_wat(duration);
let transitions = simple_init_test(wat_to_wasm(&wat).1).await;
let block_height = transitions.block_height();
let FinalizedBlockTransitions { schedule, .. } = transitions.finalize();
let (block, tasks) = schedule.into_iter().next().unwrap();
assert_eq!(
block,
block_height + duration,
"Duration should not be capped if msg has enough gas to cover it"
);
let task = tasks.into_iter().next().unwrap();
assert!(matches!(task, ScheduledTask::WakeMessage(_, _)));
}
2 changes: 2 additions & 0 deletions ethexe/runtime/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ gear-core.workspace = true
gprimitives.workspace = true
gsys.workspace = true
gear-core-errors.workspace = true
gear-core-backend.workspace = true

anyhow.workspace = true
parity-scale-codec = { workspace = true, features = ["derive"] }
Expand All @@ -25,6 +26,7 @@ derive_more.workspace = true
auto_impl.workspace = true
serde = { workspace = true, features = ["derive"], optional = true }
gear-workspace-hack.workspace = true
delegate.workspace = true

[features]
default = ["std"]
Expand Down
Loading
Loading