namada_vp/
vp_host_fns.rs

1//! Host functions for VPs used for both native and WASM VPs.
2
3use std::cell::RefCell;
4use std::fmt::Debug;
5
6use namada_core::address::{Address, ESTABLISHED_ADDRESS_BYTES_LEN};
7use namada_core::arith::checked;
8use namada_core::chain::{BlockHeader, BlockHeight, ChainId, Epoch, Epochs};
9use namada_core::hash::{HASH_LENGTH, Hash};
10use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex};
11use namada_events::{Event, EventTypeBuilder};
12use namada_gas::{self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE};
13use namada_tx::{BatchedTxRef, Section};
14use thiserror::Error;
15
16use crate::state::write_log::WriteLog;
17use crate::state::{DB, DBIter, PrefixIter, ResultExt, StateRead, write_log};
18pub use crate::state::{Error, Result};
19
20/// These runtime errors will abort VP execution immediately
21#[allow(missing_docs)]
22#[derive(Error, Debug)]
23pub enum RuntimeError {
24    #[error("Out of gas: {0}")]
25    OutOfGas(gas::Error),
26    #[error("Invalid transaction code hash")]
27    InvalidCodeHash,
28    #[error("No value found in result buffer")]
29    NoValueInResultBuffer,
30    #[error("The section signature is invalid: {0}")]
31    InvalidSectionSignature(String),
32}
33
34impl From<RuntimeError> for Error {
35    fn from(value: RuntimeError) -> Self {
36        Error::new(value)
37    }
38}
39
40/// VP environment function result
41pub type EnvResult<T> = std::result::Result<T, RuntimeError>;
42
43/// Add a gas cost incured in a validity predicate
44pub fn add_gas(
45    gas_meter: &RefCell<impl GasMetering>,
46    used_gas: Gas,
47) -> Result<()> {
48    gas_meter.borrow_mut().consume(used_gas).map_err(|err| {
49        tracing::info!("Stopping VP execution because of gas error: {}", err);
50        Error::new(RuntimeError::OutOfGas(err))
51    })
52}
53
54/// Storage read prior state (before tx execution). It will try to read from the
55/// storage.
56pub fn read_pre<S>(
57    gas_meter: &RefCell<impl GasMetering>,
58    state: &S,
59    key: &Key,
60) -> Result<Option<Vec<u8>>>
61where
62    S: StateRead + Debug,
63{
64    let (log_val, gas) =
65        state.write_log().read_pre(key).into_storage_result()?;
66    add_gas(gas_meter, gas)?;
67    match &log_val {
68        Some(write_log::StorageModification::Write { value }) => {
69            Ok(Some(value.clone()))
70        }
71        Some(write_log::StorageModification::Delete) => {
72            // Given key has been deleted
73            Ok(None)
74        }
75        Some(write_log::StorageModification::InitAccount { vp_code_hash }) => {
76            // Read the VP of a new account
77            Ok(Some(vp_code_hash.to_vec()))
78        }
79        None => {
80            // When not found in write log, try to read from the storage
81            let (value, gas) = state.db_read(key)?;
82            add_gas(gas_meter, gas)?;
83            Ok(value)
84        }
85    }
86}
87
88/// Storage read posterior state (after tx execution). It will try to read from
89/// the write log first and if no entry found then from the storage.
90pub fn read_post<S>(
91    gas_meter: &RefCell<impl GasMetering>,
92    state: &S,
93    key: &Key,
94) -> Result<Option<Vec<u8>>>
95where
96    S: StateRead + Debug,
97{
98    // Try to read from the write log first
99    let (log_val, gas) = state.write_log().read(key).into_storage_result()?;
100    add_gas(gas_meter, gas)?;
101    match log_val {
102        Some(write_log::StorageModification::Write { value }) => {
103            Ok(Some(value.clone()))
104        }
105        Some(write_log::StorageModification::Delete) => {
106            // Given key has been deleted
107            Ok(None)
108        }
109        Some(write_log::StorageModification::InitAccount { vp_code_hash }) => {
110            // Read the VP code hash of a new account
111            Ok(Some(vp_code_hash.to_vec()))
112        }
113        None => {
114            // When not found in write log, try
115            // to read from the storage
116            let (value, gas) = state.db_read(key)?;
117            add_gas(gas_meter, gas)?;
118            Ok(value)
119        }
120    }
121}
122
123/// Storage read temporary state (after tx execution). It will try to read from
124/// only the write log.
125pub fn read_temp<S>(
126    gas_meter: &RefCell<impl GasMetering>,
127    state: &S,
128    key: &Key,
129) -> Result<Option<Vec<u8>>>
130where
131    S: StateRead + Debug,
132{
133    let (log_val, gas) =
134        state.write_log().read_temp(key).into_storage_result()?;
135    add_gas(gas_meter, gas)?;
136    Ok(log_val.cloned())
137}
138
139/// Storage `has_key` in prior state (before tx execution). It will try to read
140/// from the storage.
141pub fn has_key_pre<S>(
142    gas_meter: &RefCell<impl GasMetering>,
143    state: &S,
144    key: &Key,
145) -> Result<bool>
146where
147    S: StateRead + Debug,
148{
149    // Try to read from the write log first
150    let (log_val, gas) =
151        state.write_log().read_pre(key).into_storage_result()?;
152    add_gas(gas_meter, gas)?;
153    match log_val {
154        Some(&write_log::StorageModification::Write { .. }) => Ok(true),
155        Some(&write_log::StorageModification::Delete) => {
156            // The given key has been deleted
157            Ok(false)
158        }
159        Some(&write_log::StorageModification::InitAccount { .. }) => Ok(true),
160        None => {
161            // When not found in write log, try to check the storage
162            let (present, gas) = state.db_has_key(key)?;
163            add_gas(gas_meter, gas)?;
164            Ok(present)
165        }
166    }
167}
168
169/// Storage `has_key` in posterior state (after tx execution). It will try to
170/// check the write log first and if no entry found then the storage.
171pub fn has_key_post<S>(
172    gas_meter: &RefCell<impl GasMetering>,
173    state: &S,
174    key: &Key,
175) -> Result<bool>
176where
177    S: StateRead + Debug,
178{
179    // Try to read from the write log first
180    let (log_val, gas) = state.write_log().read(key).into_storage_result()?;
181    add_gas(gas_meter, gas)?;
182    match log_val {
183        Some(write_log::StorageModification::Write { .. }) => Ok(true),
184        Some(write_log::StorageModification::Delete) => {
185            // The given key has been deleted
186            Ok(false)
187        }
188        Some(write_log::StorageModification::InitAccount { .. }) => Ok(true),
189        None => {
190            // When not found in write log, try
191            // to check the storage
192            let (present, gas) = state.db_has_key(key)?;
193            add_gas(gas_meter, gas)?;
194            Ok(present)
195        }
196    }
197}
198
199/// Getting the chain ID.
200pub fn get_chain_id<S>(
201    gas_meter: &RefCell<impl GasMetering>,
202    state: &S,
203) -> Result<ChainId>
204where
205    S: StateRead + Debug,
206{
207    let (chain_id, gas) = state.in_mem().get_chain_id();
208    add_gas(gas_meter, gas)?;
209    Ok(chain_id)
210}
211
212/// Getting the block height. The height is that of the block to which the
213/// current transaction is being applied if we are in between the
214/// `FinalizeBlock` and the `Commit` phases. For all the other phases we return
215/// the block height of next block that the consensus process will decide upon
216/// (i.e. the block height of the last committed block + 1)
217pub fn get_block_height<S>(
218    gas_meter: &RefCell<impl GasMetering>,
219    state: &S,
220) -> Result<BlockHeight>
221where
222    S: StateRead + Debug,
223{
224    let (height, gas) = state.in_mem().get_block_height();
225    add_gas(gas_meter, gas)?;
226    Ok(height)
227}
228
229/// Getting the block header.
230pub fn get_block_header<S>(
231    gas_meter: &RefCell<impl GasMetering>,
232    state: &S,
233    height: BlockHeight,
234) -> Result<Option<BlockHeader>>
235where
236    S: StateRead + Debug,
237{
238    let (header, gas) = StateRead::get_block_header(state, Some(height))?;
239    add_gas(gas_meter, gas)?;
240    Ok(header)
241}
242
243/// Getting the block hash. The height is that of the block to which the
244/// current transaction is being applied.
245pub fn get_tx_code_hash(
246    gas_meter: &RefCell<impl GasMetering>,
247    batched_tx: &BatchedTxRef<'_>,
248) -> Result<Option<Hash>> {
249    add_gas(
250        gas_meter,
251        (HASH_LENGTH as u64)
252            .checked_mul(MEMORY_ACCESS_GAS_PER_BYTE)
253            .expect("Consts mul that cannot overflow")
254            .into(),
255    )?;
256    let hash = batched_tx
257        .tx
258        .get_section(batched_tx.cmt.code_sechash())
259        .and_then(|x| Section::code_sec(x.as_ref()))
260        .map(|x| x.code.hash());
261    Ok(hash)
262}
263
264/// Getting the block epoch. The epoch is that of the block to which the
265/// current transaction is being applied.
266pub fn get_block_epoch<S>(
267    gas_meter: &RefCell<impl GasMetering>,
268    state: &S,
269) -> Result<Epoch>
270where
271    S: StateRead + Debug,
272{
273    let (epoch, gas) = state.in_mem().get_current_epoch();
274    add_gas(gas_meter, gas)?;
275    Ok(epoch)
276}
277
278/// Getting the block epoch. The epoch is that of the block to which the
279/// current transaction is being applied.
280pub fn get_tx_index(
281    gas_meter: &RefCell<impl GasMetering>,
282    tx_index: &TxIndex,
283) -> Result<TxIndex> {
284    add_gas(
285        gas_meter,
286        (TX_INDEX_LENGTH as u64)
287            .checked_mul(MEMORY_ACCESS_GAS_PER_BYTE)
288            .expect("Consts mul that cannot overflow")
289            .into(),
290    )?;
291    Ok(*tx_index)
292}
293
294/// Getting the native token's address.
295pub fn get_native_token<S>(
296    gas_meter: &RefCell<impl GasMetering>,
297    state: &S,
298) -> Result<Address>
299where
300    S: StateRead + Debug,
301{
302    add_gas(
303        gas_meter,
304        (ESTABLISHED_ADDRESS_BYTES_LEN as u64)
305            .checked_mul(MEMORY_ACCESS_GAS_PER_BYTE)
306            .expect("Consts mul that cannot overflow")
307            .into(),
308    )?;
309    Ok(state.in_mem().native_token.clone())
310}
311
312/// Given the information about predecessor block epochs
313pub fn get_pred_epochs<S>(
314    gas_meter: &RefCell<impl GasMetering>,
315    state: &S,
316) -> Result<Epochs>
317where
318    S: StateRead + Debug,
319{
320    let len = state.in_mem().block.pred_epochs.first_block_heights.len() as u64;
321    add_gas(
322        gas_meter,
323        checked!(len * 8 * MEMORY_ACCESS_GAS_PER_BYTE)?.into(),
324    )?;
325    Ok(state.in_mem().block.pred_epochs.clone())
326}
327
328/// Query events emitted by the current transaction.
329pub fn get_events<S>(
330    _gas_meter: &RefCell<impl GasMetering>,
331    state: &S,
332    event_type: String,
333) -> Result<Vec<Event>>
334where
335    S: StateRead + Debug,
336{
337    let event_type = EventTypeBuilder::new_with_type(event_type).build();
338
339    Ok(state
340        .write_log()
341        .lookup_events_with_prefix(&event_type)
342        .cloned()
343        .collect())
344}
345
346/// Storage prefix iterator for prior state (before tx execution), ordered by
347/// storage keys. It will try to get an iterator from the storage.
348pub fn iter_prefix_pre<'a, D>(
349    gas_meter: &RefCell<impl GasMetering>,
350    // We cannot use e.g. `&'a State`, because it doesn't live long
351    // enough - the lifetime of the `PrefixIter` must depend on the lifetime of
352    // references to the `WriteLog` and `DB`.
353    write_log: &'a WriteLog,
354    db: &'a D,
355    prefix: &Key,
356) -> Result<PrefixIter<'a, D>>
357where
358    D: DB + for<'iter> DBIter<'iter>,
359{
360    let (iter, gas) = namada_state::iter_prefix_pre(write_log, db, prefix)?;
361    add_gas(gas_meter, gas)?;
362    Ok(iter)
363}
364
365/// Storage prefix iterator for posterior state (after tx execution), ordered by
366/// storage keys. It will try to get an iterator from the storage.
367pub fn iter_prefix_post<'a, D>(
368    gas_meter: &RefCell<impl GasMetering>,
369    // We cannot use e.g. `&'a State`, because it doesn't live long
370    // enough - the lifetime of the `PrefixIter` must depend on the lifetime of
371    // references to the `WriteLog` and `DB`.
372    write_log: &'a WriteLog,
373    db: &'a D,
374    prefix: &Key,
375) -> Result<PrefixIter<'a, D>>
376where
377    D: DB + for<'iter> DBIter<'iter>,
378{
379    let (iter, gas) = namada_state::iter_prefix_post(write_log, db, prefix)?;
380    add_gas(gas_meter, gas)?;
381    Ok(iter)
382}
383
384/// Get the next item in a storage prefix iterator (pre or post).
385pub fn iter_next<DB>(
386    gas_meter: &RefCell<impl GasMetering>,
387    iter: &mut PrefixIter<'_, DB>,
388) -> Result<Option<(String, Vec<u8>)>>
389where
390    DB: namada_state::DB + for<'iter> namada_state::DBIter<'iter>,
391{
392    if let Some((key, val, gas)) = iter.next() {
393        add_gas(gas_meter, gas)?;
394        return Ok(Some((key, val)));
395    }
396    Ok(None)
397}