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::{string::ToString, vec::Vec};
31use core::{fmt, fmt::Formatter};
32use gear_core::{
33 env::Externalities,
34 ids::{prelude::*, MessageId, ProgramId},
35 message::{
36 ContextSettings, DispatchKind, IncomingDispatch, Payload, PayloadSizeError, ReplyMessage,
37 StoredDispatch,
38 },
39 reservation::GasReservationState,
40};
41use gear_core_backend::{
42 error::{BackendAllocSyscallError, BackendSyscallError, RunFallibleError, TrapExplanation},
43 BackendExternalities,
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::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 ProgramExited {
204 inheritor: ProgramId,
206 },
207 FailedInit,
209 Uninitialized,
211 CodeNotExists,
213 ReinstrumentationFailed,
215 ExecutionFailed(ActorExecutionErrorReplyReason),
217}
218
219impl fmt::Display for ProcessErrorCase {
220 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
221 match self {
222 ProcessErrorCase::ExecutionFailed(reason) => fmt::Display::fmt(reason, f),
223 this => fmt::Display::fmt(&this.to_reason(), f),
224 }
225 }
226}
227
228impl ProcessErrorCase {
229 fn to_reason(&self) -> ErrorReplyReason {
230 match self {
231 ProcessErrorCase::ProgramExited { .. } => {
232 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramExited)
233 }
234 ProcessErrorCase::FailedInit => ErrorReplyReason::UnavailableActor(
235 SimpleUnavailableActorError::InitializationFailure,
236 ),
237 ProcessErrorCase::Uninitialized => {
238 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::Uninitialized)
239 }
240 ProcessErrorCase::CodeNotExists => {
241 ErrorReplyReason::UnavailableActor(SimpleUnavailableActorError::ProgramNotCreated)
242 }
243 ProcessErrorCase::ReinstrumentationFailed => ErrorReplyReason::UnavailableActor(
244 SimpleUnavailableActorError::ReinstrumentationFailure,
245 ),
246 ProcessErrorCase::ExecutionFailed(reason) => reason.as_simple().into(),
247 }
248 }
249
250 fn to_payload(&self) -> Payload {
252 match self {
253 ProcessErrorCase::ProgramExited { inheritor } => {
254 const _: () = assert!(size_of::<ProgramId>() <= Payload::MAX_LEN);
255 inheritor
256 .into_bytes()
257 .to_vec()
258 .try_into()
259 .unwrap_or_else(|PayloadSizeError| {
260 unreachable!("`ProgramId` is always smaller than maximum payload size")
261 })
262 }
263 ProcessErrorCase::ExecutionFailed(ActorExecutionErrorReplyReason::Trap(
264 TrapExplanation::Panic(buf),
265 )) => buf.inner().clone(),
266 _ => Payload::default(),
267 }
268 }
269}
270
271fn process_error(
272 dispatch: IncomingDispatch,
273 program_id: ProgramId,
274 gas_burned: u64,
275 system_reservation_ctx: SystemReservationContext,
276 case: ProcessErrorCase,
277) -> Vec<JournalNote> {
278 let mut journal = Vec::new();
279
280 let message_id = dispatch.id();
281 let origin = dispatch.source();
282 let value = dispatch.value();
283
284 journal.push(JournalNote::GasBurned {
285 message_id,
286 amount: gas_burned,
287 });
288
289 let to_send_reply = !matches!(dispatch.kind(), DispatchKind::Reply | DispatchKind::Signal);
290
291 if dispatch.context().is_none() && value != 0 {
299 journal.push(JournalNote::SendValue {
301 from: origin,
302 to: program_id,
303 value,
304 locked: to_send_reply,
307 });
308 }
309
310 if let Some(amount) = system_reservation_ctx.current_reservation {
311 journal.push(JournalNote::SystemReserveGas { message_id, amount });
312 }
313
314 if let ProcessErrorCase::ExecutionFailed(reason) = &case {
315 if system_reservation_ctx.has_any()
317 && !dispatch.is_error_reply()
318 && !matches!(dispatch.kind(), DispatchKind::Signal | DispatchKind::Init)
319 {
320 journal.push(JournalNote::SendSignal {
321 message_id,
322 destination: program_id,
323 code: SignalCode::Execution(reason.as_simple()),
324 });
325 }
326 }
327
328 if system_reservation_ctx.has_any() {
329 journal.push(JournalNote::SystemUnreserveGas { message_id });
330 }
331
332 if to_send_reply {
333 let err = case.to_reason();
334 let err_payload = case.to_payload();
335
336 let value = if dispatch.context().is_none() {
337 value
338 } else {
339 0
340 };
341
342 let dispatch = ReplyMessage::system(dispatch.id(), err_payload, value, err).into_dispatch(
349 program_id,
350 dispatch.source(),
351 dispatch.id(),
352 );
353
354 journal.push(JournalNote::SendDispatch {
355 message_id,
356 dispatch,
357 delay: 0,
358 reservation: None,
359 });
360 }
361
362 let outcome = match case {
363 ProcessErrorCase::ExecutionFailed { .. } | ProcessErrorCase::ReinstrumentationFailed => {
364 let err_msg = case.to_string();
365 match dispatch.kind() {
366 DispatchKind::Init => DispatchOutcome::InitFailure {
367 program_id,
368 origin,
369 reason: err_msg,
370 },
371 _ => DispatchOutcome::MessageTrap {
372 program_id,
373 trap: err_msg,
374 },
375 }
376 }
377 ProcessErrorCase::ProgramExited { .. }
378 | ProcessErrorCase::FailedInit
379 | ProcessErrorCase::Uninitialized
380 | ProcessErrorCase::CodeNotExists => DispatchOutcome::NoExecution,
381 };
382
383 journal.push(JournalNote::MessageDispatched {
384 message_id,
385 source: origin,
386 outcome,
387 });
388 journal.push(JournalNote::MessageConsumed(message_id));
389
390 journal
391}
392
393pub fn process_execution_error(
395 dispatch: IncomingDispatch,
396 program_id: ProgramId,
397 gas_burned: u64,
398 system_reservation_ctx: SystemReservationContext,
399 err: impl Into<ActorExecutionErrorReplyReason>,
400) -> Vec<JournalNote> {
401 process_error(
402 dispatch,
403 program_id,
404 gas_burned,
405 system_reservation_ctx,
406 ProcessErrorCase::ExecutionFailed(err.into()),
407 )
408}
409
410pub fn process_program_exited(
412 context: ContextChargedForProgram,
413 inheritor: ProgramId,
414) -> Vec<JournalNote> {
415 let ContextChargedForProgram {
416 dispatch,
417 gas_counter,
418 destination_id,
419 ..
420 } = context;
421
422 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
423
424 process_error(
425 dispatch,
426 destination_id,
427 gas_counter.burned(),
428 system_reservation_ctx,
429 ProcessErrorCase::ProgramExited { inheritor },
430 )
431}
432
433pub fn process_failed_init(context: ContextChargedForProgram) -> Vec<JournalNote> {
435 let ContextChargedForProgram {
436 dispatch,
437 gas_counter,
438 destination_id,
439 ..
440 } = context;
441
442 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
443
444 process_error(
445 dispatch,
446 destination_id,
447 gas_counter.burned(),
448 system_reservation_ctx,
449 ProcessErrorCase::FailedInit,
450 )
451}
452
453pub fn process_uninitialized(context: ContextChargedForProgram) -> Vec<JournalNote> {
455 let ContextChargedForProgram {
456 dispatch,
457 gas_counter,
458 destination_id,
459 ..
460 } = context;
461
462 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
463
464 process_error(
465 dispatch,
466 destination_id,
467 gas_counter.burned(),
468 system_reservation_ctx,
469 ProcessErrorCase::Uninitialized,
470 )
471}
472
473pub fn process_code_not_exists(context: ContextChargedForProgram) -> Vec<JournalNote> {
475 let ContextChargedForProgram {
476 dispatch,
477 gas_counter,
478 destination_id,
479 ..
480 } = context;
481
482 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
483
484 process_error(
485 dispatch,
486 destination_id,
487 gas_counter.burned(),
488 system_reservation_ctx,
489 ProcessErrorCase::CodeNotExists,
490 )
491}
492
493pub fn process_reinstrumentation_error(
495 context: ContextChargedForInstrumentation,
496) -> Vec<JournalNote> {
497 let dispatch = context.data.dispatch;
498 let program_id = context.data.destination_id;
499 let gas_burned = context.data.gas_counter.burned();
500 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
501
502 process_error(
503 dispatch,
504 program_id,
505 gas_burned,
506 system_reservation_ctx,
507 ProcessErrorCase::ReinstrumentationFailed,
508 )
509}
510
511pub fn process_success(
513 kind: SuccessfulDispatchResultKind,
514 dispatch_result: DispatchResult,
515) -> Vec<JournalNote> {
516 use crate::precharge::SuccessfulDispatchResultKind::*;
517
518 let DispatchResult {
519 dispatch,
520 generated_dispatches,
521 awakening,
522 program_candidates,
523 gas_amount,
524 gas_reserver,
525 system_reservation_context,
526 page_update,
527 program_id,
528 context_store,
529 allocations,
530 reply_deposits,
531 reply_sent,
532 ..
533 } = dispatch_result;
534
535 let mut journal = Vec::new();
536
537 let message_id = dispatch.id();
538 let origin = dispatch.source();
539 let value = dispatch.value();
540
541 journal.push(JournalNote::GasBurned {
542 message_id,
543 amount: gas_amount.burned(),
544 });
545
546 if let Some(gas_reserver) = gas_reserver {
547 journal.extend(gas_reserver.states().iter().flat_map(
548 |(&reservation_id, &state)| match state {
549 GasReservationState::Exists { .. } => None,
550 GasReservationState::Created {
551 amount, duration, ..
552 } => Some(JournalNote::ReserveGas {
553 message_id,
554 reservation_id,
555 program_id,
556 amount,
557 duration,
558 }),
559 GasReservationState::Removed { expiration } => Some(JournalNote::UnreserveGas {
560 reservation_id,
561 program_id,
562 expiration,
563 }),
564 },
565 ));
566
567 journal.push(JournalNote::UpdateGasReservations {
568 program_id,
569 reserver: gas_reserver,
570 });
571 }
572
573 if let Some(amount) = system_reservation_context.current_reservation {
574 journal.push(JournalNote::SystemReserveGas { message_id, amount });
575 }
576
577 if dispatch.context().is_none() && value != 0 {
585 journal.push(JournalNote::SendValue {
587 from: origin,
588 to: program_id,
589 value,
590 locked: false,
591 });
592 }
593
594 for (code_id, candidates) in program_candidates {
596 journal.push(JournalNote::StoreNewPrograms {
597 program_id,
598 code_id,
599 candidates,
600 });
601 }
602
603 if !matches!(kind, SuccessfulDispatchResultKind::Wait(_, _))
605 && !matches!(dispatch.kind(), DispatchKind::Reply | DispatchKind::Signal)
606 && !reply_sent
607 {
608 let auto_reply = ReplyMessage::auto(dispatch.id()).into_dispatch(
609 program_id,
610 dispatch.source(),
611 dispatch.id(),
612 );
613
614 journal.push(JournalNote::SendDispatch {
615 message_id,
616 dispatch: auto_reply,
617 delay: 0,
618 reservation: None,
619 });
620 }
621
622 for (message_id_sent, amount) in reply_deposits {
623 journal.push(JournalNote::ReplyDeposit {
624 message_id,
625 future_reply_id: MessageId::generate_reply(message_id_sent),
626 amount,
627 });
628 }
629
630 for (dispatch, delay, reservation) in generated_dispatches {
631 journal.push(JournalNote::SendDispatch {
632 message_id,
633 dispatch,
634 delay,
635 reservation,
636 });
637 }
638
639 for (awakening_id, delay) in awakening {
640 journal.push(JournalNote::WakeMessage {
641 message_id,
642 program_id,
643 awakening_id,
644 delay,
645 });
646 }
647
648 for (page_number, data) in page_update {
649 journal.push(JournalNote::UpdatePage {
650 program_id,
651 page_number,
652 data,
653 })
654 }
655
656 if let Some(allocations) = allocations {
657 journal.push(JournalNote::UpdateAllocations {
658 program_id,
659 allocations,
660 });
661 }
662
663 let outcome = match kind {
664 Wait(duration, waited_type) => {
665 journal.push(JournalNote::WaitDispatch {
666 dispatch: dispatch.into_stored(program_id, context_store),
667 duration,
668 waited_type,
669 });
670
671 return journal;
672 }
673 Success => match dispatch.kind() {
674 DispatchKind::Init => DispatchOutcome::InitSuccess { program_id },
675 _ => DispatchOutcome::Success,
676 },
677 Exit(value_destination) => {
678 journal.push(JournalNote::ExitDispatch {
679 id_exited: program_id,
680 value_destination,
681 });
682
683 DispatchOutcome::Exit { program_id }
684 }
685 };
686
687 if system_reservation_context.has_any() {
688 journal.push(JournalNote::SystemUnreserveGas { message_id });
689 }
690
691 journal.push(JournalNote::MessageDispatched {
692 message_id,
693 source: origin,
694 outcome,
695 });
696 journal.push(JournalNote::MessageConsumed(message_id));
697 journal
698}
699
700pub fn process_allowance_exceed(
702 dispatch: IncomingDispatch,
703 program_id: ProgramId,
704 gas_burned: u64,
705) -> Vec<JournalNote> {
706 let mut journal = Vec::with_capacity(1);
707
708 let (kind, message, opt_context) = dispatch.into_parts();
709
710 let dispatch = StoredDispatch::new(kind, message.into_stored(program_id), opt_context);
711
712 journal.push(JournalNote::StopProcessing {
713 dispatch,
714 gas_burned,
715 });
716
717 journal
718}