gear_lazy_pages/
common.rs

1// This file is part of Gear.
2
3// Copyright (C) 2023-2025 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//! Lazy-pages structures for common usage.
20
21use crate::{
22    globals::GlobalsContext,
23    mprotect::MprotectError,
24    pages::{GearPage, SIZES_AMOUNT, SizeManager, SizeNumber, WasmPage, WasmPagesAmount},
25};
26use gear_core::limited::LimitedStr;
27use gear_lazy_pages_common::{GlobalsAccessError, Status};
28use numerated::tree::IntervalsTree;
29use std::{fmt, num::NonZero};
30
31#[derive(Debug, derive_more::Display, derive_more::From)]
32pub enum Error {
33    #[display("Accessed memory interval is out of wasm memory")]
34    OutOfWasmMemoryAccess,
35    #[display("Signals cannot come from WASM program virtual stack memory")]
36    SignalFromStackMemory,
37    #[display("Signals cannot come from write accessed page")]
38    SignalFromWriteAccessedPage,
39    #[display("Read access signal cannot come from already accessed page")]
40    ReadAccessSignalFromAccessedPage,
41    #[display("WASM memory begin address is not set")]
42    WasmMemAddrIsNotSet,
43    #[display("Page data in storage must contain {expected} bytes, actually has {actual}")]
44    InvalidPageDataSize {
45        expected: u32,
46        actual: u32,
47    },
48    #[from(skip)]
49    #[display("Any page cannot be write accessed twice: {_0:?}")]
50    DoubleWriteAccess(GearPage),
51    #[from(skip)]
52    #[display("Any page cannot be read charged twice: {_0:?}")]
53    DoubleReadCharge(GearPage),
54    #[display("Memory protection error: {_0}")]
55    MemoryProtection(MprotectError),
56    #[display("Given instance host pointer is invalid")]
57    HostInstancePointerIsInvalid,
58    #[display("Given pointer to globals access provider dyn object is invalid")]
59    DynGlobalsAccessPointerIsInvalid,
60    #[display("Something goes wrong when trying to access globals: {_0:?}")]
61    AccessGlobal(GlobalsAccessError),
62    #[display("It's unknown whether memory access is read or write")]
63    ReadOrWriteIsUnknown,
64    #[display("Cannot receive signal from wasm memory, when status is gas limit exceed")]
65    SignalWhenStatusGasExceeded,
66    GlobalContext(ContextError),
67}
68
69#[derive(Debug, derive_more::Display)]
70pub enum ContextError {
71    RuntimeContextIsNotSet,
72    ExecutionContextIsNotSet,
73}
74
75#[derive(Debug, Default)]
76pub(crate) struct LazyPagesContext {
77    runtime_context: Option<LazyPagesRuntimeContext>,
78    execution_context: Option<LazyPagesExecutionContext>,
79}
80
81impl LazyPagesContext {
82    pub fn contexts(
83        &self,
84    ) -> Result<(&LazyPagesRuntimeContext, &LazyPagesExecutionContext), ContextError> {
85        Ok((self.runtime_context()?, self.execution_context()?))
86    }
87
88    pub fn contexts_mut(
89        &mut self,
90    ) -> Result<(&mut LazyPagesRuntimeContext, &mut LazyPagesExecutionContext), ContextError> {
91        let rt_ctx = self
92            .runtime_context
93            .as_mut()
94            .ok_or(ContextError::RuntimeContextIsNotSet)?;
95        let exec_ctx = self
96            .execution_context
97            .as_mut()
98            .ok_or(ContextError::ExecutionContextIsNotSet)?;
99        Ok((rt_ctx, exec_ctx))
100    }
101
102    pub fn runtime_context(&self) -> Result<&LazyPagesRuntimeContext, ContextError> {
103        self.runtime_context
104            .as_ref()
105            .ok_or(ContextError::RuntimeContextIsNotSet)
106    }
107
108    pub fn runtime_context_mut(&mut self) -> Result<&mut LazyPagesRuntimeContext, ContextError> {
109        self.runtime_context
110            .as_mut()
111            .ok_or(ContextError::RuntimeContextIsNotSet)
112    }
113
114    pub fn execution_context(&self) -> Result<&LazyPagesExecutionContext, ContextError> {
115        self.execution_context
116            .as_ref()
117            .ok_or(ContextError::ExecutionContextIsNotSet)
118    }
119
120    pub fn set_runtime_context(&mut self, ctx: LazyPagesRuntimeContext) {
121        self.runtime_context = Some(ctx);
122    }
123
124    pub fn set_execution_context(&mut self, ctx: LazyPagesExecutionContext) {
125        self.execution_context = Some(ctx);
126    }
127}
128
129pub(crate) type Costs = [u64; CostNo::Amount as usize];
130pub(crate) type GlobalNames = Vec<LimitedStr<'static>>;
131pub(crate) type PageSizes = [NonZero<u32>; SIZES_AMOUNT];
132
133#[derive(Debug)]
134pub(crate) struct LazyPagesRuntimeContext {
135    pub page_sizes: PageSizes,
136    pub global_names: GlobalNames,
137    pub pages_storage_prefix: Vec<u8>,
138    pub program_storage: Box<dyn LazyPagesStorage>,
139}
140
141impl LazyPagesRuntimeContext {
142    pub fn page_has_data_in_storage(&self, prefix: &mut PagePrefix, page: GearPage) -> bool {
143        let key = prefix.key_for_page(page);
144        self.program_storage.page_exists(key)
145    }
146
147    pub fn load_page_data_from_storage(
148        &mut self,
149        prefix: &mut PagePrefix,
150        page: GearPage,
151        buffer: &mut [u8],
152    ) -> Result<bool, Error> {
153        let key = prefix.key_for_page(page);
154        if let Some(size) = self.program_storage.load_page(key, buffer) {
155            if size != GearPage::size(self) {
156                return Err(Error::InvalidPageDataSize {
157                    expected: GearPage::size(self),
158                    actual: size,
159                });
160            }
161            Ok(true)
162        } else {
163            Ok(false)
164        }
165    }
166}
167
168pub trait LazyPagesStorage: fmt::Debug {
169    fn page_exists(&self, key: &[u8]) -> bool;
170
171    fn load_page(&mut self, key: &[u8], buffer: &mut [u8]) -> Option<u32>;
172}
173
174impl LazyPagesStorage for () {
175    fn page_exists(&self, _key: &[u8]) -> bool {
176        unreachable!()
177    }
178
179    fn load_page(&mut self, _key: &[u8], _buffer: &mut [u8]) -> Option<u32> {
180        unreachable!()
181    }
182}
183
184#[derive(Debug)]
185pub(crate) struct LazyPagesExecutionContext {
186    /// Lazy-pages accesses costs.
187    pub costs: Costs,
188    /// Pointer to the begin of wasm memory buffer
189    pub wasm_mem_addr: Option<usize>,
190    /// Wasm memory buffer size, to identify whether signal is from wasm memory buffer.
191    pub wasm_mem_size: WasmPagesAmount,
192    /// Current program prefix in storage
193    pub program_storage_prefix: PagePrefix,
194    /// Pages which has been accessed by program during current execution
195    pub accessed_pages: IntervalsTree<GearPage>,
196    /// Pages which has been write accessed by program during current execution
197    pub write_accessed_pages: IntervalsTree<GearPage>,
198    /// End of stack page (not inclusive). Default is `0`, which means,
199    /// that wasm data has no stack region. It's not necessary to specify
200    /// this value, `lazy-pages` uses it to identify memory, for which we
201    /// can skip processing and this memory won't be protected. So, pages
202    /// which lies before this value will never get into `write_accessed_pages`,
203    /// which means that they will never be uploaded to storage.
204    pub stack_end: WasmPage,
205    /// Context to access globals and works with them: charge gas, set status global.
206    pub globals_context: Option<GlobalsContext>,
207    /// Lazy-pages status: indicates in which mod lazy-pages works actually.
208    pub status: Status,
209}
210
211/// Lazy-pages version.
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub enum LazyPagesVersion {
214    Version1,
215}
216
217impl SizeManager for LazyPagesRuntimeContext {
218    fn size_non_zero<S: SizeNumber>(&self) -> NonZero<u32> {
219        self.page_sizes[S::SIZE_NO]
220    }
221}
222
223impl LazyPagesExecutionContext {
224    pub fn is_accessed(&self, page: GearPage) -> bool {
225        self.accessed_pages.contains(page)
226    }
227
228    pub fn is_write_accessed(&self, page: GearPage) -> bool {
229        self.write_accessed_pages.contains(page)
230    }
231
232    pub fn set_accessed(&mut self, page: GearPage) {
233        self.accessed_pages.insert(page);
234    }
235
236    pub fn set_write_accessed(&mut self, page: GearPage) -> Result<(), Error> {
237        self.set_accessed(page);
238        match self.write_accessed_pages.insert(page) {
239            true => Ok(()),
240            false => Err(Error::DoubleWriteAccess(page)),
241        }
242    }
243
244    pub fn cost(&self, no: CostNo) -> u64 {
245        self.costs[no as usize]
246    }
247}
248
249/// Struct for fast calculation of page key in storage.
250/// Key consists of two parts:
251/// 1) current program prefix in storage
252/// 2) page number in little endian bytes order
253///
254/// First part is always the same, so we can copy it to buffer
255///    once and then use it for all pages.
256#[derive(Debug)]
257pub(crate) struct PagePrefix {
258    buffer: Vec<u8>,
259}
260
261impl PagePrefix {
262    /// New page prefix from program prefix
263    pub(crate) fn new_from_program_prefix(mut storage_prefix: Vec<u8>) -> Self {
264        storage_prefix.extend_from_slice(&u32::MAX.to_le_bytes());
265        Self {
266            buffer: storage_prefix,
267        }
268    }
269
270    /// Returns key in storage for `page`.
271    fn key_for_page(&mut self, page: GearPage) -> &[u8] {
272        let len = self.buffer.len();
273        self.buffer[len - size_of::<u32>()..len]
274            .copy_from_slice(page.raw().to_le_bytes().as_slice());
275        &self.buffer
276    }
277}
278
279#[derive(Debug, Clone)]
280pub(crate) struct GasCharger {
281    pub read_cost: u64,
282    pub write_cost: u64,
283    pub write_after_read_cost: u64,
284    pub load_data_cost: u64,
285}
286
287impl GasCharger {
288    fn sub_gas(gas_counter: &mut u64, amount: u64) -> Status {
289        let new_gas = gas_counter.checked_sub(amount);
290        *gas_counter = new_gas.unwrap_or_default();
291        match new_gas {
292            None => Status::GasLimitExceeded,
293            Some(_) => Status::Normal,
294        }
295    }
296
297    pub fn charge_for_page_access(
298        &self,
299        gas_counter: &mut u64,
300        page: GearPage,
301        is_write: bool,
302        is_accessed: bool,
303    ) -> Result<Status, Error> {
304        let amount = match (is_write, is_accessed) {
305            (true, true) => self.write_after_read_cost,
306            (true, false) => self.write_cost,
307            (false, false) => self.read_cost,
308            (false, true) => return Err(Error::DoubleReadCharge(page)),
309        };
310        Ok(Self::sub_gas(gas_counter, amount))
311    }
312
313    pub fn charge_for_page_data_load(&mut self, gas_counter: &mut u64) -> Status {
314        Self::sub_gas(gas_counter, self.load_data_cost)
315    }
316}
317
318pub(crate) enum CostNo {
319    SignalRead = 0,
320    SignalWrite = 1,
321    SignalWriteAfterRead = 2,
322    HostFuncRead = 3,
323    HostFuncWrite = 4,
324    HostFuncWriteAfterRead = 5,
325    LoadPageDataFromStorage = 6,
326    Amount = 7,
327}