1use 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 pub costs: Costs,
188 pub wasm_mem_addr: Option<usize>,
190 pub wasm_mem_size: WasmPagesAmount,
192 pub program_storage_prefix: PagePrefix,
194 pub accessed_pages: IntervalsTree<GearPage>,
196 pub write_accessed_pages: IntervalsTree<GearPage>,
198 pub stack_end: WasmPage,
205 pub globals_context: Option<GlobalsContext>,
207 pub status: Status,
209}
210
211#[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#[derive(Debug)]
257pub(crate) struct PagePrefix {
258 buffer: Vec<u8>,
259}
260
261impl PagePrefix {
262 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 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}