gear_core/env.rs
1// This file is part of Gear.
2
3// Copyright (C) 2021-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//! Environment for running a module.
20
21use crate::{
22 env_vars::EnvVars,
23 ids::{MessageId, ProgramId, ReservationId},
24 memory::Memory,
25 message::{HandlePacket, InitPacket, MessageContext, Payload, ReplyPacket},
26 pages::WasmPage,
27};
28use alloc::collections::BTreeSet;
29use core::{fmt::Display, mem};
30use gear_core_errors::{ReplyCode, SignalCode};
31use gear_wasm_instrument::syscalls::SyscallName;
32
33/// Lock for the payload of the incoming/currently executing message.
34///
35/// The type was mainly introduced to establish type safety mechanics
36/// for the read of the payload from externalities. To type's purposes
37/// see [`Externalities::lock_payload`] docs.
38///
39/// ### Usage
40/// This type gives access to some slice of the currently executing message
41/// payload, but doesn't do it directly. It gives to the caller the [`PayloadSliceAccess`]
42/// wrapper, which actually can return the slice of the payload. But this wrapper
43/// is instantiated only inside the [`Self::drop_with`] method.
44/// This is actually done to prevent a user of the type from locking payload of the
45/// message, which actually moves it, and forgetting to unlock it back, because
46/// if access to the slice buffer was granted directly from the holder, the type user
47/// could have written the data to memory and then have dropped the holder. As a result
48/// the executing message payload wouldn't have been returned. So [`PayloadSliceLock::drop_with`]
49/// is a kind of scope-guard for the data and the [`PayloadSliceAccess`] is a data access guard.
50///
51/// For more usage info read [`Self::drop_with`] docs.
52pub struct PayloadSliceLock {
53 /// Locked payload
54 payload: Payload,
55 /// Range values indicating slice bounds.
56 range: (usize, usize),
57}
58
59impl PayloadSliceLock {
60 /// Creates a new [`PayloadSliceLock`] from the currently executed message context.
61 ///
62 /// The method checks whether received range (slice) is correct, i.e., the end is lower
63 /// than payload's length. If the check goes well, the ownership over payload will be
64 /// taken from the message context by [`mem::take`].
65 pub fn try_new((start, end): (u32, u32), msg_ctx: &mut MessageContext) -> Option<Self> {
66 let payload_len = msg_ctx.payload_mut().inner().len();
67 if end as usize > payload_len {
68 return None;
69 }
70
71 Some(Self {
72 payload: mem::take(msg_ctx.payload_mut()),
73 range: (start as usize, end as usize),
74 })
75 }
76
77 /// Releases back ownership of the locked payload to the message context.
78 ///
79 /// The method actually performs [`mem::swap`] under the hood. It's supposed
80 /// to be called from [`Externalities::unlock_payload`], implementor of which
81 /// owns provided message context.
82 fn release(&mut self, msg_ctx: &mut MessageContext) {
83 mem::swap(msg_ctx.payload_mut(), &mut self.payload);
84 }
85
86 /// Uses the lock in the provided `job` and drops the lock after running it.
87 ///
88 /// [`PayloadSliceLock`]'s main purpose is to provide safe access to the payload's
89 /// slice and ensure it will be returned back to the message.
90 ///
91 /// Type docs explain how safe access is designed with [`PayloadSliceAccess`].
92 ///
93 /// We ensure that the payload is released back by returning the [`DropPayloadLockBound`]
94 /// from the `job`. This type can actually be instantiated only from tuple of two:
95 /// [`UnlockPayloadBound`] and some result with err variant type to be `JobErr`.
96 /// The first is returned from [`Externalities::unlock_payload`], so it means that
97 /// that payload was reclaimed by the original owner. The other result stores actual
98 /// error of the `Job` as it could have called fallible actions inside it. So,
99 /// [`DropPayloadLockBound`] gives an opportunity to store the actual result of the job,
100 /// but also gives guarantee that payload was reclaimed.
101 pub fn drop_with<JobErr, Job>(mut self, mut job: Job) -> DropPayloadLockBound<JobErr>
102 where
103 Job: FnMut(PayloadSliceAccess<'_>) -> DropPayloadLockBound<JobErr>,
104 {
105 let held_range = PayloadSliceAccess(&mut self);
106 job(held_range)
107 }
108
109 fn in_range(&self) -> &[u8] {
110 let (start, end) = self.range;
111 // Will not panic as range is checked.
112 &self.payload.inner()[start..end]
113 }
114}
115
116/// A wrapper over mutable reference to [`PayloadSliceLock`]
117/// which can give to the caller the slice of the held payload.
118///
119/// For more information read [`PayloadSliceLock`] docs.
120pub struct PayloadSliceAccess<'a>(&'a mut PayloadSliceLock);
121
122impl<'a> PayloadSliceAccess<'a> {
123 /// Returns slice of the held payload.
124 pub fn as_slice(&self) -> &[u8] {
125 self.0.in_range()
126 }
127
128 /// Converts the wrapper into [`PayloadSliceLock`].
129 pub fn into_lock(self) -> &'a mut PayloadSliceLock {
130 self.0
131 }
132}
133
134/// Result of calling a `job` within [`PayloadSliceLock::drop_with`].
135///
136/// This is a "bound" type which means it's main purpose is to give
137/// some type-level guarantees. More precisely, it gives guarantee
138/// that payload value was reclaimed/unlocked by the owner. Also it stores the error
139/// of the `job`, which gives opportunity to handle the actual job's runtime
140/// error, but not bound wrappers.
141pub struct DropPayloadLockBound<JobError> {
142 job_result: Result<(), JobError>,
143}
144
145impl<JobErr> DropPayloadLockBound<JobErr> {
146 /// Convert into inner job of the [`PayloadSliceLock::drop_with`] result.
147 pub fn into_inner(self) -> Result<(), JobErr> {
148 self.job_result
149 }
150}
151
152impl<JobErr> From<(UnlockPayloadBound, Result<(), JobErr>)> for DropPayloadLockBound<JobErr> {
153 fn from((_token, job_result): (UnlockPayloadBound, Result<(), JobErr>)) -> Self {
154 DropPayloadLockBound { job_result }
155 }
156}
157
158/// Result of calling [`Externalities::unlock_payload`].
159///
160/// This is a "bound" type which means it doesn't store
161/// anything, but gives type-level guarantees that [`PayloadSliceLock`]
162/// released the payload back to the message context.
163pub struct UnlockPayloadBound(());
164
165impl From<(&mut MessageContext, &mut PayloadSliceLock)> for UnlockPayloadBound {
166 fn from((msg_ctx, payload_holder): (&mut MessageContext, &mut PayloadSliceLock)) -> Self {
167 payload_holder.release(msg_ctx);
168
169 UnlockPayloadBound(())
170 }
171}
172
173/// External api and data for managing memory and messages,
174/// use by an executing program to trigger state transition
175/// in runtime.
176pub trait Externalities {
177 /// An error issued in infallible syscall.
178 type UnrecoverableError;
179
180 /// An error issued in fallible syscall.
181 type FallibleError;
182
183 /// An error issued during allocation.
184 type AllocError: Display;
185
186 /// Allocate number of pages.
187 ///
188 /// The resulting page number should point to `pages` consecutive memory pages.
189 fn alloc<Context>(
190 &mut self,
191 ctx: &mut Context,
192 mem: &mut impl Memory<Context>,
193 pages_num: u32,
194 ) -> Result<WasmPage, Self::AllocError>;
195
196 /// Free specific page.
197 fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError>;
198
199 /// Free specific memory range.
200 fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError>;
201
202 /// Get environment variables currently set in the system and in the form
203 /// corresponded to the requested version.
204 fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError>;
205
206 /// Get the current block height.
207 fn block_height(&self) -> Result<u32, Self::UnrecoverableError>;
208
209 /// Get the current block timestamp.
210 fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError>;
211
212 /// Initialize a new incomplete message for another program and return its handle.
213 fn send_init(&mut self) -> Result<u32, Self::FallibleError>;
214
215 /// Push an extra buffer into message payload by handle.
216 fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError>;
217
218 /// Complete message and send it to another program.
219 fn send_commit(
220 &mut self,
221 handle: u32,
222 msg: HandlePacket,
223 delay: u32,
224 ) -> Result<MessageId, Self::FallibleError>;
225
226 /// Send message to another program.
227 fn send(&mut self, msg: HandlePacket, delay: u32) -> Result<MessageId, Self::FallibleError> {
228 let handle = self.send_init()?;
229 self.send_commit(handle, msg, delay)
230 }
231
232 /// Push the incoming message buffer into message payload by handle.
233 fn send_push_input(
234 &mut self,
235 handle: u32,
236 offset: u32,
237 len: u32,
238 ) -> Result<(), Self::FallibleError>;
239
240 /// Complete message and send it to another program using gas from reservation.
241 fn reservation_send_commit(
242 &mut self,
243 id: ReservationId,
244 handle: u32,
245 msg: HandlePacket,
246 delay: u32,
247 ) -> Result<MessageId, Self::FallibleError>;
248
249 /// Send message to another program using gas from reservation.
250 fn reservation_send(
251 &mut self,
252 id: ReservationId,
253 msg: HandlePacket,
254 delay: u32,
255 ) -> Result<MessageId, Self::FallibleError> {
256 let handle = self.send_init()?;
257 self.reservation_send_commit(id, handle, msg, delay)
258 }
259
260 /// Push an extra buffer into reply message.
261 fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError>;
262
263 /// Complete reply message and send it to source program.
264 fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError>;
265
266 /// Complete reply message and send it to source program from reservation.
267 fn reservation_reply_commit(
268 &mut self,
269 id: ReservationId,
270 msg: ReplyPacket,
271 ) -> Result<MessageId, Self::FallibleError>;
272
273 /// Produce reply to the current message.
274 fn reply(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
275 self.reply_commit(msg)
276 }
277
278 /// Produce reply to the current message from reservation.
279 fn reservation_reply(
280 &mut self,
281 id: ReservationId,
282 msg: ReplyPacket,
283 ) -> Result<MessageId, Self::FallibleError> {
284 self.reservation_reply_commit(id, msg)
285 }
286
287 /// Get the message id of the initial message.
288 fn reply_to(&self) -> Result<MessageId, Self::FallibleError>;
289
290 /// Get the message id which signal issues from.
291 fn signal_from(&self) -> Result<MessageId, Self::FallibleError>;
292
293 /// Push the incoming message buffer into reply message.
294 fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError>;
295
296 /// Get the source of the message currently being handled.
297 fn source(&self) -> Result<ProgramId, Self::UnrecoverableError>;
298
299 /// Get the reply code if the message being processed.
300 fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError>;
301
302 /// Get the signal code if the message being processed.
303 fn signal_code(&self) -> Result<SignalCode, Self::FallibleError>;
304
305 /// Get the id of the message currently being handled.
306 fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError>;
307
308 /// Get the id of program itself
309 fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError>;
310
311 /// Send debug message.
312 ///
313 /// This should be no-op in release builds.
314 fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError>;
315
316 /// Takes ownership over payload of the executing message and
317 /// returns it in the wrapper [`PayloadSliceLock`], which acts
318 /// like lock.
319 ///
320 /// Due to details of implementation of the runtime which executes gear
321 /// syscalls inside wasm execution environment, to prevent additional memory
322 /// allocation on payload read op, we give ownership over payload to the caller.
323 /// Giving ownership over payload actually means, that the payload value in the
324 /// currently executed message will become empty.
325 /// To prevent from the risk of payload being not "returned" back to the
326 /// message a [`Externalities::unlock_payload`] is introduced. For more info,
327 /// read docs to [`PayloadSliceLock`], [`DropPayloadLockBound`],
328 /// [`UnlockPayloadBound`], [`PayloadSliceAccess`] types and their methods.
329 fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError>;
330
331 /// Reclaims ownership from the payload lock over previously taken payload from the
332 /// currently executing message..
333 ///
334 /// It's supposed, that the implementation of the method calls `PayloadSliceLock::release`.
335 fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound;
336
337 /// Size of currently handled message payload.
338 fn size(&self) -> Result<usize, Self::UnrecoverableError>;
339
340 /// Returns a random seed for the current block with message id as a subject, along with the time in the past since when it was determinable by chain observers.
341 fn random(&self) -> Result<(&[u8], u32), Self::UnrecoverableError>;
342
343 /// Reserve some gas for a few blocks.
344 fn reserve_gas(
345 &mut self,
346 amount: u64,
347 duration: u32,
348 ) -> Result<ReservationId, Self::FallibleError>;
349
350 /// Unreserve gas using reservation ID.
351 fn unreserve_gas(&mut self, id: ReservationId) -> Result<u64, Self::FallibleError>;
352
353 /// Do system reservation.
354 fn system_reserve_gas(&mut self, amount: u64) -> Result<(), Self::FallibleError>;
355
356 /// Tell how much gas is left in running context.
357 fn gas_available(&self) -> Result<u64, Self::UnrecoverableError>;
358
359 /// Value associated with message.
360 fn value(&self) -> Result<u128, Self::UnrecoverableError>;
361
362 /// Tell how much value is left in running context.
363 fn value_available(&self) -> Result<u128, Self::UnrecoverableError>;
364
365 /// Interrupt the program and reschedule execution for maximum.
366 fn wait(&mut self) -> Result<(), Self::UnrecoverableError>;
367
368 /// Interrupt the program and reschedule execution in duration.
369 fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError>;
370
371 /// Interrupt the program and reschedule execution for maximum,
372 /// but not more than duration.
373 fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError>;
374
375 /// Wake the waiting message and move it to the processing queue.
376 fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError>;
377
378 /// Send init message to create a new program.
379 fn create_program(
380 &mut self,
381 packet: InitPacket,
382 delay: u32,
383 ) -> Result<(MessageId, ProgramId), Self::FallibleError>;
384
385 /// Create deposit to handle reply on given message.
386 fn reply_deposit(
387 &mut self,
388 message_id: MessageId,
389 amount: u64,
390 ) -> Result<(), Self::FallibleError>;
391
392 /// Return the set of functions that are forbidden to be called.
393 fn forbidden_funcs(&self) -> &BTreeSet<SyscallName>;
394}