gear_core_processor/
processing.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2023 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use 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::{collections::BTreeMap, string::ToString, vec::Vec};
31use gear_backend_common::{
32    BackendExternalities, BackendSyscallError, Environment, SystemReservationContext,
33};
34use gear_core::{
35    env::Externalities,
36    ids::{MessageId, ProgramId},
37    memory::PageBuf,
38    message::{ContextSettings, DispatchKind, IncomingDispatch, ReplyMessage, StoredDispatch},
39    pages::GearPage,
40    reservation::GasReservationState,
41};
42use gear_core_errors::{ErrorReplyReason, SignalCode};
43
44/// Process program & dispatch for it and return journal for updates.
45pub fn process<E>(
46    block_config: &BlockConfig,
47    execution_context: ProcessExecutionContext,
48    random_data: (Vec<u8>, u32),
49    memory_pages: BTreeMap<GearPage, PageBuf>,
50) -> Result<Vec<JournalNote>, SystemExecutionError>
51where
52    E: Environment,
53    E::Ext: ProcessorExternalities + BackendExternalities + 'static,
54    <E::Ext as Externalities>::UnrecoverableError: BackendSyscallError,
55{
56    use crate::precharge::SuccessfulDispatchResultKind::*;
57
58    let BlockConfig {
59        block_info,
60        max_pages,
61        page_costs,
62        existential_deposit,
63        outgoing_limit,
64        host_fn_weights,
65        forbidden_funcs,
66        mailbox_threshold,
67        waitlist_cost,
68        dispatch_hold_cost,
69        reserve_for,
70        reservation,
71        write_cost,
72        rent_cost,
73        ..
74    } = block_config.clone();
75
76    let execution_settings = ExecutionSettings {
77        block_info,
78        existential_deposit,
79        max_pages,
80        page_costs,
81        host_fn_weights,
82        forbidden_funcs,
83        mailbox_threshold,
84        waitlist_cost,
85        dispatch_hold_cost,
86        reserve_for,
87        reservation,
88        random_data,
89        rent_cost,
90    };
91
92    let dispatch = execution_context.dispatch;
93    let balance = execution_context.balance;
94    let program_id = execution_context.program.id();
95    let execution_context = WasmExecutionContext {
96        gas_counter: execution_context.gas_counter,
97        gas_allowance_counter: execution_context.gas_allowance_counter,
98        gas_reserver: execution_context.gas_reserver,
99        program: execution_context.program,
100        pages_initial_data: memory_pages,
101        memory_size: execution_context.memory_size,
102    };
103
104    // Sending fee: double write cost for addition and removal some time soon
105    // from queue.
106    //
107    // Scheduled sending fee: double write cost for addition and removal some time soon
108    // from queue and double write cost (addition and removal) for dispatch stash.
109    //
110    // Waiting fee: triple write cost for addition and removal some time soon
111    // from waitlist and enqueuing / sending error reply afterward.
112    //
113    // Waking fee: double write cost for removal from waitlist
114    // and further enqueueing.
115    let msg_ctx_settings = ContextSettings::new(
116        write_cost.saturating_mul(2),
117        write_cost.saturating_mul(4),
118        write_cost.saturating_mul(3),
119        write_cost.saturating_mul(2),
120        write_cost.saturating_mul(2),
121        outgoing_limit,
122    );
123
124    let exec_result = executor::execute_wasm::<E>(
125        balance,
126        dispatch.clone(),
127        execution_context,
128        execution_settings,
129        msg_ctx_settings,
130    )
131    .map_err(|err| {
132        log::debug!("Wasm execution error: {}", err);
133        err
134    });
135
136    match exec_result {
137        Ok(res) => Ok(match res.kind {
138            DispatchResultKind::Trap(reason) => process_error(
139                res.dispatch,
140                program_id,
141                res.gas_amount.burned(),
142                res.system_reservation_context,
143                ActorExecutionErrorReplyReason::Trap(reason),
144                true,
145            ),
146            DispatchResultKind::Success => process_success(Success, res),
147            DispatchResultKind::Wait(duration, ref waited_type) => {
148                process_success(Wait(duration, waited_type.clone()), res)
149            }
150            DispatchResultKind::Exit(value_destination) => {
151                process_success(Exit(value_destination), res)
152            }
153            DispatchResultKind::GasAllowanceExceed => {
154                process_allowance_exceed(dispatch, program_id, res.gas_amount.burned())
155            }
156        }),
157        Err(ExecutionError::Actor(e)) => Ok(process_error(
158            dispatch,
159            program_id,
160            e.gas_amount.burned(),
161            SystemReservationContext::default(),
162            e.reason,
163            true,
164        )),
165        Err(ExecutionError::System(e)) => Err(e),
166    }
167}
168
169/// Helper function for journal creation in trap/error case
170pub fn process_error(
171    dispatch: IncomingDispatch,
172    program_id: ProgramId,
173    gas_burned: u64,
174    system_reservation_ctx: SystemReservationContext,
175    err: ActorExecutionErrorReplyReason,
176    executed: bool,
177) -> Vec<JournalNote> {
178    let mut journal = Vec::new();
179
180    let message_id = dispatch.id();
181    let origin = dispatch.source();
182    let value = dispatch.value();
183
184    journal.push(JournalNote::GasBurned {
185        message_id,
186        amount: gas_burned,
187    });
188
189    // We check if value is greater than zero to don't provide
190    // no-op journal note.
191    //
192    // We also check if dispatch had context of previous executions:
193    // it's existence shows that we have processed message after
194    // being waken, so the value were already transferred in
195    // execution, where `gr_wait` was called.
196    if dispatch.context().is_none() && value != 0 {
197        // Send back value
198        journal.push(JournalNote::SendValue {
199            from: origin,
200            to: None,
201            value,
202        });
203    }
204
205    if let Some(amount) = system_reservation_ctx.current_reservation {
206        journal.push(JournalNote::SystemReserveGas { message_id, amount });
207    }
208
209    if system_reservation_ctx.has_any() {
210        if !dispatch.is_error_reply()
211            && !matches!(dispatch.kind(), DispatchKind::Signal | DispatchKind::Init)
212        {
213            journal.push(JournalNote::SendSignal {
214                message_id,
215                destination: program_id,
216                code: SignalCode::Execution(err.as_simple()),
217            });
218        }
219
220        journal.push(JournalNote::SystemUnreserveGas { message_id });
221    }
222
223    if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
224        // This expect panic is unreachable, unless error message is too large or max payload size is too small.
225        let err_payload = err
226            .to_string()
227            .into_bytes()
228            .try_into()
229            .unwrap_or_else(|_| unreachable!("Error message is too large"));
230        let err = err.as_simple();
231
232        // # Safety
233        //
234        // 1. The dispatch.id() has already been checked
235        // 2. This reply message is generated by our system
236        //
237        // So, the message id of this reply message will not be duplicated.
238        let dispatch = ReplyMessage::system(dispatch.id(), err_payload, err).into_dispatch(
239            program_id,
240            dispatch.source(),
241            dispatch.id(),
242        );
243
244        journal.push(JournalNote::SendDispatch {
245            message_id,
246            dispatch,
247            delay: 0,
248            reservation: None,
249        });
250    }
251
252    let outcome = match dispatch.kind() {
253        DispatchKind::Init => DispatchOutcome::InitFailure {
254            program_id,
255            origin,
256            reason: err.to_string(),
257            executed,
258        },
259        _ => DispatchOutcome::MessageTrap {
260            program_id,
261            trap: err.to_string(),
262        },
263    };
264
265    journal.push(JournalNote::MessageDispatched {
266        message_id,
267        source: origin,
268        outcome,
269    });
270    journal.push(JournalNote::MessageConsumed(message_id));
271
272    journal
273}
274
275/// Helper function for journal creation in success case
276pub fn process_success(
277    kind: SuccessfulDispatchResultKind,
278    dispatch_result: DispatchResult,
279) -> Vec<JournalNote> {
280    use crate::precharge::SuccessfulDispatchResultKind::*;
281
282    let DispatchResult {
283        dispatch,
284        generated_dispatches,
285        awakening,
286        program_candidates,
287        program_rents,
288        gas_amount,
289        gas_reserver,
290        system_reservation_context,
291        page_update,
292        program_id,
293        context_store,
294        allocations,
295        reply_deposits,
296        ..
297    } = dispatch_result;
298
299    let mut journal = Vec::new();
300
301    let message_id = dispatch.id();
302    let origin = dispatch.source();
303    let value = dispatch.value();
304
305    journal.push(JournalNote::GasBurned {
306        message_id,
307        amount: gas_amount.burned(),
308    });
309
310    if let Some(gas_reserver) = gas_reserver {
311        journal.extend(gas_reserver.states().iter().flat_map(
312            |(&reservation_id, &state)| match state {
313                GasReservationState::Exists { .. } => None,
314                GasReservationState::Created {
315                    amount, duration, ..
316                } => Some(JournalNote::ReserveGas {
317                    message_id,
318                    reservation_id,
319                    program_id,
320                    amount,
321                    duration,
322                }),
323                GasReservationState::Removed { expiration } => Some(JournalNote::UnreserveGas {
324                    reservation_id,
325                    program_id,
326                    expiration,
327                }),
328            },
329        ));
330
331        journal.push(JournalNote::UpdateGasReservations {
332            program_id,
333            reserver: gas_reserver,
334        });
335    }
336
337    if let Some(amount) = system_reservation_context.current_reservation {
338        journal.push(JournalNote::SystemReserveGas { message_id, amount });
339    }
340
341    // We check if value is greater than zero to don't provide
342    // no-op journal note.
343    //
344    // We also check if dispatch had context of previous executions:
345    // it's existence shows that we have processed message after
346    // being waken, so the value were already transferred in
347    // execution, where `gr_wait` was called.
348    if dispatch.context().is_none() && value != 0 {
349        // Send value further
350        journal.push(JournalNote::SendValue {
351            from: origin,
352            to: Some(program_id),
353            value,
354        });
355    }
356
357    // Must be handled before handling generated dispatches.
358    for (code_id, candidates) in program_candidates {
359        journal.push(JournalNote::StoreNewPrograms {
360            code_id,
361            candidates,
362        });
363    }
364
365    // Sending auto-generated reply about success execution.
366    if matches!(kind, SuccessfulDispatchResultKind::Success)
367        && !context_store.reply_sent()
368        && !dispatch.is_reply()
369        && dispatch.kind() != DispatchKind::Signal
370    {
371        let auto_reply = ReplyMessage::auto(dispatch.id()).into_dispatch(
372            program_id,
373            dispatch.source(),
374            dispatch.id(),
375        );
376
377        journal.push(JournalNote::SendDispatch {
378            message_id,
379            dispatch: auto_reply,
380            delay: 0,
381            reservation: None,
382        });
383    }
384
385    // Must be handled after processing programs creation.
386    let payer = program_id;
387    for (program_id, block_count) in program_rents {
388        journal.push(JournalNote::PayProgramRent {
389            payer,
390            program_id,
391            block_count,
392        });
393    }
394
395    for (message_id_sent, amount) in reply_deposits {
396        journal.push(JournalNote::ReplyDeposit {
397            message_id,
398            future_reply_id: MessageId::generate_reply(message_id_sent),
399            amount,
400        });
401    }
402
403    for (dispatch, delay, reservation) in generated_dispatches {
404        journal.push(JournalNote::SendDispatch {
405            message_id,
406            dispatch,
407            delay,
408            reservation,
409        });
410    }
411
412    for (awakening_id, delay) in awakening {
413        journal.push(JournalNote::WakeMessage {
414            message_id,
415            program_id,
416            awakening_id,
417            delay,
418        });
419    }
420
421    for (page_number, data) in page_update {
422        journal.push(JournalNote::UpdatePage {
423            program_id,
424            page_number,
425            data,
426        })
427    }
428
429    if !allocations.is_empty() {
430        journal.push(JournalNote::UpdateAllocations {
431            program_id,
432            allocations,
433        });
434    }
435
436    let outcome = match kind {
437        Wait(duration, waited_type) => {
438            journal.push(JournalNote::WaitDispatch {
439                dispatch: dispatch.into_stored(program_id, context_store),
440                duration,
441                waited_type,
442            });
443
444            return journal;
445        }
446        Success => match dispatch.kind() {
447            DispatchKind::Init => DispatchOutcome::InitSuccess { program_id },
448            _ => DispatchOutcome::Success,
449        },
450        Exit(value_destination) => {
451            journal.push(JournalNote::ExitDispatch {
452                id_exited: program_id,
453                value_destination,
454            });
455
456            DispatchOutcome::Exit { program_id }
457        }
458    };
459
460    if system_reservation_context.has_any() {
461        journal.push(JournalNote::SystemUnreserveGas { message_id });
462    }
463
464    journal.push(JournalNote::MessageDispatched {
465        message_id,
466        source: origin,
467        outcome,
468    });
469    journal.push(JournalNote::MessageConsumed(message_id));
470    journal
471}
472
473pub fn process_allowance_exceed(
474    dispatch: IncomingDispatch,
475    program_id: ProgramId,
476    gas_burned: u64,
477) -> Vec<JournalNote> {
478    let mut journal = Vec::with_capacity(1);
479
480    let (kind, message, opt_context) = dispatch.into_parts();
481
482    let dispatch = StoredDispatch::new(kind, message.into_stored(program_id), opt_context);
483
484    journal.push(JournalNote::StopProcessing {
485        dispatch,
486        gas_burned,
487    });
488
489    journal
490}
491
492/// Helper function for journal creation in message no execution case
493pub fn process_non_executable(
494    dispatch: IncomingDispatch,
495    program_id: ProgramId,
496    system_reservation_ctx: SystemReservationContext,
497) -> Vec<JournalNote> {
498    // Number of notes is predetermined
499    let mut journal = Vec::with_capacity(4);
500
501    let message_id = dispatch.id();
502    let source = dispatch.source();
503    let value = dispatch.value();
504
505    if value != 0 {
506        // Send value back
507        journal.push(JournalNote::SendValue {
508            from: dispatch.source(),
509            to: None,
510            value,
511        });
512    }
513
514    // Reply back to the message `source`
515    if !dispatch.is_reply() && dispatch.kind() != DispatchKind::Signal {
516        let err = ErrorReplyReason::InactiveProgram;
517        let err_payload = err
518            .to_string()
519            .into_bytes()
520            .try_into()
521            .unwrap_or_else(|_| unreachable!("Error message is too large"));
522
523        let dispatch = ReplyMessage::system(dispatch.id(), err_payload, err).into_dispatch(
524            program_id,
525            dispatch.source(),
526            dispatch.id(),
527        );
528
529        journal.push(JournalNote::SendDispatch {
530            message_id,
531            dispatch,
532            delay: 0,
533            reservation: None,
534        });
535    }
536
537    if system_reservation_ctx.has_any() {
538        journal.push(JournalNote::SystemUnreserveGas { message_id });
539    }
540
541    journal.push(JournalNote::MessageDispatched {
542        message_id,
543        source,
544        outcome: DispatchOutcome::NoExecution,
545    });
546
547    journal.push(JournalNote::MessageConsumed(message_id));
548
549    journal
550}