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