From 603c5624e5a3022e8b8acc2e89e853c6d5102a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Dec 2022 17:42:37 +0100 Subject: [PATCH 01/43] test in apps/ledger/storage: test prefix iterators --- apps/src/lib/node/ledger/storage/mod.rs | 63 ++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index f24cffa6a3..cb9d6eb21d 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -50,10 +50,13 @@ fn new_blake2b() -> Blake2b { #[cfg(test)] mod tests { + use borsh::BorshSerialize; + use itertools::Itertools; use namada::ledger::storage::types; - use namada::types::address; + use namada::ledger::storage_api; use namada::types::chain::ChainId; use namada::types::storage::{BlockHash, BlockHeight, Key}; + use namada::types::{address, storage}; use proptest::collection::vec; use proptest::prelude::*; use proptest::test_runner::Config; @@ -344,4 +347,62 @@ mod tests { Ok(()) } + + /// Test the prefix iterator with RocksDB. + #[test] + fn test_persistent_storage_prefix_iter() { + let db_path = + TempDir::new().expect("Unable to create a temporary DB directory"); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); + + let prefix = storage::Key::parse("prefix").unwrap(); + let mismatched_prefix = storage::Key::parse("different").unwrap(); + // We'll write sub-key in some random order to check prefix iter's order + let sub_keys = [2_i32, 1, i32::MAX, -1, 260, -2, i32::MIN, 5, 0]; + + for i in sub_keys.iter() { + let key = prefix.push(i).unwrap(); + let value = i.try_to_vec().unwrap(); + storage.write(&key, value).unwrap(); + + let key = mismatched_prefix.push(i).unwrap(); + let value = (i / 2).try_to_vec().unwrap(); + storage.write(&key, value).unwrap(); + } + storage.commit().unwrap(); + + // Then try to iterate over their prefix + let iter = storage_api::iter_prefix(&storage, &prefix) + .unwrap() + .map(Result::unwrap); + + // The order has to be sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter, expected); + + // Try to iterate over their prefix in reverse + let iter = storage_api::rev_iter_prefix(&storage, &prefix) + .unwrap() + .map(|res| { + let (key, val): (storage::Key, i32) = Result::unwrap(res); + (key, val) + }); + + // The order has to be reverse sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .rev() + .map(|i| (prefix.push(i).unwrap(), *i / 2)); + + itertools::assert_equal(iter, expected); + } } From f78ee6079e6ed69765167d5ad988c50a0fbacfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Dec 2022 17:56:37 +0100 Subject: [PATCH 02/43] storage: remove rev_iter_prefix, which doesn't work as expected --- apps/src/lib/node/ledger/storage/mod.rs | 17 ----- apps/src/lib/node/ledger/storage/rocksdb.rs | 9 +-- core/src/ledger/storage/mockdb.rs | 14 ---- core/src/ledger/storage/mod.rs | 20 ------ core/src/ledger/storage_api/mod.rs | 78 --------------------- core/src/ledger/vp_env.rs | 7 -- shared/src/ledger/native_vp/mod.rs | 26 ------- shared/src/ledger/vp_host_fns.rs | 16 ----- shared/src/vm/host_env.rs | 64 ----------------- shared/src/vm/wasm/host_env.rs | 2 - tests/src/vm_host_env/mod.rs | 26 ------- tests/src/vm_host_env/tx.rs | 1 - tests/src/vm_host_env/vp.rs | 1 - tx_prelude/src/lib.rs | 16 +---- vm_env/src/lib.rs | 14 ---- vp_prelude/src/lib.rs | 36 +--------- 16 files changed, 6 insertions(+), 341 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index cb9d6eb21d..1bd1446c51 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -387,22 +387,5 @@ mod tests { .sorted() .map(|i| (prefix.push(i).unwrap(), *i)); itertools::assert_equal(iter, expected); - - // Try to iterate over their prefix in reverse - let iter = storage_api::rev_iter_prefix(&storage, &prefix) - .unwrap() - .map(|res| { - let (key, val): (storage::Key, i32) = Result::unwrap(res); - (key, val) - }); - - // The order has to be reverse sorted by sub-key value - let expected = sub_keys - .iter() - .sorted() - .rev() - .map(|i| (prefix.push(i).unwrap(), *i / 2)); - - itertools::assert_equal(iter, expected); } } diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 86980813fc..fa7c981697 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -891,11 +891,7 @@ impl<'iter> DBIter<'iter> for RocksDB { &'iter self, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { - iter_prefix(self, prefix, Direction::Forward) - } - - fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { - iter_prefix(self, prefix, Direction::Reverse) + iter_prefix(self, prefix) } fn iter_results(&'iter self) -> PersistentPrefixIterator<'iter> { @@ -922,7 +918,6 @@ impl<'iter> DBIter<'iter> for RocksDB { fn iter_prefix<'iter>( db: &'iter RocksDB, prefix: &Key, - direction: Direction, ) -> PersistentPrefixIterator<'iter> { let db_prefix = "subspace/".to_owned(); let prefix = format!("{}{}", db_prefix, prefix); @@ -937,7 +932,7 @@ fn iter_prefix<'iter>( read_opts.set_iterate_upper_bound(upper_prefix); let iter = db.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), direction), + IteratorMode::From(prefix.as_bytes(), Direction::Forward), read_opts, ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 011d8faac8..ac96e12cf5 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -456,20 +456,6 @@ impl<'iter> DBIter<'iter> for MockDB { ) } - fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { - let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); - let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new( - MockIterator { - prefix, - iter, - reverse_order: true, - }, - db_prefix, - ) - } - fn iter_results(&'iter self) -> MockPrefixIterator { let db_prefix = "results/".to_owned(); let prefix = "results".to_owned(); diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 8285e58bab..4c34a6c2c4 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -305,10 +305,6 @@ pub trait DBIter<'iter> { /// ordered by the storage keys. fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; - /// Read account subspace key value pairs with the given prefix from the DB, - /// reverse ordered by the storage keys. - fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; - /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; } @@ -515,15 +511,6 @@ where (self.db.iter_prefix(prefix), prefix.len() as _) } - /// Returns a prefix iterator, reverse ordered by storage keys, and the gas - /// cost - pub fn rev_iter_prefix( - &self, - prefix: &Key, - ) -> (>::PrefixIter, u64) { - (self.db.rev_iter_prefix(prefix), prefix.len() as _) - } - /// Returns a prefix iterator and the gas cost pub fn iter_results(&self) -> (>::PrefixIter, u64) { (self.db.iter_results(), 0) @@ -1033,13 +1020,6 @@ where Ok(self.db.iter_prefix(prefix)) } - fn rev_iter_prefix( - &'iter self, - prefix: &crate::types::storage::Key, - ) -> std::result::Result { - Ok(self.db.rev_iter_prefix(prefix)) - } - fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 7c842a6136..dde9eb5ade 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -68,16 +68,6 @@ pub trait StorageRead<'iter> { prefix: &storage::Key, ) -> Result; - /// Storage prefix iterator in reverse order of the storage keys. It will - /// try to get an iterator from the storage. - /// - /// For a more user-friendly iterator API, use [`fn@rev_iter_prefix`] or - /// [`fn@rev_iter_prefix_bytes`] instead. - fn rev_iter_prefix( - &'iter self, - prefix: &storage::Key, - ) -> Result; - /// Storage prefix iterator. It will try to read from the storage. fn iter_next( &self, @@ -195,71 +185,3 @@ where }); Ok(iter) } - -/// Iterate items matching the given prefix, reverse ordered by the storage -/// keys. -pub fn rev_iter_prefix_bytes<'a>( - storage: &'a impl StorageRead<'a>, - prefix: &crate::types::storage::Key, -) -> Result)>> + 'a> { - let iter = storage.rev_iter_prefix(prefix)?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((key, val))) => { - let key = match storage::Key::parse(key).into_storage_result() { - Ok(key) => key, - Err(err) => { - // Propagate key encoding errors into Iterator's Item - return Some(Err(err)); - } - }; - Some(Ok((key, val))) - } - Ok(None) => None, - Err(err) => { - // Propagate `iter_next` errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) -} - -/// Iterate Borsh encoded items matching the given prefix, reverse ordered by -/// the storage keys. -pub fn rev_iter_prefix<'a, T>( - storage: &'a impl StorageRead<'a>, - prefix: &crate::types::storage::Key, -) -> Result> + 'a> -where - T: BorshDeserialize, -{ - let iter = storage.rev_iter_prefix(prefix)?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((key, val))) => { - let key = match storage::Key::parse(key).into_storage_result() { - Ok(key) => key, - Err(err) => { - // Propagate key encoding errors into Iterator's Item - return Some(Err(err)); - } - }; - let val = match T::try_from_slice(&val).into_storage_result() { - Ok(val) => val, - Err(err) => { - // Propagate val encoding errors into Iterator's Item - return Some(Err(err)); - } - }; - Some(Ok((key, val))) - } - Ok(None) => None, - Err(err) => { - // Propagate `iter_next` errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) -} diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index 49bd5d515c..4b7edc02ce 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -70,13 +70,6 @@ pub trait VpEnv<'view> { prefix: &Key, ) -> Result; - /// Storage prefix iterator, reverse ordered by storage keys. It will try to - /// get an iterator from the storage. - fn rev_iter_prefix( - &self, - prefix: &Key, - ) -> Result; - /// Evaluate a validity predicate with given data. The address, changed /// storage keys and verifiers will have the same values as the input to /// caller's validity predicate. diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index 8943c4f8d3..d8f1dbe6e5 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -203,13 +203,6 @@ where .into_storage_result() } - fn rev_iter_prefix( - &self, - prefix: &crate::types::storage::Key, - ) -> storage_api::Result { - self.ctx.rev_iter_prefix(prefix).into_storage_result() - } - fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -291,13 +284,6 @@ where .into_storage_result() } - fn rev_iter_prefix( - &self, - prefix: &crate::types::storage::Key, - ) -> storage_api::Result { - self.ctx.rev_iter_prefix(prefix).into_storage_result() - } - fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -450,18 +436,6 @@ where .into_storage_result() } - fn rev_iter_prefix( - &self, - prefix: &Key, - ) -> Result { - vp_host_fns::rev_iter_prefix( - &mut self.gas_meter.borrow_mut(), - self.storage, - prefix, - ) - .into_storage_result() - } - fn eval( &self, vp_code: Vec, diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 8fc013075a..c678744ccf 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -311,22 +311,6 @@ where Ok(iter) } -/// Storage prefix iterator, reverse ordered by storage keys. It will try to get -/// an iterator from the storage. -pub fn rev_iter_prefix<'a, DB, H>( - gas_meter: &mut VpGasMeter, - storage: &'a Storage, - prefix: &Key, -) -> EnvResult<>::PrefixIter> -where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, -{ - let (iter, gas) = storage.rev_iter_prefix(prefix); - add_gas(gas_meter, gas)?; - Ok(iter) -} - /// Storage prefix iterator for prior state (before tx execution). It will try /// to read from the storage. pub fn iter_pre_next( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 90e6e4405f..9e61651b5e 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -706,38 +706,6 @@ where Ok(iterators.insert(iter).id()) } -/// Storage prefix iterator function exposed to the wasm VM Tx environment. -/// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator, reverse ordered by storage keys. -pub fn tx_rev_iter_prefix( - env: &TxVmEnv, - prefix_ptr: u64, - prefix_len: u64, -) -> TxResult -where - MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - CA: WasmCacheAccess, -{ - let (prefix, gas) = env - .memory - .read_string(prefix_ptr, prefix_len as _) - .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; - tx_add_gas(env, gas)?; - - tracing::debug!("tx_rev_iter_prefix {}, prefix {}", prefix, prefix_ptr); - - let prefix = - Key::parse(prefix).map_err(TxRuntimeError::StorageDataError)?; - - let storage = unsafe { env.ctx.storage.get() }; - let iterators = unsafe { env.ctx.iterators.get() }; - let (iter, gas) = storage.rev_iter_prefix(&prefix); - tx_add_gas(env, gas)?; - Ok(iterators.insert(iter).id()) -} - /// Storage prefix iterator next function exposed to the wasm VM Tx environment. /// It will try to read from the write log first and if no entry found then from /// the storage. @@ -1272,38 +1240,6 @@ where Ok(iterators.insert(iter).id()) } -/// Storage prefix iterator function exposed to the wasm VM VP environment. -/// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator, reverse ordered by storage keys. -pub fn vp_rev_iter_prefix( - env: &VpVmEnv, - prefix_ptr: u64, - prefix_len: u64, -) -> vp_host_fns::EnvResult -where - MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - EVAL: VpEvaluator, - CA: WasmCacheAccess, -{ - let (prefix, gas) = env - .memory - .read_string(prefix_ptr, prefix_len as _) - .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; - - let prefix = Key::parse(prefix) - .map_err(vp_host_fns::RuntimeError::StorageDataError)?; - tracing::debug!("vp_rev_iter_prefix {}", prefix); - - let storage = unsafe { env.ctx.storage.get() }; - let iter = vp_host_fns::rev_iter_prefix(gas_meter, storage, &prefix)?; - let iterators = unsafe { env.ctx.iterators.get() }; - Ok(iterators.insert(iter).id()) -} - /// Storage prefix iterator for prior state (before tx execution) function /// exposed to the wasm VM VP environment. It will try to read from the storage. /// diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index e4e173f47c..b9eeffecfc 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -67,7 +67,6 @@ where "namada_tx_write_temp" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_write_temp), "namada_tx_delete" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_delete), "namada_tx_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_prefix), - "namada_tx_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_rev_iter_prefix), "namada_tx_iter_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_next), "namada_tx_insert_verifier" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_insert_verifier), "namada_tx_update_validity_predicate" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_update_validity_predicate), @@ -110,7 +109,6 @@ where "namada_vp_has_key_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_pre), "namada_vp_has_key_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_post), "namada_vp_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix), - "namada_vp_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_rev_iter_prefix), "namada_vp_iter_pre_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_pre_next), "namada_vp_iter_post_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_post_next), "namada_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index aa8c160339..4ab7b6eca7 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -183,19 +183,6 @@ mod tests { .sorted() .map(|i| (prefix.push(i).unwrap(), *i)); itertools::assert_equal(iter, expected); - - // Try to iterate over their prefix in reverse - let iter = namada_tx_prelude::rev_iter_prefix(tx::ctx(), &prefix) - .unwrap() - .map(Result::unwrap); - - // The order has to be reverse sorted by sub-key value - let expected = sub_keys - .iter() - .sorted() - .rev() - .map(|i| (prefix.push(i).unwrap(), *i)); - itertools::assert_equal(iter, expected); } #[test] @@ -429,19 +416,6 @@ mod tests { (prefix.push(i).unwrap(), val) }); itertools::assert_equal(iter_post, expected_post); - - // Try to iterate over their prefix in reverse - let iter_pre = namada_vp_prelude::rev_iter_prefix(&ctx_pre, &prefix) - .unwrap() - .map(|item| item.unwrap()); - - // The order in has to be reverse sorted by sub-key value - let expected_pre = sub_keys - .iter() - .sorted() - .rev() - .map(|i| (prefix.push(i).unwrap(), *i)); - itertools::assert_equal(iter_pre, expected_pre); } #[test] diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 05542438d6..0f7040941d 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -400,7 +400,6 @@ mod native_tx_host_env { )); native_host_fn!(tx_delete(key_ptr: u64, key_len: u64)); native_host_fn!(tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); - native_host_fn!(tx_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(tx_iter_next(iter_id: u64) -> i64); native_host_fn!(tx_insert_verifier(addr_ptr: u64, addr_len: u64)); native_host_fn!(tx_update_validity_predicate( diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 50cbc7b6ef..a88176d182 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -332,7 +332,6 @@ mod native_vp_host_env { native_host_fn!(vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_has_key_post(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); - native_host_fn!(vp_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(vp_iter_pre_next(iter_id: u64) -> i64); native_host_fn!(vp_iter_post_next(iter_id: u64) -> i64); native_host_fn!(vp_get_chain_id(result_ptr: u64)); diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index d0d4511577..73a90b6d03 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -21,9 +21,8 @@ pub use namada_core::ledger::parameters::storage as parameters_storage; pub use namada_core::ledger::slash_fund::storage as slash_fund_storage; pub use namada_core::ledger::storage::types::encode; pub use namada_core::ledger::storage_api::{ - self, iter_prefix, iter_prefix_bytes, rev_iter_prefix, - rev_iter_prefix_bytes, Error, OptionExt, ResultExt, StorageRead, - StorageWrite, + self, iter_prefix, iter_prefix_bytes, Error, OptionExt, ResultExt, + StorageRead, StorageWrite, }; pub use namada_core::ledger::tx_env::TxEnv; pub use namada_core::proto::{Signed, SignedTxData}; @@ -190,17 +189,6 @@ impl StorageRead<'_> for Ctx { Ok(KeyValIterator(iter_id, PhantomData)) } - fn rev_iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result { - let prefix = prefix.to_string(); - let iter_id = unsafe { - namada_tx_rev_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - Ok(KeyValIterator(iter_id, PhantomData)) - } - fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 795ae912dc..e328a8748c 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -51,13 +51,6 @@ pub mod tx { // keys. pub fn namada_tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; - // Get an ID of a data iterator with key prefix, reverse ordered by - // storage keys. - pub fn namada_tx_rev_iter_prefix( - prefix_ptr: u64, - prefix_len: u64, - ) -> u64; - // Returns the size of the value (can be 0), or -1 if there's no next // value. If a value is found, it will be placed in the read // cache, because we cannot allocate a buffer for it before we know @@ -150,13 +143,6 @@ pub mod vp { // keys. pub fn namada_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; - // Get an ID of a data iterator with key prefix, reverse ordered by - // storage keys. - pub fn namada_vp_rev_iter_prefix( - prefix_ptr: u64, - prefix_len: u64, - ) -> u64; - // Read variable-length prior state when we don't know the size // up-front, returns the size of the value (can be 0), or -1 if // the key is not present. If a value is found, it will be placed in the diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 6ff90f56df..f4911516d5 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -20,8 +20,8 @@ pub use borsh::{BorshDeserialize, BorshSerialize}; pub use namada_core::ledger::governance::storage as gov_storage; pub use namada_core::ledger::parameters; pub use namada_core::ledger::storage_api::{ - self, iter_prefix, iter_prefix_bytes, rev_iter_prefix, - rev_iter_prefix_bytes, Error, OptionExt, ResultExt, StorageRead, + self, iter_prefix, iter_prefix_bytes, Error, OptionExt, ResultExt, + StorageRead, }; pub use namada_core::ledger::vp_env::VpEnv; pub use namada_core::proto::{Signed, SignedTxData}; @@ -258,14 +258,6 @@ impl<'view> VpEnv<'view> for Ctx { iter_prefix_impl(prefix) } - fn rev_iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result { - // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - rev_iter_prefix_impl(prefix) - } - fn eval( &self, vp_code: Vec, @@ -354,13 +346,6 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { iter_prefix_impl(prefix) } - fn rev_iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result { - rev_iter_prefix_impl(prefix) - } - fn get_chain_id(&self) -> Result { get_chain_id() } @@ -424,13 +409,6 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { iter_prefix_impl(prefix) } - fn rev_iter_prefix( - &self, - prefix: &storage::Key, - ) -> storage_api::Result { - rev_iter_prefix_impl(prefix) - } - fn get_chain_id(&self) -> Result { get_chain_id() } @@ -466,16 +444,6 @@ fn iter_prefix_impl( Ok(KeyValIterator(iter_id, PhantomData)) } -fn rev_iter_prefix_impl( - prefix: &storage::Key, -) -> Result)>, Error> { - let prefix = prefix.to_string(); - let iter_id = unsafe { - namada_vp_rev_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 { From 0d4688daea2564e6ea23882276e88db1391e81fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Dec 2022 18:33:15 +0100 Subject: [PATCH 03/43] wasm_for_tests: make --- wasm_for_tests/tx_memory_limit.wasm | Bin 133398 -> 133419 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 352858 -> 356170 bytes wasm_for_tests/tx_no_op.wasm | Bin 25554 -> 25554 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201332 -> 205677 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152564 -> 152457 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 226795 -> 228613 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156524 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156524 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157461 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158972 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170817 -> 168172 bytes 11 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 9c4ea2abccbcef666288b7ccb0a12e07dd9d4c27..09fcaea999799c8f256d0c2ba28b73dd249e8409 100755 GIT binary patch delta 2071 zcmahKYiv|i`h4f!xxLdnk1IkSb6e)jFoh}YlqtfLKA=5qr-jm1LQ?;*HigxVo#G2i zq8K_5Up1OY`Lb133kAwUCBClNm1v}Hc73zEs9A}cpzES2YB0w5_@RF1QX2i`{qcS0 zd!Fxk?)Yx=@!jUGl_kEs`q%&;8DRsEhmirs2K0P>><^{dfHpFAb1duEIy*G(2hhRd zrmh2n05=!|0IiURg9Tgz;J^xiB;fOzA(DX33yG~h%>ZA)*t=19YV5l;$(Sf!l3r0C zZfIyzprh$8z9eds{D!~OBKTIh^T;tmZQuhv&P*o%3lS} z^Z0Bu1r~l3-R_Qy7h#@P9VUFzpCe_tNN7WL84kwZf^uwHa2>%z3-Saj5_dwm`&c3g zrOG73Z2_%N$8`a+D#ObNP2u^9|AB-5TDTd?uyN6lD2uTGw*-?rK<>JWa-PqAL%A<6 z8U~n;jg=Pwv3hZJm8t5$;)Vi#so$K{TLcj$^6Z_|)lUfs(~(?i!o)ikUkN6@y|^Oc zwWs|jk+s*!jE&Rdgo_&ngnLfD@=kqstEbC^2qIkyS=I$Ab}58-$F2) z`q)ausC3fKC78gUqHFMcb+LPOO&34}k1i3R2qmFQAuo(mMiKn)lD$xaThonJP1vGR zA2W5x?y?&|WwQ*+eHP9g6URMP0Z`{42 zhX&`v6*to0tgF9);EDQ(H!b{EvxD128TN~E{JcI3A7i>9wc+euYNAd#mgS^rS9py} zHn6Z@@|{bfa8CM31ahtfTaUABP$RLOlc#9MlZ<5;ZJ(Nm*NC2ZahQAwpKFMQPb(*? zhNNC-SKucN55gIDr17kZ`kIyLvYA^vd%8u=7%*v(_ERhGgM-+#YF5pQGeByO(hDGc z(&0ZIz?_W0Iowagy9cmWMvONOPyhkEK^7T*S4Y1(!ZUbTs{=affx8s}Kj1{GV|=;)k}3aSzj6kz#_yZ}1ec&w20^eo4ZAD&fVrj?W$`BH8ZneUmCdJ~oM=wuo`pC2=u6-R{sloFtXg z`>>`p!XNSwyn(T-!>2vMc-||ym*x}1zZ^%i>zrf9=sE?|gF~A_&n1&b74s;5la0-r zx+s$QZrTr6yJbs|F$?#sxz+9KY~onek+r-RQ~~VD<+`L(A zx^4K{Tf~-YH(rB#dKU6?A$+N)2rD;i(ij_b@82-QG2A}~zrMQUj#&TSV*T*#W4{37 C>>nxs delta 2133 zcmah~Yiv|S6rMBp?%lh+yWNYSuidsgyKU+A1zR3l+CtmulkzBuihqb$P!zT(0ZSqp zTaZU$P=Ov(t&+C1pazWuub>8%Y7iuW#AxFyQItmviBU8n2^#gxEg=4KlY3^)bI$qB znb}wS%>F)e*W8)Br-yZVdex70LND}mGS;ap7J56i9(OSm>+ZbnX7{>ii>bA>YTN|S z!Qwt$2L^s_Fb1IMJPouo9!U3c4S)ml0%^hH@de@LPcyvW+5JTXUfX@OJzi88o-uQF zb+D#(4pik%&d8jV6$oaB{5g5?(z5c3sf|s|bqz9Y7p{V1u*ZpW3OS4kEdhIQxwe3_ zdHA|^Dl$Ac#-=e=%b4KOOv&eoFJY|8<#!EYzn+8(ct+n&4$czK35f35H^$zLWMC^K;<9)F57Cx$3QaK48=l&#Tez3?#Ns`mbo0e-qKw{uO+D z)H&m?2U;;bJ>S?mM8OMidis9tV!*;l;mP<@x@`%n#*S$c+SZ{JY~!p0?+hNH#qrw} zhGJ_oFTpe{%BqKivoh-wfJ#i}tbj^qASX@N+5zyDycrn^3A;6Cf~^pB9xaSRj&ezITRuzqlJ9hE~aC=)*rt%FBET9u_kcF{Qq)MQ;$qWXp?N zso_5m5x%hGQWHMBqjWL&aI~~2WLi`J)UYgCW~StEGNX!xN0@l7v?DKOCV4`bE@E-& zwMR|fAr+9@ai^m!4D1jxsY(?ohcWvojfFZWaW`S|j*L70xM ziCW+EphbwkWNP?SqIqi65MH^7r^*?V$9fkA$jgwMc`D`=&>rA~&Su^vjJAMIS$X>^ zGouQ{s3G0)WWq=(>l@ynjIgTmP=-ch30+ZYvC=inQ-L}DUD>3jv3a`Wl~WX?6rTyN z(Ctcoe}CrhyP7;6b=q_$J?3_%=cEXmXzpq76`{Po9%$@lP7O(gia z3gV@pWS(Z1;MJPv;2URW?L}3>hPjE{>!-N>bc($0z@$aiAI#kWL%68!rkR_kpUn2t z<(CSn0GSQcoei>%7z?XL*%7)FI{r}~!oyRR5I9%s*Z=QD!6zH`@WZ3Hvngckrz)h4 zy)qM?#bq)K+wf5t=D&~NV=`o17$NO#O3ObR!Fxz@Qc3(uV!W@mK3v<9eZq9Mz1N8 z`;>4W>aEd?;R%&w^XM+Xg7xbIjNaNG=e1ex>!}F+4b`=8{15+O`(4f(t#i1kQ`aq> zNvvso1Y`5>=4?K0o`0INg}8XZ61BXv;C^eg5a^xUG{&p}%v#vO$0s?<7XC#4CX<|w zjzNFl(!$d5bem-}#&YNj(Wmxd`a0Hji{)$9h!q>VS3WG(uUvi~&RMzw9&q+9?c(^` Q-KATS%ic;ZgDVIA0kS|e1poj5 diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 72e1e8f075a1bc7c0f7061848deb59e2bbb37dd9..e3bd3e5724de626414d994fe376b8bd934b16aec 100755 GIT binary patch delta 69043 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 z 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 Oll{3RHc#iajsO4|rb*TS delta 248 zcmca~obl3e#tF(wb#)90O!f5)35@lObqTEXAi!A9%*(^V%E-pZ#LmDx(KVfolZ%^` zonzuAZ6*efiT7=}H#8n#a6ADd1lT64Gp3{PXEHjAKLAT9G4nDgFexxOa%3s6DliGK zPX5ejzy^_*{m##ybK-+jE;=n zMMb;}4BV6BINc#uf$Rrby@9h5g>S+YwpoSUhKYlPL4lE*g~4(18n%kbQ@CzU4&~O` JJe}J*0sxSCMNt3% diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 584ebfdcb192dc64c52f445596312cd0127c8d51..2124f281201c5e2012436d61d4b7c06b3065587f 100755 GIT binary patch delta 46300 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 j<=2U1getw_-Tgs%KI-Yv!d7gUe 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 delta 14135 zcmc&*2Ygh;);}|MYwB)7dPp`2C6tgL5CTSE>AfikN=XQTB&3-H>29J3_~aol!h-ZJ zRbZ(C8lDJ9ks?pwfdW1eQQ?7#ii&*yxw{)~P+#Hqeeb(Ja(Dh`&YW}R%$YM~mzUO< zzguIT`B+>2+;fzncgTk_n1el+0Sev>WTFh6tYD4|GpHA_S6g)bEk?BQ!7Q+^bE-a^Jw>GWqHA1`h2=3XoUHc9lJ9X~T z^{J-^^&c?M=lne;-e>#Sbk|G{@iWCdr7@c>jwm^tI*X>dltza)R#Gz2qeM2vZppJ( z0@ovIu3}T3eU}z*7PEEf>^pHnxAH%2xJ)k_exhIWpEnE^dkyu}#$n>FAp(}W!i@_V z+aLx7Mv0TAq~KlaE6Lv6#_jW1o_*mmfMdd9uK6&k3e8g0JoA``Y24cfja}n?+B5d5 zc-wa%OpNgB{?L-dNxyy%(|Clx?88j|x^j?r``_Vb*9d=8S9Vt13mBxjExo$SZmy9o zbFB}Y!(v|CR22|g*ksoNYZ_xq#hIXyQq^O@L$v1V zABZPaoF<<1_RXre3&Hi+WKmfoLd4cceHa0AYq$l(NFy%Qc;esdgS$<*SQB!l?o@Qz z-oU=Aypk*y^0ipW*Iq5CZpsKd=2{+FTMlhrcs+3|ECsHN5AUSnfqcUnaZw*4{uVxu z9TBAwqvNJ;tE5CusoamZv5}1-JpJ!9ScBsfU%PB%Z`lKb!{k_N28UsAV&dhTeL6l}3YZ_iOisB+YxTY#J7+1+epa1suEn)ha;dI; zU8i!<79gyThl-nZ4dSP|huBSFs~5*-t`aAjMhjLihJB_b46WCyk85>R)*)%g9xX;z zdPj@H^Xx~p*p_GiK#Oa`C~>1+q+^Pf3aK-+7*gkIF{CcmVn|)B#gMwb3j3LT?MZfK zF~m~`e~XRm=T=qkz-3^ECy$bJ^))R6bluWo=(?lD(DiE-_A_a81wz+`swx0gP_3m8LVyL*K#n5m^i=m<7 z0S!~Cu%BMj8$Z~$P1_W`*$Z6Fa|g5x&~;3Uq3etmqc`WOu%Aw&%NM$)R#mw@_lg#y zTTTzI^4tzby;=+;aRuJYDAEdz8d z)ne#cB?23*5ZfAc=hRj3c0deH>xB2Jw0?O1oEB^HEDYa?_!jH<6@OuQ zJS>8@k)|YaQ@oFzb&@4Zyn@m*vs-rJ)Khe6xdR_A_Gpqpex}$z&?#LQ6Q+($M0sHi z_K~YkM?WTpc1jW_i$=JCTG79kUfF0; z#1Bumcf=WNMteEWYe-~&+tJ;hd~F7M1<#8&+E^EVZeswWY=%z$I><4m@*2|7dK*JY zenNpoUBKcDc6D_kn*nqEzC7k>=H=Zry^_GI_f6%|pozVMoFNJZh>eQFawe^4(^(q9 zMc&|ca`4|6+>M)!Hl3v@hGwcu$tYzFWjFKU(6c0>cxDeTvxkp{zp1CrVIvr2({=QR z;v`}=MJ&&3%%+MnnUBMxiNjJAw%0W@`!Gi+CFay($Hn6OOtC3vCAumy@C8y(D#&a($t8mc`>Y)Nt**)Q%E$HnZ!Rw9uomE>Z9{JR|( zGg=-1kZwf!GqLPlG3c39?3jy}Tw!dl_@Xq$y5B8UZ4qc`Xn$AKE1Sz+5tU_Cb*joB z)XfoW^z-aPF?lq0i9KD{N3UmWm6$&!$}k7I>=v^t``9S9Qw$y(#W!sgG2WgV6Ru0kHf#AV$|O8kj@ z#ZX5lUv*O{U)W@Bag3{mo0a!>)t;2jO*-sJeRX6P8z+txbIM=f)LleY^phXYRE$8D zI$QBJc90=r{tG?j$F&zqFsTMjwkmADt8nTSAM=3-3(4nz7&LoYT24_xc5+GPxa5-T z;-X~tIXI~xGq*4~tEeP9xwIrJxoli0v@ZlxdrKOEZO4F?tYU&CXQ7ptOYetZT zIArDy%c7;C^Q&+BX9QbF0VTV%jPgZ|ISFj6NSo7uFANq*&Ui6(&KYZQ4GVP!%VzwA z;;&fb&rS2GGN_TW!pM`w$8#fPKkm$Zgbf!Bo$qi86}OySS(!+mw*bR(c3x}tq$_;> zC@w}VsE<(Ev>-CD1weshOx_Hz@kKuDi)R1 zOx!|bsD)#`@@NAh75V6y2Z*4>%^aH#5gA{{1;7?eGcSO6ei9w!i%?QRx)OC;Ph?6o zG2$PAu2_c=eX#?iCvcMx+?c4P7lgGUIwqOpbzlY?DZ8P%*E*1zo0#(E7^0&d00rXU z;$U8XojAWZuEj6=hyq8-yNL;4ny?k9g+BvRt`RRNA>FECqUX00S?V#K398@A2(-JR z*^=0hNB0m}$D*rziZ3d}6ru)iirghJjvMG=@ELTm!ziM;hXMW$9dm6&zgq)~f2bfD z2F9=;J>v99*hhVMpgYZ!LaHf3!EJCP2 zn3fK3RK7vPCg95Cg9$_x8v$-I-?pz3wb_JnK5k_EbSsGFNVHaAx&b*vc@jL&l+oib z^s;>rx^du1qII%;4urW@L8aun$aMMBiH69EtJUbFM8G6*acM2y>J68ESsCN$8^wg> z^VmL7W5rPR4_DcWZH%pU)mznyv0)--bu))l98``D92!Ej7!C~j6q?67-DU)Jh6Aq- zBsvXkLG_GGiJVMS2m^z@!Wds@N_0^QcnvkFVMM=3w(UBmEPxm9%9_s%)oX~4$Efjp za*&%w!)LF#V%F%mC+5$dLcC)tP85yTcC3BlB)a$~hGEDyqRl=yJ)&$j(YO-`UUoOA79{B-LV~zr>-WN|>hg;zviv8V2|Ei6n8zwgfRqgX3 zgv6r~$d1i#0wa%mYL*oeIZ`A-6}7RX5$8v`k1^%D)FN2Yt}IP$sxc z7^<7y3zG*f@twkSeJ2uq4Gprq5%;ud2q{@!^b)ucHogvkUpk;;uvk`%C?fj1teC=- z^n9W*t5HssQ@0cfbhm?v{wihnW4cSDv3jE%JAmnmW`T}oviu8X#Wzw!KeSpaU3V$k ztvR+*W@njmb%W!OnBZLT-mxh5jkt1be5*T<75-PIYhOl`j`rb~tCugoLevZkk>qJn zfyEJ_B6-4Mp!cX)biB9Yop(W+iK(P?2*CW2Xfonr(paK5rSzEq86$`$W58vN$CwY> z6aBgiBrDTt%YIK2_1*2JYs(P@MCUy8@$g8wyG#xqjlTJ=%JJxqk(OlkB~rWQ!LJ)}?37GtC?P=Sa<29skxMSg$9fh_I#8yowcUD{7?d z6LTX)^QAD@Bed*s8f)EmFFROJ~3y61)V-V`xDW0-Q7Y)wz zVp*;UXIkn4K1OH-euo4c7De>2c;{>#$5@yg*c#qZ(&C6zbOh;I3$#1s#~vkGupawR z?39&i;56p=HE)d;kU#pPKDz6qUsAdp=EK9*9}R(nNaUOgN!)`GvOWQ`UhRhw_X68W z>@qQ=YR#ONF+yV1xwwD~bbpBv8?4V^W`;O(Zg%Rk%aAeP%dlAp!hCQ(jgXXpf1+T- zzQRj67U|yb#$UrsRe@Oew+P1)bU&gVhG2L$(M$=R0H}oOZtzt^6m&EnO4J%@G$I<( zS0DflP!+KPi&a1iq5%g17AUBCKMfnS!$7x@Ke{Ag9h5+ano~20_Q*yOWVeI;Uafjn zt!Qz+S-@b#SwbR=dpD10u$Xv0oINGxoR4J%V*B|vQE~@fPwuz9n{O3h^Nqc?<-(%a zOK6_digAk>VL!GGjv$iI4O_oG`9wQh$1XI~MI8x7;Jb%{xzAb~P7&?KESe~SFE6QK zsLkN742+JltQFB)dSoMU@N%p}zfcK;%@mFat6!u9nz{qDkzk1u6LuvCegLop=uM|l zE=JkM-^Xlv6JVMI?R?B(%K(-F1fFVSnt}2zlmlugW>W>gNeLD)v#AK+3kh~GbJ#Ed z3c*$;Am1F;9v~bbU`d3@ALSI3{VRBlFaqcdV0yu9c;BjaPkotoCmHr=u<^RtXPp*r z=|WULACubqnBI!A4AEfYi!8v>%ZD&qJdd%0`5T27;nOI7{j`x|zvVtDV5QZ(QiH8G zbg~Y{wmn-!UEOHSJk6t&+eg4o-MJD0X5k(Pox_L;V<7Y7~^ zbaK*%k72zMZ+-E!qk7cMP$GUm>UyK^RZ*AY0S_5;me22xc`)f`Dv6eNsttPL)mZ%M zqVv^~ApI<*(E|~2>}otJ|8X_Dbq02=-beq6Qrp-bLFRpQt<0_Je|KLI>Hp;OfF$un z9Mg&IUrcl8S2x_MWnCTiU&YY>-&Od4J_kKO_%CAza>4(n2cZA|)qq49w7=s4WY`W^ zo)urD;-|4L>&2z7V(t13?&Y|11SlJoK;tKxMN7X)X=&c#odi_0}EwX%?W!Ocre{))Iy>t8tiyU>n7P zjkC0bh0JXJK@7c-#wS_D{2NKU)p2q7#v?iAt@yfXC(a71Ps90V53}&_TLH*3BmRnwdQ?Q z{*pB}ZbihEY*TA$dfnsI@hkSMst#A;H;0)+r^7bibAhqL>At`)tGBKor_8lXBHYbYZ7E3o9L`fU?B{z8FNb&|7m5E_gxKsCT=5*eml`yp%|s+*Yo| zDWfL#p&*v~;k5!UUne%lUyIo98af7}ovjk5j9f(II}*&7C%bRk;b1RO9yX{u`Vfso zPbD8DysXNe>Sj;Ct{b%RQnY;O5}Fdpf^i7rBTaH|`W%joXky$Tw+iwo=J#b^>=`ji z#;Iow)-C0^gPr3h?AMT zdN$nzzj`pswBne&36zgjb2mvD+woHFVNS_gV1L{KgC=i<#PNRwwz>8O!elXYPffv* zMuJh0rCuzA*2hjBO#-&S4^JXKe6h<8nD4!fP-qC@Ym?}kO*{Ft^+6-KQ&jpEW9x3l z_QX$;nxLe7iyh_nn9e>ur4yi-lzoedN+eeW?g6TRaNdlM_c?4b`)Y#C=RH$&wOI8L z=WixheNLLp;aWBkcq^o(&t|wQ!3Z_@ZiAG+VsE38>Vt$Q9e+AmAYBvpk-%^ohjN%S zQEdFKvFCca`~>!hpv@MoZifoxc0&(+^hu(RJoL6u;D}g!H&p*N(#su@{A*MUVa`eB%V17K*~{!R8dn)dC3CBc2Dm$U z9~VOlnBh7i(fb(Yn$0G}NN`(N$2)q}?JDm~@@1-}tDH(IS`6YufJjP?Lxj#@FAVXF+zb+`}cx%w> z9#6+N}&-B*7lG^acxg%1}fRDp*&h+?$VaUCY;SIC- zEShch2tSJQS+5B5H$Y@_3%`Q04Q20mJ&HN`m%~m6W1(~L-4BmBr2Sj zw-Jr1f7XphZa4xU{WD}0Zi; zi$34!Yhr7noU;>Hv@@m_vpGKrU^RCBl)&m(okN;2le0@B=I^vMV*{L@q%gmo^P90I z%u!G@9KS)4zb(lwDJd$EWo$d}>y?tBqYH~OvqojB_1OPr=I0k>$$Ff7N;1a`eKy;R zatq5!atljyv*gcN2z$sPJsPmG4=Wn$mIetPY8Y1Q()`@4Y!5S9swL;J=B#cw ztiq3EM(3A#rtT{_$M(2^{tOq{A(^y?*+}S3LeaZNaRXZEDWP=y6rp{aE zSiG}$HnRq_$joZ-NXoFx)FxS3O;dMPe8eJ}vGqF_`0<_1tU0iOQ#zIqIVd=E==FqMpfV@46~2%R^-WObZT?eHI5 zaMS3F<~)M$3U+pE$HzOqMRI;z^$Q7%Ysu)+vY|yq1#|-v%5v2ICzBv`lob`^X3=yM z3$hES4q8pfEhx@UXflc9O>J4x(4sMg*~8OEr?nz^T?+z6LWS~VHzK<%D`zNvT|_Tq z{@F`LjxNY9EGwm^1HhYu?$BWr+yg|dQS6_cMGat3>9|tb4(!&}bP{C*kaOq_7E-4j zOf4#CMc+fbXW%InWxHx>ztY_C*>v9O+|-`e<4c2`=i2i+wd8g4h!S+K9HKLa6_u3H z(+F(0@MbM05iUiYg;!bqsFvVF2dFz3)es!&F|K`(^O+7ji5CSqmv-RI5_&^ryX^dY z`UHYT6qJ!Xw2sU!OCK#QXhnfma?biYi**Kc8qUnl3my6BN9tA?pW1j5{eX63 zN^;Axy}CD`Xk zUKjpcefbrDTd%xpKnBXkyz_c&2S}ari-u+9)2d+Sny&mA+qtSCNlTeT^1`C@elXw$ z-Wl)(v(-(3wPP|%3uqAvxka=Stq?rv=u9h;-*C9+k7^&^V|6a+#>2SJK af9l3tvIX*^mUCct9?HJnS=^mR82<-6Rf;75 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 2831315a06674278bd7e2bc8a224b3fa4ffc7634..684c3d378bbe8eb05fa74135f740e064dfceba51 100755 GIT binary patch delta 45535 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 Y>EwPoxoTA1n-($t-O=J7(xRdKe@#wpoB#j- delta 44185 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 z z+QF96(xo}&lxwXa(51QmaY^FeY4l%glYlEc4qeU@mNL2UDaHTshzQ%m8Ms)ASyAxk zwwrOGfdDEgfg`Z58!Ck}5fIz=4V5{MA+p1?B^r&uym%S`#}W4z*?n>n>Xjt^kh(hJ zj-|V<`$oIJVA3loPGj>>*?+5TJPOFpKu_v+q5+pG)S^54mPe_?jQusD938 zN#?Bw@DlQ^Z{tnx*+1|R=GC9_!yE5=2jTI?jvwPo?&d!~=9@MiJ;;}EbMr@hhuQdD zyc@pxT{IG@&-g&T@e_2;H~#ZYUconlB(H4V`W3pzpGD?`BlrVC zD$EIQ@s*UCWlq?KH&yR@0~F`IOKsFQcz|zQ`yFrQHb40g)QBPdkN)Jn!?Vm`c*(|` z^a}#c+xOw6Vgfg={e^GznO(zDhWU_Ra+&|~NT-^g`=xC28jsY@^m(PW=Bpv8fw{&j zsb;NT$}y*VrKc14IQ@)?HTXO|tu{tZoH%7Pr7M8UEds-b$s2N*5 zvSvy(fe2YgjvhT@QZhYb!sMFab(Im2PaHXW$_#Om)44b1J1&#Hf>| zjG-iE`&9Q>=uNGjG8*5_H{VpHl1z{`ed^fJ!^h6589UjkZH9ePQ|>mq_@qPz2$^0r zVe0Tp#!r|yR^T;v`=sK+(UU|kXij8P0pg(Qu`{hIzx$*pmp4ZJ(mRU9mVq8AU>*rc z1I)%=sd(e1VJvhKN%}DMvDMY7f#bXX)x&2@o;q@L)mT9hhMS@vqpco!$?%zD1v3GG zGu0C&PoFSauo2^*PVhLQ1Er&;%(8GGo$L(}()5WFMq@BRQ3dryWwSws)F=X4>f{D& zlPwu2=9vH2)wKslb(QgN&nX0G^CA)g1OiD|UI7vyAv_ug)gl!r#Q~f$?d4|oCRwt( zce#5nA*DN%QY}hRv_n2EZLNRk)JFwsb=6w69T`T3M~AV4GvYW!S{wSHz;rBHtiSKv zO+f$3&E2!#`JTUX?(Vn0Gg?hYM(iK0*5J=yFg7wGuPfB&mAd}b{F*W9-=p%ICaV&i z59$0v zTtj~MbLy9OMFnWikg|f=cMW%0p5$6J*t-1 zJ*A3x*D5utY>^#XG_SG4ZkZp8EokX%M1!7Mk*|J3m6hioep{VWoEuPo;@y|jdOkg% zhVzEBo{*pTE@F|t`#sg$wDMao3w9Zf|4h(qI^6fgixtDZ8)3!Rv{1!Y84rGm*4?hotW_?s!fo z38}NmcKRdk^(OpoD`q473BS*9?``ssW3)qZJMgWH?-AM{x$LTXc>DpVb2C3lD6C@L zHe$b;&rH%2nk`Ym@w?r4mi_?YUo^=rF)yo`JYzKt*oBH27OrA@6C@d=#BxwLjZggVw z4&;SuEy$4^#?ULFvY-MzUBQ=Ybme_Q_L_9kR^WrP;QWx^Tai9X;h{n?n_q)MGa2=N z>TSauX}}X5LYM%Z{N^P!LHFHG-1v^F+a-LvU7p)(Nm7!W#?b58nT%+f4Lax$9>)v_ zkPtrM$dIK$Ya4wZcRM{dMaN)g!LJK*B>ZM+r`HS-7D$6z)a)A6KZ znNlc3I#$k;(t38aumz)}WVN6;QhD1e2pqLfh-ofFSyFtK_I5$wu@?N`(-41N03MY9kO&5=EdAJ zbdtiCkD5ZWT9I+>c<&%mSc5rAtIGSQ>M4f(4)q7GAWR08=6WO{)rPHG#{SpM48Bz3&#|(r`XOU%Gx@tl*kO~!LZEvDy;rv3Wq4s}opmIa?+mLFOW7-V=i?MVK z60^voE&OVWj_Pxvhj*(V4deX_^%3>)Z~^GYYp=lg0<;=X8iD`gZqR|s!dAwK)1y3R znm#s|^tH3v=t~eC>HwhM^S=%yq4E|aA$;P+R|Ztgc%gt4mAInG-;jv;i*ooPx_`QE zUDO2AhicU%l#rQ+5Y!deUYzcS0YhucHETLC}eY^DSN#);qD z0thjm+u4~lwTwC)!3VbLF_VSSG2=N_Tz0SN5ZAjQYh}{yu#>csADE$MX|!+VpD5Ow zBQ3&kC&h18rwe#?OfZ_(06B9`J-o4ZDW8D_kH~JS$ zxVbs-F|=FI`64a=ELaz5GcF}BXTg?iU$&H_*|Ok`9G&4wDP1#hH0}*9&g(HjyHRk1 z04e>G?@#HOk*)mYl%7^AZL4N6eFXCC4%hSP1biI|Y)1H-`E*J*jNgcHvI4HgFtfdp zNQgdLcw$=5iR|OmY28wn#AJ6l$t10jMH)+)v=11N4nZ6}5j^>#LfNEs)yTUrNEV#| zKA+Z0RJ5GOxq3mQi`TmPyOB@%16OaK*FU6A3o?4oU=p!~bP^guCvXk% z-i&S>D?JQhV8pY7!(Bd;(X%6;@QsY#QYn49gTbMdkmP3RGZmcOrt8L+;QhBif;6P` z$hKhYXg;z{Z&}+sv`EiGnDGH|=sv8jsBdhk!D1(X99BoIB{=g$&=I>**zZ5FQ2zPWcX>_Vfac4H`Fm zq`U3+$vO@%RkO)uU=@@Lb0s?S!wk!l^X_6dUE5Q z%l3e(p4^BzdhKkAp20=JrDz!Nr7*3H(AmtV2GrCE=LV+_cX$|T&PY=<%jZ{o-L884 m{y;BMzmY$6{9vHRs^Y+0KNaXo^0L69fOY_zz;&yM5B@z2}X?=n-F7sB>Xe+%r^cY`a>tV z=gc?XoX4D*b3eXr{e0b;-5A$BHAq2I3cLb%aG*d)!BDug;#;06k{vj?qpHuMI#5PT z0>A(?KP_9~qm%+rpu`7&>xN+l0Ef<~+Qrlw^Mj=^Ml~&{DGp6_FnlL(>~2cNyLGle8H9i zw%~&m;!|1{;KU(O8gdzjOpW-7W~kRK;_MPCxx`|I(iQ%+ZZBN8O87w(p&{`^aOXYv zn_xOTU!q(VAP#jXH;WEVLfb?#lrWY#xg%7^sF#z+?|+-=IRKA}ws1>@?Evn4tXdg3 zteB)GC-y1%QurgF)DzuRF|g&{st+hE$aS@M7^+$)r96rJs!8SW!&RR#5aMV5&G%rV9H4gFw>M08EHWji{DHWEg)zR2IlZAnTT z-Lb?>w9ib^1QKRZUW@kYx->QLM8(>UDc6h`j*lOt#U?`d6)zVcwqx=`wCEZRbOopb z2&Nq~6VT9^k)-7-07E%|Go>*~h^xv*3{Dh<*%g)j-~K?Ei7RU_hJ9F6n08r&IzGj= zXH_sJj>OZd1fRrvC>Wxmxe0y{>E^pt)I(=_j1U)i<lrK#RZ<{@7wL zc{Ozm^p4U^^tVdkBHkL|0mZ?=jfgY}RS z=LdgRhj@Kxsr0Cij)W<$zA?HEw#XZ!ofO(d)z~D;7sotkmw%2$0YUxvAcFnljnoHO zd@GO@=L#t#ZWksgg6u>y^3PA)QkzF6hY*~b^lQ2mF&#@@n(_<`lH3)CE#k>tzrtoQ zG80Gn%FHlqmfy}44dph7Ze{$Ml@wj0uKdhzT*GK|@S1F+9}SBR&v~aUW*( zH(xtDJF~a1cvi1?W?RBmmKJ2wF2l1d4OuvzW-Kj^=frkSYz*AF-Q4r#Nr$_D3+7<} z0>H}S`93%29DoC7ZUBSVvT93psO_=Hb=jHS|H-zjn5Un@BqG1 z*j{=$rwR%mlU#D;WF&3;y%_b^+xK;P%)g1Q7S}Ty-W8$~0C$u&7E2(rXDW=f6qyKj z6!W4!S-JwayS!Fs%Rk~UrJFbYB8XMWCKCboZyq**zS#V=fYUfvU9;6=uEBz~A&#;p zcUrBIK$*JsI^}(xi1a+HxDwKXx2k)QATTQJn9q_f>5lNCeDKl^XlFQ!aKAE@ z3)qSo6_a+x`2QUbj2nJc_kOXUqQPi@iePlDadQ;MGvRh~(QDx@4sNV!sDU3a)$m8D zbk#Bz1XXrKmAw?S6iZrJ$y_+ph*YM#jkdst5?A%T$TZN)0!~LOwmfP|@tJv(b|Ocm7p$Vk30^WbER9oR90xoA5wv#@AJE_9cJ5eAg4bdS zpT%RCIJ;!BzjwCZu++juGx&cl5K8Laa{-eKzPM$Ujq?)E@<}V%fs;E*I=$2c%7{$> z7=YoU1uHz1QUD5+cmPmtS#|($nT%>Hrey}BKJYST7(qkpP-24RIeC3|bt2x@Ze}_< z3k!mwnw8G#HI-G-O&cC;VCLo~`bgu$8`G`bkM?Zq>-W9*j(i%*VO}1GKBTwdbG8)l zOVLv#KcNKyK{$!hki!I|%H$6;McpxpC>XlChcix4+ z3MRv|d6mNggi}t1o#S{2ZIg*mjkT;(J3U0dT2o4A&L;IiTfZrP_hB zT!Pwy_)n?B;SYgQSGJYJz_0d|yiZ|XtuMdBkd^a#9ZB}B8r1CyG&5QxGle}>H=HVMAwvh{oEALNW#cAU|=vSK?_#^rs9An%`rtsw$#gn=PAIIA%ShA?L8oras+TTm4i^0qoAr6S*y`uOT#}K45KuTqIox~P?UuQ9O zwf-2G&H08cp;4qS47spH{W%l`1eL=*NGFHigl6SDei%HE##aJq zc`jRzird*yiXc5wi!RTP+|n)sqrC{ujrt7J8?m`pT^e&OOqJLbhopRJ*Uup2z(gGB zOB4Md)YlVB21Z}pyBs};(N)*8RrrHRtNr_aqG&SqT-F=#j|6k2R%qX|CqRf_UOcs5?VR37Wm2{*&$@o5IQvpg-i5={{a%A`@Eb%B8J)SH zzvauBKF#8@bNY93W42FciM<-p^~mniTB+VR{RD#=<$JBZP!2BK6DA}`mKPv<0QXwl P)kks*@8%ZZoB6*05zbo| delta 1320 zcma)6TWpj?6rMBl@4x?cyX_|QPLj0uLI#Do~oRD&iMjSuzA5+8`hI1lH{ zcdm2JoSEgTu4`9all4I(O$*W~hvsRPf;1dSF_scXGGa3$)&|bZZtD5sgvA}e1#>X~ z0bpeDY>$(34#0skCjiM!)0G3X971qyCbSSNgJ70nt_~-dQ(ui&hJ#PHJK|dt zc{#b>>U@7$`MQcgio1fZLA8UK2yQeeRm33R;7?i>Uo`7zmx8Y0iy~mBI zWC1Sm9Jgcv?)3{BqT_g%M>$L22Jt>I^@FG~H809)cDN_rz0B-cBHnGdH}{2y@U7hD z!b=&IUwD}0lH1mhq>Z-|Bi?HBfliHh*HCFOb!yc+LbL(k=EB;131s$ciMEzvD8wzB zxltV}Tm;-!T&2>*A99#bb)`QFVr4R{BjC=p13J)^wOnoV7Se@xD>}f1kw7_E*cT`wdMJ=^`mGSJmX=xybU4JkCD6CN zWslF4PJ4n6RWfFOl8$?rTblN(e8}&V(B|P12nM^84Izv1!8{D)5+G);0+-vL3 z`AnN>6_e(W_WwH`XgBng+u@YJ%1^#&jxO*a4o)nusf6z^S@T<= zbW}0s2fOHiU3AcI*eqeBC39eZEmE8A*O~&CT`b7btf~8Z*MQ;B>i~D2B=Z@c5LC$fT7+2 zqI11zXiz0P13;j;FG+N|?*iDWVb^QmWN}He8pp*{7#dFZ(?G^?Pk#-0%=X{Z9tQ@x z2rLd{8DOce_oYqBvV9~7HF#{~9&E+;L@kSd8Wo+V}7GFnU7;)6oZy zRL>th+QI3UAHvJiJ?b0W#4UqKa2w3V=kPGbXXbSwf6jDkmYhAW|ARkf2_;nLS)V=( hzBs3q>a)4s^hc?xH^X@tUwG{3$%PM2F2Joze*t`8QY8QY diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 8711cfca76723154a25f603751343bff408550e6..3bf4ac8028c7b69e88e88bf82be1bab63575a551 100755 GIT binary patch delta 1211 zcmY*YYiL|W6rM9P_m!KQEHQNRsP^tg>0**5q$Nw!r0(R=&0}da%_f#g+;rPUH;5(} zO+j;g5dBq58Ej(GmX@NHf+1yStOy!Kq=8!M&yr%bP=X5iQN$mD;+aiFIzP^tbHDSQ znfdNH_kVCcyy48W>@fnpNQP?%K?VYt$`FzQ^t9SYY@2xnAXWZ zro-)x@jcvpD#7+Ox9sii={wLrF!*f2KXL{hg%!C7X|$aF$kshq{^D$r8??w1#3wNt ziZTJoN@=kq4Z7viY#E)hd9A5^yr9i+R~A=@m_ZXt%jx2m9*X}iZVO+`Ls8}tpZZkT zIUhHn0XZ0|H#aeLBD9UsAf*-`xx|bVKtSc8@J?{$&15N@QWwJ4@Qr~SEw2Sv&6R&a zVMX%+B^(~DiI`v^pep1(_CHTFrhk_~N4K;9A9%=Zq zoCa9Ts3jx{qU=>sHWB55?Dt589NQ^TLSxqphS8o^{gvI-4^+RYmUbI?)vq)?iK^de z68OtgDk@62_Kh7Q)ac5S&L!&~kZ!4a$)f*B@=nt>+1xd>#lc<@T1U5#w9eoC?yuD^ zug_WPOxIP=wFZ0AoM9uYZ+quB+ZcUZ4)?dK^Zk{1^sDrGzBxWU_yLDD86V1`T^I_W zO;tW00}R?TeQ0Mh7okHXhTj1TI#GM*l&gp12tYPVF*ufOK*~qiHBFf~dH{pfqb1yM zBDU|S+b^E8pjEy(R)_Ypu>ojRe0-flhfEA_Rrc%0@>Pqms+ySGj+5D`&K}>On0Dfg zBMu08f4UlkdhETu;JKrM_Tv0R1y5G0k@qj#dRQuEl<5U2Yy{{TANL4xW%vA~GUuA9 zynU);b8d78;2hP=9)BoanjLUB@xkS&qx@!mQ9o#GA+6oDg^%@UzI-~ZPx=0IL|;z+ jdPajParQ8Sgt~A(St{vriM)QL?A_Ak&r6r#r=@=Zo5M&e delta 1198 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 R&9v{CiRtqb)6n~?e*;vuZe;)f delta 2355 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 From c25a85225cc19a16056ef16589074c6b39c1d362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Dec 2022 18:34:37 +0100 Subject: [PATCH 04/43] changelog: add #912 --- .changelog/unreleased/bugs/912-remove-prefix-iter.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bugs/912-remove-prefix-iter.md diff --git a/.changelog/unreleased/bugs/912-remove-prefix-iter.md b/.changelog/unreleased/bugs/912-remove-prefix-iter.md new file mode 100644 index 0000000000..20e553a8a7 --- /dev/null +++ b/.changelog/unreleased/bugs/912-remove-prefix-iter.md @@ -0,0 +1,3 @@ +- Removed 'rev_iter_prefix' from storage API as its implementation + depends on RocksDB and it doesn't work as expected. + ([#912](https://github.com/anoma/namada/pull/912)) From 65339b16eae7099d67828f5511b6ef82ed2776f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Dec 2022 10:59:54 +0000 Subject: [PATCH 05/43] [ci] wasm checksums update --- wasm/checksums.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index b13508300c..5bd0a33931 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.be9c75f96b3b4880b7934d42ee218582b6304f6326a4588d1e6ac1ea4cc61c49.wasm", + "tx_bond.wasm": "tx_bond.852a22bb75acb8bbedd7ca1511af6e10b5a1f191ac80f5a6c7b5228bb79c6aed.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.cd861e0e82f4934be6d8382d6fff98286b4fadbc20ab826b9e817f6666021273.wasm", - "tx_ibc.wasm": "tx_ibc.13daeb0c88abba264d3052129eda0713bcf1a71f6f69bf37ec2494d0d9119f1f.wasm", - "tx_init_account.wasm": "tx_init_account.e21cfd7e96802f8e841613fb89f1571451401d002a159c5e9586855ac1374df5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b9a77bc9e416f33f1e715f25696ae41582e1b379422f7a643549884e0c73e9de.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e9732873861c625f239e74245f8c504a57359c06614ba40387a71811ca4a097.wasm", + "tx_ibc.wasm": "tx_ibc.eb7d0f6c7cba4fcc402f32ed6a09bf70827521c5539b6393e2078f3931e8fccd.wasm", + "tx_init_account.wasm": "tx_init_account.87ece7f13b327f7d15c5b6e7083b510debb1c3205bafe9e36457c8585beeb49c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c39152be5435cf3f7588ddcde82b801e98902079a152ae3df30bac69833e3ebb.wasm", + "tx_init_validator.wasm": "tx_init_validator.56d563e4b9001790d9089ff91c6c98cde95032fb57d97122e58e8b5e363fc13f.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.47bc922a8be5571620a647ae442a1af7d03d05d29bef95f0b32cdfe00b11fee9.wasm", - "tx_transfer.wasm": "tx_transfer.bbd1ef5d9461c78f0288986de046baad77e10671addc5edaf3c68ea1ae4ecc99.wasm", + "tx_transfer.wasm": "tx_transfer.c75399e44b86e22084937b69b2f6c9b9ae112706ac9f8c472d3646fc36cfaa06.wasm", "tx_unbond.wasm": "tx_unbond.c0a690d0ad43a94294a6405bae3327f638a657446c74dc61dbb3a4d2ce488b5e.wasm", "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.263fd9f4cb40f283756f394d86bdea3417e9ecd0568d6582c07a5b6bd14287d6.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ce8faf6a32340178ddeaeb91a9b40e7f0433334e5c1f357964bf8e11d0077f1.wasm", - "vp_implicit.wasm": "vp_implicit.17f5c2af947ccfadce22d0fffecde1a1b4bc4ca3acd5dd8b459c3dce4afcb4e8.wasm", + "tx_withdraw.wasm": "tx_withdraw.db97f19c3217167374f9163fc4d3738117be23041d75068e97b269e6f9810ec8.wasm", + "vp_implicit.wasm": "vp_implicit.0b6b00b4663aa2c747d51bb0fda2da0454615bcdac345011131e3372ca2598a9.wasm", "vp_masp.wasm": "vp_masp.5620cb6e555161641337d308851c760fbab4f9d3693cfd378703aa55e285249d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.362584b063cc4aaf8b72af0ed8af8d05a179ebefec596b6ab65e0ca255ec3c80.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e5df211c9016a8d5ffbba980859470e0c7abbbdf900263830585fb49fa75623d.wasm", "vp_token.wasm": "vp_token.a289723dd182fe0206e6c4cf1f426a6100787b20e2653d2fad6031e8106157f3.wasm", - "vp_user.wasm": "vp_user.b83b2d0616bb2244c8a92021665a0be749282a53fe1c493e98c330a6ed983833.wasm", - "vp_validator.wasm": "vp_validator.59e3e7729e14eeacc17d76b736d1760d59a1a6e9d6acbc9a870e1835438f524a.wasm" + "vp_user.wasm": "vp_user.91b6389650a83702c66e220893211b94cdce784d1e18ca8433c69964fc0967ea.wasm", + "vp_validator.wasm": "vp_validator.5175c7aeb34657b9fbe4211031c628e6e834037c26f4cc290bfb50f301d41138.wasm" } \ No newline at end of file From 5d9f3d70744a877b477335ccba77f62dbeae6f73 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 19 Dec 2022 14:53:10 +0000 Subject: [PATCH 06/43] Add StorageKeys derive macro --- Cargo.lock | 1 + macros/Cargo.toml | 1 + macros/src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++-- wasm/Cargo.lock | 1 + 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a0a9ae50b..60883727ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3843,6 +3843,7 @@ dependencies = [ name = "namada_macros" version = "0.12.0" dependencies = [ + "proc-macro2", "quote", "syn", ] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 4bb96bbe49..c6b5dfca4d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -10,5 +10,6 @@ version = "0.12.0" proc-macro = true [dependencies] +proc-macro2 = "1.0" quote = "1.0" syn = "1.0" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 38a9882b40..03b80d8396 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -7,8 +7,10 @@ #![deny(rustdoc::private_intra_doc_links)] use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, ItemFn}; +use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, ItemFn, ItemStruct}; /// Generate WASM binding for a transaction main entrypoint function. /// @@ -149,3 +151,89 @@ pub fn validity_predicate( }; TokenStream::from(gen) } + +#[proc_macro_derive(StorageKeys)] +// TODO: use this crate for errors: https://crates.io/crates/proc-macro-error +pub fn derive_storage_keys(item: TokenStream) -> TokenStream { + let struct_def = parse_macro_input!(item as ItemStruct); + + // type check the struct - all fields must be of type `&'static str` + let fields = match &struct_def.fields { + syn::Fields::Named(fields) => &fields.named, + _ => panic!( + "Only named struct fields are accepted in StorageKeys derives" + ), + }; + + let mut idents = vec![]; + + for field in fields { + let field_type = { + let mut toks = TokenStream2::new(); + field.ty.to_tokens(&mut toks); + toks.to_string() + }; + if field_type != "&'static str" { + panic!( + "Expected `&'static str` field type in StorageKeys derive, \ + but got {field_type} instead" + ); + } + idents.push(field.ident.clone().expect("Expected a named field")); + } + + idents.sort(); + + let ident_list = create_ponctuated(&idents, |ident| ident.clone()); + let values_list = create_ponctuated(&idents, |ident| { + let storage_key = { + let mut toks = TokenStream2::new(); + ident.to_tokens(&mut toks); + toks.to_string() + }; + syn::FieldValue { + attrs: vec![], + member: syn::Member::Named(ident.clone()), + colon_token: Some(syn::token::Colon { + spans: [Span2::call_site()], + }), + expr: syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Str(syn::LitStr::new( + storage_key.as_str(), + Span2::call_site(), + )), + }), + } + }); + + quote! { + impl #struct_def.ident { + const ALL: &[&'static str] = { + let #struct_def.ident { + #ident_list + } = Self::VALUES; + + &[ #ident_list ] + }; + + const VALUES: #struct_def.ident = Self { + #values_list + }; + } + } + .into() +} + +fn create_ponctuated( + idents: &[syn::Ident], + mut map: F, +) -> Punctuated +where + F: FnMut(&syn::Ident) -> M, +{ + idents.iter().fold(Punctuated::new(), |mut accum, ident| { + accum.push(map(ident)); + accum + }) +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index bd82ea3be8..a3f84ae2e1 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2543,6 +2543,7 @@ dependencies = [ name = "namada_macros" version = "0.12.0" dependencies = [ + "proc-macro2", "quote", "syn", ] From 4ad37a161b4909cb16b40ddebefcbc341091e2fc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 19 Dec 2022 15:08:38 +0000 Subject: [PATCH 07/43] Fix gen of identifier in StorageKeys proc-macro --- macros/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 03b80d8396..6e65f450fe 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -207,17 +207,19 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { } }); + let struct_def_ident = &struct_def.ident; + quote! { - impl #struct_def.ident { + impl #struct_def_ident { const ALL: &[&'static str] = { - let #struct_def.ident { + let #struct_def_ident { #ident_list } = Self::VALUES; &[ #ident_list ] }; - const VALUES: #struct_def.ident = Self { + const VALUES: #struct_def_ident = Self { #values_list }; } From 028924e6bd168921f62bd2b7c3e8d451206613c7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 19 Dec 2022 15:10:48 +0000 Subject: [PATCH 08/43] Fix static str type check --- macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6e65f450fe..f0c64f95a8 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -173,7 +173,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { field.ty.to_tokens(&mut toks); toks.to_string() }; - if field_type != "&'static str" { + if field_type != "& 'static str" { panic!( "Expected `&'static str` field type in StorageKeys derive, \ but got {field_type} instead" From 6612ed87742c7bd2c29f80d14815c5d767af2589 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:17:58 +0000 Subject: [PATCH 09/43] Update macros/src/lib.rs Co-authored-by: Tomas Zemanovic --- macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index f0c64f95a8..3e4c7cc861 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -227,7 +227,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { .into() } -fn create_ponctuated( +fn create_punctuated( idents: &[syn::Ident], mut map: F, ) -> Punctuated From 63a4fea75a6cbbde45a414912f413798e681d4d7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:18:08 +0000 Subject: [PATCH 10/43] Update macros/src/lib.rs Co-authored-by: Tomas Zemanovic --- macros/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 3e4c7cc861..0fe978dd52 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -184,8 +184,8 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { idents.sort(); - let ident_list = create_ponctuated(&idents, |ident| ident.clone()); - let values_list = create_ponctuated(&idents, |ident| { + let ident_list = create_punctuated(&idents, |ident| ident.clone()); + let values_list = create_punctuated(&idents, |ident| { let storage_key = { let mut toks = TokenStream2::new(); ident.to_tokens(&mut toks); From 9ac07db706779d93f83457b4a3e1f828f44d66d7 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 20 Dec 2022 11:59:33 +0100 Subject: [PATCH 11/43] Added unit test for the new proc macro --- macros/Cargo.toml | 2 +- macros/src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c6b5dfca4d..3dfbb42b58 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,4 +12,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = "1.0" +syn = {version="1.0", features = ["full", "extra-traits"]} \ No newline at end of file diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 0fe978dd52..75ba85f96b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -156,7 +156,10 @@ pub fn validity_predicate( // TODO: use this crate for errors: https://crates.io/crates/proc-macro-error pub fn derive_storage_keys(item: TokenStream) -> TokenStream { let struct_def = parse_macro_input!(item as ItemStruct); + create_storage_keys(struct_def).parse().unwrap() +} +fn create_storage_keys(struct_def: ItemStruct) -> String { // type check the struct - all fields must be of type `&'static str` let fields = match &struct_def.fields { syn::Fields::Named(fields) => &fields.named, @@ -168,11 +171,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { let mut idents = vec![]; for field in fields { - let field_type = { - let mut toks = TokenStream2::new(); - field.ty.to_tokens(&mut toks); - toks.to_string() - }; + let field_type = field.ty.to_token_stream().to_string(); if field_type != "& 'static str" { panic!( "Expected `&'static str` field type in StorageKeys derive, \ @@ -223,8 +222,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { #values_list }; } - } - .into() + }.to_string() } fn create_punctuated( @@ -239,3 +237,41 @@ where accum }) } + + +#[cfg(test)] +mod test_proc_macros { + use syn::ItemImpl; + use super::*; + + + /// Test that the create storage keys produces + /// the expected code. + #[test] + fn test_create_storage_keys() { + const TEST: &str = r#" + struct Keys { + param1: &'static str, + param2: &'static str, + } + "#; + + const EXPECT: &str = r#" + impl Keys { + const ALL: &[&'static str] = { + let Keys { param1 , param2 } = Self::VALUES; + &[param1, param2] + }; + const VALUES: Keys = Self { + param1: "param1", + param2: "param2" + }; + } + "#; + let struct_def = syn::parse_str(TEST).unwrap(); + let result = create_storage_keys(struct_def); + let result: ItemImpl = syn::parse_str(&result).unwrap(); + let expected = syn::parse_str(EXPECT).unwrap(); + assert_eq!(result, expected); + } +} \ No newline at end of file From 306829f51b8ac02d734aed231aa6b2eadc48066b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 11:21:39 +0000 Subject: [PATCH 12/43] Format and inline some code --- macros/src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 75ba85f96b..a543302d56 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -159,6 +159,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { create_storage_keys(struct_def).parse().unwrap() } +#[inline] fn create_storage_keys(struct_def: ItemStruct) -> String { // type check the struct - all fields must be of type `&'static str` let fields = match &struct_def.fields { @@ -175,7 +176,7 @@ fn create_storage_keys(struct_def: ItemStruct) -> String { if field_type != "& 'static str" { panic!( "Expected `&'static str` field type in StorageKeys derive, \ - but got {field_type} instead" + but got `{field_type}` instead" ); } idents.push(field.ident.clone().expect("Expected a named field")); @@ -222,9 +223,11 @@ fn create_storage_keys(struct_def: ItemStruct) -> String { #values_list }; } - }.to_string() + } + .to_string() } +#[inline] fn create_punctuated( idents: &[syn::Ident], mut map: F, @@ -238,12 +241,11 @@ where }) } - #[cfg(test)] mod test_proc_macros { use syn::ItemImpl; - use super::*; + use super::*; /// Test that the create storage keys produces /// the expected code. @@ -274,4 +276,4 @@ mod test_proc_macros { let expected = syn::parse_str(EXPECT).unwrap(); assert_eq!(result, expected); } -} \ No newline at end of file +} From 189e9b6e1145d38dc77c419bc2d2561d8ea4a41e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 11:27:17 +0000 Subject: [PATCH 13/43] Some TODO items --- macros/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index a543302d56..d8e271f60a 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -160,6 +160,7 @@ pub fn derive_storage_keys(item: TokenStream) -> TokenStream { } #[inline] +// TODO: emit allow_deadcode in ALL and VALUES fn create_storage_keys(struct_def: ItemStruct) -> String { // type check the struct - all fields must be of type `&'static str` let fields = match &struct_def.fields { @@ -245,11 +246,15 @@ where mod test_proc_macros { use syn::ItemImpl; + // TODO: check if type checking fails for non-static str field + // types, check if the generated `ALL` is sorted, ??? + use super::*; /// Test that the create storage keys produces /// the expected code. #[test] + // TODO: use quote! to get formatted expected code fn test_create_storage_keys() { const TEST: &str = r#" struct Keys { From a74d8e38540ec2e0f827e5282e17d1b23b701665 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 13:01:59 +0000 Subject: [PATCH 14/43] Use quote!() in macro test --- macros/src/lib.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d8e271f60a..16f4ebc101 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -153,15 +153,17 @@ pub fn validity_predicate( } #[proc_macro_derive(StorageKeys)] -// TODO: use this crate for errors: https://crates.io/crates/proc-macro-error -pub fn derive_storage_keys(item: TokenStream) -> TokenStream { - let struct_def = parse_macro_input!(item as ItemStruct); - create_storage_keys(struct_def).parse().unwrap() +pub fn derive_storage_keys(struct_def: TokenStream) -> TokenStream { + derive_storage_keys_inner(struct_def.into()).into() } #[inline] +// TODO: use this crate for errors: https://crates.io/crates/proc-macro-error // TODO: emit allow_deadcode in ALL and VALUES -fn create_storage_keys(struct_def: ItemStruct) -> String { +fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { + let struct_def: ItemStruct = syn::parse2(struct_def) + .expect("Expected a struct in the StorageKeys derive"); + // type check the struct - all fields must be of type `&'static str` let fields = match &struct_def.fields { syn::Fields::Named(fields) => &fields.named, @@ -225,7 +227,6 @@ fn create_storage_keys(struct_def: ItemStruct) -> String { }; } } - .to_string() } #[inline] @@ -248,25 +249,26 @@ mod test_proc_macros { // TODO: check if type checking fails for non-static str field // types, check if the generated `ALL` is sorted, ??? - use super::*; /// Test that the create storage keys produces /// the expected code. #[test] - // TODO: use quote! to get formatted expected code - fn test_create_storage_keys() { - const TEST: &str = r#" + fn test_derive_storage_keys() { + let test_struct: TokenStream2 = quote! { struct Keys { param1: &'static str, param2: &'static str, } - "#; + }; + let test_impl: ItemImpl = + syn::parse2(derive_storage_keys_inner(test_struct)) + .expect("Test failed"); - const EXPECT: &str = r#" + let expected_impl: TokenStream2 = quote! { impl Keys { const ALL: &[&'static str] = { - let Keys { param1 , param2 } = Self::VALUES; + let Keys { param1, param2 } = Self::VALUES; &[param1, param2] }; const VALUES: Keys = Self { @@ -274,11 +276,10 @@ mod test_proc_macros { param2: "param2" }; } - "#; - let struct_def = syn::parse_str(TEST).unwrap(); - let result = create_storage_keys(struct_def); - let result: ItemImpl = syn::parse_str(&result).unwrap(); - let expected = syn::parse_str(EXPECT).unwrap(); - assert_eq!(result, expected); + }; + let expected_impl: ItemImpl = + syn::parse2(expected_impl).expect("Test failed"); + + assert_eq!(test_impl, expected_impl); } } From fe53976459dfce0cd91392336356c0dd4f6deb4b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 14:25:54 +0000 Subject: [PATCH 15/43] Add StorageKeys unit tests --- Cargo.lock | 1 + macros/Cargo.toml | 5 ++- macros/src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60883727ba..657c6b6e41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3843,6 +3843,7 @@ dependencies = [ name = "namada_macros" version = "0.12.0" dependencies = [ + "itertools", "proc-macro2", "quote", "syn", diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 3dfbb42b58..32ff437bf6 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,4 +12,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = {version="1.0", features = ["full", "extra-traits"]} \ No newline at end of file +syn = {version="1.0", features = ["full", "extra-traits"]} + +[dev-dependencies] +itertools = "0.10.1" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 16f4ebc101..067e2eebb3 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -245,17 +245,91 @@ where #[cfg(test)] mod test_proc_macros { + use itertools::Itertools; use syn::ItemImpl; - // TODO: check if type checking fails for non-static str field - // types, check if the generated `ALL` is sorted, ??? use super::*; + /// Test if the `ALL` slice generated in `StorageKeys` macro + /// derives is sorted in ascending order. + #[test] + fn test_storage_keys_derive_sorted_slice() { + let test_struct = quote! { + struct Keys { + word: &'static str, + is: &'static str, + bird: &'static str, + the: &'static str, + } + }; + let all = { + let test_impl: ItemImpl = + syn::parse2(derive_storage_keys_inner(test_struct)) + .expect("Test failed"); + test_impl + .items + .iter() + .find_map(|i| match i { + syn::ImplItem::Const(e) + if e.ident.to_token_stream().to_string() == "ALL" => + { + match &e.expr { + syn::Expr::Block(e) => { + match e + .block + .stmts + .last() + .expect("Must have slice") + { + syn::Stmt::Expr(syn::Expr::Reference( + e, + )) => match &*e.expr { + syn::Expr::Array(e) => Some(e.clone()), + t => panic!( + "Expected array, but got {t:?}" + ), + }, + t => panic!( + "Expected reference, but got {t:?}" + ), + } + } + t => panic!("Expected block, but got {t:?}"), + } + } + t => panic!("Expected const, but got {t:?}"), + }) + .unwrap() + }; + let string = all + .elems + .into_iter() + .map(|e| e.to_token_stream().to_string()) + .join(" "); + assert_eq!(string, "bird is the word"); + } + + /// Test if we reject structs with non static string fields in + /// `StorageKeys` macro derives. + #[test] + #[should_panic( + expected = "Expected `&'static str` field type in StorageKeys derive" + )] + fn test_typecheck_storage_keys_derive() { + derive_storage_keys_inner(quote! { + struct Keys { + x: &'static str, + y: i32, + z: u64, + } + }); + } + /// Test that the create storage keys produces /// the expected code. #[test] fn test_derive_storage_keys() { - let test_struct: TokenStream2 = quote! { + let test_struct = quote! { struct Keys { param1: &'static str, param2: &'static str, @@ -265,7 +339,7 @@ mod test_proc_macros { syn::parse2(derive_storage_keys_inner(test_struct)) .expect("Test failed"); - let expected_impl: TokenStream2 = quote! { + let expected_impl = quote! { impl Keys { const ALL: &[&'static str] = { let Keys { param1, param2 } = Self::VALUES; From 32d4f492efaccde6661f114607c467ce25bf4d67 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 15:18:35 +0000 Subject: [PATCH 16/43] Add unit tests for tuple and unit structs as well as enums, on StorageKeys derives --- Cargo.lock | 1 - macros/Cargo.toml | 3 -- macros/src/lib.rs | 118 ++++++++++++++++++++++++++++------------------ 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 657c6b6e41..60883727ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3843,7 +3843,6 @@ dependencies = [ name = "namada_macros" version = "0.12.0" dependencies = [ - "itertools", "proc-macro2", "quote", "syn", diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 32ff437bf6..dbd4f81cab 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,6 +13,3 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = {version="1.0", features = ["full", "extra-traits"]} - -[dev-dependencies] -itertools = "0.10.1" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 067e2eebb3..eb026afc2d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -214,6 +214,7 @@ fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { quote! { impl #struct_def_ident { + #[allow(dead_code)] const ALL: &[&'static str] = { let #struct_def_ident { #ident_list @@ -222,6 +223,7 @@ fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { &[ #ident_list ] }; + #[allow(dead_code)] const VALUES: #struct_def_ident = Self { #values_list }; @@ -245,11 +247,42 @@ where #[cfg(test)] mod test_proc_macros { - use itertools::Itertools; use syn::ItemImpl; use super::*; + /// Test if we reject enums in `StorageKeys` derives. + #[test] + #[should_panic(expected = "Expected a struct in the StorageKeys derive")] + fn test_storage_keys_panics_on_enum() { + derive_storage_keys_inner(quote! { + enum What { + The, + Funk, + } + }); + } + + /// Test if we reject unit structs in `StorageKeys` derives. + #[test] + #[should_panic(expected = "Only named struct fields are accepted in \ + StorageKeys derives")] + fn test_storage_keys_panics_on_unit_structs() { + derive_storage_keys_inner(quote! { + struct WhatTheFunk; + }); + } + + /// Test if we reject tuple structs in `StorageKeys` derives. + #[test] + #[should_panic(expected = "Only named struct fields are accepted in \ + StorageKeys derives")] + fn test_storage_keys_panics_on_tuple_structs() { + derive_storage_keys_inner(quote! { + struct WhatTheFunk(&'static str); + }); + } + /// Test if the `ALL` slice generated in `StorageKeys` macro /// derives is sorted in ascending order. #[test] @@ -262,51 +295,30 @@ mod test_proc_macros { the: &'static str, } }; - let all = { - let test_impl: ItemImpl = - syn::parse2(derive_storage_keys_inner(test_struct)) - .expect("Test failed"); - test_impl - .items - .iter() - .find_map(|i| match i { - syn::ImplItem::Const(e) - if e.ident.to_token_stream().to_string() == "ALL" => - { - match &e.expr { - syn::Expr::Block(e) => { - match e - .block - .stmts - .last() - .expect("Must have slice") - { - syn::Stmt::Expr(syn::Expr::Reference( - e, - )) => match &*e.expr { - syn::Expr::Array(e) => Some(e.clone()), - t => panic!( - "Expected array, but got {t:?}" - ), - }, - t => panic!( - "Expected reference, but got {t:?}" - ), - } - } - t => panic!("Expected block, but got {t:?}"), - } - } - t => panic!("Expected const, but got {t:?}"), - }) - .unwrap() + let test_impl: ItemImpl = + syn::parse2(derive_storage_keys_inner(test_struct)) + .expect("Test failed"); + + let expected_impl = quote! { + impl Keys { + #[allow(dead_code)] + const ALL: &[&'static str] = { + let Keys { bird, is, the, word } = Self::VALUES; + &[bird, is, the, word] + }; + #[allow(dead_code)] + const VALUES: Keys = Self { + bird: "bird", + is: "is", + the: "the", + word: "word" + }; + } }; - let string = all - .elems - .into_iter() - .map(|e| e.to_token_stream().to_string()) - .join(" "); - assert_eq!(string, "bird is the word"); + let expected_impl: ItemImpl = + syn::parse2(expected_impl).expect("Test failed"); + + assert_eq!(test_impl, expected_impl); } /// Test if we reject structs with non static string fields in @@ -325,6 +337,20 @@ mod test_proc_macros { }); } + /// Test if we reject structs with non static lifetimes. + #[test] + #[should_panic( + expected = "Expected `&'static str` field type in StorageKeys derive" + )] + fn test_storage_keys_derive_with_non_static_str() { + derive_storage_keys_inner(quote! { + struct Keys<'a> { + x: &'static str, + y: &'a str, + } + }); + } + /// Test that the create storage keys produces /// the expected code. #[test] @@ -341,10 +367,12 @@ mod test_proc_macros { let expected_impl = quote! { impl Keys { + #[allow(dead_code)] const ALL: &[&'static str] = { let Keys { param1, param2 } = Self::VALUES; &[param1, param2] }; + #[allow(dead_code)] const VALUES: Keys = Self { param1: "param1", param2: "param2" From 05eb1330d0cd652a2648aed0002d7b50991310aa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 15:21:08 +0000 Subject: [PATCH 17/43] Update macros/src/lib.rs Co-authored-by: Jacob Turner --- macros/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index eb026afc2d..7e248d1edf 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -189,11 +189,7 @@ fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { let ident_list = create_punctuated(&idents, |ident| ident.clone()); let values_list = create_punctuated(&idents, |ident| { - let storage_key = { - let mut toks = TokenStream2::new(); - ident.to_tokens(&mut toks); - toks.to_string() - }; + let storage_key = ident.to_token_stream().to_string(); syn::FieldValue { attrs: vec![], member: syn::Member::Named(ident.clone()), From 56599b15360617833287ef36d1219fa1807a10a3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 15:30:42 +0000 Subject: [PATCH 18/43] Remove allow dead code TODO --- macros/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7e248d1edf..b1e4079169 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -159,7 +159,6 @@ pub fn derive_storage_keys(struct_def: TokenStream) -> TokenStream { #[inline] // TODO: use this crate for errors: https://crates.io/crates/proc-macro-error -// TODO: emit allow_deadcode in ALL and VALUES fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { let struct_def: ItemStruct = syn::parse2(struct_def) .expect("Expected a struct in the StorageKeys derive"); From ad0d2bcd40b93df3bc1a620c9b91798f7c781896 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 15:58:28 +0000 Subject: [PATCH 19/43] Strip out #[allow(dead_code)] from VALUES --- macros/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b1e4079169..50f14258d7 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -218,7 +218,6 @@ fn derive_storage_keys_inner(struct_def: TokenStream2) -> TokenStream2 { &[ #ident_list ] }; - #[allow(dead_code)] const VALUES: #struct_def_ident = Self { #values_list }; @@ -301,7 +300,6 @@ mod test_proc_macros { let Keys { bird, is, the, word } = Self::VALUES; &[bird, is, the, word] }; - #[allow(dead_code)] const VALUES: Keys = Self { bird: "bird", is: "is", @@ -367,7 +365,6 @@ mod test_proc_macros { let Keys { param1, param2 } = Self::VALUES; &[param1, param2] }; - #[allow(dead_code)] const VALUES: Keys = Self { param1: "param1", param2: "param2" From 73c686d7043ad917d98d6c20ed5a39fa1d270510 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 21 Dec 2022 09:08:34 -0500 Subject: [PATCH 20/43] wasm: update checksums.json --- wasm/checksums.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index b13508300c..1d0b3d93c7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.be9c75f96b3b4880b7934d42ee218582b6304f6326a4588d1e6ac1ea4cc61c49.wasm", + "tx_bond.wasm": "tx_bond.59f751c77d75d96679ec6df7376d896e3cbf9598846ea09e2caef8114de97932.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.cd861e0e82f4934be6d8382d6fff98286b4fadbc20ab826b9e817f6666021273.wasm", - "tx_ibc.wasm": "tx_ibc.13daeb0c88abba264d3052129eda0713bcf1a71f6f69bf37ec2494d0d9119f1f.wasm", + "tx_ibc.wasm": "tx_ibc.f261d127df2cb05489ad2655fcc6d0d8e7378789226ee27f0251ae90adfc1b0a.wasm", "tx_init_account.wasm": "tx_init_account.e21cfd7e96802f8e841613fb89f1571451401d002a159c5e9586855ac1374df5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b9a77bc9e416f33f1e715f25696ae41582e1b379422f7a643549884e0c73e9de.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e9732873861c625f239e74245f8c504a57359c06614ba40387a71811ca4a097.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.211265350906ad7aef3aca30d6ef463d065b738707accd0acaa19992977bfabe.wasm", + "tx_init_validator.wasm": "tx_init_validator.eac9858c4f96bbefd120e3ebe0489e1700a83e8a22d778d5aa2b14ab0627f172.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.47bc922a8be5571620a647ae442a1af7d03d05d29bef95f0b32cdfe00b11fee9.wasm", - "tx_transfer.wasm": "tx_transfer.bbd1ef5d9461c78f0288986de046baad77e10671addc5edaf3c68ea1ae4ecc99.wasm", - "tx_unbond.wasm": "tx_unbond.c0a690d0ad43a94294a6405bae3327f638a657446c74dc61dbb3a4d2ce488b5e.wasm", + "tx_transfer.wasm": "tx_transfer.5b95e6f1f6b2d4b0ec6f07ac1ec66089374ece2e4a6c5bdb32297985670a2ab0.wasm", + "tx_unbond.wasm": "tx_unbond.a33d113d04786c6638f5181b1bd65413b6b199e0c37985f444bea56430fc3f4c.wasm", "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.263fd9f4cb40f283756f394d86bdea3417e9ecd0568d6582c07a5b6bd14287d6.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ce8faf6a32340178ddeaeb91a9b40e7f0433334e5c1f357964bf8e11d0077f1.wasm", - "vp_implicit.wasm": "vp_implicit.17f5c2af947ccfadce22d0fffecde1a1b4bc4ca3acd5dd8b459c3dce4afcb4e8.wasm", - "vp_masp.wasm": "vp_masp.5620cb6e555161641337d308851c760fbab4f9d3693cfd378703aa55e285249d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.362584b063cc4aaf8b72af0ed8af8d05a179ebefec596b6ab65e0ca255ec3c80.wasm", - "vp_token.wasm": "vp_token.a289723dd182fe0206e6c4cf1f426a6100787b20e2653d2fad6031e8106157f3.wasm", - "vp_user.wasm": "vp_user.b83b2d0616bb2244c8a92021665a0be749282a53fe1c493e98c330a6ed983833.wasm", - "vp_validator.wasm": "vp_validator.59e3e7729e14eeacc17d76b736d1760d59a1a6e9d6acbc9a870e1835438f524a.wasm" + "tx_withdraw.wasm": "tx_withdraw.b9b8623217202de2cb2c3941f740f6acd8558eaa05406efc60ee471c212aa513.wasm", + "vp_implicit.wasm": "vp_implicit.6c221c5210973b8d39dae73378cb8291ffebb9fb99e790f89290a1b51eb6a690.wasm", + "vp_masp.wasm": "vp_masp.16516aeafa4c2c814cf3a4289b7f924d2551966f98b9ce136fbae361aba2a73c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e893d1f02ec8b3668a58ab69f9dd676ccb18d0cc96d3b5c9c789714552ea715e.wasm", + "vp_token.wasm": "vp_token.bfeddd3c5954174e95064f753cb743d84659f05910cbcb31a6d8b23769b59638.wasm", + "vp_user.wasm": "vp_user.40087a1e14bdafb4429427f47c5c7a0a247433d4f72927f2ec5906119192c900.wasm", + "vp_validator.wasm": "vp_validator.3f219ebb7f3f058f6681fe3d461d6296ab1d21d92ae1578ad421b272789f451e.wasm" } \ No newline at end of file From 51844e65cc3cd07ceebee14254d0465f58cd51c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 23 Dec 2022 15:03:31 +0100 Subject: [PATCH 21/43] core/storage/mockDB: remove unused `reverse_order` flag --- core/src/ledger/storage/mockdb.rs | 43 ++++++------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index ac96e12cf5..eb8ae04543 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -446,28 +446,14 @@ impl<'iter> DBIter<'iter> for MockDB { let db_prefix = "subspace/".to_owned(); let prefix = format!("{}{}", db_prefix, prefix); let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new( - MockIterator { - prefix, - iter, - reverse_order: false, - }, - db_prefix, - ) + MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } fn iter_results(&'iter self) -> MockPrefixIterator { let db_prefix = "results/".to_owned(); let prefix = "results".to_owned(); let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new( - MockIterator { - prefix, - iter, - reverse_order: false, - }, - db_prefix, - ) + MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } } @@ -477,8 +463,6 @@ pub struct MockIterator { prefix: String, /// The concrete iterator pub iter: btree_map::IntoIter>, - /// Is the iterator in reverse order? - reverse_order: bool, } /// A prefix iterator for the [`MockDB`]. @@ -488,23 +472,12 @@ impl Iterator for MockIterator { type Item = Result; fn next(&mut self) -> Option { - if self.reverse_order { - for (key, val) in (&mut self.iter).rev() { - if key.starts_with(&self.prefix) { - return Some(Ok(( - Box::from(key.as_bytes()), - Box::from(val.as_slice()), - ))); - } - } - } else { - for (key, val) in &mut self.iter { - if key.starts_with(&self.prefix) { - return Some(Ok(( - Box::from(key.as_bytes()), - Box::from(val.as_slice()), - ))); - } + for (key, val) in &mut self.iter { + if key.starts_with(&self.prefix) { + return Some(Ok(( + Box::from(key.as_bytes()), + Box::from(val.as_slice()), + ))); } } None From 5f219bfa04af9336ba32f3bbc9ae381d01b28bbf Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Dec 2022 16:34:34 +0100 Subject: [PATCH 22/43] Adds fee burning and checks --- .../lib/node/ledger/shell/finalize_block.rs | 80 ++++++++++++++++++- apps/src/lib/node/ledger/shell/mod.rs | 28 ++++++- .../lib/node/ledger/shell/process_proposal.rs | 2 +- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index d762ee91d8..758be0a403 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -2,7 +2,10 @@ use namada::ledger::pos::types::into_tm_voting_power; use namada::ledger::protocol; +use namada::ledger::storage::write_log::StorageModification; +use namada::ledger::storage_api::StorageRead; use namada::types::storage::{BlockHash, BlockResults, Header}; +use namada::types::token::Amount; use super::governance::execute_governance_proposals; use super::*; @@ -128,9 +131,80 @@ where } let mut tx_event = match &tx_type { - TxType::Wrapper(_wrapper) => { - self.storage.tx_queue.push(_wrapper.clone()); - Event::new_tx_event(&tx_type, height.0) + TxType::Wrapper(wrapper) => { + let mut tx_event = Event::new_tx_event(&tx_type, height.0); + + // Charge fee + let fee_payer = + if wrapper.pk != address::masp_tx_key().ref_to() { + wrapper.fee_payer() + } else { + address::masp() + }; + + let balance_key = token::balance_key( + &self.storage.native_token, + &fee_payer, + ); + let balance: Amount = + match self.write_log.read(&balance_key).0 { + Some(wal_mod) => { + // Read from WAL + if let StorageModification::Write { value } = + wal_mod + { + Amount::try_from_slice(value).unwrap() + } else { + Amount::default() + } + } + None => { + // Read from storage + let balance = StorageRead::read( + &self.storage, + &balance_key, + ); + // Storage read must not fail, but there might + // be no value, in which + // case default (0) is returned + balance + .expect( + "Storage read in the protocol must \ + not fail", + ) + .unwrap_or_default() + } + }; + + let balance: u64 = balance.into(); + match balance.checked_sub(100) { + Some(v) => { + self.write_log + .write( + &balance_key, + Amount::from(v).try_to_vec().unwrap(), + ) + .unwrap(); + } + None => { + // Burn remaining funds + self.write_log + .write( + &balance_key, + Amount::from(0).try_to_vec().unwrap(), + ) + .unwrap(); + tx_event["log"] = + "Insufficient balance for fee".into(); + tx_event["code"] = ErrorCodes::InvalidTx.into(); + + response.events.push(tx_event); + continue; + } + } + + self.storage.tx_queue.push(wrapper.clone()); + tx_event } TxType::Decrypted(inner) => { // We remove the corresponding wrapper tx from the queue diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4c461568a9..5f17d35e1d 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -33,16 +33,17 @@ use namada::ledger::storage::{ }; use namada::ledger::{ibc, pos, protocol}; use namada::proto::{self, Tx}; +use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; +use namada::types::token::{self, Amount}; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use namada::types::{address, token}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -575,7 +576,30 @@ where ) -> response::CheckTx { let mut response = response::CheckTx::default(); match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => response.log = String::from("Mempool validation passed"), + Ok(tx) => { + // Check balance for fee + if let Ok(TxType::Wrapper(wrapper)) = process_tx(tx) { + let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.fee_payer() + } else { + masp() + }; + // check that the fee payer has sufficient balance + let balance = self + .get_balance(&self.storage.native_token, &fee_payer); + + if Amount::from(100) > balance { + response.code = 1; + response.log = String::from( + "The address given does not have sufficient \ + balance to pay fee", + ); + return response; + } + } + + response.log = String::from("Mempool validation passed"); + } Err(msg) => { response.code = 1; response.log = msg.to_string(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 67ca13101e..54ed5fc05b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -162,7 +162,7 @@ where let balance = self.get_balance(&tx.fee.token, &fee_payer); - if tx.fee.amount <= balance { + if Amount::from(100) <= balance { TxResult { code: ErrorCodes::Ok.into(), info: "Process proposal accepted this \ From 0b511299339ae400761e3e1a2d29832e169c4f3a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Dec 2022 17:32:50 +0100 Subject: [PATCH 23/43] Fixes fee value in tx contruction --- apps/src/lib/client/signing.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index ed7ab484a9..fc67d6a329 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -6,6 +6,7 @@ use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::*; use namada::types::storage::Epoch; +use namada::types::token::Amount; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; use super::rpc; @@ -174,7 +175,7 @@ pub async fn sign_wrapper( let tx = { WrapperTx::new( Fee { - amount: args.fee_amount, + amount: Amount::from(100), token: ctx.get(&args.fee_token), }, keypair, From b93463636b8d876d610e1d2722042d35bf026498 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Dec 2022 17:44:55 +0100 Subject: [PATCH 24/43] Fixes process proposal fee token --- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 54ed5fc05b..ebe219d39a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -159,8 +159,10 @@ where masp() }; // check that the fee payer has sufficient balance - let balance = - self.get_balance(&tx.fee.token, &fee_payer); + let balance = self.get_balance( + &self.storage.native_token, + &fee_payer, + ); if Amount::from(100) <= balance { TxResult { From 0081be6122a29cd8778f5952a45b747599ec8743 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 24 Dec 2022 00:21:14 +0100 Subject: [PATCH 25/43] Fixes unit tests --- .../lib/node/ledger/shell/finalize_block.rs | 29 ++++++++++++++++--- .../lib/node/ledger/shell/process_proposal.rs | 9 ++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 758be0a403..b54f00199a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -422,15 +422,26 @@ mod test_finalize_block { let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_wrappers = vec![]; + + // Add unshielded balance for fee paymenty + let balance_key = token::balance_key( + &shell.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .storage + .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .unwrap(); + // create some wrapper txs - for i in 1..5 { + for i in 1u64..5 { let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), ); let wrapper = WrapperTx::new( Fee { - amount: i.into(), + amount: 100.into(), token: shell.storage.native_token.clone(), }, &keypair, @@ -603,6 +614,16 @@ mod test_finalize_block { let mut processed_txs = vec![]; let mut valid_txs = vec![]; + // Add unshielded balance for fee paymenty + let balance_key = token::balance_key( + &shell.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .storage + .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .unwrap(); + // create two decrypted txs let mut wasm_path = top_level_directory(); wasm_path.push("wasm_for_tests/tx_no_op.wasm"); @@ -619,7 +640,7 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: 0.into(), + amount: 100.into(), token: shell.storage.native_token.clone(), }, &keypair, @@ -650,7 +671,7 @@ mod test_finalize_block { ); let wrapper_tx = WrapperTx::new( Fee { - amount: 0.into(), + amount: 100.into(), token: shell.storage.native_token.clone(), }, &keypair, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ebe219d39a..0004db48e5 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -400,6 +400,15 @@ mod test_process_proposal { ..Default::default() }); let keypair = crate::wallet::defaults::daewon_keypair(); + // reduce address balance to match the 100 token fee + let balance_key = token::balance_key( + &shell.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .storage + .write(&balance_key, Amount::from(99).try_to_vec().unwrap()) + .unwrap(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), From db0cac3151c7ffa585eef5733176746e6bc34d5d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 27 Dec 2022 17:08:57 +0100 Subject: [PATCH 26/43] changelog: add #962 --- .changelog/unreleased/improvements/962-basic-fee.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/962-basic-fee.md diff --git a/.changelog/unreleased/improvements/962-basic-fee.md b/.changelog/unreleased/improvements/962-basic-fee.md new file mode 100644 index 0000000000..d1fa27df87 --- /dev/null +++ b/.changelog/unreleased/improvements/962-basic-fee.md @@ -0,0 +1,2 @@ +- Added a basic fee implementation for testnet. + ([#962](https://github.com/anoma/namada/pull/962)) \ No newline at end of file From c20b80f8bd96e60954f41d0ae954b21b3714d6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 27 Dec 2022 15:41:01 +0100 Subject: [PATCH 27/43] core/storage_api: use GATs to hide lifetime in StorageRead trait --- core/src/ledger/storage/mod.rs | 17 ++-- .../storage_api/collections/lazy_map.rs | 18 ++-- .../storage_api/collections/lazy_vec.rs | 14 ++-- core/src/ledger/storage_api/key.rs | 2 +- core/src/ledger/storage_api/mod.rs | 28 +++---- core/src/ledger/tx_env.rs | 2 +- core/src/ledger/vp_env.rs | 51 ++++-------- shared/src/ledger/ibc/vp/channel.rs | 10 +-- shared/src/ledger/ibc/vp/client.rs | 19 ++--- .../src/ledger/native_vp/governance/utils.rs | 2 +- shared/src/ledger/native_vp/mod.rs | 83 +++++++------------ shared/src/ledger/pos/vp.rs | 4 +- tx_prelude/src/lib.rs | 18 ++-- vp_prelude/src/lib.rs | 52 ++++++------ 14 files changed, 142 insertions(+), 178 deletions(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 4c34a6c2c4..256c1f2a8b 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -992,12 +992,15 @@ where } } -impl<'iter, D, H> StorageRead<'iter> for Storage +impl StorageRead for Storage where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, { - type PrefixIter = >::PrefixIter; + type PrefixIter<'iter> = >::PrefixIter +where + Self: 'iter + ; fn read_bytes( &self, @@ -1013,16 +1016,16 @@ where self.block.tree.has_key(key).into_storage_result() } - fn iter_prefix( + fn iter_prefix<'iter>( &'iter self, prefix: &crate::types::storage::Key, - ) -> std::result::Result { + ) -> std::result::Result, storage_api::Error> { Ok(self.db.iter_prefix(prefix)) } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> std::result::Result)>, storage_api::Error> { Ok(iter.next().map(|(key, val, _gas)| (key, val))) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 34a0f7d891..81ddd7f42d 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -323,7 +323,7 @@ where /// Returns whether the set contains a value. pub fn contains(&self, storage: &S, key: &K) -> Result where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { storage.has_key(&self.get_data_key(key)) } @@ -363,7 +363,7 @@ where /// map. pub fn iter<'iter>( &'iter self, - storage: &'iter impl StorageRead<'iter>, + storage: &'iter impl StorageRead, ) -> Result< impl Iterator< Item = Result<( @@ -406,7 +406,7 @@ where val: V, ) -> Result> where - S: StorageWrite + for<'iter> StorageRead<'iter>, + S: StorageWrite + StorageRead, { let previous = self.get(storage, &key)?; @@ -420,7 +420,7 @@ where /// was previously in the map. pub fn remove(&self, storage: &mut S, key: &K) -> Result> where - S: StorageWrite + for<'iter> StorageRead<'iter>, + S: StorageWrite + StorageRead, { let value = self.get(storage, key)?; @@ -433,7 +433,7 @@ where /// Returns the value corresponding to the key, if any. pub fn get(&self, storage: &S, key: &K) -> Result> where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) @@ -442,7 +442,7 @@ where /// Returns whether the map contains no elements. pub fn is_empty(&self, storage: &S) -> Result where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; @@ -457,7 +457,7 @@ where #[allow(clippy::len_without_is_empty)] pub fn len(&self, storage: &S) -> Result where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; @@ -473,7 +473,7 @@ where /// map. pub fn iter<'iter>( &self, - storage: &'iter impl StorageRead<'iter>, + storage: &'iter impl StorageRead, ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { @@ -493,7 +493,7 @@ where storage_key: &storage::Key, ) -> Result> where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let res = storage.read(storage_key)?; Ok(res) diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index 59eaa225e5..0e0a5ab03b 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -358,7 +358,7 @@ impl LazyVec { #[allow(clippy::len_without_is_empty)] pub fn len(&self, storage: &S) -> Result where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let len = storage.read(&self.get_len_key())?; Ok(len.unwrap_or_default()) @@ -367,7 +367,7 @@ impl LazyVec { /// Returns `true` if the vector contains no elements. pub fn is_empty(&self, storage: &S) -> Result where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { Ok(self.len(storage)? == 0) } @@ -396,7 +396,7 @@ where /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> where - S: StorageWrite + for<'iter> StorageRead<'iter>, + S: StorageWrite + StorageRead, { let len = self.len(storage)?; let data_key = self.get_data_key(len); @@ -410,7 +410,7 @@ where /// Note that an empty vector is completely removed from storage. pub fn pop(&self, storage: &mut S) -> Result> where - S: StorageWrite + for<'iter> StorageRead<'iter>, + S: StorageWrite + StorageRead, { let len = self.len(storage)?; if len == 0 { @@ -435,7 +435,7 @@ where /// will fail with `UpdateError::InvalidIndex`. pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> where - S: StorageWrite + for<'iter> StorageRead<'iter>, + S: StorageWrite + StorageRead, { let len = self.len(storage)?; if index >= len { @@ -449,7 +449,7 @@ where /// Read an element at the index or `Ok(None)` if out of bounds. pub fn get(&self, storage: &S, index: Index) -> Result> where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { storage.read(&self.get_data_key(index)) } @@ -463,7 +463,7 @@ where /// set. pub fn iter<'iter>( &self, - storage: &'iter impl StorageRead<'iter>, + storage: &'iter impl StorageRead, ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { diff --git a/core/src/ledger/storage_api/key.rs b/core/src/ledger/storage_api/key.rs index 06b3c76bad..6e3eba64aa 100644 --- a/core/src/ledger/storage_api/key.rs +++ b/core/src/ledger/storage_api/key.rs @@ -8,7 +8,7 @@ use crate::types::key::*; /// not found. pub fn get(storage: &S, owner: &Address) -> Result> where - S: for<'iter> StorageRead<'iter>, + S: StorageRead, { let key = pk_key(owner); storage.read(&key) diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index dde9eb5ade..c929aec03b 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -20,21 +20,17 @@ use crate::types::storage::{self, BlockHash, BlockHeight, Epoch, TxIndex}; /// /// ```rust,ignore /// where -/// S: for<'iter> StorageRead<'iter> +/// S: StorageRead /// ``` /// /// If you want to know why this is needed, see the to-do task below. The /// syntax for this relies on higher-rank lifetimes, see e.g. /// . -/// -/// TODO: once GATs are stabilized, we should be able to remove the `'iter` -/// lifetime param that is currently the only way to make the prefix iterator -/// typecheck in the `>::PrefixIter` associated type used in -/// `impl StorageRead for Storage` (shared/src/ledger/storage/mod.rs). -/// See -pub trait StorageRead<'iter> { +pub trait StorageRead { /// Storage read prefix iterator - type PrefixIter; + type PrefixIter<'iter> + where + Self: 'iter; /// Storage read Borsh encoded value. It will try to read from the storage /// and decode it if found. @@ -63,15 +59,15 @@ pub trait StorageRead<'iter> { /// /// For a more user-friendly iterator API, use [`fn@iter_prefix`] or /// [`fn@iter_prefix_bytes`] instead. - fn iter_prefix( + fn iter_prefix<'iter>( &'iter self, prefix: &storage::Key, - ) -> Result; + ) -> Result>; /// Storage prefix iterator. It will try to read from the storage. - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>>; /// Getting the chain ID. @@ -121,7 +117,7 @@ pub trait StorageWrite { /// Iterate items matching the given prefix, ordered by the storage keys. pub fn iter_prefix_bytes<'a>( - storage: &'a impl StorageRead<'a>, + storage: &'a impl StorageRead, prefix: &crate::types::storage::Key, ) -> Result)>> + 'a> { let iter = storage.iter_prefix(prefix)?; @@ -150,7 +146,7 @@ pub fn iter_prefix_bytes<'a>( /// Iterate Borsh encoded items matching the given prefix, ordered by the /// storage keys. pub fn iter_prefix<'a, T>( - storage: &'a impl StorageRead<'a>, + storage: &'a impl StorageRead, prefix: &crate::types::storage::Key, ) -> Result> + 'a> where diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index 7672ac6505..6ca47bb9d9 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -10,7 +10,7 @@ use crate::types::storage; use crate::types::time::Rfc3339String; /// Transaction host functions -pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { +pub trait TxEnv: StorageRead + StorageWrite { /// Write a temporary value to be encoded with Borsh at the given key to /// storage. fn write_temp( diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index 4b7edc02ce..43bc744635 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -10,15 +10,20 @@ use crate::types::key::common; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key, TxIndex}; /// Validity predicate's environment is available for native VPs and WASM VPs -pub trait VpEnv<'view> { +pub trait VpEnv<'view> +where + Self: 'view, +{ /// Storage read prefix iterator - type PrefixIter; + type PrefixIter<'iter> + where + Self: 'iter; /// Type to read storage state before the transaction execution - type Pre: StorageRead<'view, PrefixIter = Self::PrefixIter>; + type Pre: StorageRead = Self::PrefixIter<'view>>; /// Type to read storage state after the transaction execution - type Post: StorageRead<'view, PrefixIter = Self::PrefixIter>; + type Post: StorageRead = Self::PrefixIter<'view>>; /// Read storage state before the transaction execution fn pre(&'view self) -> Self::Pre; @@ -42,33 +47,32 @@ pub trait VpEnv<'view> { ) -> Result>, storage_api::Error>; /// Getting the chain ID. - fn get_chain_id(&'view self) -> Result; + 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(&'view self) - -> Result; + 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(&'view self) -> Result; + 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(&'view self) -> Result; + fn get_block_epoch(&self) -> Result; /// Get the shielded transaction index. - fn get_tx_index(&'view self) -> Result; + fn get_tx_index(&self) -> Result; /// Get the address of the native token. - fn get_native_token(&'view self) -> Result; + fn get_native_token(&self) -> Result; /// Storage prefix iterator, ordered by storage keys. It will try to get an /// iterator from the storage. - fn iter_prefix( - &'view self, + fn iter_prefix<'iter>( + &'iter self, prefix: &Key, - ) -> Result; + ) -> Result, storage_api::Error>; /// Evaluate a validity predicate with given data. The address, changed /// storage keys and verifiers will have the same values as the input to @@ -151,23 +155,4 @@ pub trait VpEnv<'view> { ) -> Result { self.post().has_key(key) } - - /// Storage prefix iterator for prior state (before tx execution). It will - /// try to read from the storage. - fn iter_pre_next( - &'view self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.pre().iter_next(iter) - } - - /// Storage prefix iterator next for posterior state (after tx execution). - /// It will try to read from the write log first and if no entry found - /// then from the storage. - fn iter_post_next( - &'view self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.post().iter_next(iter) - } } diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 05cbf7ad15..e85a221212 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -19,6 +19,7 @@ use namada_core::ledger::ibc::storage::{ }; use namada_core::ledger::parameters; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; +use namada_core::ledger::storage_api::StorageRead; use namada_core::types::storage::Key; use sha2::Digest; use thiserror::Error; @@ -734,14 +735,13 @@ where let mut channels = vec![]; let prefix = Key::parse("channelEnds/ports") .expect("Creating a key for the prefix shouldn't fail"); - let mut iter = self - .ctx + let post = self.ctx.post(); + let mut iter = post .iter_prefix(&prefix) .map_err(|_| Ics04Error::implementation_specific())?; loop { - let next = self - .ctx - .iter_post_next(&mut iter) + let next = post + .iter_next(&mut iter) .map_err(|_| Ics04Error::implementation_specific())?; if let Some((key, value)) = next { let channel = ChannelEnd::decode_vec(&value) diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 40807673f1..9bc5d20efb 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -6,6 +6,7 @@ use namada_core::ledger::ibc::actions::{ make_create_client_event, make_update_client_event, make_upgrade_client_event, }; +use namada_core::ledger::storage_api::StorageRead; use thiserror::Error; use super::super::storage::{ @@ -478,14 +479,13 @@ where height: Height, ) -> Ics02Result> { let prefix = consensus_state_prefix(client_id); - let mut iter = self - .ctx + let pre = self.ctx.pre(); + let mut iter = pre .iter_prefix(&prefix) .map_err(|_| Ics02Error::implementation_specific())?; let mut lowest_height_value = None; - while let Some((key, value)) = self - .ctx - .iter_pre_next(&mut iter) + while let Some((key, value)) = pre + .iter_next(&mut iter) .map_err(|_| Ics02Error::implementation_specific())? { let key = Key::parse(key) @@ -519,14 +519,13 @@ where height: Height, ) -> Ics02Result> { let prefix = consensus_state_prefix(client_id); - let mut iter = self - .ctx + let pre = self.ctx.pre(); + let mut iter = pre .iter_prefix(&prefix) .map_err(|_| Ics02Error::implementation_specific())?; let mut highest_height_value = None; - while let Some((key, value)) = self - .ctx - .iter_pre_next(&mut iter) + while let Some((key, value)) = pre + .iter_next(&mut iter) .map_err(|_| Ics02Error::implementation_specific())? { let key = Key::parse(key) diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index eb96948c49..4fd0a68217 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -220,7 +220,7 @@ pub fn is_proposal_accepted( tx_data: &[u8], ) -> storage_api::Result where - S: for<'iter> storage_api::StorageRead<'iter>, + S: storage_api::StorageRead, { let proposal_id = u64::try_from_slice(tx_data).ok(); match proposal_id { diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index d8f1dbe6e5..0907c1cc38 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -169,14 +169,14 @@ where } } -impl<'view, 'a, DB, H, CA> StorageRead<'view> +impl<'view, 'a: 'view, DB, H, CA> StorageRead for CtxPreStorageRead<'view, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type PrefixIter = >::PrefixIter; + type PrefixIter<'iter> = >::PrefixIter where Self: 'iter; fn read_bytes( &self, @@ -203,9 +203,9 @@ where .into_storage_result() } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, storage_api::Error> { vp_host_fns::iter_pre_next::( &mut self.ctx.gas_meter.borrow_mut(), @@ -217,10 +217,10 @@ where // ---- Methods below are implemented in `self.ctx`, because they are // the same in `pre/post` ---- - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &crate::types::storage::Key, - ) -> Result { + ) -> Result, storage_api::Error> { self.ctx.iter_prefix(prefix) } @@ -249,14 +249,14 @@ where } } -impl<'view, 'a, DB, H, CA> StorageRead<'view> +impl<'view, 'a: 'view, DB, H, CA> StorageRead for CtxPostStorageRead<'view, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type PrefixIter = >::PrefixIter; + type PrefixIter<'iter> = >::PrefixIter where Self:'iter; fn read_bytes( &self, @@ -284,9 +284,9 @@ where .into_storage_result() } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, storage_api::Error> { vp_host_fns::iter_post_next::( &mut self.ctx.gas_meter.borrow_mut(), @@ -299,10 +299,10 @@ where // ---- Methods below are implemented in `self.ctx`, because they are // the same in `pre/post` ---- - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &crate::types::storage::Key, - ) -> Result { + ) -> Result, storage_api::Error> { self.ctx.iter_prefix(prefix) } @@ -339,7 +339,7 @@ where { type Post = CtxPostStorageRead<'view, 'a, DB, H, CA>; type Pre = CtxPreStorageRead<'view, 'a, DB, H, CA>; - type PrefixIter = >::PrefixIter; + type PrefixIter<'iter> = >::PrefixIter where Self: 'iter; fn pre(&'view self) -> Self::Pre { CtxPreStorageRead { ctx: self } @@ -374,7 +374,7 @@ where .into_storage_result() } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&self) -> Result { vp_host_fns::get_chain_id( &mut self.gas_meter.borrow_mut(), self.storage, @@ -382,9 +382,7 @@ where .into_storage_result() } - fn get_block_height( - &'view self, - ) -> Result { + fn get_block_height(&self) -> Result { vp_host_fns::get_block_height( &mut self.gas_meter.borrow_mut(), self.storage, @@ -392,7 +390,7 @@ where .into_storage_result() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&self) -> Result { vp_host_fns::get_block_hash( &mut self.gas_meter.borrow_mut(), self.storage, @@ -400,7 +398,7 @@ where .into_storage_result() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&self) -> Result { vp_host_fns::get_block_epoch( &mut self.gas_meter.borrow_mut(), self.storage, @@ -408,7 +406,7 @@ where .into_storage_result() } - fn get_tx_index(&'view self) -> Result { + fn get_tx_index(&self) -> Result { vp_host_fns::get_tx_index( &mut self.gas_meter.borrow_mut(), self.tx_index, @@ -416,7 +414,7 @@ where .into_storage_result() } - fn get_native_token(&'view self) -> Result { + fn get_native_token(&self) -> Result { vp_host_fns::get_native_token( &mut self.gas_meter.borrow_mut(), self.storage, @@ -424,10 +422,10 @@ where .into_storage_result() } - fn iter_prefix( - &'view self, + fn iter_prefix<'iter>( + &'iter self, prefix: &Key, - ) -> Result { + ) -> Result, storage_api::Error> { vp_host_fns::iter_prefix( &mut self.gas_meter.borrow_mut(), self.storage, @@ -513,55 +511,38 @@ where } fn read_pre( - &'view self, + &self, key: &Key, ) -> Result, storage_api::Error> { self.pre().read(key).map_err(Into::into) } fn read_bytes_pre( - &'view self, + &self, key: &Key, ) -> Result>, storage_api::Error> { self.pre().read_bytes(key).map_err(Into::into) } fn read_post( - &'view self, + &self, key: &Key, ) -> Result, storage_api::Error> { self.post().read(key).map_err(Into::into) } fn read_bytes_post( - &'view self, + &self, key: &Key, ) -> Result>, storage_api::Error> { self.post().read_bytes(key).map_err(Into::into) } - fn has_key_pre(&'view self, key: &Key) -> Result { + fn has_key_pre(&self, key: &Key) -> Result { self.pre().has_key(key).map_err(Into::into) } - fn has_key_post( - &'view self, - key: &Key, - ) -> Result { + fn has_key_post(&self, key: &Key) -> Result { self.post().has_key(key).map_err(Into::into) } - - fn iter_pre_next( - &'view self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.pre().iter_next(iter).map_err(Into::into) - } - - fn iter_post_next( - &'view self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.post().iter_next(iter).map_err(Into::into) - } } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index e1b13648d0..2a2e87bdba 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -305,7 +305,7 @@ where } impl_pos_read_only! { - impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPreStorageRead<'f, 'a, DB, H, CA> + impl<'view, 'a: 'view, DB, H, CA> PosReadOnly for CtxPreStorageRead<'view, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, H: StorageHasher +'static, @@ -313,7 +313,7 @@ impl_pos_read_only! { } impl_pos_read_only! { - impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPostStorageRead<'f, 'a, DB, H, CA> + impl<'view, 'a: 'view, DB, H, CA> PosReadOnly for CtxPostStorageRead<'view, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, H: StorageHasher +'static, diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 73a90b6d03..9d66dcb73e 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -111,8 +111,8 @@ pub type TxResult = EnvResult<()>; #[derive(Debug)] pub struct KeyValIterator(pub u64, pub PhantomData); -impl StorageRead<'_> for Ctx { - type PrefixIter = KeyValIterator<(String, Vec)>; +impl StorageRead for Ctx { + type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); @@ -178,10 +178,10 @@ impl StorageRead<'_> for Ctx { Ok(Address::decode(address_str).expect("Cannot decode native address")) } - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &storage::Key, - ) -> Result { + ) -> Result, Error> { let prefix = prefix.to_string(); let iter_id = unsafe { namada_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -189,9 +189,9 @@ impl StorageRead<'_> for Ctx { Ok(KeyValIterator(iter_id, PhantomData)) } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Error> { let read_result = unsafe { namada_tx_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( @@ -231,7 +231,7 @@ impl StorageWrite for Ctx { } } -impl TxEnv<'_> for Ctx { +impl TxEnv for Ctx { fn get_block_time(&self) -> Result { let read_result = unsafe { namada_tx_get_block_time() }; let time_value = read_from_buffer(read_result, namada_tx_result_buffer) diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index f4911516d5..58ee37e756 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -190,7 +190,7 @@ pub struct KeyValIterator(pub u64, pub PhantomData); impl<'view> VpEnv<'view> for Ctx { type Post = CtxPostStorageRead<'view>; type Pre = CtxPreStorageRead<'view>; - type PrefixIter = KeyValIterator<(String, Vec)>; + type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; fn pre(&'view self) -> Self::Pre { CtxPreStorageRead { _ctx: self } @@ -221,39 +221,39 @@ impl<'view> VpEnv<'view> for Ctx { Ok(read_from_buffer(read_result, namada_vp_result_buffer)) } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_chain_id() } - fn get_block_height(&'view self) -> Result { + fn get_block_height(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_block_height() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_block_hash() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_block_epoch() } - fn get_tx_index(&'view self) -> Result { + fn get_tx_index(&self) -> Result { get_tx_index() } - fn get_native_token(&'view self) -> Result { + fn get_native_token(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_native_token() } - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &storage::Key, - ) -> Result { + ) -> Result, Error> { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl iter_prefix_impl(prefix) } @@ -309,8 +309,8 @@ impl<'view> VpEnv<'view> for Ctx { } } -impl StorageRead<'_> for CtxPreStorageRead<'_> { - type PrefixIter = KeyValIterator<(String, Vec)>; +impl StorageRead for CtxPreStorageRead<'_> { + type PrefixIter<'iter> = KeyValIterator<(String, Vec)> where Self: 'iter; fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); @@ -326,9 +326,9 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Error> { let read_result = unsafe { namada_vp_iter_pre_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( @@ -339,10 +339,10 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { // ---- Methods below share the same implementation in `pre/post` ---- - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &storage::Key, - ) -> Result { + ) -> Result, Error> { iter_prefix_impl(prefix) } @@ -371,8 +371,8 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { } } -impl StorageRead<'_> for CtxPostStorageRead<'_> { - type PrefixIter = KeyValIterator<(String, Vec)>; +impl StorageRead for CtxPostStorageRead<'_> { + type PrefixIter<'iter> = KeyValIterator<(String, Vec)> where Self:'iter; fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); @@ -389,9 +389,9 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Error> { let read_result = unsafe { namada_vp_iter_post_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( @@ -402,10 +402,10 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { // ---- Methods below share the same implementation in `pre/post` ---- - fn iter_prefix( - &self, + fn iter_prefix<'iter>( + &'iter self, prefix: &storage::Key, - ) -> Result { + ) -> Result, Error> { iter_prefix_impl(prefix) } From c40fd1b61a66679185e14d00f5e7dfabad0e37de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 27 Dec 2022 17:14:09 +0100 Subject: [PATCH 28/43] changelog: #966 --- .../unreleased/improvements/966-gats-lifetimes-refactor.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/966-gats-lifetimes-refactor.md diff --git a/.changelog/unreleased/improvements/966-gats-lifetimes-refactor.md b/.changelog/unreleased/improvements/966-gats-lifetimes-refactor.md new file mode 100644 index 0000000000..b3233a072e --- /dev/null +++ b/.changelog/unreleased/improvements/966-gats-lifetimes-refactor.md @@ -0,0 +1,2 @@ +- Hide the explicit lifetime from StorageRead trait. + ([#966](https://github.com/anoma/namada/pull/966)) \ No newline at end of file From dd2d5045158c93a925deadbcfdcd293d8f54927f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 27 Dec 2022 17:18:48 +0100 Subject: [PATCH 29/43] core/storage: remove redundant `StorageWrite` impl for mut ref --- core/src/ledger/storage/mod.rs | 38 ---------------------------------- 1 file changed, 38 deletions(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 256c1f2a8b..13d0db6c7e 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1102,44 +1102,6 @@ where } } -impl StorageWrite for &mut Storage -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - fn write( - &mut self, - key: &crate::types::storage::Key, - val: T, - ) -> storage_api::Result<()> { - let val = val.try_to_vec().unwrap(); - self.write_bytes(key, val) - } - - fn write_bytes( - &mut self, - key: &crate::types::storage::Key, - val: impl AsRef<[u8]>, - ) -> storage_api::Result<()> { - let _ = self - .db - .write_subspace_val(self.block.height, key, val) - .into_storage_result()?; - Ok(()) - } - - fn delete( - &mut self, - key: &crate::types::storage::Key, - ) -> storage_api::Result<()> { - let _ = self - .db - .delete_subspace_val(self.block.height, key) - .into_storage_result()?; - Ok(()) - } -} - impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) From c6564c28e98857c04d46b3c7d85d1a3e31075aae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 27 Dec 2022 19:13:55 +0000 Subject: [PATCH 30/43] [ci] wasm checksums update --- wasm/checksums.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5bd0a33931..eb4686a990 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,7 +1,7 @@ { - "tx_bond.wasm": "tx_bond.852a22bb75acb8bbedd7ca1511af6e10b5a1f191ac80f5a6c7b5228bb79c6aed.wasm", + "tx_bond.wasm": "tx_bond.14ebbb45aad0e1301df40a5c412679c05078903794b6caaa2b52e24d9bbb7594.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.cd861e0e82f4934be6d8382d6fff98286b4fadbc20ab826b9e817f6666021273.wasm", - "tx_ibc.wasm": "tx_ibc.eb7d0f6c7cba4fcc402f32ed6a09bf70827521c5539b6393e2078f3931e8fccd.wasm", + "tx_ibc.wasm": "tx_ibc.dc7969c3f1a0f1bfb8e1c942e09f9c2678c2217846e607ee39ea0a7022258bdd.wasm", "tx_init_account.wasm": "tx_init_account.87ece7f13b327f7d15c5b6e7083b510debb1c3205bafe9e36457c8585beeb49c.wasm", "tx_init_proposal.wasm": "tx_init_proposal.c39152be5435cf3f7588ddcde82b801e98902079a152ae3df30bac69833e3ebb.wasm", "tx_init_validator.wasm": "tx_init_validator.56d563e4b9001790d9089ff91c6c98cde95032fb57d97122e58e8b5e363fc13f.wasm", @@ -10,11 +10,11 @@ "tx_unbond.wasm": "tx_unbond.c0a690d0ad43a94294a6405bae3327f638a657446c74dc61dbb3a4d2ce488b5e.wasm", "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.263fd9f4cb40f283756f394d86bdea3417e9ecd0568d6582c07a5b6bd14287d6.wasm", - "tx_withdraw.wasm": "tx_withdraw.db97f19c3217167374f9163fc4d3738117be23041d75068e97b269e6f9810ec8.wasm", - "vp_implicit.wasm": "vp_implicit.0b6b00b4663aa2c747d51bb0fda2da0454615bcdac345011131e3372ca2598a9.wasm", + "tx_withdraw.wasm": "tx_withdraw.8f53ce136e07c4d1a09bebf2d3d0eba77828622a8c95d5bb3a14b6f934f0cf0d.wasm", + "vp_implicit.wasm": "vp_implicit.5b71d2ab5a9fe9bc21f116f978d1c6b8889ea835386605f7ccfc1204fff79867.wasm", "vp_masp.wasm": "vp_masp.5620cb6e555161641337d308851c760fbab4f9d3693cfd378703aa55e285249d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e5df211c9016a8d5ffbba980859470e0c7abbbdf900263830585fb49fa75623d.wasm", - "vp_token.wasm": "vp_token.a289723dd182fe0206e6c4cf1f426a6100787b20e2653d2fad6031e8106157f3.wasm", - "vp_user.wasm": "vp_user.91b6389650a83702c66e220893211b94cdce784d1e18ca8433c69964fc0967ea.wasm", - "vp_validator.wasm": "vp_validator.5175c7aeb34657b9fbe4211031c628e6e834037c26f4cc290bfb50f301d41138.wasm" + "vp_testnet_faucet.wasm": "vp_testnet_faucet.55df1439dfc51ce9bbf592e9dd74036138f749be50e09fbcfb879d582b625b35.wasm", + "vp_token.wasm": "vp_token.dfde368a9e52cbda5aec60fa54d73ee0d36abac31fa3342098ebb29100a0f5b5.wasm", + "vp_user.wasm": "vp_user.33e5dd9f09f7d4ab61330a46edc72ca9063eeff224b9b751e3c1e8124282df2c.wasm", + "vp_validator.wasm": "vp_validator.f5ba5c2dfa46ce3b223df7ec2fd879e86a903ad382beda113c0f0f457a479553.wasm" } \ No newline at end of file From f7b63784490d0b119fa63f7d52c53441bbcec031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 28 Dec 2022 13:41:07 +0100 Subject: [PATCH 31/43] re-export `namada` from `namada_tests` for `namada_core` dev-deps --- Cargo.lock | 1 + core/Cargo.toml | 4 +++- tests/src/lib.rs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 73ed4d5b08..32f775fbc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3803,6 +3803,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "namada_tests", "pretty_assertions", "proptest", "prost", diff --git a/core/Cargo.toml b/core/Cargo.toml index 3df5654132..1221b30418 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -28,13 +28,14 @@ abcipp = [ "ibc-proto-abcipp", "ibc-abcipp", "tendermint-abcipp", - "tendermint-proto-abcipp" + "tendermint-proto-abcipp", ] abciplus = [ "ibc", "ibc-proto", "tendermint", "tendermint-proto", + "namada_tests/abciplus", ] ibc-mocks = [ @@ -98,6 +99,7 @@ tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} [dev-dependencies] +namada_tests = {path = "../tests", default-features = false, features = ["wasm-runtime"]} assert_matches = "1.5.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} pretty_assertions = "0.7.2" diff --git a/tests/src/lib.rs b/tests/src/lib.rs index f39bc5b0e8..db37039878 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -5,6 +5,8 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +pub use namada; + mod vm_host_env; pub use vm_host_env::{ibc, tx, vp}; #[cfg(test)] From 3691e526ced3161a43139854711d67456e55878e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 23 Dec 2022 17:16:46 +0100 Subject: [PATCH 32/43] core: add faucet PoW logic --- Cargo.lock | 1 + core/Cargo.toml | 1 + core/src/ledger/faucet_pow.rs | 528 ++++++++++++++++++++++++++ core/src/ledger/mod.rs | 1 + tx_prelude/src/lib.rs | 1 + vp_prelude/src/lib.rs | 2 +- wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 2 + 8 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 core/src/ledger/faucet_pow.rs diff --git a/Cargo.lock b/Cargo.lock index 32f775fbc9..2965e98676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3803,6 +3803,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "namada_macros", "namada_tests", "pretty_assertions", "proptest", diff --git a/core/Cargo.toml b/core/Cargo.toml index 1221b30418..349fbb0025 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -53,6 +53,7 @@ testing = [ ] [dependencies] +namada_macros = {path = "../macros"} ark-bls12-381 = {version = "0.3"} ark-ec = {version = "0.3", optional = true} ark-serialize = {version = "0.3"} diff --git a/core/src/ledger/faucet_pow.rs b/core/src/ledger/faucet_pow.rs new file mode 100644 index 0000000000..8f8d92276b --- /dev/null +++ b/core/src/ledger/faucet_pow.rs @@ -0,0 +1,528 @@ +//! Faucet PoW challenge for the VP and client support. + +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSerialize}; +use namada_macros::StorageKeys; +use serde::{Deserialize, Serialize}; + +use super::storage_api::collections::{lazy_map, LazyCollection}; +use super::storage_api::{self, StorageRead, StorageWrite}; +use super::tx_env::TxEnv; +use super::vp_env::VpEnv; +use crate::ledger::storage_api::collections::LazyMap; +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::storage::{self, DbKeySeg}; +use crate::types::token; + +/// Initialize faucet's storage. This must be called at genesis if faucet +/// account is being used. +pub fn init_faucet_storage( + storage: &mut S, + address: &Address, + difficulty: Difficulty, +) -> storage_api::Result<()> +where + S: StorageWrite, +{ + write_difficulty(storage, address, difficulty) +} + +/// Counters are associated with transfer target addresses. +pub type Counter = u64; + +/// A PoW challenge that must be provably solved to withdraw from faucet. +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Serialize, + Deserialize, +)] +pub struct Challenge { + /// Transfer tx data + pub transfer: token::Transfer, + /// Parameters + pub params: ChallengeParams, +} + +/// PoW challenge parameters. +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Serialize, + Deserialize, +)] +pub struct ChallengeParams { + /// PoW difficulty + pub difficulty: Difficulty, + /// The counter value of the `transfer.target`. + pub counter: Counter, +} + +/// One must find a value for this type to solve a [`Challenge`] that is at +/// least of the matching difficulty of the challenge. +pub type SolutionValue = u64; +/// Size of `SolutionValue` when serialized with borsh +const SOLUTION_VAL_BYTES_LEN: usize = 8; + +/// A [`SolutionValue`] with the [`Challenge`]. +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct Solution { + /// Challenge + pub challenge: Challenge, + /// Solution + pub solution: SolutionValue, +} + +impl ChallengeParams { + /// Obtain a PoW challenge for a given transfer. + pub fn new( + storage: &mut S, + faucet_address: &Address, + target: &Address, + ) -> storage_api::Result + where + S: StorageRead + StorageWrite, + { + let difficulty = read_difficulty(storage, faucet_address)?; + let counter = get_counter(storage, faucet_address, target)?; + Ok(Self { + difficulty, + counter, + }) + } +} + +impl Challenge { + /// Obtain a PoW challenge for a given transfer. + pub fn new( + storage: &mut S, + faucet_address: &Address, + transfer: token::Transfer, + ) -> storage_api::Result + where + S: StorageRead + StorageWrite, + { + let params = + ChallengeParams::new(storage, faucet_address, &transfer.target)?; + Ok(Self { transfer, params }) + } + + /// Try to find a solution to the [`Challenge`]. + pub fn solve(self) -> Solution { + use std::io::Write; + + println!( + "Looking for a solution with difficulty {}...", + self.params.difficulty + ); + let challenge_bytes = self.try_to_vec().expect("Serializable"); + let challenge_len = challenge_bytes.len(); + let mut stdout = std::io::stdout(); + + // Pre-allocate for the bytes + let mut bytes: Vec = + vec![0; challenge_bytes.len() + SOLUTION_VAL_BYTES_LEN]; + + // Set the first part from `challenge_bytes`... + for (old, new) in + bytes[0..challenge_len].iter_mut().zip(&challenge_bytes[..]) + { + *old = *new; + } + let mut maybe_solution: SolutionValue = 0; + 'outer: loop { + stdout.flush().unwrap(); + print!("\rChecking {}.", maybe_solution); + let solution_bytes = + maybe_solution.try_to_vec().expect("Serializable"); + // ...and the second part from `solution_bytes` + for (old, new) in + bytes[challenge_len..].iter_mut().zip(&solution_bytes[..]) + { + *old = *new; + } + let hash = Hash::sha256(&bytes); + + // Check if it's a solution + for i in 0..self.params.difficulty.0 as usize { + if hash.0[i] != b'0' { + maybe_solution += 1; + continue 'outer; + } + } + + println!(); + println!("Found a solution: {}.", maybe_solution); + stdout.flush().unwrap(); + return Solution { + challenge: self, + solution: maybe_solution, + }; + } + } +} + +impl Solution { + /// Apply a solution from a tx so that it cannot be used again. + pub fn apply_from_tx( + &self, + tx_env: &mut ENV, + ) -> storage_api::Result<()> + where + ENV: TxEnv, + { + increment_counter( + tx_env, + &self.challenge.transfer.source, + &self.challenge.transfer.target, + self.challenge.params.counter, + ) + } + + /// Verify a solution and that the counter has been increment to prevent + /// solution replay. + /// The difficulty of the challenge must match the one set in faucet's + /// storage and the counter value. + pub fn validate( + &self, + env: &ENV, + faucet_address: &Address, + ) -> storage_api::Result + where + ENV: for<'a> VpEnv<'a> + 'static, + { + let counter_pre = get_counter( + &env.pre(), + faucet_address, + &self.challenge.transfer.target, + )?; + // Check that the counter matches expected counter + if self.challenge.params.counter != counter_pre { + return Ok(false); + } + let counter_post = get_counter( + &env.post(), + faucet_address, + &self.challenge.transfer.target, + )?; + // Check that the counter is incremented + if counter_pre + 1 != counter_post { + return Ok(false); + } + let current_difficulty = read_difficulty(&env.pre(), faucet_address)?; + // Check that the difficulty matches expected difficulty + if self.challenge.params.difficulty != current_difficulty { + return Ok(false); + } + + // Check the solution itself + if !self.verify_solution() { + return Ok(false); + } + + Ok(true) + } + + /// Verify that the given solution is correct. Note that this doesn't check + /// the difficulty or the counter. + pub fn verify_solution(&self) -> bool { + let mut bytes = self.challenge.try_to_vec().expect("Serializable"); + let mut solution_bytes = + self.solution.try_to_vec().expect("Serializable"); + bytes.append(&mut solution_bytes); + let hash = Hash::sha256(&bytes); + + // Check if it's a solution + for i in 0..self.challenge.params.difficulty.0 as usize { + if hash.0[i] != b'0' { + return false; + } + } + + true + } +} + +/// Storage keys +#[derive(StorageKeys)] +pub struct Keys { + /// Withdrawal counters associated with recipient addresses. To withdraw + /// tokens from faucet, one must find a solution to a PoW challenge + /// containing the current value of their counter (or `0` is none). + counters: &'static str, + /// PoW difficulty + difficulty: &'static str, +} + +/// Storage key prefix to the `counters` field. The rest of the key is composed +/// from `LazyMap` stored at this key. +pub fn counter_prefix(address: &Address) -> storage::Key { + storage::Key { + segments: vec![ + DbKeySeg::AddressSeg(address.clone()), + DbKeySeg::StringSeg(Keys::VALUES.counters.to_string()), + ], + } +} + +/// Is the storage key for the `counters` field? If so, returns the owner. +pub fn is_counter_key<'a>( + key: &'a storage::Key, + faucet_address: &Address, +) -> Option<&'a Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(address), + DbKeySeg::StringSeg(sub_key), + DbKeySeg::StringSeg(data), + DbKeySeg::AddressSeg(owner), + ] if address == faucet_address + && sub_key.as_str() == Keys::VALUES.counters + && data.as_str() == lazy_map::DATA_SUBKEY => + { + Some(owner) + } + _ => None, + } +} + +/// Storage key to the `difficulty` field. +pub fn difficulty_key(address: &Address) -> storage::Key { + storage::Key { + segments: vec![ + DbKeySeg::AddressSeg(address.clone()), + DbKeySeg::StringSeg(Keys::VALUES.difficulty.to_string()), + ], + } +} + +/// Is the storage key for the `difficulty` field? +pub fn is_difficulty_key(key: &storage::Key, faucet_address: &Address) -> bool { + matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(address), + DbKeySeg::StringSeg(sub_key), + ] if address == faucet_address && sub_key.as_str() == Keys::VALUES.difficulty, + ) +} + +/// Read faucet's counter value for a given target address. +pub fn get_counter( + storage: &S, + faucet_address: &Address, + target: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let counter: Counter = counters_handle(faucet_address) + .get(storage, target)? + // `0` if not previously set + .unwrap_or_default(); + Ok(counter) +} + +/// Increment faucet's counter value for a given target address. +pub fn increment_counter( + storage: &mut S, + faucet_address: &Address, + target: &Address, + current_counter: Counter, +) -> storage_api::Result<()> +where + S: StorageWrite + StorageRead, +{ + counters_handle(faucet_address).insert( + storage, + target.clone(), + current_counter + 1, + )?; + Ok(()) +} + +/// A handle to read/write withdrawal counters +pub fn counters_handle(address: &Address) -> LazyMap { + LazyMap::open(counter_prefix(address)) +} + +/// PoW difficulty (value between `0..=9`). +#[derive( + Copy, + Clone, + Debug, + Default, + BorshSerialize, + BorshDeserialize, + Eq, + PartialEq, + PartialOrd, + Ord, + Serialize, + Deserialize, +)] +#[serde(transparent)] +pub struct Difficulty(u8); +impl Difficulty { + /// The value must be between `0..=9` (inclusive upper bound). + pub fn try_new(raw: u8) -> Option { + if raw > 9 { None } else { Some(Self(raw)) } + } +} + +impl Display for Difficulty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +/// Read PoW [`Difficulty`]. +pub fn read_difficulty( + storage: &S, + address: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let difficulty = storage + .read(&difficulty_key(address))? + .expect("difficulty must always be set"); + Ok(difficulty) +} + +/// Write PoW [`Difficulty`]. +pub fn write_difficulty( + storage: &mut S, + address: &Address, + difficulty: Difficulty, +) -> storage_api::Result<()> +where + S: StorageWrite, +{ + storage.write(&difficulty_key(address), difficulty) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_solution_val_bytes_len() { + let val: SolutionValue = 10; + let bytes = val.try_to_vec().unwrap(); + assert_eq!(bytes.len(), SOLUTION_VAL_BYTES_LEN); + } +} + +#[cfg(test)] +mod test_with_tx_and_vp_env { + // IMPORTANT: do not import anything directly from this `crate` here, only + // via `namada_tests`. This gets us around the `core -> tests -> core` dep + // cycle, which is okay, because `tests` is only a `dev-dependency` of + // core and allows us to test the code in the same module as its defined. + // + // This imports the same code as `super::*` but from a different version of + // this crate (one that `namada_tests` depends on). It's re-exported + // from `namada_tests` so that we can use it together with + // `namada_tests` modules back in here. + use namada_tests::namada::core::ledger::faucet_pow::*; + use namada_tests::namada::core::ledger::storage_api; + use namada_tests::namada::core::types::{address, token}; + use namada_tests::tx::{self, TestTxEnv}; + use namada_tests::vp; + + #[test] + fn test_challenge_and_solution() -> storage_api::Result<()> { + let faucet_address = address::testing::established_address_1(); + let difficulty = Difficulty::try_new(1).unwrap(); + + let mut tx_env = TestTxEnv::default(); + + // Transfer addresses + let target = address::testing::established_address_2(); + let token = address::testing::established_address_3(); + + // Ensure that the addresses exists, so we can use them in a tx + tx_env.spawn_accounts([&faucet_address, &target, &token]); + + init_faucet_storage(&mut tx_env.storage, &faucet_address, difficulty)?; + + let transfer = token::Transfer { + source: faucet_address.clone(), + target, + token, + sub_prefix: None, + amount: token::Amount::whole(1_000), + key: None, + shielded: None, + }; + + let challenge = + Challenge::new(&mut tx_env.storage, &faucet_address, transfer)?; + + let solution = challenge.solve(); + + // The solution must be valid + assert!(solution.verify_solution()); + + // Changing the solution to `0` invalidates it + { + let mut solution = solution.clone(); + solution.solution = 0; + // If you're unlucky and this fails, try changing the solution to + // a different literal. + assert!(!solution.verify_solution()); + } + // Changing the counter invalidates it + { + let mut solution = solution.clone(); + solution.challenge.params.counter = 10; + // If you're unlucky and this fails, try changing the counter to + // a different literal. + assert!(!solution.verify_solution()); + } + + // Apply the solution from a tx + vp::vp_host_env::init_from_tx( + faucet_address.clone(), + tx_env, + |_addr| { + solution.apply_from_tx(tx::ctx()).unwrap(); + }, + ); + + // Check that it's valid + let is_valid = solution.validate(vp::ctx(), &faucet_address)?; + assert!(is_valid); + + // Commit the tx + let vp_env = vp::vp_host_env::take(); + tx::tx_host_env::set_from_vp_env(vp_env); + tx::tx_host_env::commit_tx_and_block(); + let tx_env = tx::tx_host_env::take(); + + // Re-apply the same solution from a tx + vp::vp_host_env::init_from_tx( + faucet_address.clone(), + tx_env, + |_addr| { + solution.apply_from_tx(tx::ctx()).unwrap(); + }, + ); + + // Check that it's not longer valid + let is_valid = solution.validate(vp::ctx(), &faucet_address)?; + assert!(!is_valid); + + Ok(()) + } +} diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index 83568c0da7..b3842dfe93 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -1,5 +1,6 @@ //! The ledger modules +pub mod faucet_pow; pub mod gas; pub mod governance; #[cfg(any(feature = "abciplus", feature = "abcipp"))] diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 9d66dcb73e..2559842fa3 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -16,6 +16,7 @@ use core::slice; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; +pub use namada_core::ledger::faucet_pow; pub use namada_core::ledger::governance::storage as gov_storage; pub use namada_core::ledger::parameters::storage as parameters_storage; pub use namada_core::ledger::slash_fund::storage as slash_fund_storage; diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 58ee37e756..b69530ffcf 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -18,12 +18,12 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use namada_core::ledger::governance::storage as gov_storage; -pub use namada_core::ledger::parameters; pub use namada_core::ledger::storage_api::{ self, iter_prefix, iter_prefix_bytes, Error, OptionExt, ResultExt, StorageRead, }; pub use namada_core::ledger::vp_env::VpEnv; +pub use namada_core::ledger::{faucet_pow, parameters}; pub use namada_core::proto::{Signed, SignedTxData}; pub use namada_core::types::address::Address; use namada_core::types::chain::CHAIN_ID_LENGTH; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 35d906c305..ca26d09c42 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2519,6 +2519,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "namada_macros", "proptest", "prost", "prost-types", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index b77ffec187..4ea64192b2 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2519,6 +2519,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "namada_macros", "proptest", "prost", "prost-types", @@ -2543,6 +2544,7 @@ dependencies = [ name = "namada_macros" version = "0.12.2" dependencies = [ + "proc-macro2", "quote", "syn", ] From abf5b8e11f53313ea44df0388a411b3d5d00ac74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 12:40:20 +0100 Subject: [PATCH 33/43] vp_testnet_faucet: integrate faucet PoW check --- wasm/wasm_source/src/vp_testnet_faucet.rs | 101 ++++++++++++++++++++-- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 4ae7ddad71..f5532b55df 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -5,7 +5,7 @@ //! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::{SignedTxData, *}; +use namada_vp_prelude::*; use once_cell::unsync::Lazy; /// Allows anyone to withdraw up to 1_000 tokens in a single tx @@ -46,10 +46,26 @@ fn validate_tx( _ => false, }); + let solution = Lazy::new(|| match &*signed_tx_data { + Ok(signed) => { + if let Some(data) = &signed.data.as_ref() { + faucet_pow::Solution::try_from_slice(data).ok() + } else { + None + } + } + _ => None, + }); + if !is_valid_tx(ctx, &tx_data)? { return reject(); } + // An address whose counter has been incremented + let mut incremented_counter: Option
= None; + // An address other than faucet that received tokens + let mut withdrawal: Option
= None; + for key in keys_changed.iter() { let is_valid = if let Some(owner) = token::is_any_token_balance_key(key) { @@ -58,11 +74,69 @@ fn validate_tx( let post: token::Amount = ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); - // Debit over `MAX_FREE_DEBIT` has to signed, credit doesn't - change >= -MAX_FREE_DEBIT || change >= 0 || *valid_sig + + if change < 0 { + // If there's a PoW solution + match &*solution { + Some(solution) => { + // Validate it + if solution.validate(ctx, &addr)? { + change >= -MAX_FREE_DEBIT + } else { + debug_log!("Invalid PoW solution"); + false + } + } + _ => { + debug_log!( + "No PoW solution, signature is required" + ); + // Debit without a solution has to signed + *valid_sig + } + } + } else { + // credit is permissive + true + } } else { - // If this is not the owner, allow any change + // An address other than self whose balance has changed + let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + let change = post.change() - pre.change(); + + if change < 0 { + // Debit + true + } else { + // Credit + if withdrawal.as_ref().is_none() { + // Store the withdrawal target + withdrawal = Some(owner.clone()); + // Allow to withdraw + true + } else { + // Only one withdrawal at the time + false + } + } + } + } else if let Some(owner) = faucet_pow::is_counter_key(key, &addr) { + let counter_pre = + faucet_pow::get_counter(&ctx.pre(), &addr, owner)?; + let counter_post = + faucet_pow::get_counter(&ctx.post(), &addr, owner)?; + // Counters can only increment + if counter_pre + 1 == counter_post && + // Only one counter update too + incremented_counter.as_ref().is_none() + { + // Store the owner of incremented counter + incremented_counter = Some(owner.clone()); true + } else { + false } } else if let Some(owner) = key.is_validity_predicate() { let has_post: bool = ctx.has_key_post(key)?; @@ -81,12 +155,29 @@ fn validate_tx( // Allow any other key change if authorized by a signature *valid_sig }; + if !is_valid { debug_log!("key {} modification failed vp", key); return reject(); } } - accept() + + let is_valid = match (withdrawal, incremented_counter) { + (Some(withdrawal), Some(incremented_counter)) => { + // A withdrawal target must have incremented counter + withdrawal == incremented_counter + } + (None, None) => { + // No withdrawal without counter change is fine + true + } + _ => { + // Missing withdrawal with a counter update or other way + // round + false + } + }; + if is_valid { accept() } else { reject() } } #[cfg(test)] From 42f6b12511a517b6f75e4eea5e17db251b695dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 12:51:48 +0100 Subject: [PATCH 34/43] ledger/init_chain: init testnet faucet storage when used --- apps/src/lib/node/ledger/shell/init_chain.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d025b2753f..2f9492d429 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::core::ledger::faucet_pow; use namada::ledger::parameters::Parameters; use namada::ledger::pos::into_tm_voting_power; use namada::types::key::*; @@ -171,6 +172,17 @@ where for (key, value) in storage { self.storage.write(&key, value).unwrap(); } + + // When using a faucet WASM, initialize its PoW challenge storage + if vp_code_path == "vp_testnet_faucet.wasm" { + faucet_pow::init_faucet_storage( + &mut self.storage, + &address, + faucet_pow::Difficulty::try_new(5) + .expect("Difficulty 5 is valid"), + ) + .expect("Couldn't init faucet storage") + } } // Initialize genesis implicit From 4354285c44a8a6b31f2b04db51a7ce6c11d314b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 13:02:08 +0100 Subject: [PATCH 35/43] wasm: add tx_testnet_faucet_withdrawal --- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 2 ++ .../src/tx_testnet_faucet_withdrawal.rs | 34 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 wasm/wasm_source/src/tx_testnet_faucet_withdrawal.rs diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 043cea9684..510ee25913 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -19,6 +19,7 @@ tx_init_account = ["namada_tx_prelude"] tx_init_proposal = ["namada_tx_prelude"] tx_init_validator = ["namada_tx_prelude"] tx_reveal_pk = ["namada_tx_prelude"] +tx_testnet_faucet_withdrawal = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index aee4f3df8f..3dcc53df2b 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -12,6 +12,7 @@ wasms += tx_init_validator wasms += tx_init_proposal wasms += tx_reveal_pk wasms += tx_vote_proposal +wasms += tx_testnet_faucet_withdrawal wasms += tx_transfer wasms += tx_unbond wasms += tx_update_vp diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 98704112f2..3ab3efd05c 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -12,6 +12,8 @@ pub mod tx_init_proposal; pub mod tx_init_validator; #[cfg(feature = "tx_reveal_pk")] pub mod tx_reveal_pk; +#[cfg(feature = "tx_testnet_faucet_withdrawal")] +pub mod tx_testnet_faucet_withdrawal; #[cfg(feature = "tx_transfer")] pub mod tx_transfer; #[cfg(feature = "tx_unbond")] diff --git a/wasm/wasm_source/src/tx_testnet_faucet_withdrawal.rs b/wasm/wasm_source/src/tx_testnet_faucet_withdrawal.rs new file mode 100644 index 0000000000..e5fd701939 --- /dev/null +++ b/wasm/wasm_source/src/tx_testnet_faucet_withdrawal.rs @@ -0,0 +1,34 @@ +//! A tx for token withdrawal from a testnet faucet account. +//! This tx uses `faucet_pow::Solution` wrapped inside `SignedTxData` +//! as its input. + +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .wrap_err("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let solution = faucet_pow::Solution::try_from_slice(&data[..]) + .wrap_err("failed to decode faucet_pow::Solution")?; + debug_log!( + "tx_testnet_faucet_withdrawal called with solution: {:#?}", + solution + ); + // Apply the solution to prevent replay + solution.apply_from_tx(ctx)?; + + // Apply the transfer + let token::Transfer { + source, + target, + token, + sub_prefix, + amount, + key, + shielded, + } = solution.challenge.transfer; + token::transfer( + ctx, &source, &target, &token, sub_prefix, amount, &key, &shielded, + ) +} From 615a245bb4e1d1d4a896852bb6a374f4c52ca4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 13:25:25 +0100 Subject: [PATCH 36/43] storage_api/lazy_map: export `get_data_key` fn for client --- core/src/ledger/storage_api/collections/lazy_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 81ddd7f42d..ec4a958002 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -334,7 +334,7 @@ where } /// Get the sub-key of a given element - fn get_data_key(&self, key: &K) -> storage::Key { + pub fn get_data_key(&self, key: &K) -> storage::Key { let key_str = key.to_db_key(); self.get_data_prefix().push(&key_str).unwrap() } From 094e63e7214d1551886d65bb7123d900cf4347cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 13:25:43 +0100 Subject: [PATCH 37/43] ledger/queries/vp/client: add method to get faucet PoW challenge --- shared/src/ledger/queries/vp/mod.rs | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index a4de26e20b..04d5a92dcc 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -7,3 +7,88 @@ mod pos; router! {VP, ( "pos" ) = (sub POS), } + +#[cfg(any(test, feature = "async-client"))] +pub mod client_only_methods { + use borsh::BorshDeserialize; + use namada_core::ledger::faucet_pow; + use namada_core::types::token; + + use super::Vp; + use crate::ledger::queries::{Client, RPC}; + use crate::types::address::Address; + + impl Vp { + /// Check if the given address is a faucet account address by checking + /// if it contains PoW `difficulty` field in its storage. + pub async fn is_faucet( + &self, + client: &CLIENT, + address: &Address, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let difficulty_key = &faucet_pow::difficulty_key(address); + RPC.shell().storage_has_key(client, difficulty_key).await + } + + /// Get a faucet PoW challenge for token withdrawal. + pub async fn faucet_pow_challenge( + &self, + client: &CLIENT, + transfer: token::Transfer, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let params = self + .faucet_pow_params(client, &transfer.source, &transfer.target) + .await?; + Ok(faucet_pow::Challenge { transfer, params }) + } + + /// Read faucet PoW challenge parameters for token withdrawal. + pub async fn faucet_pow_params( + &self, + client: &CLIENT, + faucet_address: &Address, + transfer_target: &Address, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let difficulty_key = &faucet_pow::difficulty_key(faucet_address); + let counter_key = &faucet_pow::counters_handle(faucet_address) + .get_data_key(transfer_target); + let difficulty = faucet_pow::Difficulty::try_from_slice( + &RPC.shell() + .storage_value(client, None, None, false, difficulty_key) + .await? + .data, + ) + .expect("Faucet PoW difficulty couldn't get read"); + let counter = if RPC + .shell() + .storage_has_key(client, counter_key) + .await? + { + faucet_pow::Counter::try_from_slice( + &RPC.shell() + .storage_value(client, None, None, false, counter_key) + .await? + .data, + ) + .expect("Faucet counter has unexpected encoding") + } else { + // `0` if not previously set (same as `faucet_pow::get_counter`) + faucet_pow::Counter::default() + }; + + Ok(faucet_pow::ChallengeParams { + difficulty, + counter, + }) + } + } +} From a0ba265809738b53df70ea85d887a26603f0017f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 13:42:03 +0100 Subject: [PATCH 38/43] client: get a PoW challenge solution for faucet withdrawal transfer --- apps/src/lib/client/rpc.rs | 21 ++++++++++++++++++++ apps/src/lib/client/tx.rs | 40 +++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3fb4d8eea8..85d61d0cda 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -22,6 +22,7 @@ use masp_primitives::primitives::ViewingKey; use masp_primitives::sapling::Node; use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada::core::ledger::faucet_pow; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; @@ -2005,6 +2006,26 @@ pub async fn known_address( } } +/// Check if the given address is a testnet faucet account address. +pub async fn is_faucet_account( + address: &Address, + ledger_address: TendermintAddress, +) -> bool { + let client = HttpClient::new(ledger_address).unwrap(); + unwrap_client_response(RPC.vp().is_faucet(&client, address).await) +} + +/// Obtain a PoW challenge for a withdrawal from a testnet faucet account. +pub async fn get_faucet_pow_challenge( + transfer: Transfer, + ledger_address: TendermintAddress, +) -> faucet_pow::Challenge { + let client = HttpClient::new(ledger_address).unwrap(); + unwrap_client_response( + RPC.vp().faucet_pow_challenge(&client, transfer).await, + ) +} + /// Accumulate slashes starting from `epoch_start` until (optionally) /// `withdraw_epoch` and apply them to the token amount `delta`. fn apply_slashes( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index bff1349732..2a4f323117 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -85,6 +85,8 @@ const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; +const TX_TESTNET_FAUCET_WITHDRAWAL_WASM: &str = + "tx_testnet_faucet_withdrawal.wasm"; const TX_IBC_WASM: &str = "tx_ibc.wasm"; const VP_USER_WASM: &str = "vp_user.wasm"; const TX_BOND_WASM: &str = "tx_bond.wasm"; @@ -1488,6 +1490,8 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { safe_exit(1) } } + let is_source_faucet = + rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; // Check that the target address exists on chain let target_exists = rpc::known_address(&target, args.tx.ledger_address.clone()).await; @@ -1552,7 +1556,6 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { } }; - let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); let masp_addr = masp(); // For MASP sources, use a special sentinel key recognized by VPs as default // signer. Also, if the transaction is shielded, redact the amount and token @@ -1606,6 +1609,15 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { if spending_key.is_none() && payment_address.is_none() { None } else { + if is_source_faucet { + eprintln!( + "Shielded withdrawals from faucet are not supported" + ); + if !args.tx.force { + safe_exit(1) + } + } + // We want to fund our transaction solely from supplied spending // key let spending_key = spending_key.map(|x| x.into()); @@ -1643,11 +1655,29 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { }, }; tracing::debug!("Transfer data {:?}", transfer); - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = if is_source_faucet { + // Obtain a PoW challenge for faucet withdrawal + let challenge = rpc::get_faucet_pow_challenge( + transfer, + args.tx.ledger_address.clone(), + ) + .await; + // Solve the solution, this blocks until a solution is found + let solution = challenge.solve(); + let data = solution + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + let tx_code = ctx.read_wasm(TX_TESTNET_FAUCET_WITHDRAWAL_WASM); + Tx::new(tx_code, Some(data)) + } else { + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); + Tx::new(tx_code, Some(data)) + }; + let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); process_tx(ctx, &args.tx, tx, signing_address).await; } From e75ec2d091f5a1df61f8ec1ecff350d9b436f72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 16:10:15 +0100 Subject: [PATCH 39/43] genesis: add `faucet_pow_difficulty` --- apps/src/lib/config/genesis.rs | 8 ++++++++ apps/src/lib/node/ledger/shell/init_chain.rs | 5 +++-- genesis/e2e-tests-single-node.toml | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index ebad28f311..90fd9f4546 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,6 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::core::ledger::faucet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -28,6 +29,7 @@ pub mod genesis_config { use data_encoding::HEXLOWER; use eyre::Context; + use namada::core::ledger::faucet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -109,6 +111,8 @@ pub mod genesis_config { // Name of the native token - this must one of the tokens included in // the `token` field pub native_token: String, + /// Faucet PoW difficulty - defaults to `0` when not set + pub faucet_pow_difficulty: Option, // Initial validator set pub validator: HashMap, // Token accounts present at genesis @@ -495,6 +499,7 @@ pub mod genesis_config { let GenesisConfig { genesis_time, native_token, + faucet_pow_difficulty, validator, token, established, @@ -630,6 +635,7 @@ pub mod genesis_config { let mut genesis = Genesis { genesis_time: genesis_time.try_into().unwrap(), native_token, + faucet_pow_difficulty, validators: validators.into_values().collect(), token_accounts, established_accounts: established_accounts.into_values().collect(), @@ -678,6 +684,7 @@ pub mod genesis_config { pub struct Genesis { pub genesis_time: DateTimeUtc, pub native_token: Address, + pub faucet_pow_difficulty: Option, pub validators: Vec, pub token_accounts: Vec, pub established_accounts: Vec, @@ -952,6 +959,7 @@ pub fn genesis() -> Genesis { pos_params: PosParams::default(), gov_params: GovParams::default(), native_token: address::nam(), + faucet_pow_difficulty: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 2f9492d429..0eae9dfb24 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -175,11 +175,12 @@ where // When using a faucet WASM, initialize its PoW challenge storage if vp_code_path == "vp_testnet_faucet.wasm" { + let difficulty = + genesis.faucet_pow_difficulty.unwrap_or_default(); faucet_pow::init_faucet_storage( &mut self.storage, &address, - faucet_pow::Difficulty::try_new(5) - .expect("Difficulty 5 is valid"), + difficulty, ) .expect("Couldn't init faucet storage") } diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 554dfd57b1..a9c895768b 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -4,6 +4,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" +faucet_pow_difficulty = 1 [validator.validator-0] # Validator's staked NAM at genesis. From f79616955ed296accaf2f94ac2273ed2fe8478c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 16:10:45 +0100 Subject: [PATCH 40/43] test/e2e: add a case to withdraw from PoW faucet --- tests/src/e2e/ledger_tests.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 6394e79c3d..2ce8f3d11a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -271,8 +271,10 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 3. Submit a transaction to update an account's validity predicate /// 4. Submit a custom tx /// 5. Submit a tx to initialize a new account -/// 6. Query token balance -/// 7. Query the raw bytes of a storage key +/// 6. Submit a tx to withdraw from faucet account (requires PoW challenge +/// solution) +/// 7. Query token balance +/// 8. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; @@ -388,6 +390,24 @@ fn ledger_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], + // 6. Submit a tx to withdraw from faucet account (requires PoW challenge + // solution) + vec![ + "transfer", + "--source", + "faucet", + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + // Faucet withdrawal requires an explicit signer + "--signer", + ALBERT, + "--ledger-address", + &validator_one_rpc, + ], ]; for tx_args in &txs_args { @@ -409,7 +429,7 @@ fn ledger_txs_and_queries() -> Result<()> { } let query_args_and_expected_response = vec![ - // 6. Query token balance + // 7. Query token balance ( vec![ "balance", @@ -436,7 +456,7 @@ fn ledger_txs_and_queries() -> Result<()> { let nam = find_address(&test, NAM)?; let storage_key = token::balance_key(&nam, &christel).to_string(); let query_args_and_expected_response = vec![ - // 7. Query storage key and get hex-encoded raw bytes + // 8. Query storage key and get hex-encoded raw bytes ( vec![ "query-bytes", From 0d521e034c1955bdcfa0563685cada98b8882b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 26 Dec 2022 16:12:20 +0100 Subject: [PATCH 41/43] changelog: add #961 --- .changelog/unreleased/improvements/961-faucet-pow.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/961-faucet-pow.md diff --git a/.changelog/unreleased/improvements/961-faucet-pow.md b/.changelog/unreleased/improvements/961-faucet-pow.md new file mode 100644 index 0000000000..a2168c65d4 --- /dev/null +++ b/.changelog/unreleased/improvements/961-faucet-pow.md @@ -0,0 +1,2 @@ +- Added PoW challenge to testnet faucet withdrawal. + ([#961](https://github.com/anoma/namada/pull/961)) \ No newline at end of file From cd8b45e9b83a58b13ae6f2efb725661d7a6f9879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 27 Dec 2022 18:55:59 +0100 Subject: [PATCH 42/43] wasm/vp_testnet_faucet/test: update tests for PoW challenge --- wasm/wasm_source/src/vp_testnet_faucet.rs | 47 ++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index f5532b55df..49686a6ee3 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -189,7 +189,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; - use namada_vp_prelude::key::RefTo; + use namada_vp_prelude::key::{RefTo, SigScheme}; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -358,7 +358,11 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); + // Init the VP let vp_owner = address::testing::established_address_1(); + let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let target = address::testing::established_address_2(); let token = address::nam(); let amount = token::Amount::from(amount); @@ -385,14 +389,20 @@ mod tests { assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } - /// Test that a debit of less than or equal to [`MAX_FREE_DEBIT`] tokens without a valid signature is accepted. + /// Test that a debit of less than or equal to [`MAX_FREE_DEBIT`] tokens + /// without a valid signature but with a valid PoW solution is accepted. #[test] fn test_unsigned_debit_under_limit_accepted(amount in (..MAX_FREE_DEBIT as u64 + 1)) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); + // Init the VP let vp_owner = address::testing::established_address_1(); + let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let target = address::testing::established_address_2(); + let target_key = key::testing::keypair_1(); let token = address::nam(); let amount = token::Amount::from(amount); @@ -403,14 +413,37 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // Construct a PoW solution like a client would + let transfer = token::Transfer { + source: vp_owner.clone(), + target: target.clone(), + token: token.clone(), + sub_prefix: None, + amount, + key: None, + shielded: None, + }; + let challenge = faucet_pow::Challenge::new(&mut tx_env.storage, &vp_owner, transfer).unwrap(); + let solution = challenge.solve(); + let solution_bytes = solution.try_to_vec().unwrap(); + // The signature itself doesn't matter and is not being checked in this + // test, it's just used to construct `SignedTxData` + let sig = key::common::SigScheme::sign(&target_key, &solution_bytes); + let signed_solution = SignedTxData { + data: Some(solution_bytes), + sig, + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None).unwrap(); + // Do same as `tx_testnet_faucet_withdrawal` + solution.apply_from_tx(tx::ctx()).unwrap(); + // Apply transfer in a transaction + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None).unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let tx_data: Vec = signed_solution.try_to_vec().unwrap(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -429,6 +462,10 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); + // Init the VP + let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to(); From c7c2b7df74eb4a8508a2ca117a7357abc98ad183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 28 Dec 2022 14:20:51 +0100 Subject: [PATCH 43/43] vp_testnet_faucet: R/W withdrawal limit from storage, add to genesis --- apps/src/lib/config/genesis.rs | 6 ++ apps/src/lib/node/ledger/shell/init_chain.rs | 5 ++ core/src/ledger/faucet_pow.rs | 64 +++++++++++++++++++- genesis/e2e-tests-single-node.toml | 1 + wasm/wasm_source/src/vp_testnet_faucet.rs | 27 ++++++--- 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 90fd9f4546..4ac5b069ab 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -113,6 +113,8 @@ pub mod genesis_config { pub native_token: String, /// Faucet PoW difficulty - defaults to `0` when not set pub faucet_pow_difficulty: Option, + /// Faucet withdrawal limit - defaults to 1000 NAM when not set + pub faucet_withdrawal_limit: Option, // Initial validator set pub validator: HashMap, // Token accounts present at genesis @@ -500,6 +502,7 @@ pub mod genesis_config { genesis_time, native_token, faucet_pow_difficulty, + faucet_withdrawal_limit, validator, token, established, @@ -636,6 +639,7 @@ pub mod genesis_config { genesis_time: genesis_time.try_into().unwrap(), native_token, faucet_pow_difficulty, + faucet_withdrawal_limit, validators: validators.into_values().collect(), token_accounts, established_accounts: established_accounts.into_values().collect(), @@ -685,6 +689,7 @@ pub struct Genesis { pub genesis_time: DateTimeUtc, pub native_token: Address, pub faucet_pow_difficulty: Option, + pub faucet_withdrawal_limit: Option, pub validators: Vec, pub token_accounts: Vec, pub established_accounts: Vec, @@ -960,6 +965,7 @@ pub fn genesis() -> Genesis { gov_params: GovParams::default(), native_token: address::nam(), faucet_pow_difficulty: None, + faucet_withdrawal_limit: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 0eae9dfb24..576a9517b7 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -177,10 +177,15 @@ where if vp_code_path == "vp_testnet_faucet.wasm" { let difficulty = genesis.faucet_pow_difficulty.unwrap_or_default(); + // withdrawal limit defaults to 1000 NAM when not set + let withdrawal_limit = genesis + .faucet_withdrawal_limit + .unwrap_or_else(|| token::Amount::whole(1_000)); faucet_pow::init_faucet_storage( &mut self.storage, &address, difficulty, + withdrawal_limit, ) .expect("Couldn't init faucet storage") } diff --git a/core/src/ledger/faucet_pow.rs b/core/src/ledger/faucet_pow.rs index 8f8d92276b..44086a0413 100644 --- a/core/src/ledger/faucet_pow.rs +++ b/core/src/ledger/faucet_pow.rs @@ -22,11 +22,13 @@ pub fn init_faucet_storage( storage: &mut S, address: &Address, difficulty: Difficulty, + withdrawal_limit: token::Amount, ) -> storage_api::Result<()> where S: StorageWrite, { - write_difficulty(storage, address, difficulty) + write_difficulty(storage, address, difficulty)?; + write_withdrawal_limit(storage, address, withdrawal_limit) } /// Counters are associated with transfer target addresses. @@ -262,6 +264,8 @@ pub struct Keys { counters: &'static str, /// PoW difficulty difficulty: &'static str, + /// withdrawal limit + withdrawal_limit: &'static str, } /// Storage key prefix to the `counters` field. The rest of the key is composed @@ -317,6 +321,30 @@ pub fn is_difficulty_key(key: &storage::Key, faucet_address: &Address) -> bool { ) } +/// Storage key to the `withdrawal_limit` field. +pub fn withdrawal_limit_key(address: &Address) -> storage::Key { + storage::Key { + segments: vec![ + DbKeySeg::AddressSeg(address.clone()), + DbKeySeg::StringSeg(Keys::VALUES.withdrawal_limit.to_string()), + ], + } +} + +/// Is the storage key for the `withdrawal_limit` field? +pub fn is_withdrawal_limit_key( + key: &storage::Key, + faucet_address: &Address, +) -> bool { + matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(address), + DbKeySeg::StringSeg(sub_key), + ] if address == faucet_address && sub_key.as_str() == Keys::VALUES.withdrawal_limit, + ) +} + /// Read faucet's counter value for a given target address. pub fn get_counter( storage: &S, @@ -412,6 +440,32 @@ where storage.write(&difficulty_key(address), difficulty) } +/// Read the withdrawal limit. +pub fn read_withdrawal_limit( + storage: &S, + address: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let withdrawal_limit = storage + .read(&withdrawal_limit_key(address))? + .expect("withdrawal_limit must always be set"); + Ok(withdrawal_limit) +} + +/// Write faucet withdrawal limit +pub fn write_withdrawal_limit( + storage: &mut S, + address: &Address, + withdrawal_limit: token::Amount, +) -> Result<(), storage_api::Error> +where + S: StorageWrite, +{ + storage.write(&withdrawal_limit_key(address), withdrawal_limit) +} + #[cfg(test)] mod test { use super::*; @@ -444,6 +498,7 @@ mod test_with_tx_and_vp_env { fn test_challenge_and_solution() -> storage_api::Result<()> { let faucet_address = address::testing::established_address_1(); let difficulty = Difficulty::try_new(1).unwrap(); + let withdrawal_limit = token::Amount::whole(1_000); let mut tx_env = TestTxEnv::default(); @@ -454,7 +509,12 @@ mod test_with_tx_and_vp_env { // Ensure that the addresses exists, so we can use them in a tx tx_env.spawn_accounts([&faucet_address, &target, &token]); - init_faucet_storage(&mut tx_env.storage, &faucet_address, difficulty)?; + init_faucet_storage( + &mut tx_env.storage, + &faucet_address, + difficulty, + withdrawal_limit, + )?; let transfer = token::Transfer { source: faucet_address.clone(), diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index a9c895768b..2ed4fd00ff 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -5,6 +5,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" faucet_pow_difficulty = 1 +faucet_withdrawal_limit = 1_000 [validator.validator-0] # Validator's staked NAM at genesis. diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 49686a6ee3..e976b8e434 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -1,16 +1,14 @@ //! A "faucet" account for testnet. //! -//! This VP allows anyone to withdraw up to [`MAX_FREE_DEBIT`] tokens without -//! the faucet's signature. +//! This VP allows anyone to withdraw up to +//! [`faucet_pow::read_withdrawal_limit`] tokens without the faucet's signature, +//! but with a valid PoW challenge solution that cannot be replayed. //! //! Any other storage key changes are allowed only with a valid signature. use namada_vp_prelude::*; use once_cell::unsync::Lazy; -/// Allows anyone to withdraw up to 1_000 tokens in a single tx -pub const MAX_FREE_DEBIT: i128 = 1_000_000_000; // in micro units - #[validity_predicate] fn validate_tx( ctx: &Ctx, @@ -81,7 +79,12 @@ fn validate_tx( Some(solution) => { // Validate it if solution.validate(ctx, &addr)? { - change >= -MAX_FREE_DEBIT + let max_free_debit = + faucet_pow::read_withdrawal_limit( + &ctx.pre(), + &addr, + )?; + change >= -max_free_debit.change() } else { debug_log!("Invalid PoW solution"); false @@ -198,6 +201,9 @@ mod tests { const VP_ALWAYS_TRUE_WASM: &str = "../../wasm_for_tests/vp_always_true.wasm"; + /// Allows anyone to withdraw up to 1_000 tokens in a single tx + pub const MAX_FREE_DEBIT: i128 = 1_000_000_000; // in micro units + /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -361,7 +367,8 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); - faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let token = address::nam(); @@ -399,7 +406,8 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); - faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); @@ -464,7 +472,8 @@ mod tests { // Init the VP let difficulty = faucet_pow::Difficulty::try_new(0).unwrap(); - faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty).unwrap(); + let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + faucet_pow::init_faucet_storage(&mut tx_env.storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to();