gear_backend_common/
lib.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
19//! Crate provides support for wasm runtime.
20
21#![no_std]
22
23extern crate alloc;
24
25pub mod lazy_pages;
26
27mod utils;
28
29#[cfg(any(feature = "mock", test))]
30pub mod mock;
31
32pub mod funcs;
33pub mod memory;
34pub mod runtime;
35pub mod state;
36
37use crate::runtime::RunFallibleError;
38use actor_system_error::actor_system_error;
39use alloc::{
40    collections::{BTreeMap, BTreeSet},
41    string::String,
42    vec::Vec,
43};
44use core::{
45    convert::Infallible,
46    fmt::{Debug, Display},
47};
48use gear_core::{
49    env::Externalities,
50    gas::{ChargeError, CounterType, CountersOwner, GasAmount},
51    ids::{CodeId, MessageId, ProgramId, ReservationId},
52    memory::{Memory, MemoryError, MemoryInterval, PageBuf},
53    message::{
54        ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, WasmEntryPoint,
55    },
56    pages::{GearPage, WasmPage},
57    reservation::GasReserver,
58};
59use lazy_pages::GlobalsAccessConfig;
60use memory::ProcessAccessError;
61use scale_info::scale::{self, Decode, Encode};
62
63pub use crate::utils::LimitedStr;
64pub use log;
65
66pub const PTR_SPECIAL: u32 = u32::MAX;
67
68actor_system_error! {
69    pub type TerminationReason = ActorSystemError<ActorTerminationReason, SystemTerminationReason>;
70}
71
72#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
73pub enum UndefinedTerminationReason {
74    Actor(ActorTerminationReason),
75    System(SystemTerminationReason),
76    /// Undefined reason because we need access to counters owner trait for RI.
77    ProcessAccessErrorResourcesExceed,
78}
79
80impl UndefinedTerminationReason {
81    pub fn define(self, current_counter: CounterType) -> TerminationReason {
82        match self {
83            Self::Actor(r) => r.into(),
84            Self::System(r) => r.into(),
85            Self::ProcessAccessErrorResourcesExceed => {
86                ActorTerminationReason::from(current_counter).into()
87            }
88        }
89    }
90}
91
92impl From<ChargeError> for UndefinedTerminationReason {
93    fn from(err: ChargeError) -> Self {
94        match err {
95            ChargeError::GasLimitExceeded => {
96                ActorTerminationReason::Trap(TrapExplanation::GasLimitExceeded).into()
97            }
98            ChargeError::GasAllowanceExceeded => {
99                ActorTerminationReason::GasAllowanceExceeded.into()
100            }
101        }
102    }
103}
104
105impl From<TrapExplanation> for UndefinedTerminationReason {
106    fn from(trap: TrapExplanation) -> Self {
107        ActorTerminationReason::Trap(trap).into()
108    }
109}
110
111impl<E: BackendSyscallError> From<E> for UndefinedTerminationReason {
112    fn from(err: E) -> Self {
113        err.into_termination_reason()
114    }
115}
116
117#[derive(Decode, Encode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, derive_more::From)]
118#[codec(crate = scale)]
119pub enum ActorTerminationReason {
120    Exit(ProgramId),
121    Leave,
122    Success,
123    Wait(Option<u32>, MessageWaitedType),
124    GasAllowanceExceeded,
125    #[from]
126    Trap(TrapExplanation),
127}
128
129impl From<CounterType> for ActorTerminationReason {
130    fn from(counter_type: CounterType) -> Self {
131        match counter_type {
132            CounterType::GasLimit => Self::Trap(TrapExplanation::GasLimitExceeded),
133            CounterType::GasAllowance => Self::GasAllowanceExceeded,
134        }
135    }
136}
137
138/// Non-actor related termination reason.
139///
140/// ### NOTICE:
141/// It's currently unused, but is left as a stub, until
142/// further massive errors refactoring is done.
143#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
144pub struct SystemTerminationReason;
145
146/// Execution error in infallible sys-call.
147#[derive(
148    Decode,
149    Encode,
150    Debug,
151    Clone,
152    Eq,
153    PartialEq,
154    PartialOrd,
155    Ord,
156    derive_more::Display,
157    derive_more::From,
158)]
159pub enum UnrecoverableExecutionError {
160    #[display(fmt = "Invalid debug string passed in `gr_debug` sys-call")]
161    InvalidDebugString,
162    #[display(fmt = "Not enough gas for operation")]
163    NotEnoughGas,
164    #[display(fmt = "Length is overflowed to read payload")]
165    TooBigReadLen,
166    #[display(fmt = "Cannot take data in payload range from message with size")]
167    ReadWrongRange,
168}
169
170/// Memory error in infallible sys-call.
171#[derive(
172    Decode,
173    Encode,
174    Debug,
175    Clone,
176    Eq,
177    PartialEq,
178    PartialOrd,
179    Ord,
180    derive_more::Display,
181    derive_more::From,
182)]
183pub enum UnrecoverableMemoryError {
184    /// The error occurs in attempt to access memory outside wasm program memory.
185    #[display(fmt = "Trying to access memory outside wasm program memory")]
186    AccessOutOfBounds,
187    /// The error occurs, when program tries to allocate in block-chain runtime more memory than allowed.
188    #[display(fmt = "Trying to allocate more memory in block-chain runtime than allowed")]
189    RuntimeAllocOutOfBounds,
190}
191
192/// Wait error in infallible sys-call.
193#[derive(
194    Decode,
195    Encode,
196    Debug,
197    Clone,
198    Eq,
199    PartialEq,
200    PartialOrd,
201    Ord,
202    derive_more::Display,
203    derive_more::From,
204)]
205pub enum UnrecoverableWaitError {
206    /// An error occurs in attempt to wait for or wait up to zero blocks.
207    #[display(fmt = "Waiting duration cannot be zero")]
208    ZeroDuration,
209    /// An error occurs in attempt to wait after reply sent.
210    #[display(fmt = "`wait()` is not allowed after reply sent")]
211    WaitAfterReply,
212}
213
214#[derive(
215    Decode,
216    Encode,
217    Debug,
218    Clone,
219    Eq,
220    PartialEq,
221    PartialOrd,
222    Ord,
223    derive_more::Display,
224    derive_more::From,
225)]
226pub enum UnrecoverableExtError {
227    #[display(fmt = "Execution error: {_0}")]
228    Execution(UnrecoverableExecutionError),
229    #[display(fmt = "Memory error: {_0}")]
230    Memory(UnrecoverableMemoryError),
231    #[display(fmt = "Waiting error: {_0}")]
232    Wait(UnrecoverableWaitError),
233}
234
235#[derive(
236    Decode,
237    Encode,
238    Debug,
239    Clone,
240    PartialEq,
241    Eq,
242    PartialOrd,
243    Ord,
244    derive_more::Display,
245    derive_more::From,
246)]
247#[codec(crate = scale)]
248pub enum TrapExplanation {
249    /// An error occurs in attempt to charge more gas than available during execution.
250    #[display(fmt = "Not enough gas to continue execution")]
251    GasLimitExceeded,
252    /// An error occurs in attempt to call forbidden sys-call.
253    #[display(fmt = "Unable to call a forbidden function")]
254    ForbiddenFunction,
255    /// The error occurs when a program tries to allocate more memory than
256    /// allowed.
257    #[display(fmt = "Trying to allocate more wasm program memory than allowed")]
258    ProgramAllocOutOfBounds,
259    #[display(fmt = "Sys-call unrecoverable error: {_0}")]
260    UnrecoverableExt(UnrecoverableExtError),
261    #[display(fmt = "{_0}")]
262    Panic(LimitedStr<'static>),
263    #[display(fmt = "Reason is unknown. Possibly `unreachable` instruction is occurred")]
264    Unknown,
265}
266
267#[derive(Debug, Default)]
268pub struct SystemReservationContext {
269    /// Reservation created in current execution.
270    pub current_reservation: Option<u64>,
271    /// Reservation from `ContextStore`.
272    pub previous_reservation: Option<u64>,
273}
274
275impl SystemReservationContext {
276    pub fn from_dispatch(dispatch: &IncomingDispatch) -> Self {
277        Self {
278            current_reservation: None,
279            previous_reservation: dispatch
280                .context()
281                .as_ref()
282                .and_then(|ctx| ctx.system_reservation()),
283        }
284    }
285
286    pub fn has_any(&self) -> bool {
287        self.current_reservation.is_some() || self.previous_reservation.is_some()
288    }
289}
290
291#[derive(Debug)]
292pub struct ExtInfo {
293    pub gas_amount: GasAmount,
294    pub gas_reserver: GasReserver,
295    pub system_reservation_context: SystemReservationContext,
296    pub allocations: BTreeSet<WasmPage>,
297    pub pages_data: BTreeMap<GearPage, PageBuf>,
298    pub generated_dispatches: Vec<(Dispatch, u32, Option<ReservationId>)>,
299    pub awakening: Vec<(MessageId, u32)>,
300    pub reply_deposits: Vec<(MessageId, u64)>,
301    pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
302    pub program_rents: BTreeMap<ProgramId, u32>,
303    pub context_store: ContextStore,
304}
305
306/// Extended externalities that can manage gas counters.
307pub trait BackendExternalities: Externalities + CountersOwner {
308    fn into_ext_info(self, memory: &impl Memory) -> Result<ExtInfo, MemoryError>;
309
310    fn gas_amount(&self) -> GasAmount;
311
312    /// Pre-process memory access if need.
313    fn pre_process_memory_accesses(
314        reads: &[MemoryInterval],
315        writes: &[MemoryInterval],
316        gas_counter: &mut u64,
317    ) -> Result<(), ProcessAccessError>;
318}
319
320/// A trait for conversion of the externalities API error
321/// to `UndefinedTerminationReason` and `RunFallibleError`.
322pub trait BackendSyscallError: Sized {
323    fn into_termination_reason(self) -> UndefinedTerminationReason;
324
325    fn into_run_fallible_error(self) -> RunFallibleError;
326}
327
328// TODO: consider to remove this trait and use Result<Result<Page, AllocError>, GasError> instead #2571
329/// A trait for conversion of the externalities memory management error to api error.
330///
331/// If the conversion fails, then `Self` is returned in the `Err` variant.
332pub trait BackendAllocSyscallError: Sized {
333    type ExtError: BackendSyscallError;
334
335    fn into_backend_error(self) -> Result<Self::ExtError, Self>;
336}
337
338pub struct BackendReport<EnvMem, Ext>
339where
340    Ext: Externalities,
341{
342    pub termination_reason: TerminationReason,
343    pub memory_wrap: EnvMem,
344    pub ext: Ext,
345}
346
347#[derive(Debug, derive_more::Display)]
348pub enum EnvironmentError<EnvSystemError: Display, PrepareMemoryError: Display> {
349    #[display(fmt = "Actor backend error: {_1}")]
350    Actor(GasAmount, String),
351    #[display(fmt = "System backend error: {_0}")]
352    System(EnvSystemError),
353    #[display(fmt = "Prepare error: {_1}")]
354    PrepareMemory(GasAmount, PrepareMemoryError),
355}
356
357impl<EnvSystemError: Display, PrepareMemoryError: Display>
358    EnvironmentError<EnvSystemError, PrepareMemoryError>
359{
360    pub fn from_infallible(err: EnvironmentError<EnvSystemError, Infallible>) -> Self {
361        match err {
362            EnvironmentError::System(err) => Self::System(err),
363            EnvironmentError::PrepareMemory(_, err) => match err {},
364            EnvironmentError::Actor(gas_amount, s) => Self::Actor(gas_amount, s),
365        }
366    }
367}
368
369type EnvironmentBackendReport<Env, EntryPoint> =
370    BackendReport<<Env as Environment<EntryPoint>>::Memory, <Env as Environment<EntryPoint>>::Ext>;
371
372pub type EnvironmentExecutionResult<PrepareMemoryError, Env, EntryPoint> = Result<
373    EnvironmentBackendReport<Env, EntryPoint>,
374    EnvironmentError<<Env as Environment<EntryPoint>>::SystemError, PrepareMemoryError>,
375>;
376
377pub trait Environment<EntryPoint = DispatchKind>: Sized
378where
379    EntryPoint: WasmEntryPoint,
380{
381    type Ext: BackendExternalities + 'static;
382
383    /// Memory type for current environment.
384    type Memory: Memory;
385
386    /// That's an error which originally comes from the primary
387    /// wasm execution environment (set by wasmi or sandbox).
388    /// So it's not the error of the `Self` itself, it's a kind
389    /// of wrapper over the underlying executor error.
390    type SystemError: Debug + Display;
391
392    /// 1) Instantiates wasm binary.
393    /// 2) Creates wasm memory
394    /// 3) Runs `prepare_memory` to fill the memory before running instance.
395    /// 4) Instantiate external funcs for wasm module.
396    fn new(
397        ext: Self::Ext,
398        binary: &[u8],
399        entry_point: EntryPoint,
400        entries: BTreeSet<DispatchKind>,
401        mem_size: WasmPage,
402    ) -> Result<Self, EnvironmentError<Self::SystemError, Infallible>>;
403
404    /// Run instance setup starting at `entry_point` - wasm export function name.
405    fn execute<PrepareMemory, PrepareMemoryError>(
406        self,
407        prepare_memory: PrepareMemory,
408    ) -> EnvironmentExecutionResult<PrepareMemoryError, Self, EntryPoint>
409    where
410        PrepareMemory: FnOnce(
411            &mut Self::Memory,
412            Option<u32>,
413            GlobalsAccessConfig,
414        ) -> Result<(), PrepareMemoryError>,
415        PrepareMemoryError: Display;
416}
417
418pub trait BackendState {
419    /// Set termination reason
420    fn set_termination_reason(&mut self, reason: UndefinedTerminationReason);
421
422    /// Process fallible syscall function result
423    fn process_fallible_func_result<T: Sized>(
424        &mut self,
425        res: Result<T, RunFallibleError>,
426    ) -> Result<Result<T, u32>, UndefinedTerminationReason> {
427        match res {
428            Err(RunFallibleError::FallibleExt(ext_err)) => {
429                let code = ext_err.to_u32();
430                log::trace!(target: "syscalls", "fallible syscall error: {ext_err}");
431                Ok(Err(code))
432            }
433            Err(RunFallibleError::UndefinedTerminationReason(reason)) => Err(reason),
434            Ok(res) => Ok(Ok(res)),
435        }
436    }
437
438    /// Process alloc function result
439    fn process_alloc_func_result<T: Sized, ExtAllocError: BackendAllocSyscallError>(
440        &mut self,
441        res: Result<T, ExtAllocError>,
442    ) -> Result<Result<T, ExtAllocError>, UndefinedTerminationReason> {
443        match res {
444            Ok(t) => Ok(Ok(t)),
445            Err(err) => match err.into_backend_error() {
446                Ok(ext_err) => Err(ext_err.into()),
447                Err(alloc_err) => Ok(Err(alloc_err)),
448            },
449        }
450    }
451}
452
453/// A trait for termination of the gear sys-calls execution backend.
454///
455/// Backend termination aims to return to the caller gear wasm program
456/// execution outcome, which is the state of externalities, memory and
457/// termination reason.
458pub trait BackendTermination<Ext: BackendExternalities>: Sized {
459    /// Transforms [`Self`] into tuple of externalities, memory and
460    /// termination reason returned after the execution.
461    fn into_parts(self) -> (Ext, UndefinedTerminationReason);
462
463    /// Terminates backend work after execution.
464    ///
465    /// The function handles `res`, which is the result of gear wasm
466    /// program entry point invocation, and the termination reason.
467    ///
468    /// If the `res` is `Ok`, then execution considered successful
469    /// and the termination reason will have the corresponding value.
470    ///
471    /// If the `res` is `Err`, then execution is considered to end
472    /// with an error and the actual termination reason, which stores
473    /// more precise information about the error, is returned.
474    ///
475    /// There's a case, when `res` is `Err`, but termination reason has
476    /// a value for the successful ending of the execution. This is the
477    /// case of calling `unreachable` panic in the program.
478    fn terminate<T: Debug, WasmCallErr: Debug>(
479        self,
480        res: Result<T, WasmCallErr>,
481        gas: u64,
482    ) -> (Ext, TerminationReason) {
483        log::trace!("Execution result = {res:?}");
484
485        let (mut ext, termination_reason) = self.into_parts();
486        let termination_reason = termination_reason.define(ext.current_counter_type());
487
488        ext.decrease_current_counter_to(gas);
489
490        let termination_reason = if res.is_err() {
491            if matches!(
492                termination_reason,
493                TerminationReason::Actor(ActorTerminationReason::Success)
494            ) {
495                ActorTerminationReason::Trap(TrapExplanation::Unknown).into()
496            } else {
497                termination_reason
498            }
499        } else if matches!(
500            termination_reason,
501            TerminationReason::Actor(ActorTerminationReason::Success)
502        ) {
503            termination_reason
504        } else {
505            unreachable!(
506                "Termination reason is not success, but executor successfully ends execution"
507            )
508        };
509
510        (ext, termination_reason)
511    }
512}
513
514#[macro_export]
515macro_rules! syscall_args_trace {
516    ($val:expr) => {
517        {
518            let s = stringify!($val);
519            if s.ends_with("_ptr") {
520                alloc::format!(", {} = {:#x?}", s, $val)
521            } else {
522                alloc::format!(", {} = {:?}", s, $val)
523            }
524        }
525    };
526    ($val:expr, $($rest:expr),+) => {
527        {
528            let mut s = $crate::syscall_args_trace!($val);
529            s.push_str(&$crate::syscall_args_trace!($($rest),+));
530            s
531        }
532    };
533}
534
535#[macro_export]
536macro_rules! syscall_trace {
537    ($name:expr, $($args:expr),+) => {
538        {
539            $crate::log::trace!(target: "syscalls", "{}{}", $name, $crate::syscall_args_trace!($($args),+));
540        }
541    };
542    ($name:expr) => {
543        {
544            $crate::log::trace!(target: "syscalls", "{}", $name);
545        }
546    }
547}
548
549#[cfg(test)]
550mod tests;