1use crate::{
20 ContextCharged, ForCodeMetadata, ForInstrumentedCode, ForProgram,
21 common::{
22 ActorExecutionErrorReplyReason, DispatchOutcome, DispatchResult, DispatchResultKind,
23 ExecutionError, JournalNote, SuccessfulDispatchResultKind, SystemExecutionError,
24 WasmExecutionContext,
25 },
26 configs::{BlockConfig, ExecutionSettings},
27 context::*,
28 executor,
29 ext::ProcessorExternalities,
30};
31use alloc::{string::ToString, vec::Vec};
32use core::{fmt, fmt::Formatter};
33use gear_core::{
34 buffer::Payload,
35 env::Externalities,
36 ids::{ActorId, MessageId, prelude::*},
37 limited::LimitedVecError,
38 message::{ContextSettings, DispatchKind, IncomingDispatch, ReplyMessage, StoredDispatch},
39 reservation::GasReservationState,
40};
41use gear_core_backend::{
42 BackendExternalities,
43 error::{BackendAllocSyscallError, BackendSyscallError, RunFallibleError, TrapExplanation},
44};
45use gear_core_errors::{ErrorReplyReason, SignalCode, SimpleUnavailableActorError};
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::common::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.db.write.cost_for(2.into()),
117 scheduled_sending_fee: costs.db.write.cost_for(4.into()),
118 waiting_fee: costs.db.write.cost_for(3.into()),
119 waking_fee: costs.db.write.cost_for(2.into()),
120 reservation_fee: costs.db.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!(
161 res.gas_reserver
162 .as_ref()
163 .map(|reserver| initial_reservations_amount <= reserver.states().len())
164 .unwrap_or(true)
165 );
166 }
167 _ => (),
169 }
170 Ok(match res.kind {
171 DispatchResultKind::Trap(reason) => process_execution_error(
172 dispatch,
173 program_id,
174 res.gas_amount.burned(),
175 res.system_reservation_context,
176 ActorExecutionErrorReplyReason::Trap(reason),
177 ),
178
179 DispatchResultKind::Success => process_success(Success, res, dispatch),
180 DispatchResultKind::Wait(duration, ref waited_type) => {
181 process_success(Wait(duration, waited_type.clone()), res, dispatch)
182 }
183 DispatchResultKind::Exit(value_destination) => {
184 process_success(Exit(value_destination), res, dispatch)
185 }
186 DispatchResultKind::GasAllowanceExceed => {
187 process_allowance_exceed(dispatch, program_id, res.gas_amount.burned())
188 }
189 })
190 }
191 Err(ExecutionError::Actor(e)) => Ok(process_execution_error(
192 dispatch,
193 program_id,
194 e.gas_amount.burned(),
195 system_reservation_ctx,
196 e.reason,
197 )),
198 Err(ExecutionError::System(e)) => Err(e),
199 }
200}
201
202enum ProcessErrorCase {
203 ProgramExited {
205 inheritor: ActorId,
207 },
208 FailedInit,
210 Uninitialized,
212 CodeNotExists,
214 ReinstrumentationFailed,
216 ExecutionFailed(ActorExecutionErrorReplyReason),
218}
219
220impl fmt::Display for ProcessErrorCase {
221 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
222 match self {
223 ProcessErrorCase::ExecutionFailed(reason) => fmt::Display::fmt(reason, f),
224 this => fmt::Display::fmt(&this.to_reason(), f),
225 }
226 }
227}
228
229impl ProcessErrorCase {
230 fn to_reason(&self) -> ErrorReplyReason {
231 match self {
232 ProcessErrorCase::ProgramExited { .. } => {
233 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited)
234 }
235 ProcessErrorCase::FailedInit => ErrorReplyReason::UnavailableActor(
236 SimpleUnavailableActorError::InitializationFailure,
237 ),
238 ProcessErrorCase::Uninitialized => {
239 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::Uninitialized)
240 }
241 ProcessErrorCase::CodeNotExists => {
242 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramNotCreated)
243 }
244 ProcessErrorCase::ReinstrumentationFailed => ErrorReplyReason::UnavailableActor(
245 SimpleUnavailableActorError::ReinstrumentationFailure,
246 ),
247 ProcessErrorCase::ExecutionFailed(reason) => reason.as_simple().into(),
248 }
249 }
250
251 fn to_payload(&self) -> Payload {
253 match self {
254 ProcessErrorCase::ProgramExited { inheritor } => {
255 const _: () = assert!(size_of::<ActorId>() <= Payload::MAX_LEN);
256 inheritor
257 .into_bytes()
258 .to_vec()
259 .try_into()
260 .unwrap_or_else(|LimitedVecError| {
261 unreachable!("`ActorId` is always smaller than maximum payload size")
262 })
263 }
264 ProcessErrorCase::ExecutionFailed(ActorExecutionErrorReplyReason::Trap(
265 TrapExplanation::Panic(buf),
266 )) => buf.inner().clone(),
267 _ => Payload::default(),
268 }
269 }
270}
271
272fn process_error(
273 dispatch: IncomingDispatch,
274 program_id: ActorId,
275 gas_burned: u64,
276 system_reservation_ctx: SystemReservationContext,
277 case: ProcessErrorCase,
278) -> Vec<JournalNote> {
279 let mut journal = Vec::new();
280
281 let message_id = dispatch.id();
282 let origin = dispatch.source();
283 let value = dispatch.value();
284
285 journal.push(JournalNote::GasBurned {
286 message_id,
287 amount: gas_burned,
288 is_panic: matches!(
289 case,
290 ProcessErrorCase::ExecutionFailed(ActorExecutionErrorReplyReason::Trap(
291 TrapExplanation::Panic(_)
292 ))
293 ),
294 });
295
296 let to_send_reply = !matches!(dispatch.kind(), DispatchKind::Reply | DispatchKind::Signal);
297
298 if dispatch.context().is_none() && value != 0 {
306 journal.push(JournalNote::SendValue {
308 from: origin,
309 to: program_id,
310 value,
311 locked: to_send_reply,
314 });
315 }
316
317 if let Some(amount) = system_reservation_ctx.current_reservation {
318 journal.push(JournalNote::SystemReserveGas { message_id, amount });
319 }
320
321 if let ProcessErrorCase::ExecutionFailed(reason) = &case {
322 if system_reservation_ctx.has_any()
324 && !dispatch.is_error_reply()
325 && !matches!(dispatch.kind(), DispatchKind::Signal | DispatchKind::Init)
326 {
327 journal.push(JournalNote::SendSignal {
328 message_id,
329 destination: program_id,
330 code: SignalCode::Execution(reason.as_simple()),
331 });
332 }
333 }
334
335 if system_reservation_ctx.has_any() {
336 journal.push(JournalNote::SystemUnreserveGas { message_id });
337 }
338
339 if to_send_reply {
340 let err = case.to_reason();
341 let err_payload = case.to_payload();
342
343 let value = if dispatch.context().is_none() {
344 value
345 } else {
346 0
347 };
348
349 let dispatch = ReplyMessage::system(dispatch.id(), err_payload, value, err).into_dispatch(
356 program_id,
357 dispatch.source(),
358 dispatch.id(),
359 );
360
361 journal.push(JournalNote::SendDispatch {
362 message_id,
363 dispatch,
364 delay: 0,
365 reservation: None,
366 });
367 }
368
369 let outcome = match case {
370 ProcessErrorCase::ExecutionFailed { .. } | ProcessErrorCase::ReinstrumentationFailed => {
371 let err_msg = case.to_string();
372 match dispatch.kind() {
373 DispatchKind::Init => DispatchOutcome::InitFailure {
374 program_id,
375 origin,
376 reason: err_msg,
377 },
378 _ => DispatchOutcome::MessageTrap {
379 program_id,
380 trap: err_msg,
381 },
382 }
383 }
384 ProcessErrorCase::ProgramExited { .. }
385 | ProcessErrorCase::FailedInit
386 | ProcessErrorCase::Uninitialized
387 | ProcessErrorCase::CodeNotExists => DispatchOutcome::NoExecution,
388 };
389
390 journal.push(JournalNote::MessageDispatched {
391 message_id,
392 source: origin,
393 outcome,
394 });
395 journal.push(JournalNote::MessageConsumed(message_id));
396
397 journal
398}
399
400pub fn process_execution_error(
402 dispatch: IncomingDispatch,
403 program_id: ActorId,
404 gas_burned: u64,
405 system_reservation_ctx: SystemReservationContext,
406 err: impl Into<ActorExecutionErrorReplyReason>,
407) -> Vec<JournalNote> {
408 process_error(
409 dispatch,
410 program_id,
411 gas_burned,
412 system_reservation_ctx,
413 ProcessErrorCase::ExecutionFailed(err.into()),
414 )
415}
416
417pub fn process_program_exited(
419 context: ContextCharged<ForProgram>,
420 inheritor: ActorId,
421) -> Vec<JournalNote> {
422 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
423
424 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
425
426 process_error(
427 dispatch,
428 destination_id,
429 gas_counter.burned(),
430 system_reservation_ctx,
431 ProcessErrorCase::ProgramExited { inheritor },
432 )
433}
434
435pub fn process_failed_init(context: ContextCharged<ForProgram>) -> Vec<JournalNote> {
437 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
438
439 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
440
441 process_error(
442 dispatch,
443 destination_id,
444 gas_counter.burned(),
445 system_reservation_ctx,
446 ProcessErrorCase::FailedInit,
447 )
448}
449
450pub fn process_uninitialized(context: ContextCharged<ForProgram>) -> Vec<JournalNote> {
452 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
453
454 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
455
456 process_error(
457 dispatch,
458 destination_id,
459 gas_counter.burned(),
460 system_reservation_ctx,
461 ProcessErrorCase::Uninitialized,
462 )
463}
464
465pub fn process_code_not_exists(context: ContextCharged<ForProgram>) -> Vec<JournalNote> {
467 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
468
469 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
470
471 process_error(
472 dispatch,
473 destination_id,
474 gas_counter.burned(),
475 system_reservation_ctx,
476 ProcessErrorCase::CodeNotExists,
477 )
478}
479
480pub fn process_reinstrumentation_error(
482 context: ContextCharged<ForInstrumentedCode>,
483) -> Vec<JournalNote> {
484 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
485
486 let gas_burned = gas_counter.burned();
487 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
488
489 process_error(
490 dispatch,
491 destination_id,
492 gas_burned,
493 system_reservation_ctx,
494 ProcessErrorCase::ReinstrumentationFailed,
495 )
496}
497
498pub fn process_instrumentation_failed(
500 context: ContextCharged<ForCodeMetadata>,
501) -> Vec<JournalNote> {
502 let (destination_id, dispatch, gas_counter, _) = context.into_parts();
503
504 let gas_burned = gas_counter.burned();
505 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
506
507 process_error(
508 dispatch,
509 destination_id,
510 gas_burned,
511 system_reservation_ctx,
512 ProcessErrorCase::ReinstrumentationFailed,
513 )
514}
515
516pub fn process_success(
518 kind: SuccessfulDispatchResultKind,
519 dispatch_result: DispatchResult,
520 dispatch: IncomingDispatch,
521) -> Vec<JournalNote> {
522 use crate::common::SuccessfulDispatchResultKind::*;
523
524 let DispatchResult {
525 generated_dispatches,
526 awakening,
527 program_candidates,
528 gas_amount,
529 gas_reserver,
530 system_reservation_context,
531 page_update,
532 program_id,
533 context_store,
534 allocations,
535 reply_deposits,
536 reply_sent,
537 ..
538 } = dispatch_result;
539
540 let mut journal = Vec::new();
541
542 let message_id = dispatch.id();
543 let origin = dispatch.source();
544 let value = dispatch.value();
545
546 journal.push(JournalNote::GasBurned {
547 message_id,
548 amount: gas_amount.burned(),
549 is_panic: false,
550 });
551
552 if let Some(gas_reserver) = gas_reserver {
553 journal.extend(gas_reserver.states().iter().flat_map(
554 |(&reservation_id, &state)| match state {
555 GasReservationState::Exists { .. } => None,
556 GasReservationState::Created {
557 amount, duration, ..
558 } => Some(JournalNote::ReserveGas {
559 message_id,
560 reservation_id,
561 program_id,
562 amount,
563 duration,
564 }),
565 GasReservationState::Removed { expiration } => Some(JournalNote::UnreserveGas {
566 reservation_id,
567 program_id,
568 expiration,
569 }),
570 },
571 ));
572
573 journal.push(JournalNote::UpdateGasReservations {
574 program_id,
575 reserver: gas_reserver,
576 });
577 }
578
579 if let Some(amount) = system_reservation_context.current_reservation {
580 journal.push(JournalNote::SystemReserveGas { message_id, amount });
581 }
582
583 if dispatch.context().is_none() && value != 0 {
591 journal.push(JournalNote::SendValue {
593 from: origin,
594 to: program_id,
595 value,
596 locked: false,
597 });
598 }
599
600 for (code_id, candidates) in program_candidates {
602 journal.push(JournalNote::StoreNewPrograms {
603 program_id,
604 code_id,
605 candidates,
606 });
607 }
608
609 if !matches!(kind, SuccessfulDispatchResultKind::Wait(_, _))
611 && !matches!(dispatch.kind(), DispatchKind::Reply | DispatchKind::Signal)
612 && !reply_sent
613 {
614 let auto_reply = ReplyMessage::auto(dispatch.id()).into_dispatch(
615 program_id,
616 dispatch.source(),
617 dispatch.id(),
618 );
619
620 journal.push(JournalNote::SendDispatch {
621 message_id,
622 dispatch: auto_reply,
623 delay: 0,
624 reservation: None,
625 });
626 }
627
628 for (message_id_sent, amount) in reply_deposits {
629 journal.push(JournalNote::ReplyDeposit {
630 message_id,
631 future_reply_id: MessageId::generate_reply(message_id_sent),
632 amount,
633 });
634 }
635
636 for (dispatch, delay, reservation) in generated_dispatches {
637 journal.push(JournalNote::SendDispatch {
638 message_id,
639 dispatch,
640 delay,
641 reservation,
642 });
643 }
644
645 for (awakening_id, delay) in awakening {
646 journal.push(JournalNote::WakeMessage {
647 message_id,
648 program_id,
649 awakening_id,
650 delay,
651 });
652 }
653
654 for (page_number, data) in page_update {
655 journal.push(JournalNote::UpdatePage {
656 program_id,
657 page_number,
658 data,
659 })
660 }
661
662 if let Some(allocations) = allocations {
663 journal.push(JournalNote::UpdateAllocations {
664 program_id,
665 allocations,
666 });
667 }
668
669 let outcome = match kind {
670 Wait(duration, waited_type) => {
671 journal.push(JournalNote::WaitDispatch {
672 dispatch: dispatch.into_stored(program_id, context_store),
673 duration,
674 waited_type,
675 });
676
677 return journal;
678 }
679 Success => match dispatch.kind() {
680 DispatchKind::Init => DispatchOutcome::InitSuccess { program_id },
681 _ => DispatchOutcome::Success,
682 },
683 Exit(value_destination) => {
684 journal.push(JournalNote::ExitDispatch {
685 id_exited: program_id,
686 value_destination,
687 });
688
689 DispatchOutcome::Exit { program_id }
690 }
691 };
692
693 if system_reservation_context.has_any() {
694 journal.push(JournalNote::SystemUnreserveGas { message_id });
695 }
696
697 journal.push(JournalNote::MessageDispatched {
698 message_id,
699 source: origin,
700 outcome,
701 });
702 journal.push(JournalNote::MessageConsumed(message_id));
703 journal
704}
705
706pub fn process_allowance_exceed(
708 dispatch: IncomingDispatch,
709 program_id: ActorId,
710 gas_burned: u64,
711) -> Vec<JournalNote> {
712 let mut journal = Vec::with_capacity(1);
713
714 let (kind, message, opt_context) = dispatch.into_parts();
715
716 let dispatch = StoredDispatch::new(kind, message.into_stored(program_id), opt_context);
717
718 journal.push(JournalNote::StopProcessing {
719 dispatch,
720 gas_burned,
721 });
722
723 journal
724}