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 if dispatch.context().is_none() && value != 0 {
297 journal.push(JournalNote::SendValue {
299 from: origin,
300 to: None,
301 value,
302 });
303 }
304
305 if let Some(amount) = system_reservation_ctx.current_reservation {
306 journal.push(JournalNote::SystemReserveGas { message_id, amount });
307 }
308
309 if let ProcessErrorCase::ExecutionFailed(reason) = &case {
310 if system_reservation_ctx.has_any()
312 && !dispatch.is_error_reply()
313 && !matches!(dispatch.kind(), DispatchKind::Signal | DispatchKind::Init)
314 {
315 journal.push(JournalNote::SendSignal {
316 message_id,
317 destination: program_id,
318 code: SignalCode::Execution(reason.as_simple()),
319 });
320 }
321 }
322
323 if system_reservation_ctx.has_any() {
324 journal.push(JournalNote::SystemUnreserveGas { message_id });
325 }
326
327 if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
328 let err = case.to_reason();
329 let err_payload = case.to_payload();
330
331 let dispatch = ReplyMessage::system(dispatch.id(), err_payload, err).into_dispatch(
338 program_id,
339 dispatch.source(),
340 dispatch.id(),
341 );
342
343 journal.push(JournalNote::SendDispatch {
344 message_id,
345 dispatch,
346 delay: 0,
347 reservation: None,
348 });
349 }
350
351 let outcome = match case {
352 ProcessErrorCase::ExecutionFailed { .. } | ProcessErrorCase::ReinstrumentationFailed => {
353 let err_msg = case.to_string();
354 match dispatch.kind() {
355 DispatchKind::Init => DispatchOutcome::InitFailure {
356 program_id,
357 origin,
358 reason: err_msg,
359 },
360 _ => DispatchOutcome::MessageTrap {
361 program_id,
362 trap: err_msg,
363 },
364 }
365 }
366 ProcessErrorCase::ProgramExited { .. }
367 | ProcessErrorCase::FailedInit
368 | ProcessErrorCase::Uninitialized
369 | ProcessErrorCase::CodeNotExists => DispatchOutcome::NoExecution,
370 };
371
372 journal.push(JournalNote::MessageDispatched {
373 message_id,
374 source: origin,
375 outcome,
376 });
377 journal.push(JournalNote::MessageConsumed(message_id));
378
379 journal
380}
381
382pub fn process_execution_error(
384 dispatch: IncomingDispatch,
385 program_id: ProgramId,
386 gas_burned: u64,
387 system_reservation_ctx: SystemReservationContext,
388 err: impl Into<ActorExecutionErrorReplyReason>,
389) -> Vec<JournalNote> {
390 process_error(
391 dispatch,
392 program_id,
393 gas_burned,
394 system_reservation_ctx,
395 ProcessErrorCase::ExecutionFailed(err.into()),
396 )
397}
398
399pub fn process_program_exited(
401 context: ContextChargedForProgram,
402 inheritor: ProgramId,
403) -> Vec<JournalNote> {
404 let ContextChargedForProgram {
405 dispatch,
406 gas_counter,
407 destination_id,
408 ..
409 } = context;
410
411 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
412
413 process_error(
414 dispatch,
415 destination_id,
416 gas_counter.burned(),
417 system_reservation_ctx,
418 ProcessErrorCase::ProgramExited { inheritor },
419 )
420}
421
422pub fn process_failed_init(context: ContextChargedForProgram) -> Vec<JournalNote> {
424 let ContextChargedForProgram {
425 dispatch,
426 gas_counter,
427 destination_id,
428 ..
429 } = context;
430
431 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
432
433 process_error(
434 dispatch,
435 destination_id,
436 gas_counter.burned(),
437 system_reservation_ctx,
438 ProcessErrorCase::FailedInit,
439 )
440}
441
442pub fn process_uninitialized(context: ContextChargedForProgram) -> Vec<JournalNote> {
444 let ContextChargedForProgram {
445 dispatch,
446 gas_counter,
447 destination_id,
448 ..
449 } = context;
450
451 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
452
453 process_error(
454 dispatch,
455 destination_id,
456 gas_counter.burned(),
457 system_reservation_ctx,
458 ProcessErrorCase::Uninitialized,
459 )
460}
461
462pub fn process_code_not_exists(context: ContextChargedForProgram) -> Vec<JournalNote> {
464 let ContextChargedForProgram {
465 dispatch,
466 gas_counter,
467 destination_id,
468 ..
469 } = context;
470
471 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
472
473 process_error(
474 dispatch,
475 destination_id,
476 gas_counter.burned(),
477 system_reservation_ctx,
478 ProcessErrorCase::CodeNotExists,
479 )
480}
481
482pub fn process_reinstrumentation_error(
484 context: ContextChargedForInstrumentation,
485) -> Vec<JournalNote> {
486 let dispatch = context.data.dispatch;
487 let program_id = context.data.destination_id;
488 let gas_burned = context.data.gas_counter.burned();
489 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
490
491 process_error(
492 dispatch,
493 program_id,
494 gas_burned,
495 system_reservation_ctx,
496 ProcessErrorCase::ReinstrumentationFailed,
497 )
498}
499
500pub fn process_success(
502 kind: SuccessfulDispatchResultKind,
503 dispatch_result: DispatchResult,
504) -> Vec<JournalNote> {
505 use crate::precharge::SuccessfulDispatchResultKind::*;
506
507 let DispatchResult {
508 dispatch,
509 generated_dispatches,
510 awakening,
511 program_candidates,
512 gas_amount,
513 gas_reserver,
514 system_reservation_context,
515 page_update,
516 program_id,
517 context_store,
518 allocations,
519 reply_deposits,
520 reply_sent,
521 ..
522 } = dispatch_result;
523
524 let mut journal = Vec::new();
525
526 let message_id = dispatch.id();
527 let origin = dispatch.source();
528 let value = dispatch.value();
529
530 journal.push(JournalNote::GasBurned {
531 message_id,
532 amount: gas_amount.burned(),
533 });
534
535 if let Some(gas_reserver) = gas_reserver {
536 journal.extend(gas_reserver.states().iter().flat_map(
537 |(&reservation_id, &state)| match state {
538 GasReservationState::Exists { .. } => None,
539 GasReservationState::Created {
540 amount, duration, ..
541 } => Some(JournalNote::ReserveGas {
542 message_id,
543 reservation_id,
544 program_id,
545 amount,
546 duration,
547 }),
548 GasReservationState::Removed { expiration } => Some(JournalNote::UnreserveGas {
549 reservation_id,
550 program_id,
551 expiration,
552 }),
553 },
554 ));
555
556 journal.push(JournalNote::UpdateGasReservations {
557 program_id,
558 reserver: gas_reserver,
559 });
560 }
561
562 if let Some(amount) = system_reservation_context.current_reservation {
563 journal.push(JournalNote::SystemReserveGas { message_id, amount });
564 }
565
566 if dispatch.context().is_none() && value != 0 {
574 journal.push(JournalNote::SendValue {
576 from: origin,
577 to: Some(program_id),
578 value,
579 });
580 }
581
582 for (code_id, candidates) in program_candidates {
584 journal.push(JournalNote::StoreNewPrograms {
585 program_id,
586 code_id,
587 candidates,
588 });
589 }
590
591 if matches!(kind, SuccessfulDispatchResultKind::Success)
593 && !reply_sent
594 && !dispatch.is_reply()
595 && dispatch.kind() != DispatchKind::Signal
596 {
597 let auto_reply = ReplyMessage::auto(dispatch.id()).into_dispatch(
598 program_id,
599 dispatch.source(),
600 dispatch.id(),
601 );
602
603 journal.push(JournalNote::SendDispatch {
604 message_id,
605 dispatch: auto_reply,
606 delay: 0,
607 reservation: None,
608 });
609 }
610
611 for (message_id_sent, amount) in reply_deposits {
612 journal.push(JournalNote::ReplyDeposit {
613 message_id,
614 future_reply_id: MessageId::generate_reply(message_id_sent),
615 amount,
616 });
617 }
618
619 for (dispatch, delay, reservation) in generated_dispatches {
620 journal.push(JournalNote::SendDispatch {
621 message_id,
622 dispatch,
623 delay,
624 reservation,
625 });
626 }
627
628 for (awakening_id, delay) in awakening {
629 journal.push(JournalNote::WakeMessage {
630 message_id,
631 program_id,
632 awakening_id,
633 delay,
634 });
635 }
636
637 for (page_number, data) in page_update {
638 journal.push(JournalNote::UpdatePage {
639 program_id,
640 page_number,
641 data,
642 })
643 }
644
645 if let Some(allocations) = allocations {
646 journal.push(JournalNote::UpdateAllocations {
647 program_id,
648 allocations,
649 });
650 }
651
652 let outcome = match kind {
653 Wait(duration, waited_type) => {
654 journal.push(JournalNote::WaitDispatch {
655 dispatch: dispatch.into_stored(program_id, context_store),
656 duration,
657 waited_type,
658 });
659
660 return journal;
661 }
662 Success => match dispatch.kind() {
663 DispatchKind::Init => DispatchOutcome::InitSuccess { program_id },
664 _ => DispatchOutcome::Success,
665 },
666 Exit(value_destination) => {
667 journal.push(JournalNote::ExitDispatch {
668 id_exited: program_id,
669 value_destination,
670 });
671
672 DispatchOutcome::Exit { program_id }
673 }
674 };
675
676 if system_reservation_context.has_any() {
677 journal.push(JournalNote::SystemUnreserveGas { message_id });
678 }
679
680 journal.push(JournalNote::MessageDispatched {
681 message_id,
682 source: origin,
683 outcome,
684 });
685 journal.push(JournalNote::MessageConsumed(message_id));
686 journal
687}
688
689pub fn process_allowance_exceed(
691 dispatch: IncomingDispatch,
692 program_id: ProgramId,
693 gas_burned: u64,
694) -> Vec<JournalNote> {
695 let mut journal = Vec::with_capacity(1);
696
697 let (kind, message, opt_context) = dispatch.into_parts();
698
699 let dispatch = StoredDispatch::new(kind, message.into_stored(program_id), opt_context);
700
701 journal.push(JournalNote::StopProcessing {
702 dispatch,
703 gas_burned,
704 });
705
706 journal
707}