diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 7dd7bc2d46a..fefb32ac64c 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -8,6 +8,7 @@ pub mod native_vp; pub mod parameters; pub mod pos; pub mod storage; +pub mod storage_api; pub mod treasury; pub mod tx_env; pub mod vp_env; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 3893e847ed0..fa30efb7eae 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; +use super::storage_api::{self, ResultExt, StorageRead}; pub use super::vp_env::VpEnv; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; @@ -72,6 +73,30 @@ where pub cache_access: std::marker::PhantomData, } +/// Read access to the prior storage (state before tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPreStorageRead<'b, 'a: 'b, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + ctx: &'b Ctx<'a, DB, H, CA>, +} + +/// Read access to the posterior storage (state after tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPostStorageRead<'f, 'a: 'f, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + ctx: &'f Ctx<'a, DB, H, CA>, +} + impl<'a, DB, H, CA> Ctx<'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, @@ -111,6 +136,138 @@ where pub fn add_gas(&self, used_gas: u64) -> Result<(), vp_env::RuntimeError> { vp_env::add_gas(&mut *self.gas_meter.borrow_mut(), used_gas) } + + /// Read access to the prior storage (state before tx execution) + /// via [`trait@StorageRead`]. + pub fn pre<'b>(&'b self) -> CtxPreStorageRead<'b, 'a, DB, H, CA> { + CtxPreStorageRead { ctx: self } + } + + /// Read access to the posterior storage (state after tx execution) + /// via [`trait@StorageRead`]. + pub fn post<'b>(&'b self) -> CtxPostStorageRead<'b, 'a, DB, H, CA> { + CtxPostStorageRead { ctx: self } + } +} + +impl<'f, 'a, DB, H, CA> StorageRead for CtxPreStorageRead<'f, 'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + self.ctx.read_pre(key).into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> Result>, storage_api::Error> { + self.ctx.read_bytes_pre(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> Result { + self.ctx.has_key_pre(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> Result { + self.ctx.iter_prefix(prefix).into_storage_result() + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + self.ctx.iter_pre_next(iter).into_storage_result() + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().into_storage_result() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height().into_storage_result() + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash().into_storage_result() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch().into_storage_result() + } +} + +impl<'f, 'a, DB, H, CA> StorageRead for CtxPostStorageRead<'f, 'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + self.ctx.read_post(key).into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> Result>, storage_api::Error> { + self.ctx.read_bytes_post(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> Result { + self.ctx.has_key_post(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> Result { + self.ctx.iter_prefix(prefix).into_storage_result() + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + self.ctx.iter_post_next(iter).into_storage_result() + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().into_storage_result() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height().into_storage_result() + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash().into_storage_result() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch().into_storage_result() + } } impl<'a, DB, H, CA> VpEnv for Ctx<'a, DB, H, CA> diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index c980da81da2..a9d72e84bb9 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -87,3 +87,139 @@ impl From for Epoch { Epoch(epoch) } } + +#[macro_use] +mod macros { + /// Implement `PosReadOnly` for a type that implements + /// [`trait@crate::ledger::storage_api::StorageRead`]. + /// + /// Excuse the horrible syntax - we haven't found a better way to use this + /// for native_vp `CtxPreStorageRead`/`CtxPostStorageRead`, which have + /// generics and explicit lifetimes. + /// + /// # Examples + /// + /// ```ignore + /// impl_pos_read_only! { impl PosReadOnly for X } + /// ``` + #[macro_export] + macro_rules! impl_pos_read_only { + ( + // Type error type has to be declared before the impl. + // This error type must `impl From for $error`. + type $error:tt = $err_ty:ty ; + // Matches anything, so that we can use lifetimes and generic types. + // This expects `impl(<.*>)? PoSReadOnly for $ty(<.*>)?`. + $( $any:tt )* ) + => { + $( $any )* + { + type Address = $crate::types::address::Address; + // type Error = $crate::ledger::native_vp::Error; + type $error = $err_ty; + type PublicKey = $crate::types::key::common::PublicKey; + type TokenAmount = $crate::types::token::Amount; + type TokenChange = $crate::types::token::Change; + + const POS_ADDRESS: Self::Address = $crate::ledger::pos::ADDRESS; + + fn staking_token_address() -> Self::Address { + $crate::ledger::pos::staking_token_address() + } + + fn read_pos_params(&self) -> std::result::Result { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, ¶ms_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + + fn read_validator_staking_reward_address( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes( + self, + &validator_staking_reward_address_key(key), + )?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_consensus_key( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_consensus_key_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_state( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_state_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_total_deltas( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_total_deltas_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_voting_power( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_voting_power_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_slashes( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_slashes_key(key))?; + Ok(value + .map(|value| $crate::ledger::storage::types::decode(value).unwrap()) + .unwrap_or_default()) + } + + fn read_bond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &bond_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_unbond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &unbond_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_set( + &self, + ) -> std::result::Result { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_set_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + + fn read_total_voting_power( + &self, + ) -> std::result::Result { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_voting_power_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + } + } +} +} diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 80572c1f57a..2f19b6d1a49 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -26,17 +26,20 @@ use super::{ validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; +use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; -use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; +use crate::ledger::native_vp::{ + self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, +}; use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, }; -use crate::ledger::storage::types::decode; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage_api::{self, StorageRead}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; -use crate::types::{key, token}; +use crate::types::token; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -44,6 +47,8 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), + #[error("Storage error: {0}")] + StorageApi(storage_api::Error), } /// PoS functions result @@ -118,7 +123,8 @@ where let addr = Address::Internal(Self::ADDR); let mut changes: Vec> = vec![]; - let current_epoch = self.ctx.get_block_epoch()?; + let current_epoch = self.ctx.pre().get_block_epoch()?; + for key in keys_changed { if is_params_key(key) { let proposal_id = u64::try_from_slice(tx_data).ok(); @@ -127,8 +133,8 @@ where _ => return Ok(false), } } else if let Some(owner) = key.is_validity_predicate() { - let has_pre = self.ctx.has_key_pre(key)?; - let has_post = self.ctx.has_key_post(key)?; + let has_pre = self.ctx.pre().has_key(key)?; + let has_post = self.ctx.post().has_key(key)?; if has_pre && has_post { // VP updates must be verified by the owner return Ok(!verifiers.contains(owner)); @@ -137,18 +143,18 @@ where return Ok(false); } } else if is_validator_set_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); changes.push(ValidatorSet(Data { pre, post })); } else if let Some(validator) = is_validator_state_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -158,24 +164,24 @@ where } else if let Some(validator) = is_validator_staking_reward_address_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); changes.push(Validator { address: validator.clone(), update: StakingRewardAddress(Data { pre, post }), }); } else if let Some(validator) = is_validator_consensus_key_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -183,10 +189,10 @@ where update: ConsensusKey(Data { pre, post }), }); } else if let Some(validator) = is_validator_total_deltas_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -194,10 +200,10 @@ where update: TotalDeltas(Data { pre, post }), }); } else if let Some(validator) = is_validator_voting_power_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -207,14 +213,14 @@ where } else if let Some(raw_hash) = is_validator_address_raw_hash_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); // Find the raw hashes of the addresses let pre = pre.map(|pre| { let raw_hash = @@ -236,26 +242,27 @@ where if owner != &addr { continue; } - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); changes.push(Balance(Data { pre, post })); } else if let Some(bond_id) = is_bond_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Bonds::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Bonds::try_from_slice(&bytes[..]).ok() + }); // For bonds, we need to look-up slashes let slashes = self .ctx - .read_bytes_pre(&validator_slashes_key(&bond_id.validator))? + .pre() + .read_bytes(&validator_slashes_key(&bond_id.validator))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Bond { @@ -264,20 +271,19 @@ where slashes, }); } else if let Some(unbond_id) = is_unbond_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Unbonds::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Unbonds::try_from_slice(&bytes[..]).ok() + }); // For unbonds, we need to look-up slashes let slashes = self .ctx - .read_bytes_pre(&validator_slashes_key( - &unbond_id.validator, - ))? + .pre() + .read_bytes(&validator_slashes_key(&unbond_id.validator))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Unbond { @@ -286,10 +292,10 @@ where slashes, }); } else if is_total_voting_power_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(TotalVotingPower(Data { pre, post })); @@ -303,7 +309,7 @@ where } } - let params = self.read_pos_params()?; + let params = self.ctx.pre().read_pos_params()?; let errors = validate(¶ms, changes, current_epoch); Ok(if errors.is_empty() { true @@ -317,114 +323,22 @@ where } } -impl PosReadOnly for PosVP<'_, D, H, CA> -where - D: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Address = Address; +impl_pos_read_only! { type Error = native_vp::Error; - type PublicKey = key::common::PublicKey; - type TokenAmount = token::Amount; - type TokenChange = token::Change; - - const POS_ADDRESS: Self::Address = super::ADDRESS; - - fn staking_token_address() -> Self::Address { - super::staking_token_address() - } - - fn read_pos_params(&self) -> std::result::Result { - let value = self.ctx.read_bytes_pre(¶ms_key())?.unwrap(); - Ok(decode(value).unwrap()) - } - - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self - .ctx - .read_bytes_pre(&validator_staking_reward_address_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_consensus_key( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_consensus_key_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_state( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&validator_state_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_total_deltas( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_total_deltas_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_voting_power_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_slashes( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&validator_slashes_key(key))?; - Ok(value - .map(|value| decode(value).unwrap()) - .unwrap_or_default()) - } - - fn read_bond( - &self, - key: &BondId, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&bond_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_unbond( - &self, - key: &BondId, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&unbond_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_set( - &self, - ) -> std::result::Result { - let value = self.ctx.read_bytes_pre(&validator_set_key())?.unwrap(); - Ok(decode(value).unwrap()) - } + impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPreStorageRead<'f, 'a, DB, H, CA> + where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, + H: StorageHasher +'static, + CA: WasmCacheAccess +'static +} - fn read_total_voting_power( - &self, - ) -> std::result::Result { - let value = - self.ctx.read_bytes_pre(&total_voting_power_key())?.unwrap(); - Ok(decode(value).unwrap()) - } +impl_pos_read_only! { + type Error = native_vp::Error; + impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPostStorageRead<'f, 'a, DB, H, CA> + where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, + H: StorageHasher +'static, + CA: WasmCacheAccess +'static } impl From for Error { @@ -432,3 +346,9 @@ impl From for Error { Self::NativeVpError(err) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + Self::StorageApi(err) + } +} diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0b3d19a7427..1f106bcf69b 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -11,8 +11,9 @@ use core::fmt::Debug; use tendermint::merkle::proof::Proof; use thiserror::Error; -use super::parameters; use super::parameters::Parameters; +use super::storage_api::{ResultExt, StorageRead}; +use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ @@ -684,6 +685,79 @@ where } } +// The `'iter` lifetime is needed for the associated type `PrefixIter`. +// Note that the `D: DBIter<'iter>` bound uses another higher-rank lifetime +// (see https://doc.rust-lang.org/nomicon/hrtb.html). +impl<'iter, D, H> StorageRead for &'iter Storage +where + D: DB + for<'iter_> DBIter<'iter_>, + H: StorageHasher, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result, storage_api::Error> { + self.read_bytes(key) + .map(|maybe_value| { + maybe_value.and_then(|t| T::try_from_slice(&t[..]).ok()) + }) + .into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result>, storage_api::Error> { + self.db.read_subspace_val(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result { + self.block.tree.has_key(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> std::result::Result { + Ok(self.db.iter_prefix(prefix)) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> std::result::Result)>, storage_api::Error> + { + Ok(iter.next().map(|(key, val, _gas)| (key, val))) + } + + fn get_chain_id(&self) -> std::result::Result { + Ok(self.chain_id.to_string()) + } + + fn get_block_height( + &self, + ) -> std::result::Result { + Ok(self.block.height) + } + + fn get_block_hash( + &self, + ) -> std::result::Result { + Ok(self.block.hash.clone()) + } + + fn get_block_epoch( + &self, + ) -> std::result::Result { + Ok(self.block.epoch) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs new file mode 100644 index 00000000000..d01fbfd287e --- /dev/null +++ b/shared/src/ledger/storage_api/error.rs @@ -0,0 +1,68 @@ +//! Storage API error type, extensible with custom user errors and static string +//! messages. + +use thiserror::Error; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Custom(CustomError), + #[error("{0}: {1}")] + CustomWithMessage(&'static str, CustomError), +} + +/// Result of a storage API call. +pub type Result = std::result::Result; + +/// Result extension to easily wrap custom errors into [`Error`]. +// This is separate from `ResultExt`, because the implementation requires +// different bounds for `T`. +pub trait ResultExt { + /// Convert a [`std::result::Result`] into storage_api [`Result`]. + fn into_storage_result(self) -> Result; + + /// Add a static message to a possible error in [`Result`]. + fn wrap_err(self, msg: &'static str) -> Result; +} + +impl ResultExt for std::result::Result +where + E: std::error::Error + Send + Sync + 'static, +{ + fn into_storage_result(self) -> Result { + self.map_err(Error::new) + } + + fn wrap_err(self, msg: &'static str) -> Result { + self.map_err(|err| Error::wrap(msg, err)) + } +} + +impl Error { + /// Create an [`enum@Error`] from another [`std::error::Error`]. + pub fn new(error: E) -> Self + where + E: Into>, + { + Self::Custom(CustomError(error.into())) + } + + /// Wrap another [`std::error::Error`] with a static message. + pub fn wrap(msg: &'static str, error: E) -> Self + where + E: Into>, + { + Self::CustomWithMessage(msg, CustomError(error.into())) + } +} + +/// A custom error +#[derive(Debug)] +pub struct CustomError(pub Box); + +impl std::fmt::Display for CustomError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs new file mode 100644 index 00000000000..0c0e4f4083a --- /dev/null +++ b/shared/src/ledger/storage_api/mod.rs @@ -0,0 +1,53 @@ +//! The common storage read trait is implemented in the storage, client RPC, tx +//! and VPs (both native and WASM). + +mod error; + +use borsh::BorshDeserialize; +pub use error::{CustomError, Error, Result, ResultExt}; + +use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; + +/// Common storage read interface +pub trait StorageRead { + /// Storage read prefix iterator + type PrefixIter; + + /// Storage read Borsh encoded value. It will try to read from the storage + /// and decode it if found. + fn read( + &self, + key: &storage::Key, + ) -> Result>; + + /// Storage read raw bytes. It will try to read from the storage. + fn read_bytes(&self, key: &storage::Key) -> Result>>; + + /// Storage `has_key` in. It will try to read from the storage. + fn has_key(&self, key: &storage::Key) -> Result; + + /// Storage prefix iterator. It will try to get an iterator from the + /// storage. + fn iter_prefix(&self, prefix: &storage::Key) -> Result; + + /// Storage prefix iterator for. It will try to read from the storage. + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>>; + + /// Getting the chain ID. + fn get_chain_id(&self) -> Result; + + /// Getting the block height. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_height(&self) -> Result; + + /// Getting the block hash. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_hash(&self) -> Result; + + /// Getting the block epoch. The epoch is that of the block to which the + /// current transaction is being applied. + fn get_block_epoch(&self) -> Result; +} diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 578357b7339..1fc7fa788cb 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -1,87 +1,35 @@ //! Transaction environment contains functions that can be called from //! inside a tx. -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshSerialize; +use crate::ledger::storage_api::{self, StorageRead}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; -use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; +use crate::types::storage; use crate::types::time::Rfc3339String; /// Transaction host functions -pub trait TxEnv { - /// Storage read prefix iterator - type PrefixIter; - - /// Host functions possible errors, extensible with custom user errors. +pub trait TxEnv: StorageRead { + /// Host env functions possible errors type Error; - /// Storage read Borsh encoded value. It will try to read from the write log - /// first and if no entry found then from the storage and then decode it if - /// found. - fn read( - &self, - key: &storage::Key, - ) -> Result, Self::Error>; - - /// Storage read raw bytes. It will try to read from the write log first and - /// if no entry found then from the storage. - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, Self::Error>; - - /// Check if the storage contains the given key. It will try - /// to check the write log first and if no entry found then the storage. - fn has_key(&self, key: &storage::Key) -> Result; - - /// Getting the chain ID. - fn get_chain_id(&self) -> Result; - - /// Getting the block height. The height is that of the block to which the - /// current transaction is being applied. - fn get_block_height(&self) -> Result; - - /// Getting the block hash. The height is that of the block to which the - /// current transaction is being applied. - fn get_block_hash(&self) -> Result; - - /// Getting the block epoch. The epoch is that of the block to which the - /// current transaction is being applied. - fn get_block_epoch(&self) -> Result; - - /// Get time of the current block header as rfc 3339 string - fn get_block_time(&self) -> Result; - - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. - fn iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result; - - /// Storage prefix iterator next. It will try to read from the write log - /// first and if no entry found then from the storage. - fn iter_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error>; - - // --- MUTABLE ---- - /// Write a value to be encoded with Borsh at the given key to storage. fn write( &mut self, key: &storage::Key, val: T, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Write a value as bytes at the given key to storage. fn write_bytes( &mut self, key: &storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; + + /// Delete a value at the given key from storage. + fn delete(&mut self, key: &storage::Key) -> Result<(), storage_api::Error>; /// Write a temporary value to be encoded with Borsh at the given key to /// storage. @@ -98,9 +46,6 @@ pub trait TxEnv { val: impl AsRef<[u8]>, ) -> Result<(), Self::Error>; - /// Delete a value at the given key from storage. - fn delete(&mut self, key: &storage::Key) -> Result<(), Self::Error>; - /// Insert a verifier address. This address must exist on chain, otherwise /// the transaction will be rejected. /// @@ -126,4 +71,7 @@ pub trait TxEnv { /// Emit an IBC event. There can be only one event per transaction. On /// multiple calls, only the last emitted event will be used. fn emit_ibc_event(&mut self, event: &IbcEvent) -> Result<(), Self::Error>; + + /// Get time of the current block header as rfc 3339 string + fn get_block_time(&self) -> Result; } diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index e2ffd72e13d..95e7c48782f 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -7,6 +7,7 @@ use borsh::BorshDeserialize; use thiserror::Error; use super::gas::MIN_STORAGE_GAS; +use super::storage_api; use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; @@ -157,6 +158,8 @@ pub enum RuntimeError { ReadTemporaryValueError, #[error("Trying to read a permament value with read_temp")] ReadPermanentValueError, + #[error("Storage error: {0}")] + StorageApi(storage_api::Error), } /// VP environment function result @@ -460,3 +463,9 @@ where } Ok(None) } + +impl From for RuntimeError { + fn from(err: storage_api::Error) -> Self { + Self::StorageApi(err) + } +} diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 1700be22642..af63d1f1756 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -552,7 +552,7 @@ pub mod testing { use namada_tx_prelude::proof_of_stake::{ staking_token_address, BondId, Bonds, PosParams, Unbonds, }; - use namada_tx_prelude::Address; + use namada_tx_prelude::{Address, StorageRead}; use proptest::prelude::*; use crate::tx::{self, tx_host_env}; diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index d5392895331..cf9dccfb56d 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -34,7 +34,7 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_tx_prelude::{BorshDeserialize, BorshSerialize}; + use namada_tx_prelude::{BorshDeserialize, BorshSerialize, StorageRead}; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; diff --git a/tx_prelude/src/error.rs b/tx_prelude/src/error.rs index b34d954166c..ce7b9fa5e99 100644 --- a/tx_prelude/src/error.rs +++ b/tx_prelude/src/error.rs @@ -5,6 +5,7 @@ //! avoiding `error[E0117]: only traits defined in the current crate can be //! implemented for arbitrary types` +use namada::ledger::storage_api; use thiserror::Error; #[allow(missing_docs)] @@ -101,3 +102,11 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + // storage_api::Error::Custom(CustomError {Box}) + // Error:Custom(storage_api::Error::Custom(CustomError {Box})) + Self::new(err) + } +} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 21be213ea38..7be4e4c064b 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,6 +1,7 @@ //! IBC lower-level functions for transactions. pub use namada::ledger::ibc::handler::{Error, IbcActions, Result}; +use namada::ledger::storage_api::StorageRead; use namada::ledger::tx_env::TxEnv; use namada::types::address::Address; pub use namada::types::ibc::IbcEvent; diff --git a/tx_prelude/src/intent.rs b/tx_prelude/src/intent.rs index 7b6826342ee..05f7cede911 100644 --- a/tx_prelude/src/intent.rs +++ b/tx_prelude/src/intent.rs @@ -14,5 +14,6 @@ pub fn invalidate_exchange( let mut invalid_intent: HashSet = ctx.read(&key)?.unwrap_or_default(); invalid_intent.insert(intent.sig.clone()); - ctx.write(&key, &invalid_intent) + ctx.write(&key, &invalid_intent)?; + Ok(()) } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index e1fce61deef..52d9ec3e603 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -22,6 +22,8 @@ pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; +use namada::ledger::storage_api; +pub use namada::ledger::storage_api::StorageRead; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; @@ -105,14 +107,13 @@ pub type TxResult = EnvResult<()>; #[derive(Debug)] pub struct KeyValIterator(pub u64, pub PhantomData); -impl TxEnv for Ctx { - type Error = Error; +impl StorageRead for Ctx { type PrefixIter = KeyValIterator<(String, Vec)>; fn read( &self, key: &namada::types::storage::Key, - ) -> Result, Error> { + ) -> Result, storage_api::Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -123,7 +124,7 @@ impl TxEnv for Ctx { fn read_bytes( &self, key: &namada::types::storage::Key, - ) -> Result>, Error> { + ) -> Result>, storage_api::Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -133,14 +134,14 @@ impl TxEnv for Ctx { fn has_key( &self, key: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let key = key.to_string(); let found = unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; Ok(HostEnvResult::is_success(found)) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_tx_get_chain_id(result.as_ptr() as _); @@ -153,13 +154,13 @@ impl TxEnv for Ctx { fn get_block_height( &self, - ) -> Result { + ) -> Result { Ok(BlockHeight(unsafe { anoma_tx_get_block_height() })) } fn get_block_hash( &self, - ) -> Result { + ) -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_tx_get_block_hash(result.as_ptr() as _); @@ -170,24 +171,16 @@ impl TxEnv for Ctx { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch( + &self, + ) -> Result { Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) } - fn get_block_time(&self) -> Result { - let read_result = unsafe { anoma_tx_get_block_time() }; - let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) - .expect("The block time should exist"); - Ok(Rfc3339String( - String::try_from_slice(&time_value[..]) - .expect("The conversion shouldn't fail"), - )) - } - fn iter_prefix( &self, prefix: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -198,19 +191,33 @@ impl TxEnv for Ctx { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, Error> { + ) -> Result)>, storage_api::Error> { let read_result = unsafe { anoma_tx_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, anoma_tx_result_buffer, )) } +} + +impl TxEnv for Ctx { + type Error = Error; + + fn get_block_time(&self) -> Result { + let read_result = unsafe { anoma_tx_get_block_time() }; + let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) + .expect("The block time should exist"); + Ok(Rfc3339String( + String::try_from_slice(&time_value[..]) + .expect("The conversion shouldn't fail"), + )) + } fn write( &mut self, key: &namada::types::storage::Key, val: T, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let buf = val.try_to_vec().unwrap(); self.write_bytes(key, buf) } @@ -219,7 +226,7 @@ impl TxEnv for Ctx { &mut self, key: &namada::types::storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let key = key.to_string(); unsafe { anoma_tx_write( @@ -261,7 +268,7 @@ impl TxEnv for Ctx { fn delete( &mut self, key: &namada::types::storage::Key, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let key = key.to_string(); unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; Ok(()) diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index ce856cd876b..65a6c3f6cd0 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -1,6 +1,5 @@ //! Proof of Stake system integration with functions for transactions -use namada::ledger::pos::types::Slash; pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, @@ -9,7 +8,7 @@ use namada::ledger::pos::{ validator_staking_reward_address_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, }; -use namada::types::address::{self, Address, InternalAddress}; +use namada::types::address::Address; use namada::types::transaction::InitValidator; use namada::types::{key, token}; pub use namada_proof_of_stake::{ @@ -114,89 +113,9 @@ impl Ctx { } } -impl namada_proof_of_stake::PosReadOnly for Ctx { - type Address = Address; +namada::impl_pos_read_only! { type Error = crate::Error; - type PublicKey = key::common::PublicKey; - type TokenAmount = token::Amount; - type TokenChange = token::Change; - - const POS_ADDRESS: Self::Address = Address::Internal(InternalAddress::PoS); - - fn staking_token_address() -> Self::Address { - address::xan() - } - - fn read_pos_params(&self) -> Result { - let params = self.read(¶ms_key())?; - Ok(params.expect("PoS params should always be set")) - } - - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_staking_reward_address_key(key)) - } - - fn read_validator_consensus_key( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_consensus_key_key(key)) - } - - fn read_validator_state( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_state_key(key)) - } - - fn read_validator_total_deltas( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_total_deltas_key(key)) - } - - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_voting_power_key(key)) - } - - fn read_validator_slashes( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - let val = self.read(&validator_slashes_key(key))?; - Ok(val.unwrap_or_default()) - } - - fn read_bond(&self, key: &BondId) -> Result, Self::Error> { - self.read(&bond_key(key)) - } - - fn read_unbond( - &self, - key: &BondId, - ) -> Result, Self::Error> { - self.read(&unbond_key(key)) - } - - fn read_validator_set(&self) -> Result { - let val = self.read(&validator_set_key())?; - Ok(val.expect("Validator sets must always have a value")) - } - - fn read_total_voting_power( - &self, - ) -> Result { - let val = self.read(&total_voting_power_key())?; - Ok(val.expect("Total voting power must always have a value")) - } + impl namada_proof_of_stake::PosReadOnly for Ctx } impl From> for Error { @@ -237,7 +156,7 @@ impl namada_proof_of_stake::PosActions for Ctx { &mut self, params: &PosParams, ) -> Result<(), Self::Error> { - self.write(¶ms_key(), params) + self.write(¶ms_key(), params).into_env_result() } fn write_validator_address_raw_hash( @@ -246,6 +165,7 @@ impl namada_proof_of_stake::PosActions for Ctx { ) -> Result<(), Self::Error> { let raw_hash = address.raw_hash().unwrap().to_owned(); self.write(&validator_address_raw_hash_key(raw_hash), address) + .into_env_result() } fn write_validator_staking_reward_address( @@ -254,6 +174,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Self::Address, ) -> Result<(), Self::Error> { self.write(&validator_staking_reward_address_key(key), &value) + .into_env_result() } fn write_validator_consensus_key( @@ -262,6 +183,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorConsensusKeys, ) -> Result<(), Self::Error> { self.write(&validator_consensus_key_key(key), &value) + .into_env_result() } fn write_validator_state( @@ -270,6 +192,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorStates, ) -> Result<(), Self::Error> { self.write(&validator_state_key(key), &value) + .into_env_result() } fn write_validator_total_deltas( @@ -278,6 +201,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorTotalDeltas, ) -> Result<(), Self::Error> { self.write(&validator_total_deltas_key(key), &value) + .into_env_result() } fn write_validator_voting_power( @@ -286,6 +210,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorVotingPowers, ) -> Result<(), Self::Error> { self.write(&validator_voting_power_key(key), &value) + .into_env_result() } fn write_bond( @@ -293,7 +218,7 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Bonds, ) -> Result<(), Self::Error> { - self.write(&bond_key(key), &value) + self.write(&bond_key(key), &value).into_env_result() } fn write_unbond( @@ -301,14 +226,14 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Unbonds, ) -> Result<(), Self::Error> { - self.write(&unbond_key(key), &value) + self.write(&unbond_key(key), &value).into_env_result() } fn write_validator_set( &mut self, value: ValidatorSets, ) -> Result<(), Self::Error> { - self.write(&validator_set_key(), &value) + self.write(&validator_set_key(), &value).into_env_result() } fn write_total_voting_power( @@ -316,14 +241,15 @@ impl namada_proof_of_stake::PosActions for Ctx { value: TotalVotingPowers, ) -> Result<(), Self::Error> { self.write(&total_voting_power_key(), &value) + .into_env_result() } fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&bond_key(key)) + self.delete(&bond_key(key)).into_env_result() } fn delete_unbond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&unbond_key(key)) + self.delete(&unbond_key(key)).into_env_result() } fn transfer( diff --git a/vp_prelude/src/error.rs b/vp_prelude/src/error.rs index b34d954166c..099ae2540ab 100644 --- a/vp_prelude/src/error.rs +++ b/vp_prelude/src/error.rs @@ -5,6 +5,7 @@ //! avoiding `error[E0117]: only traits defined in the current crate can be //! implemented for arbitrary types` +use namada::ledger::storage_api; use thiserror::Error; #[allow(missing_docs)] @@ -101,3 +102,9 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + Self::new(err) + } +} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index ee64edbbae5..b6d7c90171f 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -22,6 +22,7 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use error::*; pub use namada::ledger::governance::storage as gov_storage; +pub use namada::ledger::storage_api::{self, StorageRead}; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; pub use namada::proto::{Signed, SignedTxData}; @@ -92,6 +93,7 @@ macro_rules! debug_log { }}; } +#[derive(Debug)] pub struct Ctx(()); impl Ctx { @@ -116,6 +118,32 @@ impl Ctx { pub const unsafe fn new() -> Self { Self(()) } + + /// Read access to the prior storage (state before tx execution) + /// via [`trait@StorageRead`]. + pub fn pre(&self) -> CtxPreStorageRead<'_> { + CtxPreStorageRead { _ctx: self } + } + + /// Read access to the posterior storage (state after tx execution) + /// via [`trait@StorageRead`]. + pub fn post(&self) -> CtxPostStorageRead<'_> { + CtxPostStorageRead { _ctx: self } + } +} + +/// Read access to the prior storage (state before tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPreStorageRead<'a> { + _ctx: &'a Ctx, +} + +/// Read access to the posterior storage (state after tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPostStorageRead<'a> { + _ctx: &'a Ctx, } /// Validity predicate result @@ -142,42 +170,28 @@ impl VpEnv for Ctx { &self, key: &storage::Key, ) -> Result, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + self.pre().read(key).into_env_result() } fn read_bytes_pre( &self, key: &storage::Key, ) -> Result>, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + self.pre().read_bytes(key).into_env_result() } fn read_post( &self, key: &storage::Key, ) -> Result, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + self.post().read(key).into_env_result() } fn read_bytes_post( &self, key: &storage::Key, ) -> Result>, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + self.post().read_bytes(key).into_env_result() } fn read_temp( @@ -202,80 +216,50 @@ impl VpEnv for Ctx { } fn has_key_pre(&self, key: &storage::Key) -> Result { - let key = key.to_string(); - let found = - unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; - Ok(HostEnvResult::is_success(found)) + self.pre().has_key(key).into_env_result() } fn has_key_post(&self, key: &storage::Key) -> Result { - let key = key.to_string(); - let found = - unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; - Ok(HostEnvResult::is_success(found)) + self.post().has_key(key).into_env_result() } fn get_chain_id(&self) -> Result { - let result = Vec::with_capacity(CHAIN_ID_LENGTH); - unsafe { - anoma_vp_get_chain_id(result.as_ptr() as _); - } - let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; - Ok(String::from_utf8(slice.to_vec()) - .expect("Cannot convert the ID string")) + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().get_chain_id().into_env_result() } fn get_block_height(&self) -> Result { - Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + self.pre().get_block_height().into_env_result() } fn get_block_hash(&self) -> Result { - let result = Vec::with_capacity(BLOCK_HASH_LENGTH); - unsafe { - anoma_vp_get_block_hash(result.as_ptr() as _); - } - let slice = unsafe { - slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) - }; - Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + self.pre().get_block_hash().into_env_result() } fn get_block_epoch(&self) -> Result { - Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + self.pre().get_block_epoch().into_env_result() } fn iter_prefix( &self, prefix: &storage::Key, ) -> Result { - let prefix = prefix.to_string(); - let iter_id = unsafe { - anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - Ok(KeyValIterator(iter_id, PhantomData)) + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().iter_prefix(prefix).into_env_result() } fn iter_pre_next( &self, iter: &mut Self::PrefixIter, ) -> Result)>, Self::Error> { - let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; - Ok(read_key_val_bytes_from_buffer( - read_result, - anoma_vp_result_buffer, - )) + self.pre().iter_next(iter).into_env_result() } fn iter_post_next( &self, iter: &mut Self::PrefixIter, ) -> Result)>, Self::Error> { - let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; - Ok(read_key_val_bytes_from_buffer( - read_result, - anoma_vp_result_buffer, - )) + self.post().iter_next(iter).into_env_result() } fn eval( @@ -322,3 +306,191 @@ impl VpEnv for Ctx { Ok(Hash::try_from(slice).expect("Cannot convert the hash")) } } + +impl StorageRead for CtxPreStorageRead<'_> { + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read( + &self, + key: &storage::Key, + ) -> Result, storage_api::Error> { + let bytes = self.read_bytes(key)?; + match bytes { + Some(bytes) => match T::try_from_slice(&bytes[..]) { + Ok(val) => Ok(Some(val)), + Err(err) => Err(storage_api::Error::new(err)), + }, + None => Ok(None), + } + } + + fn read_bytes( + &self, + key: &storage::Key, + ) -> Result>, storage_api::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn has_key(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Note that this is the same as `CtxPostStorageRead` + iter_prefix(prefix) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn get_chain_id(&self) -> Result { + get_chain_id() + } + + fn get_block_height(&self) -> Result { + Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + } + + fn get_block_hash(&self) -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_vp_get_block_hash(result.as_ptr() as _); + } + let slice = unsafe { + slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) + }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + } + + fn get_block_epoch(&self) -> Result { + Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + } +} + +impl StorageRead for CtxPostStorageRead<'_> { + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read( + &self, + key: &storage::Key, + ) -> Result, storage_api::Error> { + let bytes = self.read_bytes(key)?; + match bytes { + Some(bytes) => match T::try_from_slice(&bytes[..]) { + Ok(val) => Ok(Some(val)), + Err(err) => Err(storage_api::Error::new(err)), + }, + None => Ok(None), + } + } + + fn read_bytes( + &self, + key: &storage::Key, + ) -> Result>, storage_api::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn has_key(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Note that this is the same as `CtxPreStorageRead` + iter_prefix(prefix) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn get_chain_id(&self) -> Result { + get_chain_id() + } + + fn get_block_height(&self) -> Result { + get_block_height() + } + + fn get_block_hash(&self) -> Result { + get_block_hash() + } + + fn get_block_epoch(&self) -> Result { + get_block_epoch() + } +} + +fn iter_prefix( + prefix: &storage::Key, +) -> Result)>, storage_api::Error> { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) +} + +fn get_chain_id() -> Result { + let result = Vec::with_capacity(CHAIN_ID_LENGTH); + unsafe { + anoma_vp_get_chain_id(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; + Ok( + String::from_utf8(slice.to_vec()) + .expect("Cannot convert the ID string"), + ) +} + +fn get_block_height() -> Result { + Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) +} + +fn get_block_hash() -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_vp_get_block_hash(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) +} + +fn get_block_epoch() -> Result { + Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) +} diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 0e47437704a..2b6ef242426 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -119,7 +119,8 @@ pub mod main { "attempting to write new value {} to key {}", ARBITRARY_VALUE, key )); - ctx.write(&key, ARBITRARY_VALUE) + ctx.write(&key, ARBITRARY_VALUE)?; + Ok(()) } } @@ -147,7 +148,8 @@ pub mod main { let mut target_bal: token::Amount = ctx.read(&target_key)?.unwrap_or_default(); target_bal.receive(&amount); - ctx.write(&target_key, target_bal) + ctx.write(&target_key, target_bal)?; + Ok(()) } }