1use crate::{
20 common::{
21 ActorExecutionErrorReplyReason, DispatchOutcome, DispatchResult, DispatchResultKind,
22 ExecutionError, JournalNote, SystemExecutionError, WasmExecutionContext,
23 },
24 configs::{BlockConfig, ExecutionSettings},
25 context::*,
26 executor,
27 ext::ProcessorExternalities,
28 precharge::SuccessfulDispatchResultKind,
29};
30use alloc::{
31 format,
32 string::{String, ToString},
33 vec::Vec,
34};
35use gear_core::{
36 env::Externalities,
37 ids::{prelude::*, MessageId, ProgramId},
38 message::{ContextSettings, DispatchKind, IncomingDispatch, ReplyMessage, StoredDispatch},
39 reservation::GasReservationState,
40};
41use gear_core_backend::{
42 error::{BackendAllocSyscallError, BackendSyscallError, RunFallibleError},
43 BackendExternalities,
44};
45use gear_core_errors::{ErrorReplyReason, SignalCode};
46
47pub fn process<Ext>(
49 block_config: &BlockConfig,
50 execution_context: ProcessExecutionContext,
51 random_data: (Vec<u8>, u32),
52) -> Result<Vec<JournalNote>, SystemExecutionError>
53where
54 Ext: ProcessorExternalities + BackendExternalities + Send + 'static,
55 <Ext as Externalities>::AllocError:
56 BackendAllocSyscallError<ExtError = Ext::UnrecoverableError>,
57 RunFallibleError: From<Ext::FallibleError>,
58 <Ext as Externalities>::UnrecoverableError: BackendSyscallError,
59{
60 use crate::precharge::SuccessfulDispatchResultKind::*;
61
62 let BlockConfig {
63 block_info,
64 performance_multiplier,
65 forbidden_funcs,
66 reserve_for,
67 gas_multiplier,
68 costs,
69 existential_deposit,
70 mailbox_threshold,
71 max_pages,
72 outgoing_limit,
73 outgoing_bytes_limit,
74 ..
75 } = block_config.clone();
76
77 let execution_settings = ExecutionSettings {
78 block_info,
79 performance_multiplier,
80 existential_deposit,
81 mailbox_threshold,
82 max_pages,
83 ext_costs: costs.ext,
84 lazy_pages_costs: costs.lazy_pages,
85 forbidden_funcs,
86 reserve_for,
87 random_data,
88 gas_multiplier,
89 };
90
91 let dispatch = execution_context.dispatch;
92 let balance = execution_context.balance;
93 let program_id = execution_context.program.id;
94 let initial_reservations_amount = execution_context.gas_reserver.states().len();
95
96 let execution_context = WasmExecutionContext {
97 gas_counter: execution_context.gas_counter,
98 gas_allowance_counter: execution_context.gas_allowance_counter,
99 gas_reserver: execution_context.gas_reserver,
100 program: execution_context.program,
101 memory_size: execution_context.memory_size,
102 };
103
104 let msg_ctx_settings = ContextSettings {
116 sending_fee: costs.write.cost_for(2.into()),
117 scheduled_sending_fee: costs.write.cost_for(4.into()),
118 waiting_fee: costs.write.cost_for(3.into()),
119 waking_fee: costs.write.cost_for(2.into()),
120 reservation_fee: costs.write.cost_for(2.into()),
121 outgoing_limit,
122 outgoing_bytes_limit,
123 };
124
125 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
130
131 let exec_result = executor::execute_wasm::<Ext>(
132 balance,
133 dispatch.clone(),
134 execution_context,
135 execution_settings,
136 msg_ctx_settings,
137 )
138 .map_err(|err| {
139 log::debug!("Wasm execution error: {}", err);
140 err
141 });
142
143 match exec_result {
144 Ok(res) => {
145 match res.kind {
146 DispatchResultKind::Success
147 | DispatchResultKind::Wait(_, _)
148 | DispatchResultKind::Exit(_) => {
149 debug_assert!(
153 res.context_store.system_reservation()
154 >= system_reservation_ctx.previous_reservation
155 );
156 debug_assert!(
157 system_reservation_ctx.previous_reservation
158 == res.system_reservation_context.previous_reservation
159 );
160 debug_assert!(res
161 .gas_reserver
162 .as_ref()
163 .map(|reserver| initial_reservations_amount <= reserver.states().len())
164 .unwrap_or(true));
165 }
166 _ => (),
168 }
169 Ok(match res.kind {
170 DispatchResultKind::Trap(reason) => process_execution_error(
171 res.dispatch,
172 program_id,
173 res.gas_amount.burned(),
174 res.system_reservation_context,
175 ActorExecutionErrorReplyReason::Trap(reason),
176 ),
177
178 DispatchResultKind::Success => process_success(Success, res),
179 DispatchResultKind::Wait(duration, ref waited_type) => {
180 process_success(Wait(duration, waited_type.clone()), res)
181 }
182 DispatchResultKind::Exit(value_destination) => {
183 process_success(Exit(value_destination), res)
184 }
185 DispatchResultKind::GasAllowanceExceed => {
186 process_allowance_exceed(dispatch, program_id, res.gas_amount.burned())
187 }
188 })
189 }
190 Err(ExecutionError::Actor(e)) => Ok(process_execution_error(
191 dispatch,
192 program_id,
193 e.gas_amount.burned(),
194 system_reservation_ctx,
195 e.reason,
196 )),
197 Err(ExecutionError::System(e)) => Err(e),
198 }
199}
200
201enum ProcessErrorCase {
202 NonExecutable,
204 ExecutionFailed(ActorExecutionErrorReplyReason),
206 ReinstrumentationFailed,
208}
209
210impl ProcessErrorCase {
211 pub fn to_reason_and_payload(&self) -> (ErrorReplyReason, String) {
212 match self {
213 ProcessErrorCase::NonExecutable => {
214 let reason = ErrorReplyReason::InactiveActor;
215 (reason, reason.to_string())
216 }
217 ProcessErrorCase::ExecutionFailed(reason) => {
218 (reason.as_simple().into(), reason.to_string())
219 }
220 ProcessErrorCase::ReinstrumentationFailed => {
221 let err = ErrorReplyReason::ReinstrumentationFailure;
222 (err, err.to_string())
223 }
224 }
225 }
226}
227
228fn process_error(
229 dispatch: IncomingDispatch,
230 program_id: ProgramId,
231 gas_burned: u64,
232 system_reservation_ctx: SystemReservationContext,
233 case: ProcessErrorCase,
234) -> Vec<JournalNote> {
235 let mut journal = Vec::new();
236
237 let message_id = dispatch.id();
238 let origin = dispatch.source();
239 let value = dispatch.value();
240
241 journal.push(JournalNote::GasBurned {
242 message_id,
243 amount: gas_burned,
244 });
245
246 if dispatch.context().is_none() && value != 0 {
254 journal.push(JournalNote::SendValue {
256 from: origin,
257 to: None,
258 value,
259 });
260 }
261
262 if let Some(amount) = system_reservation_ctx.current_reservation {
263 journal.push(JournalNote::SystemReserveGas { message_id, amount });
264 }
265
266 if let ProcessErrorCase::ExecutionFailed(reason) = &case {
267 if system_reservation_ctx.has_any()
269 && !dispatch.is_error_reply()
270 && !matches!(dispatch.kind(), DispatchKind::Signal | DispatchKind::Init)
271 {
272 journal.push(JournalNote::SendSignal {
273 message_id,
274 destination: program_id,
275 code: SignalCode::Execution(reason.as_simple()),
276 });
277 }
278 }
279
280 if system_reservation_ctx.has_any() {
281 journal.push(JournalNote::SystemUnreserveGas { message_id });
282 }
283
284 if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
285 let (err, err_payload) = case.to_reason_and_payload();
286
287 let err_payload = err_payload.into_bytes().try_into().unwrap_or_else(|_| {
289 let (_, err_payload) = case.to_reason_and_payload();
290 let err_msg =
291 format!("process_error: Error message is too big. Message id - {message_id}, error payload - {err_payload}",
292 );
293
294 log::error!("{err_msg}");
295 unreachable!("{err_msg}")
296 });
297
298 let dispatch = ReplyMessage::system(dispatch.id(), err_payload, err).into_dispatch(
305 program_id,
306 dispatch.source(),
307 dispatch.id(),
308 );
309
310 journal.push(JournalNote::SendDispatch {
311 message_id,
312 dispatch,
313 delay: 0,
314 reservation: None,
315 });
316 }
317
318 let outcome = match case {
319 ProcessErrorCase::ExecutionFailed { .. } | ProcessErrorCase::ReinstrumentationFailed => {
320 let (_, err_payload) = case.to_reason_and_payload();
321 match dispatch.kind() {
322 DispatchKind::Init => DispatchOutcome::InitFailure {
323 program_id,
324 origin,
325 reason: err_payload,
326 },
327 _ => DispatchOutcome::MessageTrap {
328 program_id,
329 trap: err_payload,
330 },
331 }
332 }
333 ProcessErrorCase::NonExecutable => DispatchOutcome::NoExecution,
334 };
335
336 journal.push(JournalNote::MessageDispatched {
337 message_id,
338 source: origin,
339 outcome,
340 });
341 journal.push(JournalNote::MessageConsumed(message_id));
342
343 journal
344}
345
346pub fn process_execution_error(
348 dispatch: IncomingDispatch,
349 program_id: ProgramId,
350 gas_burned: u64,
351 system_reservation_ctx: SystemReservationContext,
352 err: impl Into<ActorExecutionErrorReplyReason>,
353) -> Vec<JournalNote> {
354 process_error(
355 dispatch,
356 program_id,
357 gas_burned,
358 system_reservation_ctx,
359 ProcessErrorCase::ExecutionFailed(err.into()),
360 )
361}
362
363pub fn process_reinstrumentation_error(
365 context: ContextChargedForInstrumentation,
366) -> Vec<JournalNote> {
367 let dispatch = context.data.dispatch;
368 let program_id = context.data.destination_id;
369 let gas_burned = context.data.gas_counter.burned();
370 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
371
372 process_error(
373 dispatch,
374 program_id,
375 gas_burned,
376 system_reservation_ctx,
377 ProcessErrorCase::ReinstrumentationFailed,
378 )
379}
380
381pub fn process_non_executable(context: ContextChargedForProgram) -> Vec<JournalNote> {
383 let ContextChargedForProgram {
384 dispatch,
385 gas_counter,
386 destination_id,
387 ..
388 } = context;
389
390 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
391
392 process_error(
393 dispatch,
394 destination_id,
395 gas_counter.burned(),
396 system_reservation_ctx,
397 ProcessErrorCase::NonExecutable,
398 )
399}
400
401pub fn process_success(
403 kind: SuccessfulDispatchResultKind,
404 dispatch_result: DispatchResult,
405) -> Vec<JournalNote> {
406 use crate::precharge::SuccessfulDispatchResultKind::*;
407
408 let DispatchResult {
409 dispatch,
410 generated_dispatches,
411 awakening,
412 program_candidates,
413 gas_amount,
414 gas_reserver,
415 system_reservation_context,
416 page_update,
417 program_id,
418 context_store,
419 allocations,
420 reply_deposits,
421 reply_sent,
422 ..
423 } = dispatch_result;
424
425 let mut journal = Vec::new();
426
427 let message_id = dispatch.id();
428 let origin = dispatch.source();
429 let value = dispatch.value();
430
431 journal.push(JournalNote::GasBurned {
432 message_id,
433 amount: gas_amount.burned(),
434 });
435
436 if let Some(gas_reserver) = gas_reserver {
437 journal.extend(gas_reserver.states().iter().flat_map(
438 |(&reservation_id, &state)| match state {
439 GasReservationState::Exists { .. } => None,
440 GasReservationState::Created {
441 amount, duration, ..
442 } => Some(JournalNote::ReserveGas {
443 message_id,
444 reservation_id,
445 program_id,
446 amount,
447 duration,
448 }),
449 GasReservationState::Removed { expiration } => Some(JournalNote::UnreserveGas {
450 reservation_id,
451 program_id,
452 expiration,
453 }),
454 },
455 ));
456
457 journal.push(JournalNote::UpdateGasReservations {
458 program_id,
459 reserver: gas_reserver,
460 });
461 }
462
463 if let Some(amount) = system_reservation_context.current_reservation {
464 journal.push(JournalNote::SystemReserveGas { message_id, amount });
465 }
466
467 if dispatch.context().is_none() && value != 0 {
475 journal.push(JournalNote::SendValue {
477 from: origin,
478 to: Some(program_id),
479 value,
480 });
481 }
482
483 for (code_id, candidates) in program_candidates {
485 journal.push(JournalNote::StoreNewPrograms {
486 program_id,
487 code_id,
488 candidates,
489 });
490 }
491
492 if matches!(kind, SuccessfulDispatchResultKind::Success)
494 && !reply_sent
495 && !dispatch.is_reply()
496 && dispatch.kind() != DispatchKind::Signal
497 {
498 let auto_reply = ReplyMessage::auto(dispatch.id()).into_dispatch(
499 program_id,
500 dispatch.source(),
501 dispatch.id(),
502 );
503
504 journal.push(JournalNote::SendDispatch {
505 message_id,
506 dispatch: auto_reply,
507 delay: 0,
508 reservation: None,
509 });
510 }
511
512 for (message_id_sent, amount) in reply_deposits {
513 journal.push(JournalNote::ReplyDeposit {
514 message_id,
515 future_reply_id: MessageId::generate_reply(message_id_sent),
516 amount,
517 });
518 }
519
520 for (dispatch, delay, reservation) in generated_dispatches {
521 journal.push(JournalNote::SendDispatch {
522 message_id,
523 dispatch,
524 delay,
525 reservation,
526 });
527 }
528
529 for (awakening_id, delay) in awakening {
530 journal.push(JournalNote::WakeMessage {
531 message_id,
532 program_id,
533 awakening_id,
534 delay,
535 });
536 }
537
538 for (page_number, data) in page_update {
539 journal.push(JournalNote::UpdatePage {
540 program_id,
541 page_number,
542 data,
543 })
544 }
545
546 if let Some(allocations) = allocations {
547 journal.push(JournalNote::UpdateAllocations {
548 program_id,
549 allocations,
550 });
551 }
552
553 let outcome = match kind {
554 Wait(duration, waited_type) => {
555 journal.push(JournalNote::WaitDispatch {
556 dispatch: dispatch.into_stored(program_id, context_store),
557 duration,
558 waited_type,
559 });
560
561 return journal;
562 }
563 Success => match dispatch.kind() {
564 DispatchKind::Init => DispatchOutcome::InitSuccess { program_id },
565 _ => DispatchOutcome::Success,
566 },
567 Exit(value_destination) => {
568 journal.push(JournalNote::ExitDispatch {
569 id_exited: program_id,
570 value_destination,
571 });
572
573 DispatchOutcome::Exit { program_id }
574 }
575 };
576
577 if system_reservation_context.has_any() {
578 journal.push(JournalNote::SystemUnreserveGas { message_id });
579 }
580
581 journal.push(JournalNote::MessageDispatched {
582 message_id,
583 source: origin,
584 outcome,
585 });
586 journal.push(JournalNote::MessageConsumed(message_id));
587 journal
588}
589
590pub fn process_allowance_exceed(
592 dispatch: IncomingDispatch,
593 program_id: ProgramId,
594 gas_burned: u64,
595) -> Vec<JournalNote> {
596 let mut journal = Vec::with_capacity(1);
597
598 let (kind, message, opt_context) = dispatch.into_parts();
599
600 let dispatch = StoredDispatch::new(kind, message.into_stored(program_id), opt_context);
601
602 journal.push(JournalNote::StopProcessing {
603 dispatch,
604 gas_burned,
605 });
606
607 journal
608}