From be34b90b45353572c4561bf992a7ea8ed37b6b03 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 12 Feb 2024 16:33:13 +0100 Subject: [PATCH 01/96] add e2e test --- crates/test_utils/src/lib.rs | 4 + crates/tests/src/e2e/ibc_tests.rs | 167 ++++++++++++++++++ crates/tx_prelude/src/ibc.rs | 2 +- .../tx_proposal_ibc_token_inflation.wasm | Bin 0 -> 477066 bytes wasm_for_tests/wasm_source/Cargo.toml | 1 + wasm_for_tests/wasm_source/Makefile | 1 + wasm_for_tests/wasm_source/src/lib.rs | 47 +++++ 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100755 wasm_for_tests/tx_proposal_ibc_token_inflation.wasm diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b0872c600f..d2b27cf009 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -28,6 +28,7 @@ pub enum TestWasms { VpMemoryLimit, VpReadStorageKey, TxProposalMaspRewards, + TxProposalIbcTokenInflation, } impl TestWasms { @@ -46,6 +47,9 @@ impl TestWasms { TestWasms::VpMemoryLimit => "vp_memory_limit.wasm", TestWasms::VpReadStorageKey => "vp_read_storage_key.wasm", TestWasms::TxProposalMaspRewards => "tx_proposal_masp_reward.wasm", + TestWasms::TxProposalIbcTokenInflation => { + "tx_proposal_ibc_token_inflation.wasm" + } }; let cwd = env::current_dir().expect("Couldn't get current working directory"); diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 40968383a0..9f2e6b32f2 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -80,6 +80,7 @@ use namada_apps::facade::tendermint::merkle::proof::ProofOps as TmProof; use namada_apps::facade::tendermint_rpc::{Client, HttpClient, Url}; use namada_core::types::string_encoding::StringEncoded; use namada_sdk::masp::fs::FsShieldedUtils; +use namada_test_utils::TestWasms; use prost::Message; use setup::constants::*; use tendermint_light_client::components::io::{Io, ProdIo as TmLightClientIo}; @@ -357,6 +358,117 @@ fn pgf_over_ibc_with_hermes() -> Result<()> { Ok(()) } +#[test] +fn proposal_ibc_token_inflation() -> Result<()> { + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(10); + // for the trusting period of IBC client + genesis.parameters.pos_params.pipeline_len = 10; + genesis.parameters.parameters.max_proposal_bytes = + Default::default(); + genesis.parameters.pgf_params.stewards = + BTreeSet::from_iter([get_established_addr_from_pregenesis( + ALBERT_KEY, base_dir, &genesis, + ) + .unwrap()]); + setup::set_validators(1, genesis, base_dir, |_| 0) + }; + let (ledger_a, ledger_b, test_a, test_b) = run_two_nets(update_genesis)?; + let _bg_ledger_a = ledger_a.background(); + let _bg_ledger_b = ledger_b.background(); + + setup_hermes(&test_a, &test_b)?; + let port_id_a = "transfer".parse().unwrap(); + let port_id_b: PortId = "transfer".parse().unwrap(); + let (channel_id_a, channel_id_b) = + create_channel_with_hermes(&test_a, &test_b)?; + + // Start relaying + let hermes = run_hermes(&test_a)?; + let _bg_hermes = hermes.background(); + + // Get masp proof for the following IBC transfer from the destination chain + // It will send 10000 APFEL to PA(B) on Chain B + let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); + // Chain B will receive Chain A's APFEL + std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); + let output_folder = test_b.test_dir.path().to_string_lossy(); + // PA(B) on Chain B will receive APFEL on chain A + let token_addr = find_address(&test_a, APFEL)?; + let args = [ + "ibc-gen-shielded", + "--output-folder-path", + &output_folder, + "--target", + AB_PAYMENT_ADDRESS, + "--token", + &token_addr.to_string(), + "--amount", + "10000", + "--port-id", + port_id_b.as_ref(), + "--channel-id", + channel_id_b.as_ref(), + "--node", + &rpc_b, + ]; + std::env::set_var(ENV_VAR_CHAIN_ID, test_b.net.chain_id.to_string()); + let mut client = run!(test_b, Bin::Client, args, Some(120))?; + let file_path = get_shielded_transfer_path(&mut client)?; + client.assert_success(); + + // Transfer 10000 from Chain A to a z-address on Chain B + transfer( + &test_a, + ALBERT, + AB_PAYMENT_ADDRESS, + APFEL, + "10000", + ALBERT_KEY, + &port_id_a, + &channel_id_a, + Some(&file_path.to_string_lossy()), + None, + None, + false, + )?; + wait_for_packet_relay(&port_id_a, &channel_id_a, &test_a)?; + + // Proposal on Chain B + // Delegate some token + delegate_token(&test_b)?; + let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); + let delegated = epoch + 10u64; + while epoch <= delegated { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + // inflation proposal on Chain B + let start_epoch = propose_inflation(&test_b)?; + let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); + // Vote + while epoch <= start_epoch { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + submit_votes(&test_b)?; + + // wait for the grace + let grace_epoch = start_epoch + 12u64 + 6u64; + while epoch <= grace_epoch { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + sleep(5); + + // Check balances after funding over IBC + check_inflated_balance(&port_id_b, &channel_id_b, &test_b)?; + + Ok(()) +} + fn run_two_nets( update_genesis: impl FnMut( templates::All, @@ -1602,6 +1714,36 @@ fn propose_funding( Ok(start_epoch.into()) } +fn propose_inflation(test: &Test) -> Result { + std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); + let albert = find_address(test, ALBERT)?; + let rpc = get_actor_rpc(test, Who::Validator(0)); + let epoch = get_epoch(test, &rpc)?; + let start_epoch = (epoch.0 + 6) / 3 * 3; + let proposal_json_path = prepare_proposal_data( + test, + 0, + albert, + TestWasms::TxProposalIbcTokenInflation.read_bytes(), + start_epoch, + ); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + proposal_json_path.to_str().unwrap(), + "--gas-limit", + "2000000", + "--node", + &rpc, + ]; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string(TX_ACCEPTED)?; + client.exp_string(TX_APPLIED_SUCCESS)?; + client.assert_success(); + Ok(start_epoch.into()) +} + fn submit_votes(test: &Test) -> Result<()> { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); @@ -1963,6 +2105,31 @@ fn check_funded_balances( Ok(()) } +fn check_inflated_balance( + dest_port_id: &PortId, + dest_channel_id: &ChannelId, + test: &Test, +) -> Result<()> { + std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); + let ibc_denom = format!("{dest_port_id}/{dest_channel_id}/apfel"); + let rpc = get_actor_rpc(test, Who::Validator(0)); + let query_args = vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + &ibc_denom, + "--node", + &rpc, + ]; + let expected = format!("{ibc_denom}: 10010"); + let mut client = run!(test, Bin::Client, query_args, Some(40))?; + client.exp_string(&expected)?; + client.assert_success(); + + Ok(()) +} + fn signer() -> Signer { "signer".to_string().into() } diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 0857875382..1bcaffd97a 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::{IbcEvent, IbcShieldedTransfer}; use namada_core::types::token::DenominatedAmount; -pub use namada_ibc::storage::is_ibc_key; +pub use namada_ibc::storage::{ibc_token, is_ibc_key}; pub use namada_ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, TransferModule, }; diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm new file mode 100755 index 0000000000000000000000000000000000000000..d95ecf17d5fc153be41ab95cb6645b3ac79c175d GIT binary patch literal 477066 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≻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 literal 0 HcmV?d00001 diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index b5d80a65e5..de0a265d5a 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -24,6 +24,7 @@ vp_memory_limit = [] vp_read_storage_key = [] tx_proposal_code = [] tx_proposal_masp_reward = [] +tx_proposal_ibc_token_inflation = [] [dependencies] namada_test_utils = {path = "../../crates/test_utils"} diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index c8783dc001..2c113ce0f3 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -17,6 +17,7 @@ wasms += vp_memory_limit wasms += vp_read_storage_key wasms += tx_proposal_code wasms += tx_proposal_masp_reward +wasms += tx_proposal_ibc_token_inflation # Build all wasms diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 1345a5634c..30abb530c6 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -75,6 +75,53 @@ pub mod main { } } +/// A tx to be used as proposal_code to set the inflation params for a token +/// over IBC +#[cfg(feature = "tx_proposal_ibc_token_inflation")] +pub mod main { + use std::str::FromStr; + + use dec::Dec; + use namada_tx_prelude::*; + + // Denom of tokens over IBC is always zero + const IBC_TOKEN_DENOM: u8 = 0; + const CHANNEL_ID: &str = "channel-0"; + const BASE_TOKEN: &str = "tnam1qyvfwdkz8zgs9n3qn9xhp8scyf8crrxwuq26r6gy"; + + #[transaction(gas = 1000)] + fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { + let ibc_token = + ibc::ibc_token(format!("transfer/{CHANNEL_ID}/{BASE_TOKEN}")); + let shielded_token_max_rewards_key = + token::storage_key::masp_max_reward_rate_key(&ibc_token); + let shielded_token_target_locked_amount_key = + token::storage_key::masp_locked_amount_target_key(&ibc_token); + let shielded_token_kp_gain_key = + token::storage_key::masp_kp_gain_key(&ibc_token); + let shielded_token_kd_gain_key = + token::storage_key::masp_kd_gain_key(&ibc_token); + + ctx.write( + &shielded_token_max_rewards_key, + Dec::from_str("0.01").unwrap(), + )?; + ctx.write( + &shielded_token_target_locked_amount_key, + token::Amount::from_uint(100_000, IBC_TOKEN_DENOM).unwrap(), + )?; + ctx.write( + &shielded_token_kp_gain_key, + Dec::from_str("120000").unwrap(), + )?; + ctx.write( + &shielded_token_kd_gain_key, + Dec::from_str("120000").unwrap(), + )?; + Ok(()) + } +} + /// A tx that attempts to read the given key from storage. #[cfg(feature = "tx_read_storage_key")] pub mod main { From d64f985c9b6b0fefdf6bf8b4a3033b28b08ce61e Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 13 Feb 2024 01:08:12 +0100 Subject: [PATCH 02/96] move token/asset maps from conversion_state back to storage --- .../src/lib/node/ledger/shell/init_chain.rs | 13 +-- .../src/lib/node/ledger/storage/rocksdb.rs | 14 +-- crates/core/src/types/masp.rs | 25 ++++++ crates/core/src/types/token.rs | 18 +--- crates/namada/src/ledger/native_vp/masp.rs | 45 ++++------ crates/sdk/src/queries/shell.rs | 62 +++++++------ crates/shielded_token/src/conversion.rs | 84 ++++++++++-------- crates/shielded_token/src/storage_key.rs | 18 ++++ crates/tests/src/e2e/ibc_tests.rs | 4 +- crates/tests/src/integration/masp.rs | 60 ++++++++++--- .../tx_proposal_ibc_token_inflation.wasm | Bin 477066 -> 494975 bytes wasm_for_tests/wasm_source/src/lib.rs | 22 ++++- 12 files changed, 225 insertions(+), 140 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index a881171b51..19da9f29d3 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -1,5 +1,5 @@ //! Implementation of chain initialization for the Shell -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::ops::ControlFlow; use masp_primitives::merkle_tree::CommitmentTree; @@ -10,6 +10,7 @@ use namada::ledger::parameters::Parameters; use namada::ledger::{ibc, pos}; use namada::proof_of_stake::BecomeValidator; use namada::state::{DBIter, StorageHasher, StorageWrite, DB}; +use namada::token::storage_key::masp_token_map_key; use namada::token::{credit_tokens, write_denom}; use namada::types::address::Address; use namada::types::hash::Hash as CodeHash; @@ -422,6 +423,7 @@ where /// Init genesis token accounts fn init_token_accounts(&mut self, genesis: &genesis::chain::Finalized) { + let mut token_map = BTreeMap::new(); for (alias, token) in &genesis.tokens.token { tracing::debug!("Initializing token {alias}"); @@ -442,13 +444,12 @@ where // add token addresses to the masp reward conversions lookup // table. let alias = alias.to_string(); - self.wl_storage - .storage - .conversion_state - .tokens - .insert(alias, address.clone()); + token_map.insert(alias, address.clone()); } } + self.wl_storage + .write(&masp_token_map_key(), token_map) + .expect("Couldn't init token accounts"); } /// Init genesis token balances diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index c708605ad7..b5b989be85 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1815,9 +1815,7 @@ mod imp { #[cfg(test)] mod test { use namada::state::{MerkleTree, Sha256Hasher}; - use namada::types::address::{ - gen_established_address, EstablishedAddressGen, - }; + use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; use test_log::test; @@ -2027,10 +2025,7 @@ mod test { let height_0 = BlockHeight(100); let mut pred_epochs = Epochs::default(); pred_epochs.new_epoch(height_0); - let mut conversion_state_0 = ConversionState::default(); - conversion_state_0 - .tokens - .insert("dummy1".to_string(), gen_established_address("test")); + let conversion_state_0 = ConversionState::default(); let to_delete_val = vec![1_u8, 1, 0, 0]; let to_overwrite_val = vec![1_u8, 1, 1, 0]; db.batch_write_subspace_val( @@ -2065,10 +2060,7 @@ mod test { let mut batch = RocksDB::batch(); let height_1 = BlockHeight(101); pred_epochs.new_epoch(height_1); - let mut conversion_state_1 = ConversionState::default(); - conversion_state_1 - .tokens - .insert("dummy2".to_string(), gen_established_address("test")); + let conversion_state_1 = ConversionState::default(); let add_val = vec![1_u8, 0, 0, 0]; let overwrite_val = vec![1_u8, 1, 1, 1]; db.batch_write_subspace_val( diff --git a/crates/core/src/types/masp.rs b/crates/core/src/types/masp.rs index 64b18eb6b9..c2760609c6 100644 --- a/crates/core/src/types/masp.rs +++ b/crates/core/src/types/masp.rs @@ -1,11 +1,13 @@ //! MASP types +use std::collections::BTreeMap; use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; +use masp_primitives::convert::AllowedConversion; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -89,6 +91,29 @@ pub fn encode_asset_type( .encode() } +/// MASP asset with the position in Merkle tree +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +pub struct AssetDataWithTreePos { + /// The token associated with this asset type + pub token: Address, + /// The denomination associated with the above toke + pub denom: Denomination, + /// The digit position covered by this asset type + pub pos: MaspDigitPos, + /// The epoch of the asset type + pub epoch: Epoch, + /// The allowed conversion + pub conv: AllowedConversion, + /// The position in Merkle tree + pub tree_pos: u64, +} + +/// MASP token map +pub type TokenMap = BTreeMap; + +/// MASP asset map +pub type AssetMap = BTreeMap; + // enough capacity to store the payment address // plus the pinned/unpinned discriminant const PAYMENT_ADDRESS_SIZE: usize = 43 + 1; diff --git a/crates/core/src/types/token.rs b/crates/core/src/types/token.rs index 02370b9854..d7c608e510 100644 --- a/crates/core/src/types/token.rs +++ b/crates/core/src/types/token.rs @@ -1,7 +1,6 @@ //! A basic fungible token use std::cmp::Ordering; -use std::collections::BTreeMap; use std::fmt::Display; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; @@ -10,8 +9,6 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use ethabi::ethereum_types::U256; -use masp_primitives::asset_type::AssetType; -use masp_primitives::convert::AllowedConversion; use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling; use serde::{Deserialize, Serialize}; @@ -22,7 +19,7 @@ use crate::types::address::Address; use crate::types::dec::{Dec, POS_DECIMAL_PRECISION}; use crate::types::hash::Hash; use crate::types::storage; -use crate::types::storage::{DbKeySeg, Epoch, KeySeg}; +use crate::types::storage::{DbKeySeg, KeySeg}; use crate::types::uint::{self, Uint, I256}; /// A representation of the conversion state @@ -32,19 +29,6 @@ pub struct ConversionState { pub normed_inflation: Option, /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, - /// A map from token alias to actual address. - pub tokens: BTreeMap, - /// Map assets to their latest conversion and position in Merkle tree - #[allow(clippy::type_complexity)] - pub assets: BTreeMap< - AssetType, - ( - (Address, Denomination, MaspDigitPos), - Epoch, - AllowedConversion, - usize, - ), - >, } /// Amount in micro units. For different granularity another representation diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 7c3f3dee5c..014b42a467 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -11,7 +11,7 @@ use masp_primitives::transaction::components::I128Sum; use masp_primitives::transaction::Transaction; use namada_core::types::address::Address; use namada_core::types::address::InternalAddress::Masp; -use namada_core::types::masp::encode_asset_type; +use namada_core::types::masp::{encode_asset_type, AssetMap}; use namada_core::types::storage::{IndexedTx, Key}; use namada_gas::MASP_VERIFY_SHIELDED_TX_GAS; use namada_sdk::masp::verify_shielded_tx; @@ -25,7 +25,7 @@ use sha2::Digest as Sha2Digest; use thiserror::Error; use token::storage_key::{ balance_key, is_any_shielded_action_balance_key, is_masp_allowed_key, - is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, + is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, masp_asset_map_key, masp_commitment_anchor_key, masp_commitment_tree_key, masp_convert_anchor_key, masp_nullifier_key, }; @@ -413,7 +413,9 @@ where _verifiers: &BTreeSet

, ) -> Result { let epoch = self.ctx.get_block_epoch()?; - let conversion_state = self.ctx.storage.get_conversion_state(); + let asset_map_key = masp_asset_map_key(); + let asset_map: AssetMap = + self.ctx.read_pre(&asset_map_key)?.unwrap_or_default(); let shielded_tx = self.ctx.get_shielded_action(tx_data)?; if u64::from(self.ctx.get_block_height()?) @@ -484,24 +486,20 @@ where ); return Ok(false); } - match conversion_state.assets.get(&vin.asset_type) { + match asset_map.get(&vin.asset_type) { // Satisfies 2. Note how the asset's epoch must be equal to // the present: users must never be allowed to backdate // transparent inputs to a transaction for they would then // be able to claim rewards while locking their assets for // negligible time periods. - Some(( - (address, asset_denom, digit), - asset_epoch, - _, - _, - )) if *address == transfer.token - && *asset_denom == denom - && *asset_epoch == epoch => + Some(asset) + if asset.token == transfer.token + && asset.denom == denom + && asset.epoch == epoch => { total_in_values = total_in_values .checked_add(token::Amount::from_masp_denominated( - vin.value, *digit, + vin.value, asset.pos, )) .ok_or_else(|| { Error::NativeVpError( @@ -524,10 +522,7 @@ where Some(epoch), ) .wrap_err("unable to create asset type")?; - if conversion_state - .assets - .contains_key(&epoched_asset_type) - { + if asset_map.contains_key(&epoched_asset_type) { // If such an epoched asset type is available in the // conversion tree, then we must reject the // unepoched variant @@ -635,20 +630,16 @@ where ); return Ok(false); } - match conversion_state.assets.get(&out.asset_type) { + match asset_map.get(&out.asset_type) { // Satisfies 2. - Some(( - (address, asset_denom, digit), - asset_epoch, - _, - _, - )) if *address == transfer.token - && *asset_denom == denom - && *asset_epoch <= epoch => + Some(asset) + if asset.token == transfer.token + && asset.denom == denom + && asset.epoch <= epoch => { total_out_values = total_out_values .checked_add(token::Amount::from_masp_denominated( - out.value, *digit, + out.value, asset.pos, )) .ok_or_else(|| { Error::NativeVpError( diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index ea885ee4a6..fbb8b7771e 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -12,6 +12,7 @@ use namada_core::hints; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; +use namada_core::types::masp::{AssetMap, TokenMap}; use namada_core::types::storage::{ self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, }; @@ -19,6 +20,7 @@ use namada_core::types::token::{Denomination, MaspDigitPos}; use namada_core::types::uint::Uint; use namada_state::{DBIter, LastBlock, StorageHasher, DB}; use namada_storage::{self, ResultExt, StorageRead}; +use namada_token::storage_key::{masp_asset_map_key, masp_token_map_key}; #[cfg(any(test, feature = "async-client"))] use namada_tx::data::TxResult; @@ -175,20 +177,23 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - Ok(ctx - .wl_storage - .storage - .conversion_state - .assets + let asset_map_key = masp_asset_map_key(); + let asset_map: AssetMap = + ctx.wl_storage.read(&asset_map_key)?.unwrap_or_default(); + Ok(asset_map .iter() - .map( - |(&asset_type, ((ref addr, denom, digit), epoch, ref conv, _))| { + .map(|(&asset_type, asset)| { + ( + asset_type, ( - asset_type, - (addr.clone(), *denom, *digit, *epoch, conv.clone().into()), - ) - }, - ) + asset.token.clone(), + asset.denom, + asset.pos, + asset.epoch, + asset.conv.clone().into(), + ), + ) + }) .collect()) } @@ -202,22 +207,23 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some(((addr, denom, digit), epoch, conv, pos)) = ctx - .wl_storage - .storage - .conversion_state - .assets - .get(&asset_type) - { + let asset_map_key = masp_asset_map_key(); + let asset_map: AssetMap = + ctx.wl_storage.read(&asset_map_key)?.unwrap_or_default(); + if let Some(asset) = asset_map.get(&asset_type) { Ok(Some(( - addr.clone(), - *denom, - *digit, - *epoch, + asset.token.clone(), + asset.denom, + asset.pos, + asset.epoch, Into::::into( - conv.clone(), + asset.conv.clone(), ), - ctx.wl_storage.storage.conversion_state.tree.path(*pos), + ctx.wl_storage + .storage + .conversion_state + .tree + .path(asset.pos as _), ))) } else { Ok(None) @@ -232,9 +238,11 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let tokens = ctx.wl_storage.storage.conversion_state.tokens.clone(); + let token_map_key = masp_token_map_key(); + let token_map: TokenMap = + ctx.wl_storage.read(&token_map_key)?.unwrap_or_default(); let mut data = Vec::::new(); - for (name, token) in tokens { + for (name, token) in token_map { let max_reward_rate = ctx .wl_storage .read::(&namada_token::storage_key::masp_max_reward_rate_key( diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 23db438d42..1daa2a80dc 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -5,6 +5,8 @@ use namada_core::ledger::inflation::{ }; use namada_core::types::address::{Address, MASP}; use namada_core::types::dec::Dec; +#[cfg(any(feature = "multicore", test))] +use namada_core::types::masp::{AssetDataWithTreePos, AssetMap, TokenMap}; use namada_core::types::uint::Uint; use namada_parameters as parameters; use namada_state::{DBIter, StorageHasher, WlStorage, DB}; @@ -12,6 +14,8 @@ use namada_storage::{StorageRead, StorageWrite}; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::{read_denom, Amount, DenominatedAmount, Denomination}; +#[cfg(any(feature = "multicore", test))] +use crate::storage_key::{masp_asset_map_key, masp_token_map_key}; use crate::storage_key::{ masp_kd_gain_key, masp_kp_gain_key, masp_last_inflation_key, masp_last_locked_amount_key, masp_locked_amount_target_key, @@ -212,13 +216,10 @@ where // The derived conversions will be placed in MASP address space let masp_addr = MASP; - let mut masp_reward_keys: Vec<_> = wl_storage - .storage - .conversion_state - .tokens - .values() - .cloned() - .collect(); + let token_map_key = masp_token_map_key(); + let token_map: TokenMap = + wl_storage.read(&token_map_key)?.unwrap_or_default(); + let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect(); let mut masp_reward_denoms = BTreeMap::new(); // Put the native rewards first because other inflation computations depend // on it @@ -278,6 +279,10 @@ where let ref_inflation = calculate_masp_rewards_precision(wl_storage, &native_token)?.0; + let asset_map_key = masp_asset_map_key(); + let mut asset_map: AssetMap = + wl_storage.read(&asset_map_key)?.unwrap_or_default(); + // Reward all tokens according to above reward rates for token in &masp_reward_keys { let (reward, denom) = calculate_masp_rewards(wl_storage, token)?; @@ -404,14 +409,16 @@ where } } // Add a conversion from the previous asset type - wl_storage.storage.conversion_state.assets.insert( + asset_map.insert( old_asset, - ( - (token.clone(), denom, digit), - wl_storage.storage.last_epoch, - MaspAmount::zero().into(), - 0, - ), + AssetDataWithTreePos { + token: token.clone(), + denom, + pos: digit, + epoch: wl_storage.storage.last_epoch, + conv: MaspAmount::zero().into(), + tree_pos: 0, + }, ); } } @@ -420,13 +427,7 @@ where // multiple cores let num_threads = rayon::current_num_threads(); // Put assets into vector to enable computation batching - let assets: Vec<_> = wl_storage - .storage - .conversion_state - .assets - .values_mut() - .enumerate() - .collect(); + let assets: Vec<_> = asset_map.values_mut().enumerate().collect(); // ceil(assets.len() / num_threads) let notes_per_thread_max = (assets.len() + num_threads - 1) / num_threads; // floor(assets.len() / num_threads) @@ -436,16 +437,20 @@ where .into_par_iter() .with_min_len(notes_per_thread_min) .with_max_len(notes_per_thread_max) - .map(|(idx, (asset, _epoch, conv, pos))| { - if let Some(current_conv) = current_convs.get(asset) { + .map(|(idx, asset)| { + if let Some(current_conv) = current_convs.get(&( + asset.token.clone(), + asset.denom, + asset.pos, + )) { // Use transitivity to update conversion - *conv += current_conv.clone(); + asset.conv += current_conv.clone(); } // Update conversion position to leaf we are about to create - *pos = idx; + asset.tree_pos = idx as u64; // The merkle tree need only provide the conversion commitment, // the remaining information is provided through the storage API - Node::new(conv.cmu().to_repr()) + Node::new(asset.conv.cmu().to_repr()) }) .collect(); @@ -501,17 +506,21 @@ where Some(wl_storage.storage.block.epoch), ) .into_storage_result()?; - wl_storage.storage.conversion_state.assets.insert( + asset_map.insert( new_asset, - ( - (addr.clone(), denom, digit), - wl_storage.storage.block.epoch, - MaspAmount::zero().into(), - wl_storage.storage.conversion_state.tree.size(), - ), + AssetDataWithTreePos { + token: addr.clone(), + denom, + pos: digit, + epoch: wl_storage.storage.block.epoch, + conv: MaspAmount::zero().into(), + tree_pos: wl_storage.storage.conversion_state.tree.size() + as u64, + }, ); } } + wl_storage.write(&asset_map_key, asset_map)?; Ok(()) } @@ -609,10 +618,11 @@ mod tests { .unwrap(); // Insert tokens into MASP conversion state - s.storage - .conversion_state - .tokens - .insert(alias.to_string(), token_addr.clone()); + let token_map_key = masp_token_map_key(); + let mut token_map: TokenMap = + s.read(&token_map_key).unwrap().unwrap_or_default(); + token_map.insert(alias.to_string(), token_addr.clone()); + s.write(&token_map_key, token_map).unwrap(); } } diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index e58ffe93a0..b13c89b8e3 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -17,6 +17,10 @@ pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; /// Key segment prefix for the convert anchor pub const MASP_CONVERT_ANCHOR_KEY: &str = "convert_anchor"; +/// The key for the token map +pub const MASP_TOKEN_MAP_KEY: &str = "tokens"; +/// The key for the asset map +pub const MASP_ASSET_MAP_KEY: &str = "assets"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -146,3 +150,17 @@ pub fn masp_convert_anchor_key() -> storage::Key { .push(&MASP_CONVERT_ANCHOR_KEY.to_owned()) .expect("Cannot obtain a storage key") } + +/// Get the key for the masp token map +pub fn masp_token_map_key() -> storage::Key { + storage::Key::from(address::MASP.to_db_key()) + .push(&MASP_TOKEN_MAP_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Get the key for the masp asset map +pub fn masp_asset_map_key() -> storage::Key { + storage::Key::from(address::MASP.to_db_key()) + .push(&MASP_ASSET_MAP_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 9f2e6b32f2..6622443565 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -463,7 +463,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { } sleep(5); - // Check balances after funding over IBC + // Check balances check_inflated_balance(&port_id_b, &channel_id_b, &test_b)?; Ok(()) @@ -2123,7 +2123,7 @@ fn check_inflated_balance( &rpc, ]; let expected = format!("{ibc_denom}: 10010"); - let mut client = run!(test, Bin::Client, query_args, Some(40))?; + let mut client = run!(test, Bin::Client, query_args, Some(100))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 040857603d..36d1a7926d 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -3,13 +3,15 @@ use std::str::FromStr; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::state::StorageWrite; +use namada::state::{StorageRead, StorageWrite}; +use namada::token::storage_key::masp_token_map_key; use namada::token::{self, DenominatedAmount}; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::node::NodeResults; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_apps::wallet::defaults::christel_keypair; use namada_core::types::dec::Dec; +use namada_core::types::masp::TokenMap; use namada_sdk::masp::fs::FsShieldedUtils; use test_log::test; @@ -2125,11 +2127,24 @@ fn dynamic_assets() -> Result<()> { let btc = BTC.to_lowercase(); let nam = NAM.to_lowercase(); + let token_map_key = masp_token_map_key(); let tokens = { // Only distribute rewards for NAM tokens - let storage = &mut node.shell.lock().unwrap().wl_storage.storage; - let tokens = storage.conversion_state.tokens.clone(); - storage.conversion_state.tokens.retain(|k, _v| *k == nam); + let mut tokens: TokenMap = node + .shell + .lock() + .unwrap() + .wl_storage + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + tokens.retain(|k, _v| *k == nam); + node.shell + .lock() + .unwrap() + .wl_storage + .write(&token_map_key, tokens.clone()) + .unwrap(); tokens }; // add necessary viewing keys to shielded context @@ -2215,11 +2230,21 @@ fn dynamic_assets() -> Result<()> { { // Start decoding and distributing shielded rewards for BTC in next // epoch - let storage = &mut node.shell.lock().unwrap().wl_storage.storage; - storage - .conversion_state - .tokens - .insert(btc.clone(), tokens[&btc].clone()); + let mut tokens: TokenMap = node + .shell + .lock() + .unwrap() + .wl_storage + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + tokens.insert(btc.clone(), tokens[&btc].clone()); + node.shell + .lock() + .unwrap() + .wl_storage + .write(&token_map_key, tokens) + .unwrap(); } // Wait till epoch boundary @@ -2445,8 +2470,21 @@ fn dynamic_assets() -> Result<()> { { // Stop decoding and distributing shielded rewards for BTC in next epoch - let storage = &mut node.shell.lock().unwrap().wl_storage.storage; - storage.conversion_state.tokens.remove(&btc); + let mut tokens: TokenMap = node + .shell + .lock() + .unwrap() + .wl_storage + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + tokens.remove(&btc); + node.shell + .lock() + .unwrap() + .wl_storage + .write(&token_map_key, tokens) + .unwrap(); } // Wait till epoch boundary diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index d95ecf17d5fc153be41ab95cb6645b3ac79c175d..b032d18a83a90665b9945285b4ce4bc78c316f05 100755 GIT binary patch delta 174429 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 z�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 TxResult { - let ibc_token = - ibc::ibc_token(format!("transfer/{CHANNEL_ID}/{BASE_TOKEN}")); + let ibc_denom = format!("transfer/{CHANNEL_ID}/{BASE_TOKEN}"); + let ibc_token = ibc::ibc_token(&ibc_denom); + + let shielded_token_last_inflation_key = + token::storage_key::masp_last_inflation_key(&ibc_token); + let shielded_token_last_locked_amount_key = + token::storage_key::masp_last_locked_amount_key(&ibc_token); let shielded_token_max_rewards_key = token::storage_key::masp_max_reward_rate_key(&ibc_token); let shielded_token_target_locked_amount_key = @@ -102,6 +107,19 @@ pub mod main { let shielded_token_kd_gain_key = token::storage_key::masp_kd_gain_key(&ibc_token); + let token_map_key = token::storage_key::masp_token_map_key(); + let mut token_map: masp::TokenMap = ctx.read(&token_map_key)?.unwrap_or_default(); + token_map.insert(ibc_denom, ibc_token); + ctx.write(&token_map_key, token_map)?; + + ctx.write( + &shielded_token_last_inflation_key, + token::Amount::zero(), + )?; + ctx.write( + &shielded_token_last_locked_amount_key, + token::Amount::zero(), + )?; ctx.write( &shielded_token_max_rewards_key, Dec::from_str("0.01").unwrap(), From 325a2400b67c9fab9ad1f8cd126e1e29dd001149 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 14 Feb 2024 01:53:31 +0100 Subject: [PATCH 03/96] check nam --- .github/workflows/scripts/e2e.json | 3 ++- crates/apps/src/lib/node/ledger/shell/mod.rs | 4 +++- crates/tests/src/e2e/helpers.rs | 2 +- crates/tests/src/e2e/ibc_tests.rs | 25 ++++++++++---------- wasm_for_tests/wasm_source/src/lib.rs | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index d9b43599d9..c2a5b7ed8e 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -3,6 +3,7 @@ "e2e::ibc_tests::run_ledger_ibc": 155, "e2e::ibc_tests::run_ledger_ibc_with_hermes": 130, "e2e::ibc_tests::pgf_over_ibc_with_hermes": 240, + "e2e::ibc_tests::proposal_ibc_token_inflation": 900, "e2e::eth_bridge_tests::test_add_to_bridge_pool": 10, "e2e::ledger_tests::double_signing_gets_slashed": 12, "e2e::ledger_tests::invalid_transactions": 13, @@ -36,4 +37,4 @@ "e2e::wallet_tests::wallet_encrypted_key_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1, "e2e::wallet_tests::wallet_unencrypted_key_cmds": 1 -} \ No newline at end of file +} diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index d922969e9f..629650a8db 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -365,7 +365,9 @@ where /// Merkle tree storage key filter. Return `false` for keys that shouldn't be /// merklized. pub fn is_merklized_storage_key(key: &namada_sdk::types::storage::Key) -> bool { - !token::storage_key::is_masp_key(key) + !(token::storage_key::is_masp_key(key) + && *key != token::storage_key::masp_token_map_key() + && *key != token::storage_key::masp_asset_map_key()) && !namada::ibc::storage::is_ibc_counter_key(key) } diff --git a/crates/tests/src/e2e/helpers.rs b/crates/tests/src/e2e/helpers.rs index 8248057d59..f543337416 100644 --- a/crates/tests/src/e2e/helpers.rs +++ b/crates/tests/src/e2e/helpers.rs @@ -319,7 +319,7 @@ pub fn get_epoch(test: &Test, ledger_address: &str) -> Result { test, Bin::Client, &["epoch", "--node", ledger_address], - Some(10) + Some(20) )?; let (unread, matched) = find.exp_regex("Last committed epoch: .*")?; let epoch_str = strip_trailing_newline(&matched) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 6622443565..1bf59a5b82 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -390,7 +390,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { let _bg_hermes = hermes.background(); // Get masp proof for the following IBC transfer from the destination chain - // It will send 10000 APFEL to PA(B) on Chain B + // It will send 100 APFEL to PA(B) on Chain B let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); // Chain B will receive Chain A's APFEL std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); @@ -406,7 +406,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { "--token", &token_addr.to_string(), "--amount", - "10000", + "100", "--port-id", port_id_b.as_ref(), "--channel-id", @@ -419,13 +419,13 @@ fn proposal_ibc_token_inflation() -> Result<()> { let file_path = get_shielded_transfer_path(&mut client)?; client.assert_success(); - // Transfer 10000 from Chain A to a z-address on Chain B + // Transfer 100 from Chain A to a z-address on Chain B transfer( &test_a, ALBERT, AB_PAYMENT_ADDRESS, APFEL, - "10000", + "100", ALBERT_KEY, &port_id_a, &channel_id_a, @@ -464,7 +464,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { sleep(5); // Check balances - check_inflated_balance(&port_id_b, &channel_id_b, &test_b)?; + check_inflated_balance(&test_b)?; Ok(()) } @@ -1737,7 +1737,7 @@ fn propose_inflation(test: &Test) -> Result { "--node", &rpc, ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(100))?; client.exp_string(TX_ACCEPTED)?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); @@ -2106,25 +2106,26 @@ fn check_funded_balances( } fn check_inflated_balance( - dest_port_id: &PortId, - dest_channel_id: &ChannelId, test: &Test, ) -> Result<()> { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); - let ibc_denom = format!("{dest_port_id}/{dest_channel_id}/apfel"); let rpc = get_actor_rpc(test, Who::Validator(0)); let query_args = vec![ "balance", "--owner", AB_VIEWING_KEY, "--token", - &ibc_denom, + NAM, "--node", &rpc, ]; - let expected = format!("{ibc_denom}: 10010"); let mut client = run!(test, Bin::Client, query_args, Some(100))?; - client.exp_string(&expected)?; + let (_, matched) = + client.exp_regex("nam: .*")?; + let regex = regex::Regex::new(r"[0-9]+").unwrap(); + let mut iter = regex.find_iter(&matched); + let balance: u64 = iter.next().unwrap().as_str().parse().unwrap(); + assert!(balance > 0); client.assert_success(); Ok(()) diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 135320b8b7..39801653fb 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -126,7 +126,7 @@ pub mod main { )?; ctx.write( &shielded_token_target_locked_amount_key, - token::Amount::from_uint(100_000, IBC_TOKEN_DENOM).unwrap(), + token::Amount::from_uint(1_000_000_000, IBC_TOKEN_DENOM).unwrap(), )?; ctx.write( &shielded_token_kp_gain_key, From 95c5d52a9bec305e16c714cd772bf70460a6ed0e Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 14 Feb 2024 10:09:56 +0100 Subject: [PATCH 04/96] merklize root of masp convert_anchor --- crates/apps/src/lib/node/ledger/shell/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index 629650a8db..5cec4c844b 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -366,6 +366,7 @@ where /// merklized. pub fn is_merklized_storage_key(key: &namada_sdk::types::storage::Key) -> bool { !(token::storage_key::is_masp_key(key) + && *key != token::storage_key::masp_convert_anchor_key() && *key != token::storage_key::masp_token_map_key() && *key != token::storage_key::masp_asset_map_key()) && !namada::ibc::storage::is_ibc_counter_key(key) From 596c9c5fd1fcf37c1b67abbc5b78e4f6dedd6c43 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 14 Feb 2024 11:44:24 +0100 Subject: [PATCH 05/96] leave assets in ConversionState and store the hash --- .github/workflows/scripts/e2e.json | 2 +- crates/apps/src/lib/node/ledger/shell/mod.rs | 4 +- crates/core/src/types/masp.rs | 21 ----- crates/core/src/types/token.rs | 16 +++- crates/namada/src/ledger/native_vp/masp.rs | 45 ++++++----- crates/sdk/src/queries/shell.rs | 59 +++++++------- crates/shielded_token/src/conversion.rs | 76 +++++++++--------- crates/shielded_token/src/storage_key.rs | 8 +- crates/tests/src/e2e/ibc_tests.rs | 7 +- .../tx_proposal_ibc_token_inflation.wasm | Bin 494975 -> 495150 bytes 10 files changed, 118 insertions(+), 120 deletions(-) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index c2a5b7ed8e..d28f9d75de 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -3,7 +3,7 @@ "e2e::ibc_tests::run_ledger_ibc": 155, "e2e::ibc_tests::run_ledger_ibc_with_hermes": 130, "e2e::ibc_tests::pgf_over_ibc_with_hermes": 240, - "e2e::ibc_tests::proposal_ibc_token_inflation": 900, + "e2e::ibc_tests::proposal_ibc_token_inflation": 600, "e2e::eth_bridge_tests::test_add_to_bridge_pool": 10, "e2e::ledger_tests::double_signing_gets_slashed": 12, "e2e::ledger_tests::invalid_transactions": 13, diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index 5cec4c844b..1427f10296 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -368,8 +368,8 @@ pub fn is_merklized_storage_key(key: &namada_sdk::types::storage::Key) -> bool { !(token::storage_key::is_masp_key(key) && *key != token::storage_key::masp_convert_anchor_key() && *key != token::storage_key::masp_token_map_key() - && *key != token::storage_key::masp_asset_map_key()) - && !namada::ibc::storage::is_ibc_counter_key(key) + && *key != token::storage_key::masp_assets_hash_key() + || namada::ibc::storage::is_ibc_counter_key(key)) } /// Channels for communicating with an Ethereum oracle. diff --git a/crates/core/src/types/masp.rs b/crates/core/src/types/masp.rs index c2760609c6..3fde934ce3 100644 --- a/crates/core/src/types/masp.rs +++ b/crates/core/src/types/masp.rs @@ -7,7 +7,6 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; -use masp_primitives::convert::AllowedConversion; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -91,29 +90,9 @@ pub fn encode_asset_type( .encode() } -/// MASP asset with the position in Merkle tree -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] -pub struct AssetDataWithTreePos { - /// The token associated with this asset type - pub token: Address, - /// The denomination associated with the above toke - pub denom: Denomination, - /// The digit position covered by this asset type - pub pos: MaspDigitPos, - /// The epoch of the asset type - pub epoch: Epoch, - /// The allowed conversion - pub conv: AllowedConversion, - /// The position in Merkle tree - pub tree_pos: u64, -} - /// MASP token map pub type TokenMap = BTreeMap; -/// MASP asset map -pub type AssetMap = BTreeMap; - // enough capacity to store the payment address // plus the pinned/unpinned discriminant const PAYMENT_ADDRESS_SIZE: usize = 43 + 1; diff --git a/crates/core/src/types/token.rs b/crates/core/src/types/token.rs index d7c608e510..8351e512d0 100644 --- a/crates/core/src/types/token.rs +++ b/crates/core/src/types/token.rs @@ -1,6 +1,7 @@ //! A basic fungible token use std::cmp::Ordering; +use std::collections::BTreeMap; use std::fmt::Display; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; @@ -9,6 +10,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use ethabi::ethereum_types::U256; +use masp_primitives::asset_type::AssetType; +use masp_primitives::convert::AllowedConversion; use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling; use serde::{Deserialize, Serialize}; @@ -19,7 +22,7 @@ use crate::types::address::Address; use crate::types::dec::{Dec, POS_DECIMAL_PRECISION}; use crate::types::hash::Hash; use crate::types::storage; -use crate::types::storage::{DbKeySeg, KeySeg}; +use crate::types::storage::{DbKeySeg, Epoch, KeySeg}; use crate::types::uint::{self, Uint, I256}; /// A representation of the conversion state @@ -29,6 +32,17 @@ pub struct ConversionState { pub normed_inflation: Option, /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, + /// Map assets to their latest conversion and position in Merkle tree + #[allow(clippy::type_complexity)] + pub assets: BTreeMap< + AssetType, + ( + (Address, Denomination, MaspDigitPos), + Epoch, + AllowedConversion, + usize, + ), + >, } /// Amount in micro units. For different granularity another representation diff --git a/crates/namada/src/ledger/native_vp/masp.rs b/crates/namada/src/ledger/native_vp/masp.rs index 014b42a467..7c3f3dee5c 100644 --- a/crates/namada/src/ledger/native_vp/masp.rs +++ b/crates/namada/src/ledger/native_vp/masp.rs @@ -11,7 +11,7 @@ use masp_primitives::transaction::components::I128Sum; use masp_primitives::transaction::Transaction; use namada_core::types::address::Address; use namada_core::types::address::InternalAddress::Masp; -use namada_core::types::masp::{encode_asset_type, AssetMap}; +use namada_core::types::masp::encode_asset_type; use namada_core::types::storage::{IndexedTx, Key}; use namada_gas::MASP_VERIFY_SHIELDED_TX_GAS; use namada_sdk::masp::verify_shielded_tx; @@ -25,7 +25,7 @@ use sha2::Digest as Sha2Digest; use thiserror::Error; use token::storage_key::{ balance_key, is_any_shielded_action_balance_key, is_masp_allowed_key, - is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, masp_asset_map_key, + is_masp_key, is_masp_nullifier_key, is_masp_tx_pin_key, masp_commitment_anchor_key, masp_commitment_tree_key, masp_convert_anchor_key, masp_nullifier_key, }; @@ -413,9 +413,7 @@ where _verifiers: &BTreeSet

, ) -> Result { let epoch = self.ctx.get_block_epoch()?; - let asset_map_key = masp_asset_map_key(); - let asset_map: AssetMap = - self.ctx.read_pre(&asset_map_key)?.unwrap_or_default(); + let conversion_state = self.ctx.storage.get_conversion_state(); let shielded_tx = self.ctx.get_shielded_action(tx_data)?; if u64::from(self.ctx.get_block_height()?) @@ -486,20 +484,24 @@ where ); return Ok(false); } - match asset_map.get(&vin.asset_type) { + match conversion_state.assets.get(&vin.asset_type) { // Satisfies 2. Note how the asset's epoch must be equal to // the present: users must never be allowed to backdate // transparent inputs to a transaction for they would then // be able to claim rewards while locking their assets for // negligible time periods. - Some(asset) - if asset.token == transfer.token - && asset.denom == denom - && asset.epoch == epoch => + Some(( + (address, asset_denom, digit), + asset_epoch, + _, + _, + )) if *address == transfer.token + && *asset_denom == denom + && *asset_epoch == epoch => { total_in_values = total_in_values .checked_add(token::Amount::from_masp_denominated( - vin.value, asset.pos, + vin.value, *digit, )) .ok_or_else(|| { Error::NativeVpError( @@ -522,7 +524,10 @@ where Some(epoch), ) .wrap_err("unable to create asset type")?; - if asset_map.contains_key(&epoched_asset_type) { + if conversion_state + .assets + .contains_key(&epoched_asset_type) + { // If such an epoched asset type is available in the // conversion tree, then we must reject the // unepoched variant @@ -630,16 +635,20 @@ where ); return Ok(false); } - match asset_map.get(&out.asset_type) { + match conversion_state.assets.get(&out.asset_type) { // Satisfies 2. - Some(asset) - if asset.token == transfer.token - && asset.denom == denom - && asset.epoch <= epoch => + Some(( + (address, asset_denom, digit), + asset_epoch, + _, + _, + )) if *address == transfer.token + && *asset_denom == denom + && *asset_epoch <= epoch => { total_out_values = total_out_values .checked_add(token::Amount::from_masp_denominated( - out.value, asset.pos, + out.value, *digit, )) .ok_or_else(|| { Error::NativeVpError( diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index fbb8b7771e..8eac3c25f9 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -12,7 +12,7 @@ use namada_core::hints; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::masp::{AssetMap, TokenMap}; +use namada_core::types::masp::TokenMap; use namada_core::types::storage::{ self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, }; @@ -20,7 +20,7 @@ use namada_core::types::token::{Denomination, MaspDigitPos}; use namada_core::types::uint::Uint; use namada_state::{DBIter, LastBlock, StorageHasher, DB}; use namada_storage::{self, ResultExt, StorageRead}; -use namada_token::storage_key::{masp_asset_map_key, masp_token_map_key}; +use namada_token::storage_key::masp_token_map_key; #[cfg(any(test, feature = "async-client"))] use namada_tx::data::TxResult; @@ -177,23 +177,20 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let asset_map_key = masp_asset_map_key(); - let asset_map: AssetMap = - ctx.wl_storage.read(&asset_map_key)?.unwrap_or_default(); - Ok(asset_map + Ok(ctx + .wl_storage + .storage + .conversion_state + .assets .iter() - .map(|(&asset_type, asset)| { - ( - asset_type, + .map( + |(&asset_type, ((ref addr, denom, digit), epoch, ref conv, _))| { ( - asset.token.clone(), - asset.denom, - asset.pos, - asset.epoch, - asset.conv.clone().into(), - ), - ) - }) + asset_type, + (addr.clone(), *denom, *digit, *epoch, conv.clone().into()), + ) + }, + ) .collect()) } @@ -206,24 +203,22 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - // Conversion values are constructed on request - let asset_map_key = masp_asset_map_key(); - let asset_map: AssetMap = - ctx.wl_storage.read(&asset_map_key)?.unwrap_or_default(); - if let Some(asset) = asset_map.get(&asset_type) { + if let Some(((addr, denom, digit), epoch, conv, pos)) = ctx + .wl_storage + .storage + .conversion_state + .assets + .get(&asset_type) + { Ok(Some(( - asset.token.clone(), - asset.denom, - asset.pos, - asset.epoch, + addr.clone(), + *denom, + *digit, + *epoch, Into::::into( - asset.conv.clone(), + conv.clone(), ), - ctx.wl_storage - .storage - .conversion_state - .tree - .path(asset.pos as _), + ctx.wl_storage.storage.conversion_state.tree.path(*pos), ))) } else { Ok(None) diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 1daa2a80dc..57b92f216b 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -1,12 +1,16 @@ //! MASP rewards conversions +#[cfg(any(feature = "multicore", test))] +use namada_core::borsh::BorshSerializeExt; use namada_core::ledger::inflation::{ ShieldedRewardsController, ShieldedValsToUpdate, }; use namada_core::types::address::{Address, MASP}; use namada_core::types::dec::Dec; #[cfg(any(feature = "multicore", test))] -use namada_core::types::masp::{AssetDataWithTreePos, AssetMap, TokenMap}; +use namada_core::types::hash::Hash; +#[cfg(any(feature = "multicore", test))] +use namada_core::types::masp::TokenMap; use namada_core::types::uint::Uint; use namada_parameters as parameters; use namada_state::{DBIter, StorageHasher, WlStorage, DB}; @@ -15,7 +19,7 @@ use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::{read_denom, Amount, DenominatedAmount, Denomination}; #[cfg(any(feature = "multicore", test))] -use crate::storage_key::{masp_asset_map_key, masp_token_map_key}; +use crate::storage_key::{masp_assets_hash_key, masp_token_map_key}; use crate::storage_key::{ masp_kd_gain_key, masp_kp_gain_key, masp_last_inflation_key, masp_last_locked_amount_key, masp_locked_amount_target_key, @@ -279,10 +283,6 @@ where let ref_inflation = calculate_masp_rewards_precision(wl_storage, &native_token)?.0; - let asset_map_key = masp_asset_map_key(); - let mut asset_map: AssetMap = - wl_storage.read(&asset_map_key)?.unwrap_or_default(); - // Reward all tokens according to above reward rates for token in &masp_reward_keys { let (reward, denom) = calculate_masp_rewards(wl_storage, token)?; @@ -409,16 +409,14 @@ where } } // Add a conversion from the previous asset type - asset_map.insert( + wl_storage.storage.conversion_state.assets.insert( old_asset, - AssetDataWithTreePos { - token: token.clone(), - denom, - pos: digit, - epoch: wl_storage.storage.last_epoch, - conv: MaspAmount::zero().into(), - tree_pos: 0, - }, + ( + (token.clone(), denom, digit), + wl_storage.storage.last_epoch, + MaspAmount::zero().into(), + 0, + ), ); } } @@ -427,7 +425,13 @@ where // multiple cores let num_threads = rayon::current_num_threads(); // Put assets into vector to enable computation batching - let assets: Vec<_> = asset_map.values_mut().enumerate().collect(); + let assets: Vec<_> = wl_storage + .storage + .conversion_state + .assets + .values_mut() + .enumerate() + .collect(); // ceil(assets.len() / num_threads) let notes_per_thread_max = (assets.len() + num_threads - 1) / num_threads; // floor(assets.len() / num_threads) @@ -437,20 +441,16 @@ where .into_par_iter() .with_min_len(notes_per_thread_min) .with_max_len(notes_per_thread_max) - .map(|(idx, asset)| { - if let Some(current_conv) = current_convs.get(&( - asset.token.clone(), - asset.denom, - asset.pos, - )) { + .map(|(idx, (asset, _epoch, conv, pos))| { + if let Some(current_conv) = current_convs.get(asset) { // Use transitivity to update conversion - asset.conv += current_conv.clone(); + *conv += current_conv.clone(); } // Update conversion position to leaf we are about to create - asset.tree_pos = idx as u64; + *pos = idx; // The merkle tree need only provide the conversion commitment, // the remaining information is provided through the storage API - Node::new(asset.conv.cmu().to_repr()) + Node::new(conv.cmu().to_repr()) }) .collect(); @@ -506,21 +506,25 @@ where Some(wl_storage.storage.block.epoch), ) .into_storage_result()?; - asset_map.insert( + wl_storage.storage.conversion_state.assets.insert( new_asset, - AssetDataWithTreePos { - token: addr.clone(), - denom, - pos: digit, - epoch: wl_storage.storage.block.epoch, - conv: MaspAmount::zero().into(), - tree_pos: wl_storage.storage.conversion_state.tree.size() - as u64, - }, + ( + (addr.clone(), denom, digit), + wl_storage.storage.block.epoch, + MaspAmount::zero().into(), + wl_storage.storage.conversion_state.tree.size(), + ), ); } } - wl_storage.write(&asset_map_key, asset_map)?; + let assets_hash = Hash::sha256( + &wl_storage + .storage + .conversion_state + .assets + .serialize_to_vec(), + ); + wl_storage.write(&masp_assets_hash_key(), assets_hash)?; Ok(()) } diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index b13c89b8e3..7a6e4aae02 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -20,7 +20,7 @@ pub const MASP_CONVERT_ANCHOR_KEY: &str = "convert_anchor"; /// The key for the token map pub const MASP_TOKEN_MAP_KEY: &str = "tokens"; /// The key for the asset map -pub const MASP_ASSET_MAP_KEY: &str = "assets"; +pub const MASP_ASSETS_HASH_KEY: &str = "assets_hash"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -158,9 +158,9 @@ pub fn masp_token_map_key() -> storage::Key { .expect("Cannot obtain a storage key") } -/// Get the key for the masp asset map -pub fn masp_asset_map_key() -> storage::Key { +/// Get the key for the masp assets' hash +pub fn masp_assets_hash_key() -> storage::Key { storage::Key::from(address::MASP.to_db_key()) - .push(&MASP_ASSET_MAP_KEY.to_owned()) + .push(&MASP_ASSETS_HASH_KEY.to_owned()) .expect("Cannot obtain a storage key") } diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 1bf59a5b82..81a3901056 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -2105,9 +2105,7 @@ fn check_funded_balances( Ok(()) } -fn check_inflated_balance( - test: &Test, -) -> Result<()> { +fn check_inflated_balance(test: &Test) -> Result<()> { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); let query_args = vec![ @@ -2120,8 +2118,7 @@ fn check_inflated_balance( &rpc, ]; let mut client = run!(test, Bin::Client, query_args, Some(100))?; - let (_, matched) = - client.exp_regex("nam: .*")?; + let (_, matched) = client.exp_regex("nam: .*")?; let regex = regex::Regex::new(r"[0-9]+").unwrap(); let mut iter = regex.find_iter(&matched); let balance: u64 = iter.next().unwrap().as_str().parse().unwrap(); diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index b032d18a83a90665b9945285b4ce4bc78c316f05..b0a8e5d0e1308168c0dfdccc9d70e44466de321c 100755 GIT binary patch delta 25011 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 S*^-n56_3+MNeCF1^M3$St*piX delta 24824 zcmch<349dA5;e+*`w$c|oGLdWqJW6k!+;=%2j?b$5akL4Xw)dE zDELH0158vz@S!jIP@s5q{t5ncXai@BjS#f1jT?>FMg~>h9{Q>ZvC%xViPrv8*Bm6&X z9{Y;zXYIdbyV*DFOZFk#!^+rq><}wwU4CSh>Azyd{P4?(~Ebp4pxG z^jHzoJs1D&*jw1iJ%N=JuXo3D_x`gxUEb}*+ai1Ox`)@i>&oJ;4m_4;8=AokZpP-A z`&Yf|GPze48Zi@E1vA{|iZKb_;8{Gj19!!;7@!lR1CKPgfhtqY!?JLDOrr$J{VoqM zfe@KvXx=9Z&&IT6vam_)9DaCB@hh=Wj05!zPbB^1PE6od&1OHx3g zxFRLMK(My9@bPf0sgoQjd3h>jzMwctpE4g705dytLz+JY^e?iKB5d^Gx0j zm-j6mvsjOo-wkGI#Se7O<9x$ch1J&>+72GGyYQRGn-^byZ6qe3cu3c;Xdaqpm!Q8t zWVdB4t8?ECiVHWrIc62|j<}G_nU&%BirnJsx_7`>3j;kq!r%TqSF^3fKlFT$Xm9NO zv4&(u-|HDaezNfWelN3m#Z&vQVD$@5>~xtV|@=xw8 zH2bF(kGwfgPY%^8;5p{TU8+{`Pa~JtMkDj4y&Vy-xFO73Xlz7xm!Wr0n(dG7?(!;V z_BcZW&`rRoAg}_c$N=hKRk?DENB~gZuGR`VhJ1)j1rUgM2*I!`0Lg0wsQEI7rXD1* z0|3-Ve;k@?0iZ@gt$J;miUvri7K0fQY=s@FUgmTmSiP|*VE#BmG#16(=DfgdCQvQb zSQvilEN?6#{gD`Xum{vfl&1SZluEt+7~+dJSPuSDxtBOa<>`J8mB$6k%k1(u$4_tDjUL9K*{?r>TO5zQ6$SVB}GuhdFBwNku!{t+FDUbtq_4U`;PR5x)&HSzBY!Z6b^iH4|}_?UXdSv zyB9rA2INT5DZ$q*DE?~gml3GAdBYQ&Z7m%5QZHmyzO*(i5yr)Ro| zSR_8^0|2OBL!$x!pyO%ipb$riYN$>D07L|j*D8GTtNP3;{PwGiOhu410`t6(aLz)Gfb_}ND{3ivxemPx*13zX<{tTWuS*fk z-JfoklQ0vJ(%*jB{rb@(XHVPZ#ryYw$~(u>GZYmF^q*U*I+o6?;*Y*rAski5(3OUh z5IUn3iq%T9!}ul?-;_Q>5^Hd20x&I+${d7*mY7Zq1X)LC9gZpnl12u~97ss6Y37vS zpo$?%!KnyQ+H^UkipvSnd@4z~gb+<(SeMX+9hXz83a3;UI9Q+f4da3NK!3VlHXm;v zOsDx5roB8|J{_kHj>dEhJe1K+A(1&DB9{mW)n4lqTsE6Uhn}}vCo6M>)<+&z(ttI? zDZi}#(!)ouQw>87{#>0JhFQH*hZOKqV#PbZ-zPAb|4HJ1IMN5G|C7W&baedxFNtq` zZ1Mjn@lnU8bz_z_w<%)`M$4Mtj3qN>$tRn$<}hCJ!{+Q}g4b`s{>53o^+Lw?Ygpt0LT$tSr1CrI_dXL+N7HepXj8Uo%CEcHnQPP9@7@yRThUG#GmFa z^2+@#bA^1@m7}2MnN`;GY}QHRr{~MfJ=xnxttq|OP>tow((73^btd`-wmpLXm~VYJ zm_5wdTUL*uEQYZia^Ou27TKD76GlVBdFEzzJykeU zYbqy=U^@XaC$c(n&`6f_mwh~380zDWVWd zZW7dpL?UCSu?B2~Y(0&&tF3zF^wMNG)V&I@*N8?U^QN(D|E~=}pU(Q$|LY<2^2En- zHHInEng5Do5Mg83JUuvuhVsI67VowPB9msYfr&*ngUyThFJh9G?R`d0oxuhqT?+5z zaiQSz8LSz5R`Qvwg|RN5Vq7ocG3sXro{l#a^tve0*qAQ|&15NQ+kj;@wI^y@0p4?$ zy^CN$vH;8Iu>tQLxPeleL>Ar@B{i>`2(vA`BL!@B2^QHhrQC-XL5eEGx?MTT5*@^Pr- zQUY%+V|Cl3bBGAQLWD?{b%099d!mv{Y09|eifxUSv%0p2K|VU-qsu7B+n3wyM;9D~ zOS)hcE@zFrti*cjS=I+ZiY3;ur|a?m{34(G7aWFlXRY)9VmHJ@t+RDwf1WjP54)NL z1FU(npp50ATH+_{E>xEfeahOg?bgOm8P~m=$q6I3(2YI52=D81`##nZ3_H1x^|XD) zTofjD4-58*PYK}wVrfPYAnz$>2YBfhvd@?7r`n}oU{fSjSufoTWnxUqSVO;J_cB6# z=WDho!!~}U)E`*C5=ybf1w47ze%58c>jZ#AMHsQzG*sQDIF@z|vi%VNDFlN+lv4H7_vqq?%^TXrYgYPCJU+9*}~M+Iw1D!00V z)uXPyQNiwQ0dWu}nt~_a9ZF>f7ibLH63SE5VP~vC-?2NGdm&j5zG^hfVvFT>2U#XR zo-hA6h@ISTmcV?vTph5TsaB28k$3j1T;@khI@$%v=kI60Hvs}1Ho0{@e z+3yGT2c+bNAK59!o|K)Bu+LHCJxZRHY<85*>a4UNUBzmHf`cRR&r+b_;h;_mbbAma z4;^J|410zjMh#5xSR|D$nqNo=*0%+1>dJ5HinPLtFXI9MER3_m{yDk&F0)D>=NAoJ zJlkM!VEuK>=DqzId?kbV+y?h)BAW*c&5Rrf5Cj`*U<)DH)*~UN;hk?K~2?6 zmoLSn#m!{&Vz|jGZgEdRXePs($q3BM7QH6^`)9+pYDV=W~7*`4@QE4VZ$C8s^|Do zp5xQeJ8o$Gu~EznEl2gbkJ?q&$N5qL({p?gR7JgkGX~%qB?CRsMxqA}Fpvsh)LJdS zVxP&z`TxYj4VUt8gTup({~tWWK>sHeb`A07{{au-%lus)qRL**ecj)-2$-K_-X%c^_B80vD*<_Vge*S=dM42J^Y!hGg;N z1RcVd#l4WwDzr-P$$obhHxkejWSWs+X7MbaZYGp8A)4XDFfU0aPMVOo?Gqze za+1|!akBg*8{np%``ohGDV7lxx89FKCdv*t#je&N+TIem=oHJk?g~AIr&ZJA>~Utn z6?+^R*5g>eTb@6~nliV{s$#;YuJ z%zBf^;y5Um#ZcW-C;MtsmHV#; zCj1UF77)S|3R9BHK}hG z`80)dT*D$@=$&1dVB#I*s7l#q#T8O@kj(YcqDYNtYIVeGw@GGhfKBe~`km~GZYDkm z7ZllQJL`T>VG!gu8xmUsIj5N#;w4p&=v<0bab+^7B#24Wm{Td>Iu~kfhh59$F+Q>k zYSa!7^l^Pd!^b(Sw}Ha|J;(s1DP(M65O82A$fPJf*z261k(khII|P{`SFHsg>RB8d zWH8b6TCI7=p4wK}7l~Kf{`%08XGk2(%~2%W{`V+)z8gea{B0G-Oe zjM4_eL;GR9a%zT1m0nJ&+$L3U8L3JKFEqaD%PHk!YQj6!SX2r>_!uy;caHUrI#@3J zU~*%zLHLQYYeNOAg&z}vDjdhk!o=o!um<>Cvn(hWJwrtLP4#@8eTPsC=4;!cG^sTdUw|l7!n$X@Iwns948QZypc|A zrlZYTL|6%Mslbo}SU7B)M58tpB^ohC(op>Isy1WLRL0PkK?;W#L@LrqrXEF6DTbW{ z?x1JrMyTn0O-)9w=JYg^$i`A1sABw3dni#x8j%f{Wj5UyBavE7a_GjOlMY3*z@`*$ zctME-8*__4F+iQ=40kp6C6R(EZM0P}A9{g(4#f=3A5w;D1hftqs#T+Zl{IiEN=T5) zYrqit&_OI+tDz`3M7G2Mgj8pP0|-%C?EpeF@&WLW{)LECBb3GtLCpa$LL>zU5h-^7 zAtI#!M2FKr>_z!dKMk1rZObRxC+yfW!wP48=(>q6rsj z029DS9ckankvS6IWDLMTxXA5ue5vlTmCsbQqyp$bY3$0{wvh6drs z+gcbOFw#lBXWAoP&6E~mM}%+^NkmB_qDkhX9gRTawBcqqN=8OCYHJ}z5!6D($SkFV z+2aT~<}w{rrfzsZjT4(HgpzElV6x$Xn&6MEf}W5HuC<$uCRK?7w`>)RR~?9UR8X~> zt~9WfA{uKr3WyHSC}NZXMuPw;AoL#_IMld@28o6p1@yo$kphAqHaqMAIQkb&`WJ0@ zNdE?V$O%y4hvN!U_~Aq%n?5K1SA`!9F7knga&;$zFEqphy0Vg!SltUZKkWpB!VIncz_g(L{MZ$ zu32Rnjl~533A#p(as)ZbbTta_Sn@>7zaKAv|6u_`#oq}ScKkVD`0w$;>MO(xt*;a? ztk%H3DkoqlLi7;gpA#?y%Jdpu!Lj~tyt0D|#j7yB$HH}uGwPBq#|`x{BLV;FK?{e$ z^@}u&nYa-YITU*qV0J!-xxqS)dKtNyF*vRQoCEGbT?npc z*vrTU3p5mfnh^)0D9}(a2s3O3U&z?4HDa;9C)PEgDg=^r;4kDl5))JH$aQSky!=V7uPoLyRezFaN6sCw4qPhNq(H=p zEWS&Jq$g-frFQA)hej$yVLWC@A<^MvBmuoKm|nQr*q@{QE37shxq-yfkxs~r=#Csm zW>`Os{H{ zlt6+a2O-P~Oqt}zq{EO$8-$=t3|Dq)(#x3Yz#}s|iUQg?v)zQ;JrI4vyM^0}_jxdL?iwb1B{5AyA4>))o#Bpo^%!dkV%t}3{drj@&E2=rKl$60zV2HNO!PW?l zZ$T6_ZK44{u$ioycbXxt0st~LfEcB05!ha-`CZNoAc(Uzd7BRuguU%xI{?N{WIV zrp{LBISpJf-*YCTbUqlTUn7tt+C9v|?1D-IB#Y#BowrT;6TD)jd`IW$vbLLN{<(CM zDovBOySXPLg?hux^R)Y~n?GXL+`E&1dWo_9E3PE2ZrVA^h}6W##G zLV}&Id0*O_)`&47Ux>Vo=yCoiZ?P2@#m5WmqR)us1#aBfuslipJ_ShScZtYFSVC~p zw?+P#!S8NSf}C@$nJ&VZn~-<*{naJq4dMBzf!_;5_* z3OV*FK8SD2mv3IhhoBixBmNA3^eefuC2u1)W^tc{u=M9=zm!9=_*L#BsA+m+y%|~y zQS#+ZIcSDfSALYmZ*hN1B{7$k%xH{~GAfC?tfX-hP%ZsZKHP*~#W#E@*EZp8f)ray zttsz<1CQ*cyp~w@=(}>z_bj<9oo<`RL6+4mMci^vaM4B-T%W%zBXg$e(fP_`wpxDD zjJJhc-`tE(W9Q|Lmcg7M8#d>6@bdlg(dN8m-0}Tj@eSY*rmymA`F?YLFFMq!1@Gv7 z1x03>8V@hsFQ00`r@EgYSh5WR`tWAFp1h|ezlAMPP4IFDmfVVua#w#1P6 ze7_aH4h_V&=5*526v@@jP#G>{Izx4-!-Oz4Tv7%$AP*MFfK3ILeCkA`LeI>KujD(e z`D|7sySCxixYvDy!M!DO+wjg%legON0dyoezB{jL7wxcGwB!A>+TSxU0PzTXH4csQ z?xQ@NJZj$0<*H8nE_TrRy%Xf7&+!8oQZ2jGTtS)q3ba8tvDL={Ymp0mJg^#>&{c4p zCXxYTIey{&UasiO`>@aD{>~tJ&}!L*H{r>DroFDhy6;-v9x5%L%jS9R&D7RgRudBj zDmV}Y+aU|O^Y$3y``vjjDvIsNA7aIFUQgZ$$=f~oeXQ7O+KWHOkloXp-+`n}AKn+x z*?o8kOD?8s_ZWjV6{E)O8f~h#Sl)0wZ;@7lSQj9-5vvM0{`!->CDzjG`KPSwiXXs( zM&>poq^Y>-BVx?TIS}3~bGHLD%f1IvTXSA8zv(dYS!VfRxu!4op({K4@+9>B^S=Bg z>R?`f-U`X<{dvAPesQO~b^uTF96m(Kyot8GaBMJU0H*nrtQf$PS(W@{0Kc1^kRt{{ zs;cBe1NjT8o-v50|G8e096ku9=9Jt$h!12x$=ZYYOzi}V6>`rY-Ydpl{SZ%kf0D-s z^Ut%^*+<=Hi1~C4sT>F_nh@k7>;A$$%Gs1Fz*A;d|yk)rko6A|1o z(?1?ppYhjA%y#3S&2H?&g4rDVz0lxDpHPZSyMnzuZn`6~_D997QY3bJVJt3pfaZRh zhc;%xk6x#jH9Y?9CTvJiuV^I_)CGJWgMg>lYBh{=0YmuPW$OuiM!!m$!GRFk zXcICU%*2_oX0?+EUI9A%oip5An)_DfCL~H0&Y-|F%pHeh^#r~?s)p$sCi2O(e}x^v z8C5S#Cem2HKp5aiQ-SPoKUngsHR^tR^Tf8uoXLDkhEq82s6*b#bg z`3ZK?I`#nnj@Pfo0^y}UB(qLrh&jfUL-nJx%c1R|USpBv62 zeN2*`gSzGlv}GoktB;|{5jB^d3gpabSVw-fPEF$)LwaU9AHc76I#dn?SL6Rneq73* zs)N>yS$s9aM^-W=z<+P0zTb+bK$hd87y3-^>Z&1~$pM0|l5io)Y3|GsD?67*vf95f zo=yL7Pmqm^u$dwBv-w*m@SM$@nEY}+KIL~B!!!bGS34SY5>+>hL<;Go{#n146Ao;~k zMm@{#iKVL;jYT&7>9p!u-a`KIEN>2+rqA({xMG~Sj*p6?JGj$c=K;R^g#7JwJ~pN3 z0_3VAMoRZ;c7QGBCRp-^iunP2&nAa&h8P#g-dp&sV9zsKXw}*)kv5cQZUmbr$6EYG z+(X%B@!Ckfw3D-T*^m&Lu#1}O`} zK>j0f%Oj@-xJ{o9rL zm-B1tK6x4CRpI$1a`bkdCbyUK4u6|^-3LV-EBj0Snjp(~dO!a?RawS3+cMIrqWv+e z)V}2{GgVpCmF%PJ|1D&{S~fYr3t6?b{Q&$Sqz%5~u?&k#>x2Abwq?~J{y}8<8K`0l zGhcpkh>yjR+xajIE7BeahFMK%zaRL09=Lu{usOoqjW0~)`#)fYO5~XzcoQV`e&h{8 z=vLn!Iqt^8yc~lZY?WUhgO%MXA3Dw#)|f91+hTNoBJl)o`M1s2{U`W+uv_xL3H~hf zZ_Y^`EzsWLpZFc@4_WmSZ-gZMXWoajbj;6i*@5-`X^zhy_F6xmfpi1={%YPxjy%h6 z$NR)zVQUVaMOfs#clkB=KFbDW$&_j`PgL!$6eZR4F)AI{a_6f{d8C?80y9ROLqjEU z!8y3w^Q;}`&>PgN{0(MpoRP=?wiUcZ*f5P)>{x%YeZm#pgl9sz}ivC0(QNP5S1u z@{1@j8_mgK(c)mR6^fb6-De#W-!De&L*)js;!TLafmrc0lGz@yv_=wim?eCCEbGLH zhyS)D?1~fLfc~@b;tyKLp79FUi>>m-1Tg{*p`Ivmk<3pN*RgZfo?FS?_a}c0$;gX2MloyEgr2Ki`ueNeX*J? zm!H%ZZ`sKsGO?lP0M=gLP%OpwBwscZ8%SzSoE5d?Q;o#^c1=UJ$P&{?Twcr)3vAe6 zJE{T`vw6?$3M4Z8#zS&eP1@6n)IscIKU;|%g@>W0(Fx!5DvW3C!W4P$ z)nX$sQaeKk*&OSWF4PT&_>^l!BOATe`mNU@X4xyPt^y7PwUKN+)lC4nMSk5~T!n=9 z0E@R+{dzz%;)+f?3vRNJU>3<^J;fO8eDv=nnqj)9_d@3&1i$pg=(fn_eZ&jMebz@b zM(#o%@qEMG!Rk7ys|+pCQ9im+WW*RLM$9;9Md@{DxLBULPGs=XGcx*mF^Da(MqCfU zVJED#z5*XOZLzZYiw{_1S`n}pf*UwdJlkB2bq|wLh0pYZa?Jh0{z4~npcvF31oa8q z*+$S%MGFcAn<+H;hu2t328tLCraduO+ydrUXNHJEO7n+`CbSh%e6zTyqT2d!Rw|Qo za3dYPes{RIK|#4$jt%8#b3fVo+P8>0b)Wwwv@ol!#4p24b`8sS(2ObmcWqj?viML(A8m5n>TW zF>)lNjm?$QN0E-oBcsGz<=%6%G5O`FpwO+rZJ-QybAAI&h$&b#b41(^aQ^94QO7y!= zq|)`+QMIobPDQ(q8awFGGko` zHesOM$&Y~inGPtS{x$mU8Mm8_OSFJ0y@!n;-G%~osEj@hCi$p2vH8U|owN@Tx) zXoDmW5Vu@q)4_!Wu%_A#m)*JBX0|2>=-Y~MAt0iN)scCkPA#evY4pY=K8i=qVRuOY z-PVfiJh6>DlM8vGB{`!l=8E@_$}{uC7i^{VpGTmN(30T`#0xEd|24F4s zUq2?cv&ZBU3$ehKSpG#~Ar0`A#h8f4a*7L$FZsz zVNr`}FifU(b4k@sxpRn?A~!uDu4)k$O8pOgEvy%xCTl$jM{AKRdQvq3yWf94tXFXmO zcS4-kzbdYVgKT}bNff{>%$HM&p@&ldn+R^nH{huNu=7oLsz`6$h7}T? z^#^amq2Fpfvt3w>>fgFUG^8}oNjEs@9w$BPq;+=Mcs-qToRcnh(l_kXs@N%}5@BoD zzrRIP5*$Cy7$=m=1w})NoP3e zvrhVflU6xtTA5w1Ynf<3-MFhvOi}&$@)L2RP9trxPxPdcsry8c+Qu_ozB*W|DkI3q z<}&YAtHDkzr z?K|-UpJecgRv3N+qf@SKE&1aStWYJ^%%dWK`mp4f*hPWH(BopX zO-ThGZu!-5u@!)2C*a$w78+Q;oe&4e*ZJxcoLw83Y+dz}fZ-{SO@0=)A$jy?#G7Yi z#A(>5GxD+1;ssuMO13#8zO!SNRh0kQF-_2m20#Vlx)%9&ad3B zkvG5cS8WJeYTdx_<7ugM3?z(Lv($xyA$-q8FF188=Gs-*M5C|Au(l@H#c`DQ8^X~1 z%yC_+M5YL>Dg07jG&YGEpitQW<@@__OF#kSZ9>D=ntV`b4N|Bvy6cpH4JCXM3~wP8 z#9kFzQAX(00uw+7T%j{Pd6%X&K`63R!w`#Qsis}yG*cQw{_nrZuTIvrG1Q2zJ>j8n zqyt~;K@hU-X<5gu)oX)aV510R+tY{}gV6>84Peljg}D~~_CDr)0^gV6@Jk`e32v=( zgye`(K;%`ohTrhAx;=;wt>@c%z+MFg?p2kC9uj?tynFNDc3}& zsC76Qcsk4_B!wY+dbHNmXOD*8Mw5p;+H@qh#c3ProzDyXoR?ZO&miwOBD#4zPd1F# zR%6-Q9CMraXv`_n7yT2LazXApp<@7ZR2VAT7p*Zf56;! zTAoSJ7GG6fO*GSSWQW9yWaBvkRN`V4T~Rqq>6vP|K2dw@>f?`~r(9hJilA$%=9zE+ z?jf7!!vJ(OC?D4j&BKq#oFolr$8v3wR<~6VT{5}WsAXnP%E8}mll(Yw;F)yzfP)zv zeMG>Q!5>^n#R)m4|74QZI%yXu+eZ{s%Av4DvQ08%XR92Qtkp^b)rc(qFxI=BF8q=k zFc5LVHC>vlje{@nd$N|w>K|riTu{|LKyl~bW0jTPhAHb(*)CP<$qveCsapRIz)uLr z_Y(dR;5(bb91@04g64-)xbTfUpQ;T4iT-I?{{|otA5H?_8tLXC+!_e^p8LO%+taij zsKC>;{#{WaDZD~_j`WX)ocLb+N6;P;i+3+n;O0W$cbdwNft5m@?X%CJL_xMowQ^F zEr!vS@VbWDKcR~QuhOPq^|E$drER1G)k%%DY$UHY)+S)P#MeX{8@7KFW%yv-WmA~0 z#qwNJjrMP@YNpL3>;7DGZ3HS_Yz`)r$buGHf8@Sxp*?NbDo*NXYq)_oM={ZFsCQ9I ztUfaqu^8qQp3!4T+AXhSI7hi_>qaz>C!8$wa7Xa$k2TB#*O_HCtQ-Q#TV zMwlfc$NcO9CIQE5wohrS}kz$+P2zC zrdAv`COTHFIA`#E1iVxEV>@kCRA_~<=C_B8fxlH9aK{;EAdNd}&KU^4)O69c-DoU~ z>IXfo&YiTESggIql#wNxCx5zHGa|9X;8rDa8J)GdF<44qYiKE%C%bpny122DaJt<715?;TVk*Vr??D~K7&=!FskM-2nuwzz}UfL*zG{3i24H>iYuG3~}(NEDJ z@QIE0X^CC`UTlr*-(Txee~lUeW<>Pycz^-f+6=po%i^EH;NwDpCqj-Kpw*WD?yq$~ zr3?MF`mNV4c6SuCM8Gl|b&ZCqp7#o*4ihQ2_ft7^fHo}CM(~C;6)N8~0DWF#`3GwE z^OlzgM+uQQk3Y%>-2wj6Cz4QBFLp9}7_+>Kbd=tWQm=7$s5IHAgVKR0Weep!L$%?7Mcfz$q#njFb2T5YcyDebt&KB_kPx@v z%U$lRZFp_R65&G!SarZdhUk<#ws6^~bos(8o@lLl5FQ@sexrxA-)oV28|r6$>AEB4 zLFqnzkju{tw|g?=uxVO?eGrN(zGTiWy(og;go3GhO7@$s`EUzz!gTE={_#9nYlhZ_ ze}+Y3hBls;&XaG?&@!)nYaR@q#|XPzsP0&*%Y~b%EaK9#nmd->nOZmJ=P;VlFKYy5 zYP+uZ>rKOEY4!Q$M{)Z_+r~VX{IHnp8PEoGqu&$?{;&`(xvO8e!Yu&Y*SQX#AFrlT z`a|~}VRzN#W@hrfI9+%3GAq}}y#Z|)&eHJ%XCoq3(AibVD!C{Z-fh0zo~tcDf}70^ z<+wbpqy6reeJ3!F{4@`e^rm$rPpjZaKb)sMCfFO+P5By5Oy98b9@YLS=vQBwFV_0d zuf9CE7|SRQy7231$+0@ZE_}w1YeX-tvh=0e?dd4FXT8>vjkH#+*Gju1JNl?L5Xq}Y zwMIzx9n}s*O_}R;ahJ=r%zEdT)>n%fxzLN8%QeMHs?yHsEJwzj)$WcPbg$RN@FUq} zJ6$nb+;YZQ?K-*ptX3(LtF_^5u)MDtUD|Z7+*+-*NOa|RT|~v}q=26#csBC5HR~FT zG!7}?9nNW86LXP|L4G09hBkcuIju!X8S;k8dtKd-#xR#FQ%wN zoY$3&lEx@O_bPCqJLa@({Tn=t!0afb$t&mT^`puF zO;Wj6^|rt~q<8cMF>6P-Tzxm)UD0dL|Nv=N!O5KFs1<4GdHx2dTZg7_RJ3ZIy8jbW8^uHD5 z<-dhq$QsHKnmz@;%eqI?Yk7i9%XrZ1^2mgrwVtwzj)8TO6Or(358{Yh@64+oloh%@ zfc2Iw-TDZ=?jgCzt-nyG{=;5Ze~|tU9{d!JE118N^76G`wJx$wgdQVTM(B+*z67!# z1qDd2N7^RZ?q`AA7ooS*TTKIFsZK9BC{m9b(+~LpKzkodB>n{Rdy!8M%YTM^Mp*uH z?U{Bub@G@AQ|`HYa!a`X{Qx7j--Hx1;JS0{mFm~50JoWLkZqo|?xm-c`BY-u;Q#w=bh|%kEq!lsx3#_wT5UWo~{A`xj zH3r4+qLsl&Kbs|EJ^Bz_?;7pVZ{{0j%My?NCc8^ciPO9A9Ray1PQQbn3CQGl{V_IO zu8G%Yki^&a;^#Y%4)yAD5;y00UAKeSlXz$poAYF)SARO;t~p-UZOG4a;CD%xpa+;H z(-QUiY>MoXsJ|3_92N;b?C#n#S5_tJZ}NBM$&E>R6J9;fDo@hy;cN^-HZXOb45aEU z`R)aBQ>xyTpIIQQQql2#vPYWUD5@ae>q-NaesW5h-Zp9%vc&6t^3^oGX~ubEYf~0! zJEY_BB&y=mX?k&>*8;DrJ)Q=5Qt-I(oSE-+9m2B@&&PPS<0;1TBAy~V%keydXBwWd zc!uG*4o@3ASK+CJCkan9o{Nuw9-eRTe1hjaJjHlk!t>N4-m6^8@G=L_L_BxlX@KWu uJpJ(W!P6PfNZB_-Z-gzqDH&ix$zyU|2DnuD*n+YQ9go{8%ZRvb>i+@g6>gIN From d08536a46e1661ae3dc064aff7a69b84c3ed5a5e Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Feb 2024 00:57:56 +0100 Subject: [PATCH 06/96] propose before ibc transfer --- crates/shielded_token/src/conversion.rs | 1 + crates/tests/src/e2e/ibc_tests.rs | 69 +++++++++++++------------ crates/tests/src/integration/masp.rs | 3 +- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 57b92f216b..5b8f05e762 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -517,6 +517,7 @@ where ); } } + // store only the assets hash because the size is quite large let assets_hash = Hash::sha256( &wl_storage .storage diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 81a3901056..5c9fdb0f6e 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -363,9 +363,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { let update_genesis = |mut genesis: templates::All, base_dir: &_| { genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(10); - // for the trusting period of IBC client - genesis.parameters.pos_params.pipeline_len = 10; + epochs_per_year_from_min_duration(50); genesis.parameters.parameters.max_proposal_bytes = Default::default(); genesis.parameters.pgf_params.stewards = @@ -379,6 +377,34 @@ fn proposal_ibc_token_inflation() -> Result<()> { let _bg_ledger_a = ledger_a.background(); let _bg_ledger_b = ledger_b.background(); + // Proposal on the destination (Chain B) + // Delegate some token + delegate_token(&test_b)?; + let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); + let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); + let delegated = epoch + 2u64; + while epoch <= delegated { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + // inflation proposal on Chain B + let start_epoch = propose_inflation(&test_b)?; + let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); + // Vote + while epoch <= start_epoch { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + submit_votes(&test_b)?; + + // wait for the grace + let grace_epoch = start_epoch + 12u64 + 6u64; + while epoch <= grace_epoch { + sleep(5); + epoch = get_epoch(&test_b, &rpc_b).unwrap(); + } + sleep(5); + setup_hermes(&test_a, &test_b)?; let port_id_a = "transfer".parse().unwrap(); let port_id_b: PortId = "transfer".parse().unwrap(); @@ -390,12 +416,10 @@ fn proposal_ibc_token_inflation() -> Result<()> { let _bg_hermes = hermes.background(); // Get masp proof for the following IBC transfer from the destination chain - // It will send 100 APFEL to PA(B) on Chain B - let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); - // Chain B will receive Chain A's APFEL - std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); + // It will send 1 APFEL to PA(B) on Chain B let output_folder = test_b.test_dir.path().to_string_lossy(); // PA(B) on Chain B will receive APFEL on chain A + std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let token_addr = find_address(&test_a, APFEL)?; let args = [ "ibc-gen-shielded", @@ -406,7 +430,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { "--token", &token_addr.to_string(), "--amount", - "100", + "1", "--port-id", port_id_b.as_ref(), "--channel-id", @@ -419,13 +443,13 @@ fn proposal_ibc_token_inflation() -> Result<()> { let file_path = get_shielded_transfer_path(&mut client)?; client.assert_success(); - // Transfer 100 from Chain A to a z-address on Chain B + // Transfer 1 from Chain A to a z-address on Chain B transfer( &test_a, ALBERT, AB_PAYMENT_ADDRESS, APFEL, - "100", + "1", ALBERT_KEY, &port_id_a, &channel_id_a, @@ -436,32 +460,13 @@ fn proposal_ibc_token_inflation() -> Result<()> { )?; wait_for_packet_relay(&port_id_a, &channel_id_a, &test_a)?; - // Proposal on Chain B - // Delegate some token - delegate_token(&test_b)?; - let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); - let delegated = epoch + 10u64; - while epoch <= delegated { - sleep(5); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); - } - // inflation proposal on Chain B - let start_epoch = propose_inflation(&test_b)?; let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); - // Vote - while epoch <= start_epoch { + let next_epoch = epoch.next(); + // wait the next epoch to dispense the rewrad + while epoch < next_epoch { sleep(5); epoch = get_epoch(&test_b, &rpc_b).unwrap(); } - submit_votes(&test_b)?; - - // wait for the grace - let grace_epoch = start_epoch + 12u64 + 6u64; - while epoch <= grace_epoch { - sleep(5); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); - } - sleep(5); // Check balances check_inflated_balance(&test_b)?; diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 36d1a7926d..81571aab14 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -2138,6 +2138,7 @@ fn dynamic_assets() -> Result<()> { .read(&token_map_key) .unwrap() .unwrap_or_default(); + let test_tokens = tokens.clone(); tokens.retain(|k, _v| *k == nam); node.shell .lock() @@ -2145,7 +2146,7 @@ fn dynamic_assets() -> Result<()> { .wl_storage .write(&token_map_key, tokens.clone()) .unwrap(); - tokens + test_tokens }; // add necessary viewing keys to shielded context run( From d559e3a51b20259632d01abdc624c93accb5fc60 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Feb 2024 01:29:59 +0100 Subject: [PATCH 07/96] fix integration test --- crates/shielded_token/src/conversion.rs | 2 +- crates/tests/src/integration/masp.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 5b8f05e762..5bae33358f 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -519,7 +519,7 @@ where } // store only the assets hash because the size is quite large let assets_hash = Hash::sha256( - &wl_storage + wl_storage .storage .conversion_state .assets diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 81571aab14..fe96a7b6a9 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -2128,7 +2128,7 @@ fn dynamic_assets() -> Result<()> { let nam = NAM.to_lowercase(); let token_map_key = masp_token_map_key(); - let tokens = { + let test_tokens = { // Only distribute rewards for NAM tokens let mut tokens: TokenMap = node .shell @@ -2239,7 +2239,7 @@ fn dynamic_assets() -> Result<()> { .read(&token_map_key) .unwrap() .unwrap_or_default(); - tokens.insert(btc.clone(), tokens[&btc].clone()); + tokens.insert(btc.clone(), test_tokens[&btc].clone()); node.shell .lock() .unwrap() @@ -2415,7 +2415,9 @@ fn dynamic_assets() -> Result<()> { let storage = &mut node.shell.lock().unwrap().wl_storage; storage .write( - &token::storage_key::masp_max_reward_rate_key(&tokens[&nam]), + &token::storage_key::masp_max_reward_rate_key( + &test_tokens[&nam], + ), Dec::zero(), ) .unwrap(); @@ -2588,7 +2590,9 @@ fn dynamic_assets() -> Result<()> { let storage = &mut node.shell.lock().unwrap().wl_storage; storage .write( - &token::storage_key::masp_max_reward_rate_key(&tokens[&nam]), + &token::storage_key::masp_max_reward_rate_key( + &test_tokens[&nam], + ), Dec::from_str("0.1").unwrap(), ) .unwrap(); From f6b150fb68e8edcda6e08db4fdd944f6aeee7eca Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Feb 2024 23:01:29 +0100 Subject: [PATCH 08/96] change the epoch query interval --- crates/tests/src/e2e/ibc_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 5c9fdb0f6e..93959506e8 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -392,7 +392,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); // Vote while epoch <= start_epoch { - sleep(5); + sleep(10); epoch = get_epoch(&test_b, &rpc_b).unwrap(); } submit_votes(&test_b)?; @@ -400,7 +400,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { // wait for the grace let grace_epoch = start_epoch + 12u64 + 6u64; while epoch <= grace_epoch { - sleep(5); + sleep(10); epoch = get_epoch(&test_b, &rpc_b).unwrap(); } sleep(5); @@ -464,7 +464,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { let next_epoch = epoch.next(); // wait the next epoch to dispense the rewrad while epoch < next_epoch { - sleep(5); + sleep(10); epoch = get_epoch(&test_b, &rpc_b).unwrap(); } From f07501fe348c210f0bc1162b038f48d71a85251d Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 18 Feb 2024 16:47:10 +0100 Subject: [PATCH 09/96] add retries --- crates/tests/src/e2e/ibc_tests.rs | 159 ++++++++++++++++++------------ 1 file changed, 95 insertions(+), 64 deletions(-) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 93959506e8..9f2559ea3f 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -363,7 +363,9 @@ fn proposal_ibc_token_inflation() -> Result<()> { let update_genesis = |mut genesis: templates::All, base_dir: &_| { genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(50); + epochs_per_year_from_min_duration(30); + // for the trusting period of IBC client + genesis.parameters.pos_params.pipeline_len = 3; genesis.parameters.parameters.max_proposal_bytes = Default::default(); genesis.parameters.pgf_params.stewards = @@ -382,10 +384,10 @@ fn proposal_ibc_token_inflation() -> Result<()> { delegate_token(&test_b)?; let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); - let delegated = epoch + 2u64; + let delegated = epoch + 3u64; while epoch <= delegated { - sleep(5); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); + sleep(10); + epoch = get_epoch(&test_b, &rpc_b).unwrap_or_default(); } // inflation proposal on Chain B let start_epoch = propose_inflation(&test_b)?; @@ -393,17 +395,12 @@ fn proposal_ibc_token_inflation() -> Result<()> { // Vote while epoch <= start_epoch { sleep(10); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); + epoch = get_epoch(&test_b, &rpc_b).unwrap_or_default(); } submit_votes(&test_b)?; - // wait for the grace - let grace_epoch = start_epoch + 12u64 + 6u64; - while epoch <= grace_epoch { - sleep(10); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); - } - sleep(5); + // wait for the next epoch of the grace + wait_epochs(&test_b, 18 + 1)?; setup_hermes(&test_a, &test_b)?; let port_id_a = "transfer".parse().unwrap(); @@ -417,31 +414,16 @@ fn proposal_ibc_token_inflation() -> Result<()> { // Get masp proof for the following IBC transfer from the destination chain // It will send 1 APFEL to PA(B) on Chain B - let output_folder = test_b.test_dir.path().to_string_lossy(); // PA(B) on Chain B will receive APFEL on chain A - std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let token_addr = find_address(&test_a, APFEL)?; - let args = [ - "ibc-gen-shielded", - "--output-folder-path", - &output_folder, - "--target", + let file_path = gen_ibc_shielded_transfer( + &test_b, AB_PAYMENT_ADDRESS, - "--token", - &token_addr.to_string(), - "--amount", + token_addr.to_string(), "1", - "--port-id", - port_id_b.as_ref(), - "--channel-id", - channel_id_b.as_ref(), - "--node", - &rpc_b, - ]; - std::env::set_var(ENV_VAR_CHAIN_ID, test_b.net.chain_id.to_string()); - let mut client = run!(test_b, Bin::Client, args, Some(120))?; - let file_path = get_shielded_transfer_path(&mut client)?; - client.assert_success(); + &port_id_b, + &channel_id_b, + )?; // Transfer 1 from Chain A to a z-address on Chain B transfer( @@ -460,13 +442,8 @@ fn proposal_ibc_token_inflation() -> Result<()> { )?; wait_for_packet_relay(&port_id_a, &channel_id_a, &test_a)?; - let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); - let next_epoch = epoch.next(); // wait the next epoch to dispense the rewrad - while epoch < next_epoch { - sleep(10); - epoch = get_epoch(&test_b, &rpc_b).unwrap(); - } + wait_epochs(&test_b, 1)?; // Check balances check_inflated_balance(&test_b)?; @@ -676,6 +653,29 @@ fn wait_for_packet_relay( Err(eyre!("Pending packet is still left")) } +fn wait_epochs(test: &Test, duration_epochs: u64) -> Result<()> { + let rpc = get_actor_rpc(test, Who::Validator(0)); + let mut epoch = None; + for _ in 0..10 { + match get_epoch(test, &rpc) { + Ok(e) => { + epoch = Some(e); + break; + } + Err(_) => sleep(10), + } + } + let (mut epoch, target_epoch) = match epoch { + Some(e) => (e, e + duration_epochs), + None => return Err(eyre!("Query epoch failed")), + }; + while epoch < target_epoch { + sleep(10); + epoch = get_epoch(test, &rpc).unwrap_or_default(); + } + Ok(()) +} + fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { let height = query_height(test_b)?; let client_state = make_client_state(test_b, height); @@ -1369,6 +1369,54 @@ fn transfer_timeout( Ok(()) } +fn gen_ibc_shielded_transfer( + test: &Test, + target: impl AsRef, + token: impl AsRef, + amount: impl AsRef, + port_id: &PortId, + channel_id: &ChannelId, +) -> Result { + std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); + let rpc = get_actor_rpc(test, Who::Validator(0)); + let output_folder = test.test_dir.path().to_string_lossy(); + for _ in 0..3 { + let start_epoch = match get_epoch(test, &rpc) { + Ok(e) => e, + Err(_) => continue, + }; + let args = [ + "ibc-gen-shielded", + "--output-folder-path", + &output_folder, + "--target", + target.as_ref(), + "--token", + token.as_ref(), + "--amount", + amount.as_ref(), + "--port-id", + port_id.as_ref(), + "--channel-id", + channel_id.as_ref(), + "--node", + &rpc, + ]; + let mut client = run!(test, Bin::Client, args, Some(120))?; + let file_path = get_shielded_transfer_path(&mut client)?; + client.assert_success(); + let cur_epoch = match get_epoch(test, &rpc) { + Ok(e) => e, + Err(_) => continue, + }; + // check if the epoch isn't updated + if cur_epoch == start_epoch { + return Ok(file_path); + } + } + Err(eyre!("Generating the shielded transfer failed")) +} + #[allow(clippy::too_many_arguments)] fn shielded_transfer( test_a: &Test, @@ -1382,34 +1430,17 @@ fn shielded_transfer( ) -> Result<()> { // Get masp proof for the following IBC transfer from the destination chain // It will send 10 BTC from Chain A to PA(B) on Chain B - let rpc_b = get_actor_rpc(test_b, Who::Validator(0)); - // Chain B will receive Chain A's BTC - std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); - let output_folder = test_b.test_dir.path().to_string_lossy(); // PA(B) on Chain B will receive BTC on chain A + std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let token_addr = find_address(test_a, BTC)?; - let amount = Amount::native_whole(10).to_string_native(); - let args = [ - "ibc-gen-shielded", - "--output-folder-path", - &output_folder, - "--target", + let file_path = gen_ibc_shielded_transfer( + test_b, AB_PAYMENT_ADDRESS, - "--token", - &token_addr.to_string(), - "--amount", - &amount, - "--port-id", - port_id_b.as_ref(), - "--channel-id", - channel_id_b.as_ref(), - "--node", - &rpc_b, - ]; - std::env::set_var(ENV_VAR_CHAIN_ID, test_b.net.chain_id.to_string()); - let mut client = run!(test_b, Bin::Client, args, Some(120))?; - let file_path = get_shielded_transfer_path(&mut client)?; - client.assert_success(); + token_addr.to_string(), + "10", + port_id_b, + channel_id_b, + )?; // Send a token to the shielded address on Chain A transfer_on_chain(test_a, ALBERT, AA_PAYMENT_ADDRESS, BTC, 10, ALBERT_KEY)?; From fd3a74d0bd9c918b4da113059ad7a88b518ce371 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 19 Feb 2024 01:29:01 +0100 Subject: [PATCH 10/96] wait long --- crates/tests/src/e2e/ibc_tests.rs | 68 ++++++++++++------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 9f2559ea3f..31d3152923 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -363,9 +363,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { let update_genesis = |mut genesis: templates::All, base_dir: &_| { genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(30); - // for the trusting period of IBC client - genesis.parameters.pos_params.pipeline_len = 3; + epochs_per_year_from_min_duration(50); genesis.parameters.parameters.max_proposal_bytes = Default::default(); genesis.parameters.pgf_params.stewards = @@ -384,7 +382,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { delegate_token(&test_b)?; let rpc_b = get_actor_rpc(&test_b, Who::Validator(0)); let mut epoch = get_epoch(&test_b, &rpc_b).unwrap(); - let delegated = epoch + 3u64; + let delegated = epoch + 2u64; while epoch <= delegated { sleep(10); epoch = get_epoch(&test_b, &rpc_b).unwrap_or_default(); @@ -415,12 +413,13 @@ fn proposal_ibc_token_inflation() -> Result<()> { // Get masp proof for the following IBC transfer from the destination chain // It will send 1 APFEL to PA(B) on Chain B // PA(B) on Chain B will receive APFEL on chain A + std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let token_addr = find_address(&test_a, APFEL)?; let file_path = gen_ibc_shielded_transfer( &test_b, AB_PAYMENT_ADDRESS, token_addr.to_string(), - "1", + 1, &port_id_b, &channel_id_b, )?; @@ -1373,48 +1372,33 @@ fn gen_ibc_shielded_transfer( test: &Test, target: impl AsRef, token: impl AsRef, - amount: impl AsRef, + amount: u64, port_id: &PortId, channel_id: &ChannelId, ) -> Result { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); let output_folder = test.test_dir.path().to_string_lossy(); - for _ in 0..3 { - let start_epoch = match get_epoch(test, &rpc) { - Ok(e) => e, - Err(_) => continue, - }; - let args = [ - "ibc-gen-shielded", - "--output-folder-path", - &output_folder, - "--target", - target.as_ref(), - "--token", - token.as_ref(), - "--amount", - amount.as_ref(), - "--port-id", - port_id.as_ref(), - "--channel-id", - channel_id.as_ref(), - "--node", - &rpc, - ]; - let mut client = run!(test, Bin::Client, args, Some(120))?; - let file_path = get_shielded_transfer_path(&mut client)?; - client.assert_success(); - let cur_epoch = match get_epoch(test, &rpc) { - Ok(e) => e, - Err(_) => continue, - }; - // check if the epoch isn't updated - if cur_epoch == start_epoch { - return Ok(file_path); - } - } - Err(eyre!("Generating the shielded transfer failed")) + let args = [ + "ibc-gen-shielded", + "--output-folder-path", + &output_folder, + "--target", + target.as_ref(), + "--token", + token.as_ref(), + "--amount", + &amount.to_string(), + "--port-id", + port_id.as_ref(), + "--channel-id", + channel_id.as_ref(), + "--node", + &rpc, + ]; + let mut client = run!(test, Bin::Client, args, Some(120))?; + let file_path = get_shielded_transfer_path(&mut client)?; + Ok(file_path) } #[allow(clippy::too_many_arguments)] @@ -1437,7 +1421,7 @@ fn shielded_transfer( test_b, AB_PAYMENT_ADDRESS, token_addr.to_string(), - "10", + 10, port_id_b, channel_id_b, )?; From 4a0d74713f54313a6e6433c255988eda770d50ec Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 19 Feb 2024 23:06:33 +0100 Subject: [PATCH 11/96] wait more epoch --- crates/tests/src/e2e/ibc_tests.rs | 38 +++++++++++++++++++++------- crates/tests/src/e2e/ledger_tests.rs | 2 +- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 31d3152923..0dbd757f3d 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -91,7 +91,7 @@ use crate::e2e::helpers::{ get_established_addr_from_pregenesis, get_validator_pk, wait_for_wasm_pre_compile, }; -use crate::e2e::ledger_tests::prepare_proposal_data; +use crate::e2e::ledger_tests::{prepare_proposal_data, write_json_file}; use crate::e2e::setup::{ self, run_hermes_cmd, setup_hermes, sleep, Bin, NamadaCmd, Test, Who, }; @@ -398,7 +398,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { submit_votes(&test_b)?; // wait for the next epoch of the grace - wait_epochs(&test_b, 18 + 1)?; + wait_epochs(&test_b, 9 + 1)?; setup_hermes(&test_a, &test_b)?; let port_id_a = "transfer".parse().unwrap(); @@ -415,6 +415,8 @@ fn proposal_ibc_token_inflation() -> Result<()> { // PA(B) on Chain B will receive APFEL on chain A std::env::set_var(ENV_VAR_CHAIN_ID, test_a.net.chain_id.to_string()); let token_addr = find_address(&test_a, APFEL)?; + // wait the next epoch not to update the epoch during the IBC transfer + wait_epochs(&test_b, 1)?; let file_path = gen_ibc_shielded_transfer( &test_b, AB_PAYMENT_ADDRESS, @@ -653,6 +655,7 @@ fn wait_for_packet_relay( } fn wait_epochs(test: &Test, duration_epochs: u64) -> Result<()> { + std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); let mut epoch = None; for _ in 0..10 { @@ -1740,13 +1743,30 @@ fn propose_inflation(test: &Test) -> Result { let rpc = get_actor_rpc(test, Who::Validator(0)); let epoch = get_epoch(test, &rpc)?; let start_epoch = (epoch.0 + 6) / 3 * 3; - let proposal_json_path = prepare_proposal_data( - test, - 0, - albert, - TestWasms::TxProposalIbcTokenInflation.read_bytes(), - start_epoch, - ); + let proposal_json = serde_json::json!({ + "proposal": { + "id": 0, + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "requires": "2" + }, + "author": albert, + "voting_start_epoch": start_epoch, + "voting_end_epoch": start_epoch + 3_u64, + "grace_epoch": start_epoch + 9_u64, + }, + "data": TestWasms::TxProposalIbcTokenInflation.read_bytes() + }); + + let proposal_json_path = test.test_dir.path().join("proposal.json"); + write_json_file(proposal_json_path.as_path(), proposal_json); let submit_proposal_args = vec![ "init-proposal", diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 7f93e0f50c..d451533d46 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -2705,7 +2705,7 @@ fn proposal_offline() -> Result<()> { Ok(()) } -fn write_json_file(proposal_path: &std::path::Path, proposal_content: T) +pub fn write_json_file(proposal_path: &std::path::Path, proposal_content: T) where T: Serialize, { From 8c40f0eec35ec561e81caf5d22480606288a6138 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 19 Feb 2024 23:16:13 +0100 Subject: [PATCH 12/96] add shielded-sync --- crates/tests/src/e2e/ibc_tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 0dbd757f3d..15f66bbe10 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -2148,6 +2148,16 @@ fn check_funded_balances( fn check_inflated_balance(test: &Test) -> Result<()> { std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); + let tx_args = vec![ + "shielded-sync", + "--viewing-keys", + AB_VIEWING_KEY, + "--node", + &rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(120))?; + client.assert_success(); + let query_args = vec![ "balance", "--owner", From cea308fc4973a07f27b733e0b6f469ebea4299c8 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 20 Feb 2024 01:06:09 +0100 Subject: [PATCH 13/96] reduce epochs --- crates/tests/src/e2e/ibc_tests.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 15f66bbe10..852168e7ef 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -363,14 +363,8 @@ fn proposal_ibc_token_inflation() -> Result<()> { let update_genesis = |mut genesis: templates::All, base_dir: &_| { genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(50); - genesis.parameters.parameters.max_proposal_bytes = - Default::default(); - genesis.parameters.pgf_params.stewards = - BTreeSet::from_iter([get_established_addr_from_pregenesis( - ALBERT_KEY, base_dir, &genesis, - ) - .unwrap()]); + epochs_per_year_from_min_duration(60); + genesis.parameters.gov_params.min_proposal_grace_epochs = 3; setup::set_validators(1, genesis, base_dir, |_| 0) }; let (ledger_a, ledger_b, test_a, test_b) = run_two_nets(update_genesis)?; @@ -398,7 +392,7 @@ fn proposal_ibc_token_inflation() -> Result<()> { submit_votes(&test_b)?; // wait for the next epoch of the grace - wait_epochs(&test_b, 9 + 1)?; + wait_epochs(&test_b, 6 + 1)?; setup_hermes(&test_a, &test_b)?; let port_id_a = "transfer".parse().unwrap(); @@ -1742,7 +1736,7 @@ fn propose_inflation(test: &Test) -> Result { let albert = find_address(test, ALBERT)?; let rpc = get_actor_rpc(test, Who::Validator(0)); let epoch = get_epoch(test, &rpc)?; - let start_epoch = (epoch.0 + 6) / 3 * 3; + let start_epoch = (epoch.0 + 3) / 3 * 3; let proposal_json = serde_json::json!({ "proposal": { "id": 0, @@ -1760,7 +1754,7 @@ fn propose_inflation(test: &Test) -> Result { "author": albert, "voting_start_epoch": start_epoch, "voting_end_epoch": start_epoch + 3_u64, - "grace_epoch": start_epoch + 9_u64, + "grace_epoch": start_epoch + 6_u64, }, "data": TestWasms::TxProposalIbcTokenInflation.read_bytes() }); @@ -2171,8 +2165,8 @@ fn check_inflated_balance(test: &Test) -> Result<()> { let (_, matched) = client.exp_regex("nam: .*")?; let regex = regex::Regex::new(r"[0-9]+").unwrap(); let mut iter = regex.find_iter(&matched); - let balance: u64 = iter.next().unwrap().as_str().parse().unwrap(); - assert!(balance > 0); + let balance: f64 = iter.next().unwrap().as_str().parse().unwrap(); + assert!(balance > 0.0); client.assert_success(); Ok(()) From 3088e2be009d91c8e34149057eeb8af8a64c99b5 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 20 Feb 2024 11:02:00 +0100 Subject: [PATCH 14/96] add changelog --- .changelog/unreleased/features/2601-update-masp-token-map.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/2601-update-masp-token-map.md diff --git a/.changelog/unreleased/features/2601-update-masp-token-map.md b/.changelog/unreleased/features/2601-update-masp-token-map.md new file mode 100644 index 0000000000..40cda95710 --- /dev/null +++ b/.changelog/unreleased/features/2601-update-masp-token-map.md @@ -0,0 +1,2 @@ +- Enable to update ConversionState token map by proposal wasm tx + ([\#2601](https://github.com/anoma/namada/issues/2601)) \ No newline at end of file From 1eac4a753222add23f3a4571de156898e2ac49fa Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 12 Feb 2024 17:11:46 +0100 Subject: [PATCH 15/96] Adds a `buffer` for replay protection keys to allow rollback --- .../lib/node/ledger/shell/finalize_block.rs | 3 +- .../src/lib/node/ledger/storage/rocksdb.rs | 42 ++++++++++++++++++- crates/core/src/ledger/replay_protection.rs | 10 +++++ crates/state/src/lib.rs | 22 ++++++++-- crates/state/src/write_log.rs | 14 ++++++- crates/storage/src/db.rs | 9 ++++ crates/storage/src/mockdb.rs | 25 +++++++++++ 7 files changed, 117 insertions(+), 8 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs index b72539c89e..47aedac79f 100644 --- a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -88,7 +88,8 @@ where self.wl_storage.storage.update_epoch_blocks_delay ); - // Finalize the transactions' hashes from the previous block + // Finalize the transactions' hashes from the previous block. Also cache + // "last" hashes from the previous block in case of a rollback for hash in self.wl_storage.storage.iter_replay_protection() { self.wl_storage .write_log diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index c708605ad7..b881a06e95 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -551,11 +551,25 @@ impl RocksDB { tracing::info!("Removing last block results"); batch.delete_cf(block_cf, format!("results/{}", last_block.height)); - // Delete the tx hashes included in the last block + // Restore the state of replay protection to the last block let reprot_cf = self.get_column_family(REPLAY_PROTECTION_CF)?; - tracing::info!("Removing replay protection hashes"); + tracing::info!("Restoring replay protection state"); + // Remove the "last" tx hashes batch .delete_cf(reprot_cf, replay_protection::last_prefix().to_string()); + for (hash_str, _, _) in self.iter_replay_protection_buffer() { + let hash = namada::core::types::hash::Hash::from_str(&hash_str) + .expect("Failed hash conversion"); + let last_key = replay_protection::last_key(&hash); + // Restore "buffer" bucket to "last" + batch.put_cf(reprot_cf, last_key.to_string(), vec![]); + + // Remove anything in the buffer from the "all" prefix. Note that + // some hashes might be missing from "all" if they have been + // deleted, this is fine, in this case just continue + let all_key = replay_protection::all_key(&hash); + batch.delete_cf(reprot_cf, all_key.to_string()); + } // Execute next step in parallel let batch = Mutex::new(batch); @@ -1561,6 +1575,21 @@ impl DB for RocksDB { Ok(()) } + + fn prune_replay_protection_buffer( + &mut self, + batch: &mut Self::WriteBatch, + ) -> Result<()> { + let replay_protection_cf = + self.get_column_family(REPLAY_PROTECTION_CF)?; + + batch.0.delete_cf( + replay_protection_cf, + replay_protection::buffer_prefix().to_string(), + ); + + Ok(()) + } } impl<'iter> DBIter<'iter> for RocksDB { @@ -1613,6 +1642,15 @@ impl<'iter> DBIter<'iter> for RocksDB { let stripped_prefix = Some(replay_protection::last_prefix()); iter_prefix(self, replay_protection_cf, stripped_prefix.as_ref(), None) } + + fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter { + let replay_protection_cf = self + .get_column_family(REPLAY_PROTECTION_CF) + .expect("{REPLAY_PROTECTION_CF} column family should exist"); + + let stripped_prefix = Some(replay_protection::buffer_prefix()); + iter_prefix(self, replay_protection_cf, stripped_prefix.as_ref(), None) + } } fn iter_subspace_prefix<'iter>( diff --git a/crates/core/src/ledger/replay_protection.rs b/crates/core/src/ledger/replay_protection.rs index 5400d1d2d9..e49a109df6 100644 --- a/crates/core/src/ledger/replay_protection.rs +++ b/crates/core/src/ledger/replay_protection.rs @@ -24,3 +24,13 @@ pub fn last_prefix() -> Key { pub fn last_key(hash: &Hash) -> Key { last_prefix().push(&hash.to_string()).expect(ERROR_MSG) } + +/// Get the full transaction hash prefix under the `buffer` subkey +pub fn buffer_prefix() -> Key { + Key::parse("buffer").expect(ERROR_MSG) +} + +/// Get the full transaction hash key under the `buffer` subkey +pub fn buffer_key(hash: &Hash) -> Key { + buffer_prefix().push(&hash.to_string()).expect(ERROR_MSG) +} diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 8274277158..451b0c6249 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1069,8 +1069,7 @@ where batch: &mut D::WriteBatch, key: &Key, ) -> Result<()> { - self.db.write_replay_protection_entry(batch, key)?; - Ok(()) + Ok(self.db.write_replay_protection_entry(batch, key)?) } /// Delete the provided tx hash from storage @@ -1079,8 +1078,14 @@ where batch: &mut D::WriteBatch, key: &Key, ) -> Result<()> { - self.db.delete_replay_protection_entry(batch, key)?; - Ok(()) + Ok(self.db.delete_replay_protection_entry(batch, key)?) + } + + pub fn prune_replay_protection_buffer( + &mut self, + batch: &mut D::WriteBatch, + ) -> Result<()> { + Ok(self.db.prune_replay_protection_buffer(batch)?) } /// Iterate the replay protection storage from the last block @@ -1091,6 +1096,15 @@ where raw_key.parse().expect("Failed hash conversion") })) } + + /// Iterate the replay protection storage from the buffer + pub fn iter_replay_protection_buffer( + &self, + ) -> Box + '_> { + Box::new(self.db.iter_replay_protection_buffer().map( + |(raw_key, _, _)| raw_key.parse().expect("Failed hash conversion"), + )) + } } impl From for Error { diff --git a/crates/state/src/write_log.rs b/crates/state/src/write_log.rs index 17de478659..9390749326 100644 --- a/crates/state/src/write_log.rs +++ b/crates/state/src/write_log.rs @@ -526,7 +526,12 @@ impl WriteLog { } } - // Replay protections specifically + // Replay protections specifically. Starts with pruning the buffer from + // the previous block + storage + .prune_replay_protection_buffer(batch) + .map_err(Error::StorageError)?; + for (hash, entry) in self.replay_protection.iter() { match entry { ReProtStorageModification::Write => storage @@ -552,6 +557,13 @@ impl WriteLog { &replay_protection::all_key(hash), ) .map_err(Error::StorageError)?; + // Cache in case of a rollback + storage + .write_replay_protection_entry( + batch, + &replay_protection::buffer_key(hash), + ) + .map_err(Error::StorageError)?; storage .delete_replay_protection_entry( batch, diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 7eef20b918..025fd90396 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -260,6 +260,12 @@ pub trait DB: Debug { batch: &mut Self::WriteBatch, key: &Key, ) -> Result<()>; + + /// Delete the entire replay protection buffer + fn prune_replay_protection_buffer( + &mut self, + batch: &mut Self::WriteBatch, + ) -> Result<()>; } /// A database prefix iterator. @@ -294,6 +300,9 @@ pub trait DBIter<'iter> { /// Read replay protection storage from the last block fn iter_replay_protection(&'iter self) -> Self::PrefixIter; + + /// Read replay protection storage from the the buffer + fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter; } /// Atomic batch write. diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index c23801536c..77a3774f7b 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -708,6 +708,21 @@ impl DB for MockDB { Ok(()) } + + fn prune_replay_protection_buffer( + &mut self, + _batch: &mut Self::WriteBatch, + ) -> Result<()> { + let buffer_key = Key::parse("replay_protection") + .map_err(Error::KeyError)? + .push(&"buffer".to_string()) + .map_err(Error::KeyError)?; + self.0 + .borrow_mut() + .retain(|key, _| !key.starts_with(&buffer_key.to_string())); + + Ok(()) + } } impl<'iter> DBIter<'iter> for MockDB { @@ -789,6 +804,16 @@ impl<'iter> DBIter<'iter> for MockDB { let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) } + + fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter { + let stripped_prefix = format!( + "replay_protection/{}/", + replay_protection::buffer_prefix() + ); + let prefix = stripped_prefix.clone(); + let iter = self.0.borrow().clone().into_iter(); + MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) + } } /// A prefix iterator base for the [`MockPrefixIterator`]. From 3984f37c67812b7b8a14a785e353aa495db22d53 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 12 Feb 2024 22:29:52 +0100 Subject: [PATCH 16/96] Fixes key deletion in rollback command. Extends rollback test to cover replay protection keys --- .../src/lib/node/ledger/storage/rocksdb.rs | 89 +++++++++++++++++-- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index b881a06e95..31f054f1fa 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -555,10 +555,15 @@ impl RocksDB { let reprot_cf = self.get_column_family(REPLAY_PROTECTION_CF)?; tracing::info!("Restoring replay protection state"); // Remove the "last" tx hashes - batch - .delete_cf(reprot_cf, replay_protection::last_prefix().to_string()); - for (hash_str, _, _) in self.iter_replay_protection_buffer() { - let hash = namada::core::types::hash::Hash::from_str(&hash_str) + for (ref hash_str, _, _) in self.iter_replay_protection() { + let hash = namada::core::types::hash::Hash::from_str(hash_str) + .expect("Failed hash conversion"); + let key = replay_protection::last_key(&hash); + batch.delete_cf(reprot_cf, key.to_string()); + } + + for (ref hash_str, _, _) in self.iter_replay_protection_buffer() { + let hash = namada::core::types::hash::Hash::from_str(hash_str) .expect("Failed hash conversion"); let last_key = replay_protection::last_key(&hash); // Restore "buffer" bucket to "last" @@ -1583,10 +1588,12 @@ impl DB for RocksDB { let replay_protection_cf = self.get_column_family(REPLAY_PROTECTION_CF)?; - batch.0.delete_cf( - replay_protection_cf, - replay_protection::buffer_prefix().to_string(), - ); + for (ref hash_str, _, _) in self.iter_replay_protection_buffer() { + let hash = namada::core::types::hash::Hash::from_str(hash_str) + .expect("Failed hash conversion"); + let key = replay_protection::buffer_key(&hash); + batch.0.delete_cf(replay_protection_cf, key.to_string()); + } Ok(()) } @@ -1856,6 +1863,7 @@ mod test { use namada::types::address::{ gen_established_address, EstablishedAddressGen, }; + use namada::types::hash::Hash; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; use test_log::test; @@ -2087,6 +2095,26 @@ mod test { true, ) .unwrap(); + for tx in [b"tx1", b"tx2"] { + db.write_replay_protection_entry( + &mut batch, + &replay_protection::all_key(&Hash::sha256(tx)), + ) + .unwrap(); + db.write_replay_protection_entry( + &mut batch, + &replay_protection::buffer_key(&Hash::sha256(tx)), + ) + .unwrap(); + } + + for tx in [b"tx3", b"tx4"] { + db.write_replay_protection_entry( + &mut batch, + &replay_protection::last_key(&Hash::sha256(tx)), + ) + .unwrap(); + } add_block_to_batch( &db, @@ -2124,6 +2152,34 @@ mod test { db.batch_delete_subspace_val(&mut batch, height_1, &delete_key, true) .unwrap(); + db.prune_replay_protection_buffer(&mut batch).unwrap(); + db.write_replay_protection_entry( + &mut batch, + &replay_protection::all_key(&Hash::sha256(b"tx3")), + ) + .unwrap(); + + for tx in [b"tx3", b"tx4"] { + db.delete_replay_protection_entry( + &mut batch, + &replay_protection::last_key(&Hash::sha256(tx)), + ) + .unwrap(); + db.write_replay_protection_entry( + &mut batch, + &replay_protection::buffer_key(&Hash::sha256(tx)), + ) + .unwrap(); + } + + for tx in [b"tx5", b"tx6"] { + db.write_replay_protection_entry( + &mut batch, + &replay_protection::last_key(&Hash::sha256(tx)), + ) + .unwrap(); + } + add_block_to_batch( &db, &mut batch, @@ -2143,6 +2199,14 @@ mod test { let deleted = db.read_subspace_val(&delete_key).unwrap(); assert_eq!(deleted, None); + for tx in [b"tx1", b"tx2", b"tx3", b"tx5", b"tx6"] { + assert!(db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap()); + } + assert!( + !db.has_replay_protection_entry(&Hash::sha256(b"tx4")) + .unwrap() + ); + // Rollback to the first block height db.rollback(height_0).unwrap(); @@ -2160,6 +2224,15 @@ mod test { .unwrap() .unwrap(); assert_eq!(conversion_state, types::encode(&conversion_state_0)); + for tx in [b"tx1", b"tx2", b"tx3", b"tx4"] { + assert!(db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap()); + } + + for tx in [b"tx5", b"tx6"] { + assert!( + !db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap() + ); + } } #[test] From 948aebe4427bdaa1f9e88d678d33b2c1d8bebe7e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 12 Feb 2024 23:02:40 +0100 Subject: [PATCH 17/96] Caches tx hash even in case of a delete --- crates/state/src/write_log.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/state/src/write_log.rs b/crates/state/src/write_log.rs index 9390749326..08bad50bec 100644 --- a/crates/state/src/write_log.rs +++ b/crates/state/src/write_log.rs @@ -542,14 +542,24 @@ impl WriteLog { &replay_protection::last_key(hash), ) .map_err(Error::StorageError)?, - ReProtStorageModification::Delete => storage - .delete_replay_protection_entry( - batch, - // Can only delete tx hashes from the previous block, - // no further - &replay_protection::last_key(hash), - ) - .map_err(Error::StorageError)?, + ReProtStorageModification::Delete => { + // Cache in case of a rollback + storage + .write_replay_protection_entry( + batch, + &replay_protection::buffer_key(hash), + ) + .map_err(Error::StorageError)?; + + storage + .delete_replay_protection_entry( + batch, + // Can only delete tx hashes from the previous + // block, no further + &replay_protection::last_key(hash), + ) + .map_err(Error::StorageError)?; + } ReProtStorageModification::Finalize => { storage .write_replay_protection_entry( From 39b4abdb0bb80dba6539f9704156d4a0bee0a2a3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 12 Feb 2024 23:05:26 +0100 Subject: [PATCH 18/96] Changelog #2599 --- .changelog/unreleased/bug-fixes/2599-reprot-rollback.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2599-reprot-rollback.md diff --git a/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md b/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md new file mode 100644 index 0000000000..2f6c401aa3 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md @@ -0,0 +1,2 @@ +- Fixes the rollback command to correctly restore replay protection keys. + ([\#2599](https://github.com/anoma/namada/pull/2599)) \ No newline at end of file From 38e073d19e452cb1f57efb3629cc063a0a756f97 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 13 Feb 2024 17:20:01 +0100 Subject: [PATCH 19/96] Removes unused `iter_replay_protection_buffer` from `State` --- crates/state/src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 451b0c6249..1a4cdafda2 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1096,15 +1096,6 @@ where raw_key.parse().expect("Failed hash conversion") })) } - - /// Iterate the replay protection storage from the buffer - pub fn iter_replay_protection_buffer( - &self, - ) -> Box + '_> { - Box::new(self.db.iter_replay_protection_buffer().map( - |(raw_key, _, _)| raw_key.parse().expect("Failed hash conversion"), - )) - } } impl From for Error { From 53ebd5043b56058f5199171388fbff388c10d2d8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 27 Feb 2024 12:00:30 -0500 Subject: [PATCH 20/96] WIP making token map lazy --- crates/core/src/types/masp.rs | 4 -- crates/sdk/src/queries/shell.rs | 10 ++-- crates/shielded_token/src/conversion.rs | 30 +++++++---- crates/tests/src/integration/masp.rs | 68 ++++++++----------------- 4 files changed, 46 insertions(+), 66 deletions(-) diff --git a/crates/core/src/types/masp.rs b/crates/core/src/types/masp.rs index 3fde934ce3..64b18eb6b9 100644 --- a/crates/core/src/types/masp.rs +++ b/crates/core/src/types/masp.rs @@ -1,6 +1,5 @@ //! MASP types -use std::collections::BTreeMap; use std::fmt::Display; use std::str::FromStr; @@ -90,9 +89,6 @@ pub fn encode_asset_type( .encode() } -/// MASP token map -pub type TokenMap = BTreeMap; - // enough capacity to store the payment address // plus the pinned/unpinned discriminant const PAYMENT_ADDRESS_SIZE: usize = 43 + 1; diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index 8eac3c25f9..a9ae5fcf1e 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -12,7 +12,6 @@ use namada_core::hints; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::masp::TokenMap; use namada_core::types::storage::{ self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, }; @@ -20,7 +19,7 @@ use namada_core::types::token::{Denomination, MaspDigitPos}; use namada_core::types::uint::Uint; use namada_state::{DBIter, LastBlock, StorageHasher, DB}; use namada_storage::{self, ResultExt, StorageRead}; -use namada_token::storage_key::masp_token_map_key; +use namada_token::conversion::token_map_handle; #[cfg(any(test, feature = "async-client"))] use namada_tx::data::TxResult; @@ -233,11 +232,10 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let token_map_key = masp_token_map_key(); - let token_map: TokenMap = - ctx.wl_storage.read(&token_map_key)?.unwrap_or_default(); + let token_map = token_map_handle(); let mut data = Vec::::new(); - for (name, token) in token_map { + for res in token_map.iter(ctx.wl_storage)? { + let (name, token) = res.unwrap(); let max_reward_rate = ctx .wl_storage .read::(&namada_token::storage_key::masp_max_reward_rate_key( diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 5bae33358f..83698b1052 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -10,9 +10,9 @@ use namada_core::types::dec::Dec; #[cfg(any(feature = "multicore", test))] use namada_core::types::hash::Hash; #[cfg(any(feature = "multicore", test))] -use namada_core::types::masp::TokenMap; use namada_core::types::uint::Uint; use namada_parameters as parameters; +use namada_state::collections::{LazyCollection, LazyMap}; use namada_state::{DBIter, StorageHasher, WlStorage, DB}; use namada_storage::{StorageRead, StorageWrite}; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; @@ -26,6 +26,12 @@ use crate::storage_key::{ masp_max_reward_rate_key, }; +type TokenMap = LazyMap; + +pub fn token_map_handle() -> TokenMap { + LazyMap::open(masp_token_map_key()) +} + /// Compute the precision of MASP rewards for the given token. This function /// must be a non-zero constant for a given token. pub fn calculate_masp_rewards_precision( @@ -220,10 +226,15 @@ where // The derived conversions will be placed in MASP address space let masp_addr = MASP; - let token_map_key = masp_token_map_key(); - let token_map: TokenMap = - wl_storage.read(&token_map_key)?.unwrap_or_default(); - let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect(); + let token_map = token_map_handle(); + let mut masp_reward_keys = token_map + .iter(wl_storage)? + .map(|a| { + let (_, address) = a.unwrap(); + address + }) + .collect::>(); + let mut masp_reward_denoms = BTreeMap::new(); // Put the native rewards first because other inflation computations depend // on it @@ -623,11 +634,10 @@ mod tests { .unwrap(); // Insert tokens into MASP conversion state - let token_map_key = masp_token_map_key(); - let mut token_map: TokenMap = - s.read(&token_map_key).unwrap().unwrap_or_default(); - token_map.insert(alias.to_string(), token_addr.clone()); - s.write(&token_map_key, token_map).unwrap(); + let token_map = token_map_handle(); + token_map + .insert(&mut s, alias.to_string(), token_addr.clone()) + .unwrap(); } } diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index fe96a7b6a9..d0539594e4 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -1,9 +1,11 @@ +use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; use namada::state::{StorageRead, StorageWrite}; +use namada::token::conversion::token_map_handle; use namada::token::storage_key::masp_token_map_key; use namada::token::{self, DenominatedAmount}; use namada_apps::node::ledger::shell::testing::client::run; @@ -11,7 +13,6 @@ use namada_apps::node::ledger::shell::testing::node::NodeResults; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_apps::wallet::defaults::christel_keypair; use namada_core::types::dec::Dec; -use namada_core::types::masp::TokenMap; use namada_sdk::masp::fs::FsShieldedUtils; use test_log::test; @@ -2127,26 +2128,21 @@ fn dynamic_assets() -> Result<()> { let btc = BTC.to_lowercase(); let nam = NAM.to_lowercase(); - let token_map_key = masp_token_map_key(); + let token_map = token_map_handle(); let test_tokens = { // Only distribute rewards for NAM tokens - let mut tokens: TokenMap = node - .shell - .lock() + let mut wls = node.shell.lock().unwrap().wl_storage; + let mut tokens = token_map + .iter(&wls) .unwrap() - .wl_storage - .read(&token_map_key) - .unwrap() - .unwrap_or_default(); - let test_tokens = tokens.clone(); - tokens.retain(|k, _v| *k == nam); - node.shell - .lock() - .unwrap() - .wl_storage - .write(&token_map_key, tokens.clone()) - .unwrap(); - test_tokens + .map(|res| res.unwrap()) + .collect::>(); + for alias in tokens.keys() { + if alias != &nam { + token_map.remove(&mut wls, alias).unwrap(); + } + } + tokens }; // add necessary viewing keys to shielded context run( @@ -2231,20 +2227,12 @@ fn dynamic_assets() -> Result<()> { { // Start decoding and distributing shielded rewards for BTC in next // epoch - let mut tokens: TokenMap = node - .shell - .lock() - .unwrap() - .wl_storage - .read(&token_map_key) - .unwrap() - .unwrap_or_default(); - tokens.insert(btc.clone(), test_tokens[&btc].clone()); - node.shell - .lock() - .unwrap() - .wl_storage - .write(&token_map_key, tokens) + token_map + .insert( + &mut node.shell.lock().unwrap().wl_storage, + btc.clone(), + test_tokens[&btc].clone(), + ) .unwrap(); } @@ -2473,20 +2461,8 @@ fn dynamic_assets() -> Result<()> { { // Stop decoding and distributing shielded rewards for BTC in next epoch - let mut tokens: TokenMap = node - .shell - .lock() - .unwrap() - .wl_storage - .read(&token_map_key) - .unwrap() - .unwrap_or_default(); - tokens.remove(&btc); - node.shell - .lock() - .unwrap() - .wl_storage - .write(&token_map_key, tokens) + token_map + .remove(&mut node.shell.lock().unwrap().wl_storage, &btc) .unwrap(); } From 3aa5898376d63618481c63053ab9b4702d41c742 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 28 Feb 2024 00:44:18 +0100 Subject: [PATCH 21/96] making token map lazy --- .../src/lib/node/ledger/shell/init_chain.rs | 13 ++++++------- crates/shielded_token/src/conversion.rs | 7 +++---- crates/tests/src/integration/masp.rs | 11 +++++------ .../tx_proposal_ibc_token_inflation.wasm | Bin 495150 -> 483495 bytes 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index 19da9f29d3..e8a242e89c 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -1,5 +1,5 @@ //! Implementation of chain initialization for the Shell -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::ops::ControlFlow; use masp_primitives::merkle_tree::CommitmentTree; @@ -10,7 +10,7 @@ use namada::ledger::parameters::Parameters; use namada::ledger::{ibc, pos}; use namada::proof_of_stake::BecomeValidator; use namada::state::{DBIter, StorageHasher, StorageWrite, DB}; -use namada::token::storage_key::masp_token_map_key; +use namada::token::conversion::token_map_handle; use namada::token::{credit_tokens, write_denom}; use namada::types::address::Address; use namada::types::hash::Hash as CodeHash; @@ -423,7 +423,7 @@ where /// Init genesis token accounts fn init_token_accounts(&mut self, genesis: &genesis::chain::Finalized) { - let mut token_map = BTreeMap::new(); + let token_map = token_map_handle(); for (alias, token) in &genesis.tokens.token { tracing::debug!("Initializing token {alias}"); @@ -444,12 +444,11 @@ where // add token addresses to the masp reward conversions lookup // table. let alias = alias.to_string(); - token_map.insert(alias, address.clone()); + token_map + .insert(&mut self.wl_storage, alias, address.clone()) + .expect("Couldn't init token accounts"); } } - self.wl_storage - .write(&masp_token_map_key(), token_map) - .expect("Couldn't init token accounts"); } /// Init genesis token balances diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 83698b1052..af4ee53dcb 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -9,7 +9,6 @@ use namada_core::types::address::{Address, MASP}; use namada_core::types::dec::Dec; #[cfg(any(feature = "multicore", test))] use namada_core::types::hash::Hash; -#[cfg(any(feature = "multicore", test))] use namada_core::types::uint::Uint; use namada_parameters as parameters; use namada_state::collections::{LazyCollection, LazyMap}; @@ -19,11 +18,11 @@ use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::{read_denom, Amount, DenominatedAmount, Denomination}; #[cfg(any(feature = "multicore", test))] -use crate::storage_key::{masp_assets_hash_key, masp_token_map_key}; +use crate::storage_key::masp_assets_hash_key; use crate::storage_key::{ masp_kd_gain_key, masp_kp_gain_key, masp_last_inflation_key, masp_last_locked_amount_key, masp_locked_amount_target_key, - masp_max_reward_rate_key, + masp_max_reward_rate_key, masp_token_map_key, }; type TokenMap = LazyMap; @@ -230,7 +229,7 @@ where let mut masp_reward_keys = token_map .iter(wl_storage)? .map(|a| { - let (_, address) = a.unwrap(); + let (_, address) = a.expect("The address should be stored"); address }) .collect::>(); diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index d0539594e4..d771b08821 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -4,9 +4,8 @@ use std::str::FromStr; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::state::{StorageRead, StorageWrite}; +use namada::state::StorageWrite; use namada::token::conversion::token_map_handle; -use namada::token::storage_key::masp_token_map_key; use namada::token::{self, DenominatedAmount}; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::node::NodeResults; @@ -2131,15 +2130,15 @@ fn dynamic_assets() -> Result<()> { let token_map = token_map_handle(); let test_tokens = { // Only distribute rewards for NAM tokens - let mut wls = node.shell.lock().unwrap().wl_storage; - let mut tokens = token_map - .iter(&wls) + let wls = &mut node.shell.lock().unwrap().wl_storage; + let tokens = token_map + .iter(wls) .unwrap() .map(|res| res.unwrap()) .collect::>(); for alias in tokens.keys() { if alias != &nam { - token_map.remove(&mut wls, alias).unwrap(); + token_map.remove(wls, alias).unwrap(); } } tokens diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index b0a8e5d0e1308168c0dfdccc9d70e44466de321c..62e2a253c43e2b9b5610ab8b6c17cd6d233123cc 100755 GIT binary patch delta 149983 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

6we;Ydto?Y{qz83+&hq5E)2nk{u%ATx=U7F#< z7Lcoa#Yz^*G|7AkQvtvmD~Zh3tr$M-0%*AW#>$`;iC2{s8QsCtojpehVX|ue9l?G>GqH@)AI6h4|6J(E#7&^gRDXfRBy` z@e|(hARg1OCdB`GfS>rl4oQ)j$1L);ElmPuyF#&qv z$r#N~1NxV?VEGG#47BpN2+OnbwepBMm|%IiD99pbCC=EEluS!8wF}`>MUv2k7$Xya zUx*J_d+E_={?Oc3pojio`ly5yio+1UfR&dNTEq~3khY*JfmMkyy3Xug8-7@>8< zmbC(pk&Qnjvo{T=Gr^aHjdj#&f}i^pRbyEnb1`PM(qT0xM-^ympqk@wn9jWZKs3E} z%m(6yPa6+KOaL-mtF`ZPjdq?H(la7&|^uwa8UPFZK9&zY+AN{omsY!fOz;j}c%*S>3tti(A^M#f|vCM!`9YXe5sA+qMC zFPgE6xZnktWyK0YV}fbL%ItE=gG;p9h3I6z2()PGWOT^ClnJJ@F%!HRwKe`2W`fDs zQV1Rxdo$~^0^c#PaMfB@CgLr`7W&Jhz%2CJB-Kp%UsV5-o)=|4neL7z(=2ppnS6H)ow>d`U16YVn{`GO=Eq6 zAuxur5tgxj2Sv@)vp#$41~xhwh3*ilvU)YZ)Vmt4$NF7~g##u<)kz`gJp)GY9j9Q= zNX*J`R`{0|V5}<)IJicrmqW?dWHwOyYE7>u8LOb!Q2z|sg$!la5u}03>Z+U>6z@ka zPqkf~Jt3Q*RXM$F5X#&2iY)XiS2(L3GtsxG^>joONeHkxykguD2lqNUVz5|}bX&0K zU{n1^lo2QEh`)i_k~>L#TEs-D07JkcY4#1Uxe{59SjB zmhK6_VOyktcuYnZA8;zi)$X99k^P(HQF|WuU}1X^j|1J|=9EDO(fVP*9p=dae|6BD0NVpA9V$Ujl?aPzd0E?r3` z(V}`r8rb+%Kz&bn*hBA!UW(|?I6GXBkJdv9)SZ{vS|tDgu9qqxOla5{is(otg+omxT?&&_sH z2=}~&FrVsXATS#%Hr6rCW%6Q*+=9E=+b#fP03V;{N8#=Wslqhm{dGfQAnzFo%@!6= zJkLbR7kc4n7M2r9JqB(7Xy#&RkrmmC+TYu595geOG}O| zl<&4=FxC0+Us^H@jsJa`VfKP-#AR*zU&rB*Y|6g@=Bc1i46-RUcf$ZS$6LX=e-tWD zOg<&f5W5bl?K;1G0i3QWwN2ADmy-gz{JDrTxSBX)u4cA&9%w^R)`JB);N=)K8>`u= z*=AhtHg?jPvn>#ArSDz-0us)1*(4d-7bKF5l27P#i2+b`n#{JVsbuR~-;T*7?!u)ybqeswB+oY|xA_al}ybKE{{nb7rswpBjZi*w!phADRdm zLj^Z_MyRq-S{%Hzw=@a_0ZoM|a9|#$mD)SGOwm`$#ln=PE==N*~v9|pT4@FNg2;i zUyWGzm1*yGH{Vr6I``Ru_+9yzn(~Hh-ju{sY`0G1CI*P(v>jTm>u?7hOPfdSOIQ-4 zWU1m_S^EHuc8R8!+XT60+Oq;$LCe^AV5DM$BE{*-tQcM|#{?=EBigG2uxv z#w;Tpcg~x#4BwU%Z3YG`!>vin_@HGhO%`2_kFx7o>+J*9ps}i%a2b%5VW8-gs(%}9 zH=8WyH~dsW}diNq!srdSi+(=!(k%HRvTac$Jsa? z%H+MC-^gp%*0D1seu5yH*uF7!K6N7>xE6_W7TL2NiETBGzFyiG&bWd27evGWHMje1 zCUNeP@VpeXYc9LO&pff8n4~ydgrkLP5Kd=a3T{scAll_N2w=OZsv*JJDj~gaXhfLT zn5l82ij`Gcoo_i$Imuqk07wcxjMHbdsY>QmDA|*z& z5g{K=%$~;2V7iy`dn)mZ=b#s!l*kV{XU@ijfc_hCQAB@033%5I!#IHSo^>q-Aian5 zmzq?A$WzWCHhm7&Xo<_D^UAnSsRs52x_9^q>k8M{`nlFNvk#;77+9Pu*2K)kL`i67 zH8|D3T%|Gii62#|ww`yLuAiF>#Md$?jQw zv_bC_sdgV#00XoX40>|Uptl4)wn6V?aQVV88mo}5P&j5GS-pdEDx&w z5_Y=TypYin4NH{`5Az}70qE-##9Ck0}@WZlT;3G=hKWp1qjiH9QnQ+NbMa8-apT%_HcVwiV=X`CD_7<5~dV1W)j znk(;iV91U&@;jsOZ$I|8Ss@_|d2`ngRAD+2LY)H+s%jU_S5B@kWVB!`O+eRDaeFw5 z?DAAF>;w_J7jvCt#^&grMZKi?VIL=RkOfp7OB`Al2`9@?Ifg$a;4~zqJ%swno+i&Q z_Hh`-z0=0r#tKfE2?c zhD=+2X4UPUFQ&G**{W8Nm4w8sdNU-IO(~;_DhtqMYYbD_$+84|`yBW#$X|*D9Duuk ziVRK%852$z^am5by_KNrLit=VX~^qp`ejDAWMJ9?rP~ck8=v~uSu^C)OgPK^|Joa2 z|8;-ef#gnIb%qFz%*cQth4;o!CUl*JyKsEJqYgP-qdni81U89qhkx%euqn7`h0Gnr z>5v(vpvex#FP%RPG|!AdbDkh%jzAM<$QU%kKY!}Sne*ERfy{%YYmmqSnS)L82=phA z>7cP$S9^GX_$v#T{vBN-Q7}Ma6qlK9u9)fv3%MTPUf_D<1c5Vfg4+$={oz6G=W^xT zKByhw3zZW%PXOKq#;%b^gK>Mf2X>KDF{BYuObLnd9TZ2P7kivMYyo4vbbK)8NquPo zOzkW>fQwdgot4zHlvfXy?0YSccnlRVF~$ob1GRJ|h{gc}=KS`Ta7dYA|F>o}8n^wE~XnPd>Y-9Ix~ z4mbI0v>I-5i{YkZEo`5owP4b%F^kG(aS;up&(8Q`GDFE7FcZmDn0<_SwWk_Ce0+{q zXtaH2jzXlf(HZMB13gmPphip~K-V-FapzQ<0HwoB9HA2)U5&|$#r!3DwDs6pZbCL~ z1B=e^1l=mTqf^Pygj*4WC>&?_)GBzvS$YU=CCpLW^Fif+aTt#nHE@!kPLU|F9*oL0 z9{bKv?i&B5M)8UHoUp}lz)%b)1;gTzH7?OuOw@AX!Z{~bra(^og$o11D@;)A^dgKE zI}n21V`Fzs^SygK{~FZ_UEC;`z*`}$b0c^0vxyV5+kk$t^N9Bx6OGS2@d0mR-P~R(Xo0Xf-uB+E^Z>99}x($2WiQmJ?v7_ zj8~|EjKfUB*RYGPaIcI;Cp2@c*0GihH7MNPk}+hTX~|fsj1Q9GT9(sZ@LRHR!o+w? zR|5KP)Acl+5X$v;S8`Yx*H>W;Jl?g;5i2x2+^WZ+r|K;4kJ=U2RRilo9(llm1JJ^{0y$_d^k*|Hu8~@=&`2~%~ zzjw0txyH=zFP-^0)uQWH75S=W{6sm{klcGKQKZMlB=d0kg91q(q7^vf4(Fkg>jC|Nf5iY=jwA1567w9^$U#7qAL~#lk?03+GL^%CHccJ^|oj4L|cyYW)0g zf8)(hejq9_dS!;KETLOoOpcf!{xe~8m0+ko6O;@vVXGK;*TqSF=5Q`4Ywv}nxbWee z=1wkjxl>?Ts8(`i#^J157=xGA))Q<9-6R4}3R z6-}XSo{|!pLfbuA1~NX`?2^+mg)V?5Qh&i8Im?((iWJ zl2N?NB^Qz0=8}j7!gso4Nb)w9M1m0B>XIu+zQZL?Bl&ihM5qw{u}cc7x4Pt+B)7Qa z1tg3)Nn_coElFyZFP=1HxFJ1C-lD}}tb4fnylCL58bC*1e9YbBgv;-@;Z_~a!DLrvf&S1S}QSp%BBBO>Hl!)&nx`{ zm)@)NlP>)QrT^WfA5{ALF8xKNzehS^>L(6MRq2`Oi-D_ zbyC+J7n5nt{`Yam@4aADrUpJQCc~i!Oa}F5*uv2<`Gz(A>1p5fj=l+t9M0h>S_+S) zRrh}(rB(m^jvTiiFLsjd{{j+>r&5D@X#5SHHzzuq66yX|EEy@}fd62ejf>wGO_Q=A!&Tp60?zlxJzrGax@ z(X()}bE#E$*}BxS5r3IWEgJDJCna(Z&wi!L#JK(QaYZ<_J{l*b9>K0bO+s<9z@=8B znsF)Qh^M*K(1>p)$5{z%FL8Nr>VFy61LwcbCH-%3C2Dh!U#X>L=8ka6y(u5pUg2Y+ z>n$h_hLz2ULvxKYj(jl^lgF2Y3+2a7uwoc(Y_}bzL`NkSuwK}-NisB8RpxXWyK>kv zTKHc=y-}it|2=3KEzHva%W(Yf72+(yeU{V00OMgrCC4yNdvtb8LEf;&kxC75O#j3g z#{K)-pFS9|8E`EZj#1Ril!p`NUOt+0FSoW_Y+w0kwy%6N=U$%1xq~aqEe$^c4B;u6S zJRze|J{99jurcMfC-87Wyuc$^Y}Ac~@Q}B$_7O;AI6+x!8#1?qIiE0#OF0ivJhJlZ zMr!B%%F^tilFH+Xttq0EEfrfk5aF6MTSRyiAFMn~aT}UwqZ<>%k%b1PbmMT8i#IuY zA4~{`=! z=lkm2E#-bivBy%9`ztB){%OUMk4DGrHut9GB+5YoUIGKpT7U2QruWXy=7$8>h4GD^ zH;{beR`%!PcY4!4f((>TxsPaf%SXolWv15`rC*^L4sM@JN0qY=inh*wbGYl3>F{c- zjzV53?qZ$VxC(oz53wjfKr~t;Z>%V^s`0%8AE8=2*0&R%db~a7UF{Xd&*<{{LOP9D zMZYRKi6BOzm4p>nJfgVb=@d&N9HU1>JmXV2?*maeuE%V75UJ3pX~=!bGFrgzuw}G> z-yzFr0l$Nm(E@%4ETe@g?XwI)-?=7SRKO(d_b1Jpn+VCd7(Jupm5HiBA;M~fK8vwz zQ?h|>5E!_m;LVB(hEk7u1WqROvm|I?CpSI@Lg8QyXQ*8O~N z{LN+W!)=mv79jhHlym$g74NRgyCjuILBm&I18n8M28bOXaAd*`xMh+{nF3A|!BSQZ zD=6sXBaTIIZO}TmHW18$@;i)2P`_N2dQ6p|$=b9Uh3#8QOwca0F0CKMu3EYl z#9DLZK$7wpJD~P7>lhpyh0lJmm5e_xkMZOSEB4q6E4Du=hI10A_4!&C?oG-$M#(() zTRM?4KrgJ=-P4L`yFLl4nT`n%>*&&B(-hhun$`yXl@U=@S8Pd>^? zvBdQ;iT2%TInCg79^-&zv>^F?meGRbCoQ7|$?vg@79_vhGFp)QPBNBA%b08w;yC9M zCVSsrgf@v5itK4I=f~gB(Iowly!?1a4U(51|7C~Qmjn2X&NsOUK-KkZn~gi%#8?lU zOS9HPG{9C#>1fVnTGdgquG~7&($fGhK+}{H8lV<$V{nOO95q1GloJ{t zBvs8(jwSbQfTWyAnJ_I`#~YPnp5WHD@wS@x`W`V}Zq+-o7c*YZ%M+zQ#UXfw-!MgN`bc|MxaaW1YU#5R}J99FI7h0>x0;<*zfWSe9H*(Xgr7kQvmIRRXS;KZ$?@!#q8=2sGx z4~yi+ZtwS+ivpOB7lkeN@gH=1Ls5R0q6x^KqXjiIF~hUzo@Z(1P|jfDe^fegcB0#P z;nE5EE8Kt*-3?D+{O@OZ1K!!=Kbhs-ezM}$Dp)3`qdA=my*@^rNfjxX|GU|6x?=ip z6T|pF|@!yOB(yejkZeGmnB??F67U^g|~U}Ya1z=BCttq1loZ#5xwUn7>}jZ{7TyRYzw9!hGQZoKhBV02%Vt#{~4ui zI%Y==89Nh+$unG2F*zE^nT|c=PywJtpimmW&hvVw?IQoY&%3%Sf2@Y0A>QzkIo^kt z-h*Cg zeED3jdvSUAQ-AZ_+kgHKAO7J}u3OH?%AS zyB*gnr|B0KVMY|hx>cefN#`_TDoN)wVtUXrVs67E_!4zaQ}qj%Orxffbf#F+P^JOp z99G+Tag|4(#+>80V?37 znZK+MkcaE%Dp-`m_K6k~#ZuNxf5tLvmx#U??j&ito?>_pNz)Y(h8PP@C*m)L?;~k? zp<=j$q$v}M;q4?%n@|itMiTO%+CEIuG*89w{UlAXR1EJRX-^B21P8j-A@{YqmuWMG;Ily`AC|&q!`{w(lk-Ua2LsvEBjHBrUog7 zA0%mdkYacj$qplBnz(aeI&bF@Z-j^D6`7RvWX0P$qs}@9?%_z)bv0+Nr zi-twUb-ju$Hk94kOK>#mdA27wQP(@|C~v%sdRdj+PuLOP1cd4ZZYjUF7x?2k*^5~P z*GGC?peRoEHm9Ziy}<(IbCNd^G!|#S-5cqw@S*Huy*MejU~o6WkHOE$wmawFD2uh+ zAsXX<$V1@<2kFv8chqg>pRAov_68TInyKDkm9Ee9;uOJ^W59`3$3cv}tQ)SQeOQrl z-P*^B=Q`FG1+P(JTVHUwI})3P#{J!W3LQrngudXS;bLHsT8|T6O61a+%0W4ya0*Z* zdm@>z|E??oUq#06#u<8F5#QqT8(nCsi;0R)$BE>zIja%Tj1A9#G3DbnYgYfc#1c6A zbz;Cq*>zcQ>rl@N`FalLqVdX**YBM_UR&VJxsV!prZWLubU1fXygc5TcDX>4u{B(X zik}Y)*JGwzS>KL^M{_LZ+X52Vz<;Jk5v(8(`aT3BkKEj-tqsa4erj*esuBCmahYW-Z z$R?h@q5x54UqOt@7}0Yzk4l>r|2(vWW%?sI6JeY#=J<=%74GFf2_L~u!TcxX;KuuM zUU2@CxWTmrCEg)n94*0I$M4xd=jYfOOOALYidjX|w5N)tbd`#I^q36UGH=JgS};z* z#|uUifcUTqF%UM^)BpLyZ;oy}o`^h#91vglKs#f)xEfS-jCH4j3BqQG9 z$ph6oR)#n1Vrgf0IY`-Em6tuwp6Rh?5K3-1e}V5QxSsiNGgx3j=2yJGUd->3Uz4-O zrm+?luEPN|4>bS7UI70S5Q@KwdG*{59q>KM;q#qaD9$+*TLXADIkX4oH?HgK8W2FP zLqO($Rv@XLmo?}k7D?c(4P^4WymkFQ$fEv|X~`CD0UsMs`gSftjjnzI}) zx~UGUJKK@XdKw4$w_B{YBby%vMyEg{!^D7nmTS1QanGw}jeld2H+#WMZq4Q0&(=<0 zkbd2ox!{GYnG(_YwEgV16|)cXV2h~Md(@IC+LG~Z8UM2(h{SMmhKkL|@czo#L%s=yE{E^@#Cr-B4yuiDcM)~9Vl6f=fGwwCoR3u;pUz{! z9^3ZmU|*z~7O)o*E?9gpK}s1UWYUJQKDM}n#)kY%l5{vD|* z#VN3J$J4RUyT<|bUk~Xmz@2q~_5fiWm=sM15H4_5gu&t#2jmg%g)E1oq1-_?kX{Nh zg7U?n=c9o<31u!%FoFChX4aTGb8CL^y5iw_Su|BXq5U zV3atA0xiRX&cYR_cKs&Vm)tJ* z+zhKP+zo{-af5!7_!->9=XB=632r)*Cp)-_k9EO@o|}3UKhkBaU&G5MFJ^@46x1*# z;R-l5H@VTNrwfuBMgrMjT#SB&FG_B1ryt?zcBdw)j~oI6nP+%(2#o79B0qInF-xtp z$+T%V3~ND$9Ux2&#xOrQ$RULg9!)o(7OY_Tux!Wj9y?^5;7X@AOrLli%?!| zd+{Xl$QT5-Q%8t9=kW8)t+P(7;Ecc0uPdL^D%5f#*mc4ti^OR2b3_o!fSIp!=7-(E za=!h4FuG?n(oY}co`WjlPvbhmeN7CYrf8om^zlp3L4-IFaz}h(_ejS3ZSJtioXKxyLjPk1Bb+qO&H5jImyGt* zwe=c$;zWzU6HhwRK?J0@uf-CMczoiZwK=)sKdn*9oRRsFt}E7hjN0$1NjnAvtG_2^ z%i5S9BiHD&c7tV^iB9Zh2o5TJg7IA??Z_BLf)3@ry0!)MgX=-R+;pKb zSmC`6wu-n>^xDQGZQ4rWyuc*8^YJos=5u1&G_r=sis%KlKofvANN}77CB3* z#CJ2U33spLGroJ@LI=mqgR$J2&8`!k8|!CVz;$OoNFpL}Z$D^)>qI|zg6rM=APTNK z`oR=j@9YOvaNXYTxVdfpLJrn4;}ob_k6Y?Bu9RvC?rhbvysX-GU8#0ZSE{Ys_o;W0 zGN>2i<)5|5AK2uVHu<$pesz=IxykR}wB8*BNW&NUi#GZ7P5$5}zkQRxVw2yt$*&~O zp%V1K|D%=JNBD#Y!KAJ*YTM*4^Vm}lktI%FjM|;bicWz@7y%HI7W?C{A_hs@_$&0X zhzjFaFW8|Ea9*_&+)#oH*vm*DMe`pSoF26Zb%2Sg;NcN7!VtK1oEff-v6mGaa9@yL zBtOP(w-fty{A6^XhgPZi;S}C}Y87iPZWV$c7pZxw$1$%a5EseLi8JqTUxl^MSN#rs zG+zyOKK)Q8JSlvD*h96AnPcu=u#>j=yaz*Q2nz^-KYg}+%@0L&+XgbZit>6EQ zcd7?3|MsnfF^75*P34AQiwA4b?Vq*FFVz3Y0#AuW>E2~$%;oI@w?j0B+S|8^>((Yh zW9=9W#RX51?Jy1t!n87Plo`wl{r9`Iw zB~&a@%JCPFeKwZ!sD*@6mH6j~!_JCVve*gpnjAJwoolt>i;l`LLbAg)kJWxeO}sTh^~DC{os6q`-Drx#SOV zK|6;GQy!W=7icdsQ5_xG%Y}^uyi(T3HyNoZ#E(Ul)g&To^l#z&v`B(F>H7!;qS2Ot zGT`UpX(LhPc0)XKES+V{+S<%{nnZO-bAt;$d?Uuf8|!6xL-Bwf8_t^Qz>(dBAC_BO zyEd=J<+7}GRawf!`+#RUm}11aFs8N-fL#bq@!7>{QxNZ7PXXFvL5#&jx8m^F`!m2*l;dFy)uAc?dOBjRXAdp!R!7-~ebCCN zcT2Pfs1QC>7cCMNB+Fj9hx$CFSFm`pc1PJrrLwLbw6^ivw!_R&4nP;!LCrbCQPn~D zyH&|NzMudwcBe<(T5c*acUp<~uBAkl-EFfD3{K+PX+;*a6cJ~9^k)=crdf$}F~^oT zd0L71ZrWFtkJO{IH`4GNTgi*2RTAG#t7P@G67k)%5@$>+5#Pln;-FCLHn#RAzMEDF zE-9@=lG|xza5XUe%XCWvpWLbph(wpNt)Vh>Y`+4rDf9)Hp;JH&l`8r5aPQ-g&2s?K zI?K{-bn25J7HGw59nmDkE)=|8tg|oL1^{JSY{jqwzwg)2w<9s?EIoG2(99BtAc9bZ z;&do44+Q9ge1OHt`~&oY_Y3=L|L$p7Cp2=ZVg2C3PNA*ez$ z2fkV>yhOeaw}i?6MiIE8U$~GHeiVzP6lO2PD5zg$Hr5AJflBaEWHq3GFlU8wDpW@F z%0pUYkNi>)<;5rQs>bA~IszC!s^N)BWH$!MP%}s+f8czu1{cHf7q-`V8)*iuXOZW9 zt@Qdi8iIa`D0>Cj^=^HJd63WpnmiIj8^E01hGggzWis_n-PL)<2q-Bq;hk;{g!4N= zr}{4Ya)YI7>Ix*n14TDNdy~0aP*tLM>mD#^RrD*R3 z%{z8#vXbN(jbZ}TFgYyhUA2mqToQki59z=mY3M5#b90S$yjko!c0mzA@x7fu2m=Mi1uXC~wOy}iIRa>+2KGdy?~ zNu&nV@Bqh=iHL?d1-5BtN?r!(FGI_Uh70_58GKMJUtyN5@^;Qxpin!&SD|40(oDWu zuZ(~FY;RCuDNbGwG;tQ5e{#?ne}ws4=KaZxdY*5f<(#2yILF_`E6*O4!h+5gRX*#Q z__pq0#l066j5E+`poGt1KW%HNknt+S*9j_RD~BTxcuI4@ zr{xKgfYpzB@Idk)d(;C?_;zpPrajA+j7-59NYH(3!A$| z;p1EsvMN0If4CnCNB`N~LnowcZlQf{?_Yz%w%!to{r_d}&Euq~uD}1P>gt}YXQroN zm|>WKnr3l8b_F-KMi5X0+z{7@Yyt`hxJGSMRNQfi$W}8QZ8L&g$?P!E&b%;rUJ9dBL%slLW@FX*E6Oja$kBURV$qg68NW=oqr7W| zsN_M{>4Hnt{b>f)plImU$yZ2sWezA_lWfKG02^pvgu%r{CL+-!7BC4)S1n$q$M@r5 zdENb<^$!|n_ddL@Xtyt^B%$K*BpUQNn;P0d=fWAhP3KY!R|8UDqIHO-^1;WbwrgQ% z>rMU(yZnGvn<}1B##HBr>BFVJnz5DW)?jpBTkZ znm64L2Kl3=adIMzr3sUhKW|yu$WZ!6wuJmgA7X6}F&gELBx=pua~M6m7b)|y_aHekeNU{WYG=;z< ztxQ9mfgwnL_yoR*Q!VDE7Ick4yqpp{42euaHHxxb!rVn#q!uk|Ymvch4Pb894+4{b z=7XX2GK`Bdq>4E)Ix<_w7&IkWyjYoZ?pGCyjEaiYy?&86Fw@f#kEE@f$aTAwHbrLg ziIdUY=-dn+G=+^a`Lj-LE`fUeRr}j@<UCpkp!%JLzGhxzWaWQ_$*`vEMBBdK>Ew zmaX7pkf2nP?9F@{bU9W;xmVymuhcW^tNO;B*EdMgJUZEIc^#T_(nCggy~mnkw6(0I z^^Vrk9L4mk<-}w8NK+@XdNjl=B^$HNGXCmLJvxtE0)wns1G^9!ZMzS}KWd`gGWXx! zW-{8vKBe`wcDpEy$qGXpg|g)KCB+r~{o)c7XUv&xH*Kf}+H(Q5FtOEA3AD@N8OC32 zk&$O{$wuFJ2p2E{^ZaNN$C&5!IX9zyXy0QSjGgO@c0jbj8u#0a!`Z##?|Y!#i$iMj z4z&9ZmCodSM@xz9+tIZ6JK-f-pPEB%@=55j=bPzxF*RQ~<+eEgxdZKPg+uuAu0U?r z+Nw9-U)`d24{l=BdlXx2^yYS?O?q=%(nh@put{6*UD37@-mPJQe7zB>msWVIsm>Nt zom+78W!(~e)$SIi6?9GuJ9Px|5i34X@|kklW2mzX+2iyL4q8OkI>G^U)%}rIBv96UB)3f^`11; zS*e zVOjo#D>fy}UIa<+(rey4t*%g-Cor%i7Wdo89^|o+4OR)vqG%@!>ZHLD?Y9v{G3^P> zq8vP7N6G1$-cqlO82}@*=*N5;0{($Z9E5LD1!@We*Oq!CTw#ku0TF#8NLqrC;(@aTX0AiLviC)`GWE_4DC7YB|Dn2A}8{n&>q*XKiKY8ui4M1eD(OfNME~< zFqTC8$p_o@g_6i0qdUFLMgvJSNh>}QvEf}b(JZSD#S$cN5FBpXYOA`~7>!VDQ(f!@ zrn=^{5zJvT;L?~z^o2(b*Ej`6HI1{o)6BiCM&5V92KBrz;N6k;39>lv?yUx-9^UuN zXP$TWz6-1-!@A2cKR4mL5HF9aa?3?&zhH@AckFSrroPyEK5d8)v55_7U2A(;tl>x* zBY>Cn(w&5^zvvLVu6gSpv{q%5Ua4j9s`Ou><(aMM)je@o$*owk?Yl5wkytgG#zEk-XiFv`EfmD83tMgNBlozLwcdK(-;AD}T%v-%T zw>qiV$^@ZnXu!4$9=F0p=ae?^=eNf_*5mPM%tOU(R0A^ivLhgI;U0+%YT1@3iW-;S zC|i$HkYYDdmTSjr^fG4JLW3xnJGBkN7$hi+JHRtHqrw^YsFN|NcP!>dE zs*3~;f#rZ$C}V19eLandXyTE!cc299wHt7vQ;{> z#V*dt@Cl|^8`QJc^MPMR4=EWektk6>W*ZV_Dq^Nxilh2%Nx;<7A5CpHB+R%273Y`n zaUxQ3S|^wz?Gnt_7$Hc7`9}+v%)so{8CVb!Ou<(flj;Iu^5-on@@KQERTuy_jq6 zP&?-HR(>^vmg&SuloYg1kZfqwytl(fm(^VOcA>fXFjWwm^4EWkEM38$@ajV_qWN3c z>Y#GVM$HeWZ>D-X#~72hHZ?K=RWriWy!^Aje#{CL7iQ!kfZY(>RT&;vt0f+QBpV)P zer9@+kyGf+ynuYh^_4w(b8C-+b4>+y)~AXUv~G&}2MOD*Q%T#tTX*PHGq2uxuMYjs zdewj77^dX^0_!_z6IgF;k%k@(W5PNHucttCIN(UP$v+bBg$G)JRK6zYghJR0fa-+} z|4A>z_zFyZJJ;wOIr56xS%RzKpJbm_JY>@c475Fmo z^xl8c3;$0%X;A#S;_0$hUf|}|R^Us-6Vo}kBT&A~pk36x7zpYyhnu}`Y)C28?_*oV zK+*(?*%m2gE2Ck2wZkBQT3XB!1x;3B&38>K4dbgBvRX&+Ek)JrtH+PVKso!0Vf=qP zfT$J4`n4NCROg*4Rw!ptdk+>S!v;5(v~8{qImFf^WU`%?U%TlsOCL=_sVd3;2p|w} zA>z>rEFFX?$UqMU|QCBMez3 zoHxM66wg#f28{4}C=w8jrA@+71Lg6>^{AOEfu<0sC`y%b;79Mq(`It>E$xM%}h2!IAEo`QD z{c>5B!798gOYZu*P6NBUke?a*KlBEKZg0VU(TVONgj;6+$ zM{O*6QV+G9B4b6cb1S8j1T-IU!$gV0p8@;XnHTYqwIK)gWNl!MTW^+xFylt*~~7&e31s&dzf*;6)IIOB0`XInY`1@ov9j&p?uE?%|x3`I9eF! zC`3XMmdd=w!W_a>jCr|?k1Ut#1eEYoz&00fv<#9;FTRg@mh_I&L)IpJN^5m!H>1gA zSw^kUwjVQ_-S8XcgOAxv@AwPdM@jj3BM$ZbCkyrjHi%mRT91!M#gH+oZqGy^d8jdGu^Ve z1=Qs~`yvc0lCPWph%HcT2l&Y1bdrTja@0y?KqDWNz)(p z;%3g#B|r zb|BJ(upEms24xg=y^3<@V8suw!}Wlt;jvY}p^l{m1+heHiJ(TiqQFqp2La-ZSTb$mO5uP_%$RIOk05FxCFx2h z1xCQy{HMz>^)z58r-N5(@lKdB+mVs*0mq5->HHW8#AWU>53JLto{rQ7Jz7J@i#h}0 z;k5)jLT{FOg%WJ_+2;CyR6Q45)Po6nyyBxhkmo=~CCSUBT|mO& zc?<+-aA2XF=)t-pnc~L@SeYQ$UD$rzQ3`<3QA)(rX!ZW&Zzj4FG+a5s>1G;mJ$pM1 zk6i2Iv`?>x(M+`7CUK@@5W)a^JqVEQW$$5WA1b82Cb5TA7X;pu*vD!nPcCt%Umj;i zBB``mRU<9m0Nv*qMkWL{3{V4uF=%bZUn{9`D z$1VYqgj}X{lxe>44jqJFbMK+G3nMUeX{%}G$^wh2#6|HVK7%L{!YBZc7(>6g0ic4V zPQx&y=IC*FE5LFian$9xzJc`Ay-wx#R^(`Hu;IkkK4Rsn@p%E;C@G1q=A{Tpy0Gc{n$#jA?AC`XFp*%H)_sn9*;OH<>3p{&BJMSxu>sP zkj3D6AfIw5iPMbpnR>Cc+5)rYraIWb3-H35+wM56j!oQyWP~=PpO>7_C9_8KjB~!W z{F=|73)AGJqZxy}@!>9F26m;YM5d!1TDdG~P{?eQi7UnLgQYn3Wu(HB7#>fURs^3mCh&S zIMBswadEt(4|r+b&+9|43ku^FPj|S5&_@gSr0(PGXPAsk^wmRU$AOMoWzvTd%<&FE z3aa(`1Ti?!5sMK*u^5idbBHZCVlM^=A;mx~GQ}Wu=F{97VXy}EigB(*9fL67){)i| z2>Qrd@dSc!YVgUI)P>Ch`BN60K&S~9f#p2`jFwL@ClCmq-)#16mIWseskwl7nEOq+K48 z&vfDeT9ql&2?Q@hwW)KN=jki}FExwmD1C4S0ZMc|kz?)*A)%fq)T)r%PxHFLoW1}h zFhs)ZgM4T{6M-^Vj6j`<0h3F%)JTGgKcFNIfqR4J=Z1NeC}1kI2lXmZEY01KGwdKw zva8RUh_B8cusz?aWn6@zq~Vgy{9J7z6h=r$@(Ka=z?G(}!mY8}}j0ducvc-RUVDi1E64OqE^ zQh~YJCxF5Cfm|j*bsgjYb(8BlhoRBwRRY-g;uFsI2@6S z8J2+6$O4leyrTBjc(qY0afYq5Cgo^tO;{Q6uSiB1Qfnk3fK6)x0YqaU$*A%YQozN= zgi@02(Lz=sM_-s*`e|@CjX{tNs16%wHeVRYWDF>pT?Si0q!$JoMwok%d9+hdIwk{skVC%1hS@EcbWK%(dLw(-mawl_EqX*8@gR>f%rR96*s0OEP*v)_-ipih+ z$C{C1Pf?cWl$T&lk$Q%1Y2pi!o(`h|>M&}`>#M`42I>ovI1*XOtH6$s=IUVES2IR2 zbL4n1!+CPdnMk909Jm%I;n{+@AWvXgRY0W)JSx3Dp=-RiUw8+F znMZFywTG(N#Rh&UUw7Gv%F+5ub5zjSmVQFdq6daKm{}n8>1$e`$mO_lOi!6S_V9)a zQLw9~cow)b(xTQwFLAXmHM+eckX*&8FxfB`7U`hq&=ja?5(i`W43^XZr+6+#hs5ed zMvCQS=_-y2nQfr1SapR>d^7rE5@JV}X_L;y!~;Hy{4s9F{g-hcpKy?83KaKW9{F=7 zChpHZvLHT3Vv7El)YG-*L`)%mB!&|QMi0dZgjhnSy4OobQwiq8Qa!NXNfE*F!NK6t zW?|1dC$XOlk6@=~xMLr4m{^Wk^?(kO`RG z;MD^+vj|j=ihS0|$c4noM5hVz4^rt+IE+Y$0wmJ{7eeo3sjAQ}k%R3|bU}frrpCOT zlNXD$`&pK1?NAC)O9@s^$j7`*VJsbKanq>nGL%TYgYqw9eGrBqYehDA3QIc2Bo7E) z%VhN?X^=@)x0M+}FmkLM0)ml?OuP-*RvX+~V~A@buf%p56-FNq+dTOi;> zc^j~?o9^LkAcEeB&PsXmOrQsv`KOj2jl$ZDlvc#>tW;@pmb%5q+&dGDxhxVP?sbls zEzQ|*?B%V7o1qvsD-CQM<{)62Y*FyrWMl?!R*$QM|6ugytsFcNpe7ZG*4YMV2^N%P z4$9wieYn$ar52R`D1(l%`0#fPBpjWwQZy0zN=cf4*?`t0a;2t|%xd(5zu7BDF5_5q zEXs_o=vS?a(G(Ng92N!@_x!ak|DLxjw&{<>-v@uQWA3O8t$7EBuG-Ypn1jHCw1cqr z!P@fHx2R)q=qg-NpzWyd?Hk|h7z{;;pNLU?jIV4E;A2_w^l=oA+gAMLIUvP!^q zA)B?vN9RWwgY*7PFYd}Ip$}QMkV#D?pv==@&4bndyEh|ITAV$bJ-sj&(2|ODred^) z7S0e$HPI>QK!Z$4sb4J?XHcMV*wJla<}{7dnpS9?tI_EHT<{=agK?#l`vr_uu3Ucey>cure1k&rlBF<~ z=oz7PqXAnqz#@YL>qm?t_I$=j8S7ffg8c<_B8Y(P(%#gRm=1xWKC$`c6UiFsf(~X1 zMJ%)cO%OqR+H1U5H_=?UoQSFXa^^irz9D4P5i@zgc+U=Zovm|%D+>c`xWS-dQxjDF z$=u%HLjPbwIHPGsC>Z0RCL?ILC8G7D^b+u*(_u#X>Ntxud)+a>B3~(hnI^P}NS@O4 znQ`Q63nsFWB_Jl4J7~rqNf!waLrLNA7YG=mCWk6(Q37#KJOV*lK_JRZKL+0~R-0IM zb8I7bOR$bH2drmXh+6>9#+`%~SR&hT{9=^Sy@bK=#al9inVz8`srSr;a~$_VhG|)ngWHDp^!5e&U!tWwot!5{a4lgO+XU zyX0g?2|M4MsS0FT&Ky;AgWh+Gy|1k&bUf6;jhRu`w~1Ah^KSmz8YuDw4Ikyjj*o@` zzLwsu<2im|^dd9qDKt#?ooz@U`9mg=&||*Ys&Sbm zx9ILgGb3ZLV|wX~3RG7e@f8h8gmD)eN|GiHXs8xE=rs>Uwq(6}UApZcQ=ps`wl-&w zymUYz5Jl#6J3Dwps3HZ2eNrwqTo|x4AcK$riGU2!M2wd%uEQE=7S2CX zKoRt5Fq$UK&}pX3A@i{K9Uz(pw+_=G(Ln+9N31K0TpSbQdlPzOnrUzu zGOxM9tWEJ=p;)<6Guc!&5`&EP+J}ad{2X&9h^*gPL&@5;70SwBv^Nt|nmZXBhgIf` z8Y8(@sDuo~W)9lAbOnPNC4f)pq2dPm0z-pjM}XbnIJGXm!#<3Gn=H5mNT-y7Oc)U) z^cj~;K2TMVO=;w9hzupU7!oYwXdrh`wZahHV{GP7@=vt-n<{ajNcLhzer5z_2}fq> z<{Cpg?M0ea_*)QZ8cB_lg*x6fZ6ume?U87j&l-s)YgZ`JN{vX9{Fg|JWCjM5G&CrI z&C;5oDp@+zqh_{JD~p5-BVixQ2%#|b5mHSia5G+%v&uD&YZVMR%TH3x>s>Hn&5Mb( zdUBIkliV{lg@A~w@s?N0xc-L4oN+~g-GEtChQP|kk{qUV66``Vma@B(zHq5 z;^-{-sh$x>yVBg$3tUKIdVve)#ED`W>SDyDnbm_9W9~5w9ZX=zqhL7NWD;_8Yj1}6 z1RRZb#L?x&5)7{^B;@EQB&3Rnp(Ahf1F9r8f}tHTG?s}}UV|9xwGq9B16`4xjoR0C zh~lCptVOUHoscVD3mhAoJPkkftTkG}5X7>zv9dS@1?c?CS$pOSXZb?3*G&@v6lKQy zyb%)2C;86J*P2e6iK_H-Gb(XZ9by$f;U6*_x3a)5^a=DA=t=@40XWkg*|g-PL8~2A z#R#uRcJ0P2BUC&u5Wq4<%1p@sA`iweLlz7yfKsVx=w<3l%~sL{>`W#3iu_95kHe9> z{2CnjA^B~Bxj%Vx*o7S%REw2VK^Pl~elmqkkycWQGbMq^8am$!o z^tmA2_GnBc;9L}I{iK@E}O6rXjGpP5O4 z05gkX&n_)TY}j{sTtg?E@0e_7SxZ+73l}CusQkUm*l>kw5goyVAU;PAZSsiT53oK)3x8@oVcmNhFi`QS} zW=~y*k|xF?B$<3`a3%l-B~bF%Ns5(?y05uaes> z0>WO$d3|$*lucsU>WH1pm&ou-M6P!e2_^HV2Upr4gSk6FM+SlR%8h)mPt>JNykRCZ4p^^6K+-4)s&sE-+$j4BmTBJsjSAXq5a zBn_cuZc(lr^24T)x0xJ+7KBh@9A8FHvEG|=Mu(aWdk);ljTTfAy7f}j;J{39=9ObJ zx0=qz9B6V1GYQl0JZW%HQ%Sk`hgw8w4N0&0C&I+d{1WPb(3laV@|;=cZ%gzEzhD(2 zsB)xD=;S+bRP)FStR*sLMyGrU++-$J+QS)CnYOb%2nr3_7d2TB#5Q`_wSRhz*-mSV zgJX0%wAj@8$EtOzSZiVWOKTlfkUE=5Xz>HA>gcvres9Mr2i*nlw<}VnfTpkx z{dX*~cqpnBODeJplvd%}3++1p2TSalJ=vY0-9ktN9k$7F89RzzXGKCbb>_%{wz|=S zpM{jREZjq9vdChRm&7*fjmQBu(D~Ds@C8B=e2N`kTt+q&5M{GH%;sY|*oyZrvw3f) z*=!B7+4gm_*|O7YHiy}~_I0z_w9{;!3A5Swb+g&9(`?p<**y7mvXR7pc*ktIgYr4_ zM0dIkG74tth`rgdOH-JVPtU_H>e+7IG+jc{Kr(%+t0B6|h1)D#uK%c(AQPuuE>4JV z*eGDAOXctUOx_~%r-~SusSe0xIAVnpV8pFBs=+#1 zRM?S~FJbpg6!9y@mLQXGXpsrQ%r4oGgRP2+1Otz8acTom%lt_vG^Cd!F0sOdM-nn@ z^H67S0s#9Ya~U(<%8T117#-Aks3XduWWFY8COxzIVtTgMC{1XF{^{0Xcbn=)qwwcNXSG@!MT+6k)KK^4qs(UgL^$x8*PdQfGFIXI|;ARKN? z)9`e{O_o=rN=nQ4jtNQ$OSep8bn1`0T6OAAnhNZk;>GAoJhDCEBTr*%DIu*VT;G(wt5Duho(-O?@@1RpeMP62AmH z{6m#~b)?cG-HS6cW}l@j_%r#mnWnHGpWmYMJ-*e!uloGu+IhmNpB1h8alNWn=#tsa zrHzP*9TXi*86anwqX;-qWL%?FHq!e|?#XlN2UiMBqB_GAk*mM3cnJOa^Q1Gwg45vv2V)z0Ynb z+4wnW9<%)i@3Z&RJ=WXgM1@rG5 zPWX(PmrL{r3?__$y5TvnDeQ@>Xi^v!SQKphhUuQRJ!MWP&8zE(3*8_Lx##Z|f@c(lz$dfv9l__GK|OB3?p0qmMcTS{xr) z@=OQm;?Pio^Xu-nTgEZrOyoz+=Lk?o%aipZ7rb!QxK>3ikZ0$&|AW>i^H*NXF|x;; ziVlJAvhz^XR`DnEkiUI^+r$6q0Jquy#z1#4mv>%0(A@Pg&38v05{|ZI61{t{>E1EU>FSGS& zN2$aK-1(IxHOJ-JK)ZnnE)Dc7dKqmoXXWp_k{wrIiaIU3;=0}3Bb^N^{iAnt2ig7o zD|T~-R0ND7Zm|95c5~}W{(2QC+jN!x-fr%0MDlibCn(MQ-Q7I^@7Ns$7w{ z_hQ%n(b^Szxlh}?58cN-+wtd&cBeZVUhRg7^P>rQ0b1U5#HD&pb*Kkduk`LaJ^jJtb~Z5RKFF|kye z#<(3)&%6vZbo8Ga?e)$obt#z)v z*nec4JD}SK@CDicegB!8AALf3Pi)@+(KW zbS(~gwZfDB*q-xx(N|L`UMbG>OL-^MU+4vBR0&m8X_YTI8KNxJ$6O>{?=Gi(Uk z35+B+PIM1QefoDp6(JEc9vEZKvk!F36PNwtG5?YS-NwY#IKC&xZ8$dI2no5P|Mvsk zqadKEliZ2zn$yYOJjp%9`E;#+)lqJTbZMHSO$KGl*7*x2gQ5%9`S%~}*7~6UKrF6yn=c+`UBO!=|_mqVZ`{+&<2e>-^mD?iE|hB7 z#N@tSxs|u0{Zf6&*#vJAPiJ-r=0m0?_*Y8JKC-wxpjk*_6TDHu95`(k%p5WZfBiwB zd>7|~2mMP9b$4t0hvI8s#1fgLSI>XQWFN!+pAQY1nmH_JYTv`a{>2Xkn7KyLW#TRe zGglqv4(YlxoJYbJ!q|G4+r#l65`H`SU8lNT8t+gRC9QOIw_ttCIi#!3{(`C43hDoq zQ-d1ZGu7?o{OBQn(^NxTWrxQ&WZvO!eQJZUNct7@;Z8_n^Wk4Y8cZg?lr)&&n=m9v z|7n3FIdGc0%(?m%4OANNo;$+r;&(fuIFfaB*1Y1Md<0c};uZgfBceF1W9@(=HPF2x z2{$<5Fvgi27z-=>+m3X5igC@D{Ho&p=@tLOBcnXJ_#=-B$~pNcx2yBFS43jHQs-`_ z+136n)PeeZaFlzX@)&-!+od?d{U^iSyGIv?lFt6XW88{=J)U$cRQ&Q9rwr9**?Ozu z;toSB%W}h~oOc|~wu7;;v|Zp~U7Sa(n|!XuPx+*$2l?C{<`cnBh51DA@4|d6f9NcC z-@U#Lc1x2x!|uX2+%f+4g>Ke=cCK6ESI>6Kvtx*PJe7Hxrw`AVq`&KI2A8rj|M1zU zA!Do8UOC&HlxQw1%UIKifQHS)(Xt}AJMOMl{)TJ(&*!=m3z7GC@a`Fl_A>5<82lFQ z4l(#o?ck5w!8S;U7uylID{&60$yl>^pT$#VS!;he&pjgHtg7^TEI^Z4U+EvZz#Z&t zuk=?eaQp1tqbg%H6LZnxStm_jddlKi-mC>Py@j*AxeFGZvedGs5Uws;x_J8B6X!0N z6ZtQy^50wF_DZfG-1A%Uc+x_*(pgjGpS94vRj6uQ5ERBJ7exrZ_)Gxb0PB+ zYW=OJxOFPIbbVvAhUguwXy4$CGAh3FMzj}Ye>F(h2nZPRN zoO<6q!yR0@3Rtx7=g)A@uCO7s{B=JsaJBS+^D~yi`?lr&Ki4v9 zZ#vxXxsLZ|hx-SvL-FV_&7Zr@?Ozx*Eo1FMycvtnShRGZH*4|Yg^TxB`tX^^`@ry- z#QW=r!|TaYBg14KA<7Ikybt0zID8J_eJJ~?^~XAlM}(`cQnyZR2Ol2#Z4WcnFcQ!B z2;h!9N_!+v92U~#*Y}8k`H#V^v>H-IQ@-^L92(D*_&A<8JU#R~fy5FoFsw;DllN#I zRa)uv#N84-calcWY}|8rPT(mo-*@^cOJ>dVjz44XO@yT>-;xTk4FC~qdHj@7)S9`t z=kd(vDKDS6aK`kda~CeyyF7CwiSgT)M?a-Kk>@0y1w8S}ECgP}b8_e(&pT-{_pg7* z?N)Lkc{ak9&p(!G^k03*t#MW!>A(Mw+rQT{gcu3HTf{wi=1gzFDJLC2Yq5m+%(-*s zE?x53k$(QCZr5D>Q5iGbMglP+M77NG5J*S)=l;~4Jv?4RVW2&{XyFq0W#NK}vlh%* z3hv_JS%1vK?#QumuV%8CKuhC(i{*!inmu>H+@-UEjFwKH($vXMn ze(hHI$35bXD~vfdppLzXBxx1F;wz7f)&oi{h)(cZmIwOxb}aC8d4D8oI0;~DM&yer{x0dR(izxHqI-4V8P_jG@+ z$K7*!e((5zcPH|e?2~Nl%oC^7a{r^J-F|-A6K>Yodc5ECgxj@j;0z6|`FEc3$2{R4 zUw#*06UjnVhxXnC+z)t7A!Ejh?dNyYTbc!RN=}9stYZ9=I=P2cPGU{iQpn@gZWS8n&Q7ZX$Ct7QGoYJS)P6!}?F;zrM!al&qc zr%qoyclv^*Z7^`Y-}JQGy-n`R{R5wNcWL8wiGObUSm*ioKTWmbExS}LTXMX2V!%O? zWs(jNtQH84Bm6Sp%XzN&3ja1_OuQpc@t=CeUEo+J`9q&|FR5I4QpS=3b*00Uz_*`w zdpDjiYwnyAmU{iXK~dn%3MA6Xll<&+?xgaG3o^#$06gzGW`n$Mea;=4i{dwy1a^xE zz8NHU57K|~oV%amk9gi4T^@!1GU<=l$RfjcpNH`YeCT<1Xt8F0L+*6)^BdjHey5FC zj|d;S5e+^{Tn1VygUrv_$gGj@t}nQI>wV-4Zl9r1yjd`;8nIz%#xU9uG)nST`orno z!c&$?(>Z?ODGO#Uu{NIUcirSpJ^sCuGu9;H>_Y=2WFmMxaAORf04yep!uJQ3@QL6n zfJepP9|J4j_fGa--sJAx6vgXDTlrs1Hv*uy+TO|E_a(P!VifNt;wWC68X|dmCI0Pu zT^k7WJ8lUA|I@c&^}Cz=s#o2i<!+RZ}F_@GySXXOAhdFd&%wTPkPx+6^=Y5W3>Oj5ql(01b3xk{ef5V z;55%w?cl3{<+q7EuHk(x&$oEwmckKF>$iiYpx}3~l{qy?tTFFKo+xn~Bg65J!x|Xm zH<7fe^eCP@kKCjEFJ5zZ?L1}r;w7`jYO>Z=>fYpk{8QKSN5Ag&_8)lN-OoSg*KRLF z^R&}~@lJ4O|L4DU_t|9~UP59vd^C(2xBPjA^6Yn7#{c8X?ge|+o}RIm5aC{s)s1(& zx?e?Fe*U*~MdNSW5mnY(tlMSPFL z_h_h~%D?bf_ey`wi|(Pxjb~=8LyGwfuj%{EfE^{b#qzc&HSgc}s$1WPC?s?mzB9>1 zj6Qe4snbuKJJb90oBoNciBlV7sH#DTHRkaCOv=AJm`5#);0xNp zX9G*PN8v}~mTWqWr#LZ!SAO%F+tl2GGTDUyWw=uc^FMsW-KVmR{F2ld>z}pREiaFB z(cQ}Z{Ns@Mlo#Fp{wteVFi0=+Ki=%N`14f_2<9g&S^UI+<;QQO;(yeMX&^Nncuw0-M@l~ z=wcx-zh=2VW{Z1vuLixtOe@er5H@- zGA*5UK3cBwALIY(O}E-vbiV)FH{B}+)nl-fs_+lOH2_^tZ;CS}FPX7$=B)Xc@7hYS z-u{ihclU8tEnoY}@7=eZ!ZW1b#Iu#>FFfuA8LIg2(5%o98Dy z>v^8#+064ho<{%pw^`DevckXeZ7gwTt?(ax8$;JUE7rdAwp(uhpKe6=uhEFL*KBit zS>mkumVfMaxAR_eTWs`)y>=0=TGk8dF@Dpa2B#jtIf8CEZPt%rE)K?Q6g0@BWdyM*-<% zmA!?5cpXWOQK2*WlbG|kW!?S>?7MfuP8@J2((W%Iu#lnPh_}Vqu5CGD@x33+rY-3@fSbu_6>;1*kO*IN zvaQ}n+V1tcz!VBSjnJ_qPTVmHkuIMVHXyr=`E!PHJ@VOb0Rj?ZCAG+(7X ztE$gOI+v+n${`;tbrWPmfX<26=e0%I_I|~*25`?E)f|mEj7w9qrZFk!#EOC8o z+=M)RD++y2fxG(a$0s`$#tve|Y&(;ZLALW86?2FFjv1jmhES5_lnISZ{7F3bOUwGv z`%K&VSJ{cJvn}hB4z^WuU74NO;}vv?&k31RVLQjs3{#858}v#owWwL3Sueg*tND{9 zFL&Nlghj1wbuYYSC;t2ZDhzGwKEt-1Zxoa7`7?w_8Q$}Rd6U7*%HV1>Q}}kC5z-%I zBv?x`EKdUngS1r%NmEH%XnhQh2^`oMk;uhg7ws?8KYHv6K82DOmLnDEw?> z?sY%{BrDA?BH$bImNo7@zvW|hL19@FgGjw?)n;5f5eLY>GnuSY{--AG#0zsVje)GH zj!D~jgW>}UIr8ejJNH-8uRk`(EcbmwA*WNavyi4H7UnGLaB^x)K-+PkU^(X!D=`9{ zpuZwyAhd5WYJP`0H9qbLPtJ=Xug8?njE!gy8K$w%ifx`EFme7bS<*p~>G&ck|2fmL zUKKD3pyeTS84%hrPh`tkEtKsBbzML?oz5z^1r&;qeaQ69d}y==L3-Fo{9Euf*%I0h7HRfL4}9&x}L9gm^{9F6wM~HZZFx1 zU!d_UrrzBwdfFK;DNw84xV1ljfSUJ(2-D+XItp!`PIH~!WaKV-kj2DL@fv2kBdZ-X+pn+wHqAR4w;P+Z}!h+@i!^*zO%vsfXO3 z*oo&M*+p_+NL|lbXIXz!f?jspB4R1p6djh(TSZuj<1e->4>@7?b9Q9cRo@1?fAshK z)V-=O<9P-Ml~W_uaLQ?&!$Px_xcX(wdWYJ$AHj!T4(xM#&@lHl==jmS*<*lEaktQd zv3}43_X@_U<)@Y zkrJs!x3#YzMCZ0SkFA~cclTC%@G;C`yCK;mc0V-^SulqJ*cB<1hN5 zZumd{!tLfbC#-$@3yu`+zVzs*y+kV5mD%HC9@!IZJ3BeV5Z&+ZLkEwzDZOb3vBp>~cs=qduNH&%hDo3zhi8O3D z*RJc>L;=yS=l)JrcIKD}WYj#+smz`n^XS#3Vwdc&{aR!6y3uJUIduP45ATCU#+hyu zA%iCI>l)6T$j}JTbCR>0)$y>xfvvH7y<Pk2z`gPv# z?6T^bSc=+hU8+aM;Le9NR^J_~L6;SsQd1d!qF>`h-JpVd{6ADCdmk=MF#6d&lU~I& z10s;|{DwrQidAus8xv(#`h$3yUdiT)d*e}*<{^JZRWjH8$ausXliBp&V%4nQJyBKh zLw|KuvPa=-L>$;!t@__M^qE!u7rJLpI@sPlSyMiTvZe^Q&&`&n02TqLjH*XTx>5ki z(m#z=ss1&md+MhQ1pWcl$>zd&7h2Y8vb3ir)BD9p-JX`LtJo{%;a%cXTj?LhP-;(4 z_Dp{#=8@Ywv0M7)mexAw?8GkVrLk^w|C}gGUDm%fMxn>ClLe(UBa41y+&%qb%%kCQ zyR3XEjo3(w8ji5D9{PW(PE-GqGwl9BF5&{~V)@rbjwgA0I>r7_g~dwz_ky znw@%8rCx}2Z*AX>R%$^k%T9x{^#(@o?zC42swI*mk%~7uDr(;#OXD2(PGcj#3w~kJD zlo(UUvilf2UH82f1{Q$a>)onm_pz*HxZNWllhhSbG{Rr*-7dH)PKufgK;Dhz5-8Ve zUTiN(cCV8(Sp$G1sauPC1MbFLS@i=gQ9i(X?^1DItgqdU)!ot?V-ei?>Yf!(#62dJ z)L9k3i~Dr0<`h~{R;m4s<7)rQ!s_g@)az}@Cp9uw@7itq)=8FaDP?wrAghxg%bF1cGTl>wPsSCG&h2!z0`lfWI1yYty)rhdoaj;gmz{$cf2;f|`e9(QZ?wwPG8 zVVBiCPKKWR6PL!w|Ywx@+$!4I0an$QxaR zAN^cVr&Xx%uabN!#_ zy%yYJ{Q4)KzHIC&Xz+@D^^Y|gXJYo#Y!XFFvdd9v|9UbDn@mUS zVW=^?%&@G_1dIZR={}fQMzQ9(g^&lB5ynGmqU_OoOK$t~GE6FTon2d+e!I@{*K|sH z-EW8#Hlo^HOUu$Fv0C-H(B38eivK^Il0ysMJr3%Yel@qmZu$|d(L-%clqT%P?Wky- zmGQC?v^HaeU4I^nK|O8DK7eLK7Fk;s**IW1-BIS8MGP+P*&Wd+X}~sx)g?O=Zih%4TfSKgVe)2dB<{(pEbCIWv7ha}|1B&+ z&A9uJ@(u9he)Aqiqegtj+3v8hEQ*EhkB(tU5}O+Smh&;0dABSN^S%`yq$XcuK#Kne z9A_`JtpKXmAUF`;|?sI(hgdehx z*4AypJw@)@YU;0IYH@a`h)g2A69w3#iPS3?1{ZfChJ68j$C(UOyJeyXII05=hINar2UXl93DWTA0wY<8zceWM*(=L@aMi#mD~ zc|C5`v0L4fRBR29(E39AN?z{2-zC|&%jjQ4m4u>@OP06%oK_#p`F;hn^M5Y)2R0^q zIk%qcPisszr*hv%g1qM!8k3Df>}!JPHfG4guaH5sS+t;{uTz(~P zd(+=Y(k@3p!KWit?IYZ#y5aqp9su+lP?Jgy^h=tOO~YpqVZ9>EA;JVAYz`x22S(To zMno&Ukj{vH_9C|-eU?AHDVej+@|W}KzH4m!wJ&$Or5E>!V#9z;(^KE|f7z7mR@jq> zrI3<+n%k@D93puDMuoJT;}DuoNB+1!qWr6W&mk8C+JiS?t$>aD{L^lZ@gw9R2!wZ^3-D12_q-{g&nHTDF>=uBG7+@X&3#xr3GB>wyudDQ z`npfG-QdQ6z=Gu{vg_{U+a43V`yp7_0s3^S9*sH~{rY&>bS?%rot!L7=P)Bgzot8# z>hiJbAF#PACcCPMkh=#!|1cl^2LuxRcD>!s)s^%|s1e+^Ia$3PcknCVF{V=+wLN)s zve8O)C`KX1#IC7lNE6S*D)yra1qV~XDQWN7t$w@V^Sq1n54zx_Th)=-2(N>N;68wkhnc zj4^2FXk%p1sK>o?gN8{qkl^mCm5gPg@v(HreQCPt@$usC&86P)ztIa@;A7PPbW+&^ z${NajME3On{U_Ob)SWw=h5!th-Lc%my%D#WCG7?0Y!e_0-?M~7KV#v$X>bG*!%ULu zE)wB)R8C#0o}vg3w>Qv5WhpOgqWn?7{ioP9Bnt;)EZT%epi_XvV%d!TmYXFK^-3Sn2 zbsO#?R<+^wYhv|K%b-=80wc8Ui91BAusRK}h}A^`BCM`ZqE@V~0<`xptE`yp|FVUa zoiQ_P*}-M$mtr!@KBTN;#fF$iV3{2sL%lt=_$4TW zoSZeWjTL4A=_Z8XQUsp!kX`X+yfQztYukD}5_%LO>T(`!A7y=ekB8fPJZ2ASo9pBD zpz`UEY(2E(JYnxyJ}-wX6mWZ@to#Uo@c@aD*j`TzhTow4!~xCeZv-BuKJM&_vh*2o z50;|Rm-LA$3#CfO{Wvy`rnD4Qeko~=qX?I!sPx&C7yZbwd-XqKQ;);}Q&JoEDw@Zb zrS@PvtC|SUpRNpuZ&fSCh6Z7D* zx@)1R^@@3TbNb>a7qwrsoO&_x!6R`vibZwVZc)+l$yR(86FA+nPB*LW3S`n|<#<$8 zc|Y9Saf`<)eiIu6!etHGHj|;al(F7<%Fd=21d=7dzw@L$FkKa^fpb68*i9puc@e+= zEwy#)Y33B%1NKZ-{cay{gxj4~RcY0oL#$OMq@w)y)O#K7mN_I}i@LNCAk|b>emCxI za=&LcmCq)Yj!CCpwfh#TmJguPg3h%AmP;KIYxsbn4MeW^AWqCrqLln2?(vk}61B&m z%=v{qXy?rtNI_9^rr{n?2-~v^ptU`#6|`s2p7pqk?b!?vwP(BBQF}^78??tX#}n}N zngjl+ZPseb?$PcZr_Jsjhpj-xtv)*f(Y_x?`+l4U5CiUb&@OpWR%yFu>}y%SmitXY z&5o##9>P3)KU10h>=6rhHd>!~QqfH1kTloC7obxsEw!(mn3pP0bP z`l%F<*!A*2mrG1S@w zw4Mz^igSSNjz0=Bg4~DO?&jBYxIFNG@Omaua^wGlli>f$!hp#`+2*@dQ)OVJ;N?3?!FOYsN(xp{sZ3YegcQ6FcMI-73bu)f6#P9*K1wl>g5S=?Wy{jb%(3RM zda28?4p+sB4h;xxCUAqwzs3Zv`qfwy2X$&nuZh*zw6IH54D@fACf1!j3}+K{toO`R zwI277ma>02UEFaU|2~mN_#+3)WIa$>Hw-XYPYJSCN|hh{SPA@>Wm&2Ad8d9FD{N2$ zGCbU=uI-|U6m0U;8<25Xg`G$q$Dx=H0j^ zT=B}P?ZniHC{Cv!mHpPpWnr*5C zQ1cqjLWivZm=3V~C8joETb=uaif8smS7hTZ^1x0!fo%Ju;?6Dry+q`{XM-Rs@yXeY z<4UTs-Q^6@+WUgMDe2n_hq15P6o25;=XcfaBLv;itVO<#HAX4fWPqXwAr#g+=?6jA&(;M z=m`0ryOjFM{R(6H&I7*G-|9YTwqLbJvM&9?D;Sc>$h~Lvul+%LuprfGi9baleUD|o zqC)c3wf>SllDUFR0}oYG;_IkN!=Av-C*VbZFAtf20qd&twTX5~C$um&%9;#%K8q$# zx1=_c!S^?^;rtzdcfLHgf^;OagZivPS^xYSvDw%icmI+3`E{ZHLyvGqKXjk^5K;l^ zYZpH-)FqyOK5WTc%5DQR-5++zVT7!nePh@Z#Tz_2to<1P=M4zkr7g^>TS>3m- zw5%7=B~#Pw;s9gjyw%gWy+yb_qWUKPZ+n94Gk?ug=u>x4Gz`nt8m>wL5nR5{FRL(5h?YIFBmisi@9kbT5 z9wn9H{1(oBO*s%Jmez9KwP{XG-=Ic49!*lfSb6;ns@I8{+7;Yd9Cr0v=hA9rh#HJ8 zUjiGCM3%_Ww~Ot)IF7Q>bNZs=-Ub78OXKckyB{*InJzc%=+u;w^aF6{9(x;q$f@6i zDF2y=tiK@}`&HX+%w3QS*5Q!Y5sg2e2R>oeB&RYGqmULl@3A8==@`p87P)6a{s)Hr z7P#Iq+MtWjFllV^5M=5OxfOPvxBng1xZrt{-OPQkrvB^tv`G^Oz<9rDtWi?eG$&s& zG}-*5?yLQ`{m6s--zM}Qw)2W`VaB6#cMn?LQ72uln@umm!~)1~-)@W=?~vkF!XBW0 zcL~t3mk|ITg9sBc=w8eEp#Vah*3&KTTpFBiX-<@|K|Tg?PGS>NcW9X)JMr0L9Abi) z4N(0I#|_BK0KX;U11Qn}Z8BJUH=CwGmO{d_GN11qG^BYBgG3vk_n3Z6_~@Kw9izGW z^57UvGskEW*CXSFbS0v}?<`U;`i~7u))mU&36WaK-$c;}D`r?Ppvt?NK?w!_OMs&x z%Cpl*j#rkD=M5^RmX1oidpp`@0rZp{%_|wAJ|JG(BbBqq zh*Hd%qc%$UhcThwOxylypb(@b9<$-YxEzWeD&c&Q(-;0s^oMf)D4&Bk=#m)sa0so1 zY+?ufo;{cyMQ=#5g6b3QvaG*;1ds!H->ZS4BjEKGR{FPuA#b0_(j~y2if1`LRIN)_ zFg8NDr8ht>aloo1*!+;Y$=l|A-?BdPFCUTg3eowe^7#3u@=??yI{(DS((T0I%hAcJ z5j1ask9bIEdpLAuEW8cic7RZk@!htPO>_XO8wfH$LC3KSbPG9fU!K+N5c+Z-MwYw) z&Z#8k_JUvqj3?2TNBh!{U?6cPQjxQgWOFT@?)+MeJxn$XbFjgi1LcJCcZe^=l> zlO8f7ml?ZavbtHvz}=%V|i{Z zH1lXT%Q}y09s=ecpuh~xj(XZ1>|6gfW z9v@Ya{NL4Y&SWx4CX-19a!p7e2~0BN0!3sI;tFyI%ArC|4!I;Mc&x!iITR6Dwef)9 zNsJ1pXi!`ZT@+CiTyfDA6;VM!W$^%ZQTJQ@=7l#9{C$4EU;i=F)m`0PU0q#$*DKPW z?`}e1=H9OQQhr`P7Hryk7xqBF-+~9JPIh9(-07IaQP4hm9tTP!^^O_Qpz}!qqd>!% zdsOed4vQD)W!WP`W|ZjZS3}e`f+N{idAPG=UgJc82KeH4}B6yn5Pqu!Rz)we2)?3gywX(-eXNIPuo6sHHca}V-kWha1 zpM4&>E^Rs-Yq{vkwCSQpTNZZGwP`<9sD&uHHN}+@F_8h=5vQkKLXeT z0PA*F5r#k0={=SBrg&P_AEDTuIFpl1z6cz_mfc{KJpi^$kaJTr!r&1Bg~`rEh?q$J zyXDUQ21G0&U~VXWH2mw~&)T7+BOk%o&nV7htUV2s8H{c6-wbYsDPW$*-wJ;f{AU~j znrDFHDbhRxoK2~s_sf^S-!Y>=f&W+Vr-iWRbrA>K+u+B+wjL;|H<9Er#=kDl#1Bwc}1SyS*LQ%o?qgs<-IRp=NCU4hbf^MV~Xw#T_x%B zAf5^T+tV8|Z@Tz@Grd25<^o(j*xd-jju7H5`>2O}2n_YfO|S-p*6K2l6d$Eg2d%SU zNy;`TSmj0PI>8sA)}cDXyU}OQ3P?&FkhRJGG>}VjWYeE5Spf1QlcX09^F;$#H}44ymdg21p~@iqXg}}ZZwz1=-Tsp)Ntb{!PfvYOFCyD; zp1$$IM2vvM(&$T`Fp!1Vh3MBOc;!HRtrx~i#|~s&`eb8D@Xs%h#cOhozW}Qh=w45& zoMQ>kGxJ4OQu8%)VHtqRd`|U@=bHwyiroAOlJv&+m^& zhUV1ns>bq)LKgrBsLf!S@q~XNy7+fLK$&%ywQC?^FaIR`h~5FcZQ$%+$vFB*<*!t+ z)Fyr}hSpyo&F0hmrac&8P9mU`^0qw9w_~DJ{+%SH(bN3pPf5~zFf45p+W!wVxQIx= zBFGZbh=v5Fq)sfyeD^S9cn?j&IslrCs2@@zFM#!|hXCHrU*WBYLB{QJ49Ojd`kg^+kC7w`o0IMPqrLKNh??D}b8;NUPrR zktE#!b(h~Dqs5d?ark*1eMsvy9F}7d(F6Mii=V@UlKeOg z13&pYQFIX6QqGpOjThta4LCAZe#(5{X#-Hz8_})Puyv`tOev>3F@h`CA%*t$hiRh> zJ1b~3a>@`OqU^{xUVc2d{xqY%)ZMGiutD0Kb^ z9bq`3%8GA8hX+9#*Yxk)(U=DO>v*h>Wb2VO_}gX1(WUkuT4ilQkow`)XwbbBR0(30 z*T6~jUMTO2p@ItRxN`O($4Z5Oqq6!chJh-^94e|bp9@t-%MtH-HGVr%9m#NU*5HQ{0&w>94`5y^5--kN3XF=nxRFeJM0AvD4y4&H2z;C*wH;O#Ib1eb(e>psw z6@KBu)G1(31rpoGJCH7YAVAY4y`9eYj%JrPNuHgS;M7$?*Q6v9328zM8iJhe#d^hb zfn6dr{y&h9p(w!nit0@(4`L@E3b+qjGWGsdjdOo$39!NBiIYa}1z8Ym0Xe~dl}Jp0 z>oZF|k`k3J?(?hxNlJ=7$P!@x)u2pJvz`dpq~^!(mC>aQpqC)+U^FJ#4?0cfE#9nl zclAJ5Hl25)S_}!g190%12cr`5D6J8;AV;nLS{{EfzDsl!audRHq33Et3y2K7e{=|0 zlCt@ri&?6?n@<{s0X|p*ez^viW#Hf2qGky}%g+Yzq#6=}))#;&Xrtga1x=bkfGKAS z0GM*N68?5MTTg%~XFCZnLgpH>sY=LD@_!BU^E*(?*AiciMc(@!oaS_)lmA+7+_5o{5daiJP!{g zP9LkXb+a^Z@K_$OV#Tpl4RkDyOHj9VX>5rC|0`+aZ-EGT8$XD@LBT@YT#*7dM$qYz znfGZmKkzlk-U9#|9(jS65OV~81J{g`W2jyr`(gw5&|&T3lrO|7mL<=q-Os|30cT+e zz8l`|yicjQ?prN#%zn1_DWr$~_7DjFi!*UI_YBAT5h^*RV0MSiah8I;XB5nvhT}m1 zX;IO2)+AA#GpU3%=z&T<4AJT@7!SuleS2Z##Mp!Mnq&&C{Sk(fn=z7kw;5V{0TjRY zP1WNrBOMoY-tB5H7wL4Pk|m)dO!+-2iIXI1dIsH$K-)L>a2gD#LEfs z)6sSIfU%*)a-nk}fa3%##WmfV0KlA{)b$~ZsGlIid;&g{9r0v%s4sv*GMHdE_q%Ss zbp*>uz7MHP*DX06Iw9A^kB?yC0shm9cCWRdh91Cn%cGV6D%E|+QVlBEm6X7zjl}Zl zV>C3TYxohfwe1_Qj5`E@*ePVuVl^Yi;vK$GDy!zpOGYm*52YG&7uHJLEer{Y`5l{ECl*%zQX4UFu_3o?X<~PNDt+QlE2xT)Ho8m zHsl}Svpr@_Gcwukck+ay$fmV3M2w5$lNLl&+`p z>NGNF`|*FExGyNqr@TNh9V1r3FDM=bU?KpbxIRj;=pusTEdI@9tVdJ#!C12&6eE$^ zf39Ia&PMj*CL?0y_lTfVFYjL=>&Feia)kEHFeBKr9XNj)jxqcQb|nBnf<6V=1X6m- z;bX-ulC&Jlx%3Qy)l^%HqwZs(d|7>A0&Z6vjsn-cZWyqmF;ab{Kh}|0VG3%j0A7uu z_bE?9c8s;uDh(5-JHYfikD}XQ5v_29Fq5aI45wj#A}2%ECHFi2(gfaVG|s9QLe(zG zKyrnpJ8uHe0w69?Nq0{KV7fTFEyh%~XWl7ILpn z<{L+|o0{&kWEAJLCAzK`s@}*uuA7ZIiF>&vOw(ANYn~;*^Dd^*PORsoJyaRS3fwxJ#Un2(|bd$WSXW58a1#o%4OS+@vhUMxT1=bCUEl;+$h(4y+1XXhxTC zmLRoy(FfS=BkamJS z&hoybWa<-HU6!3o`-Qj7!F5oTQw#mH5bN+=W83=8luKMUUY|2nOfeyJYRj z7EBN*`~gwksa=^-+t9VXz+Ne~L{J17QCHKjsW24l)%;GR!^N&p0Fyo9aUtyr1@QUN zb_j`teK7iIx94StJuhJ}%rp{aUyGc`NLagFFgxsmnKr`6-{(mT92l_e|Hfq@Vd@JR z+OI(}(FPerT_aKFyaY3$LtjDElj*cW*#I#fm8}Hrl~>8UoFhGJ~WOLM*9tDkOqt z3BeTK4uRamY26LGW>@Ft$NQ4SN=xQ4M;g1l0!Rv zN~?+=+V9ADs>;}glgG(YF@?xkBnL%kOih{#G;^o$p&VISgS0px7UdrAhAbYzh-jE2 zT?d~!e+|Y+s$o!8=wCfn50;UwI;FF$ZtsU_t`2{htkNuO68T}MO-);dO)yG!ldRCc zy5&Y(v!GOSu(!PhwoeIj872q$_$&$eZ%3-xtr+(RX`ignzgm8|ByFdF{jysB1eOGZ zc^D6CwfuW#Xd|=WDB3TKqwjqU>-u zej=gh%J)>WnM!Kcwm~(lO;)~soU=N%Te*HJ|G177^yx7NO4<&#Es;@x;Y|6Ju>e_Z z#M$?lk@&aub4copKZDDBUp@2d{c>xy&|l;jRW2H*!vU@=M1{ z(ou|N9VLHiF3w%DjN}`wMBL+W9|93A?YRuVuyQTWnt-~_ngn`s`Rxs?TW}{K)DCfz zxufIczXGaNz3zQTLmJ=Nz*6)~ggzPKhZ|U0FteM1%^;dpc+L++G}9MMus?yeHl6K7v~k7_5Yco4am^HL${NhJe`-a18HiK* zV@u0G)W5)@X?Yf3F_8r>NTtr&Zm3u@@5$)6C!?ibk8=>vt6$CVqGhLH!dS$_0O{BB zlM^v`@0$cmHgAG9^ID)51B}5Gi>|A&ouKW$bq3YoksOP5 zYy>W6qJPotL2XcXT%UeNZX}0aL79*o&Xy!vu9oPFO_A6^ z3ibdB*-S+Hk{sW_zXyKhsW?f0+MvfMUkjw!|6v4(+!c&JCWZ*m($ddFpuA0|1srzD z-*lpk7%nAsKpyNoE}Oz&1PFsuQ2H26vk(I#4SdFccMjTA+^loiOYqc}G(4;qxTb8t zEqMokp)|P~3!gF$KkgTw#BCiTORWeR6SCamUTCTx!HV8V&1ERMf0wOvS?z=_cUh2;1AD1VM3;Q~v-wS_U{ z5%KB`ZyT5Fw0ochCc$t%O)RD>vNi{kSGwz`3DfIjty`OtX$=}m$;~@@lsxo;nY;(m z`|m%6tSMMQog^l-qH`8)#X|uC7NhImk8OlM@z18Qv@WfyV1yVAs20oWObmbl;IKKT zhFI*_vgW_EWlm>v<@jBXVR9Tr{`Nlp>0h7!yl$1db_c`jko5?(r>4zX!@CC-Aw{3$i|PUjVO=^`DLLQ192sdbi@*%T;|Xd zZpX0}>aN6l^pnM5o)_40=`|~M=^{vj%#Al6tG|n z>}4Y?Fl#QhS6g8Hc`DYi)Don4ys>0BlH&|IqQx3y@E+H~)_}qIRQyUvZVg+?lOCP6B6eq1W zFjYu&+_Wnv7{abX*yA27##G0OPv z!PvcnBr*@E67GpHZQ<+KuNnM0USC{W zS5aRQsi>@s)K;|peI1+Et02;NOP>80Z1%%viqp7ik^N)V3JMZygY4UtHBd3jwm z4|mb?`FL5kB~(^K8cHLTCELWCztdfu4z`^X(06_u43qo7eo=_D` ztry@7B*=h>!2o+&qQ;6Qwn4&0iHJH-FQG&Ym1?xqPFt(756Y-uM@4zRzqR&0b0&a~ z&-1qL=QT3xve$k6@9V$Txpv!}dq2~+^#iN?tLt^QesQjD{x=nKF7~dz+PS!e?5io_ zm=Y$EQI>PdSm!O@y}tf6?<-!xb@Oh?_dK7dLtOti|8r(``WY{i@w!|$XR~He%J==8 zm-Vy0?`1sS_57^kW*j1}*V003Enc_p^iZ(HqkvcNJ(ogmF9rCYr`COk|NQc{Z@S0S zeTvPdwC%tY@%)UN^*qn@UGlT4ooQ<&-Sj`Yr34*N#B1+UoJROx)=Z^E*^bw4^UO!O z)JR;@PG(Nh)+?MN9A~aG&&fHJT+VT_6+dwD<(!jgao_HKl7Cs>bvkmre%+0RhsT^! z-HAp&`iKd4C~R52?SR)+UtWCU8~@~zO_#j!(zpJJ@0Q;9#vltKJ1RT*Sgoc zx4AprUGC@IFSuWH$J{Tu54(H3zx8%|+uggo!5iJb@$Paz?cMDySXTWv_mkf2`bWJP zcX*X`J>PfxkNTSXFYdb2*Pr!@!9V_^SDsuw?QQRP_j}%Z(0#vqllxuwd+uMlx41jp zo81q(f9-zEJz~2%>TY%KbU*HX*!>9qeISW{l=z3-ZSL)E%rbn!-R<6C{(Z{*8~1PB zN8AV9zjGgQzwYjHKj8gC-F?D+#Xq@Ey8r5qyZ`Ji_;>d|+;u;2f9Rh6BlnQI-K+lD zUweQ2aqmv=?&S|Y~bPF4>)Ny7Zt@R3KH72Yz%ZhyoX@JL)7xsgl8xKrcrvRdR;TBRF|hRdc4=+Gw~EWvH=sWAxOvN1Vu`n#P?|skN%(mAti)yVj}tQT>!@U))mQ zN0m){U;r8BVyond`O&q*nVOvhb7|_P-smUHO!X3ETD{V=u{xzn!6|9|)HXvjQ^*s` zM8k~MyP~l~FE;nEWm;=N%gL<_wP{gKDtpIhN*otM=DH}ERO#?rEe+8p=fR);Sw z1CC=0vJGzuvVpt8`xB~>y4b83e^~BH$;`V|tN(;k8}ytoPxOFOV};Gm?zd4b^FF6; zvJ0y7%Ft9@RW*yftXkCanqlQCWffQ!78R@tbEe?2RX4~*J(tz0UXWi2WO#y;G66%% z6BM*mUQp7?Tt1tvDFLTXC3vgr@jB@SCX1P|?szM(-6$=yDLGP6-eS;r% zRxK1r_NLC}40ubb2BP}yiqm7puK1D=wB-AM=3o{&JLoT4$$B=Hyu~belm|%-3JH=9 zfpr-cywQFCO8??A0B1ink52e!h9?+GfTU@7W^@B~;g|g0g)dBB_`*vre7|ibS@*ii zwKOd};~(@d))XC0%8b}fOv}8~DXSlrq*68ePOOuCcGOIc4RBuqE)pO~;lh*`T+{&! zU~^+NX8@z9CKy^V0f{X1wASymYv};V=HY;WW~4sD0{;CC1Z>9o=wGjga4n`NWI>!S z8s|@?U4Sn+I=sf)WS_S@-`Wg1h~GSNlNA|$#z+6&m;lK1Kav9 zT0R>}PU8^Oq-C{RVT}U!z8l_lp119YbHeJD_l-d(^K7BUBd(#%jBX;k5;>)9EgkX2 zPkUYQ*2lbJeBrO%`v(s@ z(Ll6UBf2ymzuS#p{=(UNthHcFR2A3}GZa^%N%k-gmJ&dz&GOv8$KB$dA0K~@TV0*D z)PR7~h&_?=>h+Na26-Y``~}Jlvxe(ZB);t)_sDz-RPmSZao;#6O@&z&6(Cq;$cz{! zpzG8Kv&I*FBFxwA|+|V7->!=WcRaVNMF~huBO-nCgUua( z;lbvPVLy$Iz1CSJm0$IF_hT>K_~h;J#OK}FOVh?D*>hFjkWd6kMohX?Ip8YWp?|8Z ze8@f8?4hUBvL4?9OW&KgHFC#XgLKzh(|R+@BAE&BWBbfp$O(wqkKA8bzF z`$YWhhunq7-FZL29s<2Ub9Ac`D@bI664;T8_KhUy(n6Pl&jeeSf5H8WWOU4Z&llWT zNA02Y#^~sMS95RlzN0xk`b7Nf7u+R%z|J&4Dgi@9IznWKPxzwy{x%9U=l$as-67Mj zM7en0m)xTPu~&V`eJ8=AUvf`~Fa9~|z_;R`{oFmaMR?CbZ9AuMzIWgw_kF4my551? zx9u-3D_N?{29&Og*MG%*mpAh52fq3hcdiTU%-QEIjc0t#J)Zdbuep~spqFvEP{7Q| zpqWS+DdhAlECAjCoei?03MM77x>AUL_BD4tt#s{k&#?`M@yI^+SntUv;xF!VXS-|S z@9lF}yN}0RkGgBU@h9TfJnBB9@XWt=zfnBM;!A|8{6oCpG51LCnaAU^9&_ip=f!V* z%pFj2lw|q5s>`a0TU5@A|L!q&kroXxKgcuS78de1kGUtfe!ObGyJ9v{q00Wikm?hN zc~*3JVt+ic-wo?WiPSe=s$`@!22TBN(0$tG8hQ)6KX3KNrDxZnk2sh(})hyRkV~$wLc1F zj&_MWw>#U8xMX#&2rx{ZNz5VG@p-l0LoB=P?UxL8QGceR4T)~LmWtc?Q~BJst*4*b z^38ilfA^kk-#`6$?|b)b>pf$UcmF-xPCn!9-V^t1d*F;C*B;qaZ~tZr-=see>CbNc z+0LKJ=e^SRP{r$Ld=;bc0TajuozbrE#rL-YfgMJf>xiFjbx-%6yeGc9mYL%{ePeuK zn>%kITwO!UM=h0yyi%`CJ*j~fqe9dgJ*44&b=wb4JH-xn|2^B5uUqBaF|uvrx<%gO z_iT&T4QkYntb60&yCsiF4!L8*C~Qhg0-l2m+Tl9-s?B6_r=PyOd(UrJ+y}kVa{AYB zfAw9q?HAvT@0fJw_qN%A>{jnz^hzb0tW>@fZ+M$~k~=Sc{QS%Xm63ZHr^N7Kh}OnR zaNA<{H804Fx3;)l$4|1>hQjz1j?z@p%?=G^FD#UyXzl0?u2Y#?+FdSg``87Y+bX}w z-M8sg9l6`S;9l(AcyBzSIA5y5jFTJfg%MBn{4f-vJ=+ayZQJ#3%u3PmKh7Z zWpi4~Ma$%?-pQ>6sCGF~aK!E|TIemJ;0+5s)SGfXzIdTGK>9nR34XoMn?pcmf2W{- zpKFvIKU!iGCNDnwNN?6LlC2^q6`sN1EV`l>=7LP*z?*lZAE4<8b*x4xD6N_bXBz?B zm!60O6K@*uf|aImW-Rhgms0%v&_m+R40yY9i0*KPja84DGG0XWchrpV5I-iRk)AV^ zp=ieq633&-AF`f_q8@Q7o-u^vwK_mqp@wb)zl z(co7XBm1|;KU(Y!5_Bx_<`OJhVp6ZQsdp3DDSz2a`D07GzVljTtjS0-&PvZk{RS$$ zdIAbY^%cNaCG%aEhd!Ic60u2Q?umXCcqR5M8|BxvI@-keaUdvOvD9lnr(`z9?(Aqz zqGgncp3FtNbAi`~9`4ZwO~C>Un806Z)qLQds#K}dpsrK30ZY-5apK=hx|?UPbC^#I zX{@mRE?c$rSMF(=o{YA?YChFX`_RQ1W8FMuV2hCWVO|f~~wx4yL zcjx`vesR|8+*7xmefFx-O`q1##4cyX7hRZn8QBk?J+rW1tF_N7eJLJ3KQn9FZO<<# zEO&$?Ze`xKaKnW&4yW8@H$GyW*LT}#?wt6IJ#J6@XY0I{7boQ>r^Fj1X(Hvxiaezi z$wvQj<0EE8QqC)G`J4vzHLvu;&t)EWA+O!>%YNs!#B1+#bK90=TH_x-?ahcUy3T8h zU;eaLiT9l2<>Q;Bmag38wQak*J0HLGI#N4Yylg!7v{#5fyxi+lvD~&j7nI|-J>8Tt zIm3^6^R_L!=?L4;#M53o*%_M}|EZVV_LF1U;!pm$J6olb|5|3i$Hz0!B=qQ(Xa_WK zX}oU2J1)L)!mGG{8Q(GCEq=qFOGY2*3IECn>sxxlcPeAJCw!yA=eol?_%P@_(;aR% zCARj2f5CE1y3%cKWk;WKkQ!$L!4&VTW+MMg5!7g+yH>d*UidR_-pY-l$5CeE3ZO_# zTt?GMuu;q*)n1Sq+JcS2#;EP=KH3=dE~{kYD}Uzo)B)V5&_SbpnOb-wBZ%%N@-D)U z5x!p;6Mikc$wazq>i>%H!%98dT?;>;$S1mL;mwL%hsEwP!mpHS;hzzHeQquMpg!#A ztA#HkoNTLw8vCuJ&mkNRG3C6K$PHv%Ot^;@KB5|1iJV3F8#;Lv;Vr$jaH~Fy^WklT z|3r;f6YeIwgz&jSExbhqCWyR_@CSq+QpO%e@kfMr63W)_5aBj`xV60&zPnCjgvcp` z&oR9B5RNncKPDWVi)wjAc&iF;32Wio2_K@_%L#YSu7y_+KG|OjKdJ&_^J`J&ig1VG z&oS!b2|rF(?`3Ofj)eypzYBYj#}l79`FtMtc}`&(MMits^( zbDJ_gK>b$|PV(U>!fQ!iO*p~^-KhfKV#_q4y=2_3j9rv?2jMUopHRj>)7GC59wa=M z@aENw<&&Ys^sTfm_4;h4Y6yx@X{Mp^v)y_v-)O7Q|ARD(mYj2{AMbdQbF(^)6*R*%O||N*rQXZ$ry~ zg^0ZR!Q0H>#?9cK>Is*t>_ktvMBy_%;Ry;Sd%{75&-H{y4;4bET+rIIuJl>1)?~RP zA4VvH&(M8z|D?2$38E-LEliHQ&iL)mdW+{D9el!#x-gUcf=wLow)iZyniCb^Z>3gw zCjP>+-VycX!Ksl~tClgauXY$|v@2h$9upkT;H!aje_xq*k$O3qt+m7);0LEl6?Y%f zxy$jU+zz6KEJZ0;OeZ~j6=1XuJ@0O=&K$~|gS^*T9~!=TGgB29$l6=r_yYl;nLHH| z^5n_((skv;fWL#sVE5Kl4kke@!+B%uKb5cFvOlQ9I`epu-LUA&Ch{Y)!`Q1+62ksu4g(!1L~8V^jV_qpadxo*t7~=fb2SKG+37jx>mBunZQ5F-BVcObkNGo zTF{;>0Qu}t+e!^hr&nu_a=}N3@@=b~g%&m@IW%djRgpu1cu#QS#sB&n@07aTEq622 z&w{H$+ZZW`S#%n0)L}#^+a}IlO7IfZ)Dt6aU@V+f!mkM^Wegl`) z!jgBjw>BEK=gurew3Vn%k{y$2V(e#K2Nlr>-v)grbcP&Lj)|cI&Mpn1Kd}3Pm1;H` zJA@6=w{%DgGume=GF10UL!1J4rN09_j`pS{NBY>EPRk(RluB%j2HqMV6jy$UKC|`} zflIA5owuakIy9WNUx)ri_z?tiBUfwYyk}?RzcVOemcKll#fY3&=_uF% zNZj{rCdE1xgB~#mgoR>|3+GB}blwv#)TUe&I5ddmo=F7@r<#f`;AArs4R8h*b<}HY zcD0huzdPvG{5g_MnZ7Ll8{0e=`)P1B06uG@4D4QOhQp9)8NR@;5L7rZXw-#cY;40v zC6jSI7z@c$T0M=i5amy4b!nnbv|vtvn;WNTA(^+4!SOL@C()B8+zmlakg{;Y724X_ zd3Edz@XA;DtxM6K6CODH&l0d9tZo576R|LTM$ zrG^k4UY`&knj-_=u+uOLG@)V9iw5VOG}%fyA0 zHiRINdUnjE9nPe;D@5%dwFJViPYwOo3{!kxL~N;ZQ;(DkFed1M9yT_*lLUZZX>vAr zr4p&2I10_qlT}HxUPc1QOuz5sdx@-hH72$KK=*^A>ZU1yP4G%n5c7Vi+CpcFZ;WGq))~EbuJHgsu+ydrIP=1_wG*uos4;5QpE&YG)1@N7<3S zmCg}C-HR4*=C8sG$cvUj`5KAiYQf7Ii4))@jd7bnNXUS_vb07lE6PU6hwXFOg8V!0 zLUi)(vK(P_ZaHthvF}C#9Hoqbgm{0(FSQ$%oF5r_{`u!yHkY#)ll|(ET?P;gXikUB z*SF@4Jg-mXf+_sQF3}-dEJv6hDTS>Ta>Z={gQ2{5Etv?OOCaa@_oeLK$ah;p4#maP z!&XnIw>DUk06r&DnYD4{XE^d*VgUp=P9#Eiy6iq3R#Vv>SVbjI-O=&@>5IW?x0n`K zgM|Kn1FQM}3s}wlx54UMqvy&55X?9taae?a9|Z% zvXJ`+04s*Smju??&A=+{3h}SoP9X)>__2&%w&G(7tKn?%WWgIcv<1JpV#M;Mjwlf&*NgtV4&OU zg2g+fQBRSz-?;G?TKxXD;~||5W$yU+=@#Eea7Vd6?+ztTyVv#zSqI%4d%y*mPzJm` zMUko7p!xVsNX8~5t=zp=1S_+~g>b!0GAP-R$X`N?IKUtD_m-+%3AYtxsv_TuxFsW# za9H1SGnUKO(?rU`t20%@YjE(aVc*r+jdtK7C);)~Wb}ofvUIU8{2LRwvoHKxmR@w^ z`SvS=)^qPdCPVPkzpg8*UFY9*WzaSBy9a*t{N2Mp{{FWXIadW;=iYTCg@#VLo^lTV zTCc3Os$A>Pb$hP=`tUWs+5d{ayQ ztN50J-{qcWINWI|)o?{MFae|Bzk7D&bJrp=%Za2rbj>0gp;6%0!P^&bTVE2lP&H#wmM%IyUA22%x{Vc|y;* zh9trk>l59QXs}YwtyK%bRn7eGlVs(|YBMa)6{uWgcpBV{s@R{($za(m19xr8|5>+~ zyzB=su5|#q=3hzdv*cHlZ@4~eC2lYK+4@XmO2@ItXQmHHHYPPh7ekV@7nMt(_p|v= zRxe|H*-1;rEXM{)Z!3s+7})({Nqo?lS38p!H&ktO?n+>nI6#iUqiAezoCyUf zf3h^+MFspX#FuxUZt8iz)u&kKi{ zH!_A-4mcEb5L~2dLRlnhNHmZ$@J#FHIeVoyU@m3H zMWcnYF%nD(2589cJ5{nD>Gkw_^EVu@dZN7$aq0f(xE$m2v3SXqr2htV?%&?t?97I~}$pHUM5>lwH((@rj^Gw6*; zsnh6|B%fFqHh}U{mRM$02Ifq4r@4Al?Xy^sq;?^xui8N&^cW(5f|g#ZcI%*~9CV8S zU=UbWZ#75X@5HoJMMGYNfh8A|!Xkq!T0lbkPH+xt)FWzC1g5HOjm%Lp`^X%qReO`n zvq%QDYS<`)=2%@oCc$8m`3jPiTD^K~l0J$w16-6O+n7vKH@zgAItWQNbudV>sp(@$ zR%(rw7m;jg*%F!VpiP9SZYWc4(3f-@Hgw*>U_p|1Y=ciOYN)x0*CKWzYuW=At*D)o zZ#O2ethNnFInR;V`hb!v{822!voC<=029Q8EuaTz4DQ~Mvz}aM{S_c=DX0Q! zLQ$rgjaF=AYgSk)QY}UE{=B*f2Nu(-9BUVu7`327ebrI|)Iwsi>dR6sTL?_Xz*HIo zPzfw+R6xTmNzu04T-Zw*Ej&%b_DT{|Y#ML)Gx3*TCL2KurU79zNRefR*vQS~IBHGi zmJb%4W#`sm=LQipB9(_soKjLI=A2bsJv9r_2s&*{LPUaeV-lhgq#KhEnIPSmM6Q0= zEJP_N(3nL+QN3WbSw*ICHj~n#&JzN)@aj$w0s?~wUQVzeSfK6&bvPgaR+W1^ZdMtba?jB6d8O0~>7j%=7#cJ3 zbke2hgNVM+wp|Uh2B*Vs`%z`4s%}rK0)lm&;B<&5ias>R0!Q;9iY_vX_;Q? z2pT{%X6lnMld!h?e+1F)jSdlrXftVKsboTis;mhuctx>+?pDw13=J{TG&B-hHoqbb`R8!R`IXh;A5^4 zE%5cFL9;=r#0Jyxqi-UPXRwnEVmfo2U>=Ev9gTA-;kGrZaYQ7Pun87q&=KF1z#i@G z6T%~KO8)M{tOHdt+mMM$61*IH{wQLFqU%;!FISfL8YdB^AjJf+g)lEcXG%`Piv<8g zK<{IHkWKgKoXi3tVckHXfy(=VN^v5f@^sp*o=(eosC#o;2AGjzWm30o5Sl7F1Q3|J z{uEPWT8#@b+Ms9@4_7j!Oec31>MhkAvokK($hRYX;IGE-uEhh-+$Ws=jTs`7a5$)L zbw4nv#0?ZaTsv&<6dK4J>3fm>TEg742B90AFU=82LF-^`XxJdA>42p$^JHoGV&*9y zN5g~*0!6^1NRcY|>qT5M5GXn(%dMGaSb>r`Imia{w7*?O^FqgyLWU8hZq%*tY*QyA zt}fM02aDJr93b*@GXquG9 zXw*!T(lE6GwxmXCmZlCu+9k`0c8HE>BV+|&G-ll)Hta*2q_sSHSuzJBjJHKTZm;^% zX51e^h$}BQng!-{%`j~fkpbq36qjBrYl||Pc)s4;qLAwa^^ge*hc)$wWznG=w~T*e z^T^TvqFw(2l?$S0#?P~4F6BnMQ%Qh_<3;(`AlX;=~6fisNBc3ZeBINcoV zuJreko-*kCCLWyQ&z~ElgeLknqHQKPzS-1qd|aF3SC7sHSBm27D{5b^qyR(-yMjr; zLUk6Vgk*j9Moa>*jfdxO4uZ+ddNvI@`NRqanM2u!rd6uO#HPb8#5FQ{WK08?6?d$a z&67ZZQ?9kBNl{xR`e#jNQu&Q2Qr%)&g8ExgqM^Oxe(8PTATpwauI|wRHt9|E5fG*Ug)8*^m=+8Ywi&1Zf#Gh9aEHwlO&M9Y(2s3WzzX z`KXf&El1S$X^^B?`AoVc#wh@|Oq>F+p7mJc+YaMmHt6r_3FjMr+!LOp5SO##6prD|22KiJQ-+~8i9_<_1j+@r)k8{eb&n2q19_+A^o zPVqf9{uZLUZ1l}Uci8Bgh#q9Fw0<7Z12%dF(S3@p8G8E0TYvahf4lobJ=X<*n^J8y ziI<0(SP_5W6u&P%EA(63<6?J(-#3H}QD06P!cFD*d8VF?;U%XJ^IxRVw9QHNI@`AS z4YfJbv{_&2AJP|$;xA}(+>W_*v#8G)iD(iMv%`(H>t+^S81mw*+~-QAb1$!4BwOy* zULw$G_+w`k4-)ikS|_rx+SvruZ4fKT=M{M`jaF+x8vtZf@?zSW47K7zG`e?wbyjmW zavBozYKJiuQ-eGnp1~}WHESxXGYZaT0Po`r`|8W_7=;kfpTytJ=!ZDWL|ygD>gLRS z%A8A{4uIf&YmJ01tSCoo@d7YY(6p;q{j?nET6~ltX(G`MyEa#dd>VDmO0ZWnw7E2G zwYhpK;$9Jy#9YJ~G(gO~9nnKxvd%hJD@9M*B$HZ)8=bo|yic7T#HTTbZz2?XN)V35 z$!!vop?fBy$DBMP4B)oQC7xn*L{ptZCZ{1)jw9N+b?HV<_v?u0W};cki-?zcR93E02R` zKbqeX>P}F7YX3^en!BEVSn{l~-6n_0&vcidPiZH)@O`cXUKI?vyk^u}!33Q|`At{c z6=V&MM>Eg?KOpAPk2X9y)G33NSuErmV|B)bnoNCUNyWPjhdfALFjZ$r`hq*O@0EfUk5|-LkT$V zBatJal~XV_KlK-#1%8Ubq?4Oz(Sd?!(}~7sWF=<935sYC285t7)xb`5sZ$JRajxKa zoGWNm?6g78$|aieWY9EanDs2qgp416WUk+`l_+u+`9zK;$w2u@w@3|)&6h;RkDhy4{m58nbAIg#fXFrixA|NynMsti-t=&7aSG$4ES^3_&hBb?-g^rG}#Tww2nKN z_o+;MCy!DFBnu9egE-cu|tpgDnyKJ6_ zTAQeYqFWD&n5d=k$}P4SA+H(B`3+Bg&^0G(3qZ({_)%8kTaO@&OJ4Gl)n#sqn>(^< z)}FQTu@)?#X+18avo>ysmT8i}oVDSXsFelK(7MZRU0V1xY}mVGb=DZs%d>FFevk&Q z_@a!$L?(+sg&Ceefg>_zV%0k+1maPL&fD_AefT{OV3mL{8--Cl=}^ragO~)zYUv4O zo@p!1KXp@LS!DQ+04ZP3=--)(UU0LXPP65Jo%vnms@@?TwPs0iVwA^3hgO z1|F0$1H)H$M725h-On-MU25TON+lk>vNj9RDE5#(eVI>v3ACcJt#AOMmRyLyOBCnCut6a(gANRt@Oro zcEBU0HSVyQxR#p@kjEQKBESrIz}Z|{;k;j;KB!O8^~N+|$+LwvBkH>$21-x5vL}lY z`}j=J98(+mDGO*l_^dlLJbWdduZzU?+~h2u!$Y&{*5n`v2L!$7AwM~$Lmg(7?E6tf zBJnic))Y8xZPAgLH8av}(yuAYNiYSalu%N~qNi~kMc(b)I2I6=!T{()XyPxN6P}qm zJO@EaR#Mgy79pv<;Mp*T1=;xKwf^jSRw4t$7Yx6+>NbwuWjihETvq1@rX%5iPS=b- z#EB-sLZaxtQ;S$cu_2LvCw?b#VKqo=yaFpMzjcx@2w=PdouHx<6Nb~JOYlY3lmF=$ zndQXh7Jq3eE-w`f05gr3-~nz4oA(Adml0CG9#jT-FOVU8g?BEQBl#LvVY0dw63!sp z!*wRTiUb+r3QRVdgu?U@9?I3iMTGb9JtX`wE8kAYRo8Ge;WMQB2@m=;jsKX=adcgzCCp{~5vsO1D5KVT0lMc%)Wp;cLi~Gb-loT6h5>>9Sh5o&dk< z3%#}2T10(B-~_kEG=qq(^|9+_6v?{danuGW#=Q1hmjeQ0@?LBIvZTM2=dt4=l5w+h zU28eW9Obw`pBnUGymq;L7_V$;zjbx!zyh>Lq#pYf(sM7;GY5?5r0ak!HY=^2HMMyR zDr3}DRUaPCoDE34pgvn&idxQW#ojYWeFR6^D>QnSLhArAk}liNf3XG_z9({uI|V*z(z?VZUVyYP( z9|35b4p&8`OxA$1wvuJYn8`vRGd4hzK;-~w`F>`j3Z@&t6gYZql7-C!Y~#%*@)9xB z_`h>H44v1@I-tOvgnD#sheD(b{*Go-S7#x>)@&-mGFNozEGlpzS{0T6!gTJt3U=f zS*}7sI$7iJGBr|}QtI+pWuExX;2}|VO5(Fi*=KuKud&J6>=9BjBQcZn&M&s*+gQ-ayURosUfd*%La3Zf%VA>BMf*C?+$3-y9&7Zz;BRt61lWpyRIWd z+k!FQ;NXZyPiji_wvyrsVzVcV_jajbpL9?y93t?5Ov!@W6 zRoYcRRZ|^~v#1022&&ewS_ifNLxI9jNgm>6_+xU z4AzcyFES6(9<;|x6bb{emZ$9~b7v;K6bARhycoKXBbTCV^emXgn38q0n8sOqf`4Y@ zy9&}Lv=CZVLsM{f0*#oKb3>L>j8IBNx_O~C&)_^85n2@_E4k)x*>Cw?{f$Czjmy}Z zu#c0|iz-;Yvdf)ac?Rsh3O62uTo~O5Ct5JN5l*&XbE^{>&SP{V9LZyJBix$D=tel2 z$LL15BOkscI6vCTqaw`48mV6sjU-89EXIeb77Zsk^F_8gqsd>}@+T>o4bB*Xc4LIw zTs;GoPHW%>Yc{{Rx`w^9sTG^6D=@Fw#BrOe$FVC)P#%#i8&L9~LJA#F$am4};4Lp{ z|5AJ0Kg|YjmIZ81K`kixCKDXFNN?-rzV< zk8%Rj(L}M9MloTh(eX6e7ns%MerU~F=KF--W=r~HiZX7s-L_D7qfoakG>3iZZj`#f zHoo2l7se}B`Y)>+q!KfP0^+>F#GyMpTfv2jglCK@L~goOJc9+;YSNK&s_c9>C51+k zLR*qT!%Fl0ImfQK2f9fAa z!;ompN5^QGvnccTfcbln$ux7=H+ zGu~R`O+Zv*(Uu|BNv({wRt+K9+FG5d^hj%Uw!-1oYU`FQTewDN7Mq1fMjL+n`FCAa zogd7;stW$4uG!9&RhiI7)W17aH|2uXtH>HAEA41+x%y@`yr*0}N#U+?)sQv_^oq12 zCLFAo>S#@H+*Q@LOzX&4^NsA23@BKkjwJmA=LK&%|E{3U@Phe_^Gf-N&kkl@Rb4UV z9)4x@JZ$)wAvD;x1lPILpqpS#T48>0(pA;tnk%dcTB)!{J*)`MqdaW}vy<|{8CMPM z`}MQ?@BPmo|MC~km9&kSepHQ{HiP;6JLz0InBdLR%c|MEWhqN&5`*6Mf)sMyS7fj5 zETl4=;SvH7NwQdu6Qt0IRVV)Vc6*JE63kGV(?KCWETDW2hJGUBCb>&Vnf}Q+WODX2 z)6|Hc5(zKbZLRofg;me3sCx(fU;|{welV0N zkuLH^x^8E46-mCuSOP1 zdGt_{HQ>Eo05IU4YsGOM>kV_(T9)Z|pu9$Ygl8Rtwd^dtU2V-}9l(eebYLwrr<+*I zIwCY_cDEz??zUfMjJ51c>KkiWMy$YC%OKhIPB{-r#xEQ8X4TIWHA$>v<`Ox{#5$If zPY`x6F%+z0i8g1AV;T}ddj|0h;}{coeJB}y0L7hW^jzclR+Rz3IDSZ#LeVfQkj`DM zAl|;ZwpNpr=3fbVPem{nzi5fW_BY}B7)`;%FV>HPp^EBTV_n}QM zizWyvS4vAUEKE3y=46Sm;8%}L35i*w3J0HB7>mtDw`q9gjsD#FMQC_DIx$l`P4*`A z8;8^KknBa_%pqi0O=fS-0GV{M)bv9^ou!y#6-gSp8*WAz`a6(sMB<@qMel))>)B#_v- zwC5B@S82GS(A2q-wzT)cMj3jrMW7qE31qs=HGqyfcaFn(04fGH4m?6TG9g3iHo$mg zeCxK)o#08ZATWn1p-%ig{zE#`K?2WmIAIms8SlmdCgd>$!biH&qwg6Ata*kAAVIXb zMCd(Ps>Z9A`lln}8JCu#=mq9yp&1!QF_EFG+7^cq2DQ}t%r=b^WDbwcwkT(u3nnr$ zWElq+BYJWAFPD}I0qT-OtwS@7u?cDz{X52`MTC1WD9J{(w_M}7m~6C#P-KH4>P;P) zyMQxes@hfNJ)u7(V-FeU6W+>k<7*;}S{L~7!%H&Vb!!-Jp52tu?^Hb^glIG^jF44y&KR(IRy*G;n3b;ga9iHdlkL&7Gdb7* z-5jCY1c_cN#~K0l&`j@YKrj@^R{P<`YSKPdQ`2-W+tF!8gkU94!``XkLEpqil>IE*mxul z_#;KVcwx4qy59DM2x$~EZAkS(@?LaHz_-QN>O=$b_Gr~Bmof7xexMj<$05FWO`+qy*HnjeICUe{}-*lV;1&9 zJll}r(m`snh_>n-2fYho7L3(k;$;zI)AGnvbn8@fG>vYs<^I$L=Qn}Tq*=i+aBCdo z8v5Zq^+$g9gMB~vJ5;a(%6R_H&y9TLmhWAA2V&bkW&Heypa1ud@BHn~PmnRDjIZtg z_`z=<_+R&Zo{YW9U`PiE4fcKHYY_f1r=qqADXH4#6mC= zHy*;tnJ^3kY{Q)^ygJwRM(7a?5_1JTZ&vTbiy zS?x)h#MD7JS`Ozc+*-yhh;XEAEfmA$#6nTRLcz{sfdczj^%S_Mx=ZwOrnL{CF7HrswzUtSM~^Bw|AqE} z`H21c1`ZHvt+j#CM0>4*cbnZ&_SPmwjgMkpiHzKIBpM5YuUUjgGq`Jt2aDvo z#V(C>nTzgIU$xw$MJ>1a5fYdxV3G8OFr7F7nMN9|V{~scj^Jkyg<;sCRvvRD#z=?~ zDk3B-8+o~VvyE0pPIPay(fr5>hJqPtB`F^8GEih6RE zXyzI@(X)Q#KJ&()VeW`D8N*P-yK#pvB8))sWI8kys-Je?zYt0|D^+#UE#@OpD))|(Ce6C;Fe?bO65@AI6q;$t z=-d`-96XRO7jaH8C~96=W%w)DVs08nZqQ368wVb9N^&uVE%OemfdP$^yGxJ*o~bBS zdriH`QnY1@i>6>kXJlIQ!!{DyXbUL{E02S#6+Y|US{*(aph`Z)yqefmPf8)Nv3=Ka zj$wH{D!@y$4pp3LJIJ^4q|~%w8lf42&j{HQnrBz@i5>q*?zt)JV1b)YU`CX|GM|;o zXhfkQ9UBs*^XHUhAM`Tt%;DlwqI_okxwZlAB>qhuN*Mwuv=gKn)+PXDU!<)@64+2cOX=(Lj z;}B7&Ek}Szj9q}M>nk1I)*91R;m;RX4bm9@1wcoQ&$Qo~W33I z9hd;hv`S!nIx0cG_M{S&*)-vDdCH|jg9IMA2$*QC7494{HC{q1m(*EMH{RZ7TGs;D zs@e9VSTS9y&PRSNSf8wB-fFZgW4eIQd@xz=5ZfuI{YZBrA5>R6-%(r0h2K=zx5oLp zLd56&3IXwb3T0ynR(c0BM)-nyUC2(O)B$`)xpUSyKcP_cG`9)TP0e!=rY2PyUx?^G zh`NUUnpv5Bk+&o#S?DRKW^M9(0H_ANvTEoi8w2f3Y!?`;>U_7aA^@5@L*9!iO(~07 zIk_VkT*V#1s-9XT*07@A0Sol2IVm9v^A&UQAr0_fy1>k0x;PuISRhN=+jWW^)tTwykY$AhauI!Um zCk6|5@&ck<7#3qE=-Y{B20npkG15F5?s23u@u3a=8NG{TBiLO;kq7 z&Ph8qK2ki<$FZ3o+{KCg)_kzgm^1#Aafge@)$1tv;N+cQ50JlFU~y9(EdE;;Eq}Mm zF8qy)&&n1T$@^1oI8*F!+8S-TE4bEOV=X(#K*@Sw2|?8Nhvg}59#P=65vvC3W-QmlC`f zg6#y{Q*Ve_9Qo_PP(TzWU4b-YqEG&>-+uFI4)CW0B73zCGAhr^r_x^z+9QdDyN%@G zRmNYrIZGIwI<;tTBW`4$NFmGo>DEuE>Ke zK>-Gh<7q%UX){ot%nP(saJqI$QwO4R2U&N{a#mHC^Vr%0| zj-&+R)Po@AF*$y50=Y@#WhzS;(HieNpW|bBnwt$abDika1z-7%I0jWJguF?jSFoTA zxZz`(6Jq}t(Lz))j&A0C@PKztwbWcV#G_p^!{jLEK$z$a@|%xQ)WQec0EAQT0Bi@l zd7z%`N0W4 z7%92Ws$a+OqJs`T!O^&`G=y4ZXxXte3B_1O3Tb8ufzE^F_hX4+{QY~~zWTTfP`ub_ zSdQ&=m3nRTDJ)oGos7I_2~Iv%U@RDcQ69dCjJ&O&r*TVSSO^B2a91w!uMLrr(1?uO zdhjm8%GVZs_0!LI=2SyBn(JkWj2!AG#~MJ7jx}(HR(Y*ULCrD&-pb`8jvF*wNCZZn z;wjT6fe{$FmM>VqES?CA#VLW2>&O9IzhDJM=Cn)@l;`nIoM!F9@no5u4%>AsbavdU znY+rsDodbcUCL2{$fj8hvAIE6jnsBh4(NPtRkhJlFby;y$0U1xR;Y{*BfKgM*O zFJ>?RtHD=GVwF#yJY~)i2+QcFA)KqpVHlLZXHzdaaZt8BqXF>vYQ!$a4eTbu4DZHC z!UuhMeyz=7CgNN@Kqi%J%?FC;JuxV91N4Wz|6$OtH(>vH8Kb9xe*BW4pFJG(J0f#P z^M`^y3$u13e}YobQtzu}{wNgFuC|}tiW*)5CUgfI0l|t0p79b8JA&W6 zi03>wVEyJj%X+Dk8IR0h&ZBXwvLd z&s(A|*)+cLi8O=Ls*E;j+B%_#sajKN@+_tt=3me_hxEEgnzX9ORK1iT$4a>VOA zGE?=FGU6x?NF?H@Vpbeg?1Ppin_fsFjovPbR6yW;Nw<3mA3V3VL8n6Uy4UOI!-$CK z^|g!~01NtQw5qw^WQqmKnh+UFG;{M@LO~8W)k-4^56psmZmQ;@k*SDaR4}q{kd)$? z5D9Mu8Ss|zEyG#r5<(HaLPPW=3jdcZm{!x6dsh0Z>&=P(?%S=ofS2cmfJmR z3wf1eLjidhH<70S0Lhbv8kn<$bOG|wGR(l~)bpBpI%!!h8~*osd5fkKrBeEN6G4AJnr!B7tKw%-?vA)x{$ixw^G*l zX;x=$vF`Wn*1gxXs|98{t3V7h?{cEy8(Sy%n7R_^Z8GqagInwQpnKw^?J0QOfdFZ( zxip^|!*Scz~}w2sIlP2fOhv_n0VE7xKYkjlWu@)e8C zGOasZmCX1`_WXaSDQ(30Z9LL&=r95Sw2`=Y&mT#BSoF)mI zxLn(4UNF5}OPf>$kM|aaT#ocHm*CaqTcdWhdf0e(YrJjK$IHdp!^b;rvZjrf>$kzO zI$gfVfDgb1anaU*-);x|_GG|L@b<=d-#&f3!(lq!$sk=*35-Fpve{TzVAZ^#Yc#R4 zMXgioEbvUQ4eJDk3JNsIqVsGch;f(@5_W9t5#-bCG)qHKWZe^7Ay*Q4F)Kqt*1hN( zK)x|DY_o5)i=T@n^D`4umIgN(_K-I9JHo>4LB~Ay#KW$td%c??ooGE7&pT5YP&+UB zuci#J(2JgK%Fs4PKT^he<6>mq#_^(mX(|GEc+qzn8D47HF!cTRST<(s**SiNH!MVB z4sW`qCJsAiX!sQDx0jv!wGHzrHaTHc__HTVQE0mDvYlIAI+b~LgM-Wh`>34wm36X|vqg%Md zI~Iv;qhrdp!DXq1kebAW6Ivi%#0`p|U4D#(23cgwMDrSHcB~-!o03g7OgyrYX1@%N zOuw^TqI!+*;A=s+pMGbruxcdfmrLgnyE4|4b{KBh&LSjwWNJH+tc@h*8jC}k35SS> z?{mBOot3zLpW9u>%q8De30mg0c4P7|cfFx$V)Z(3!}7r*cu1>L67g&{!6I4{51ZYL zT%0pls%J2?I)$N8qiRr};(t+dU2wIjZa7u9wNW>He_j_u10!J@JB!|}FzO}fJnn1^ z5vy#K1D(|l(II$GhDRfKn#6&J2gHnsh*W02!Yl+05=E;psASQR7liQuXSyvWhgYta zlVdkdITj>{O?B0gbOkWKuqXVXXc*{Oj<{4^DA7)Q+g$jrF1ATvM*@s}^GbAr_(>}r zomydGo3EqHOw>^e7TZ{FEm)|SX9O`IC~ejXY6ghXrVrGiQ^|~M`^MgxPD%Hf)OeUQ znTZA)-7If(eSD33P3ll?K*BOz!?Q?fE>HtqRxiVnFVWoqlfTpv>xbKNybni#QU9gb^8%)NC$!M%Pzd>a(0)ro>#Hh)z2xl@rY%-Rn8081Iy2goS zUor6Oa8dQevl*5{EGz|oJY*c9|Cs$xW0fSv>qI-Npu|CdRHFYA3P~SOIFs8`98)V= zUg3RNA?Y!N`mIG+uA&%I?^j5APbpX=L-TBz;K4zl?@3b&r)1@f;U~Vr+iyzq&du7l zVaDg@XTy2G_W^!wyeML7|FKk@{BWYg@v9*Q`D%V6w$`_DSge=I1>gL_dcu!8Hq!Q6 zu_w#+tWUC`8w++dvoXrOlZP;P8Ur%ih980K&Fzp}Y_k>Pf6yM?_rv`>sJZ|RbU1q% zwrS_I)&*0%EgzI&ZFu@ZoVn*NPFmD?0=L~I^8v?1dRlde&A`LCSdX^i5{w}{+Cv5nM+f+Y&pArB)_5{-J(Oc_`XLKPDjBFT`RI92S*np7$Vc`vU!(E5`G}-_ z0*vYep23(6ZjZ^D2V*dIhI2?YGJmjg#t9Vc&{cSFqroF|C!TbJN9b}q=?0IGP)*vx zw5bn|u#b!KaHz?ENu(~SCJmDiuS=MQ8Ekd68Jd|48Mr>Ime8TOWyCiaCeNb=i!?P{ zb&^kdml&r4@&KpR>0+$ba%8AmpB)Ubdos26*rzE#u^vfkx|^5ieLb{68XjhkqRq`7 zUFb{F+6IpDjB$B3zqvwf-TzCz&Q)#Q3~Z#CobC)NtvhCO8_$iuAviW|#ZEaN%wado z9L=Khcy!u~S@bq7x*e2R^foPenr6}UVB(}Ty20*tF|YF1N^%kb_4 zh`}iMTSL{60kIuN0@(IEdvCZH%oAFi2-vMdM?8^rMoBCMGsrjwjyyxSuZ(^&UQR~8 zVbP_4Ujr=;dKn4%%juehG@fH(TY!EA9*wZ;G%i%^z=V;!rwbk)njiS0Xyb%I1hopH zCOY$^av@meLAU_gmZa^ga4KDmcEsO$hd-m<3wcYvjwyt2i*-IVAL7&vaT>!zq)i4i z^9osJ5Cn&ppss{&gJ2#1p+4!{hk1kJ0HA9ruz(mIagtM^M}Lk9s|4a+3&>bQWujs6Q5#= z_g5{j?ooI&Z)PXyT?)HYI>Zny_IYRWWrxBgTm??jqg8-)F2K5Vb_%RKjmQ1C7XWLJ z!Rx9SR!3W20#;?F%bhU|R)J0po2$}*>Jot}5SN1LXP^`bTN7NX&I{ps zhHb6dpF)?&DA2|z36D8I+!Jl(-{PMm^Gb632H-I!bCP3*?pFK=QueGNv{j2hEH z|Ana0j5^p+PJrJ2Kpb#(PzAOQm|t)Yj&4SfqwQe%I~_mx)y`UVuEmdDRuTXJN6ZNZ z7huU)0)F(Q_;E_W4}j%^Q!IX*V(?=l(i-3OZtiNyQpLmD?P-adxST)<;1ITBH8Y@8?jY81 zVjb7e_;FlIFxj}rp=C0mB*$3gP*2$_X_Ux7)`Jdr)U*i36(0JVSc!nNL?4k2n18Yu zy~>$umM0+}ZPHQMP=SV6WPH_o`~m-uf>YvM@A2n#tc9moMFBTPkbYVGoH6y; zcNXdY5>aOq*rp7#&AiAciGvca)(GLX9VUZDUu~-l*Jn)Sm{v!cG7xLflA1CGOgBv# zOO^3qGVI8FDUshYuRim?QiIU-&jD|?* z$wiYY4N(^dTa(d{DZS`W-Q+ZQG8P&mZ;peV+<(#SsB3r;%Pm)6!7>Pp@vPB!KKj?| zA913^3=;|(9U9~0YV6C%e(lD1!@9wl%r{k;q^g#Vhj$RCY4%24s(KBXAMk4N(Zl{V z>&z~&@&>Byog3~e1sPnAFrC5v2{NbC6`E$4bmvvnFP9b5q)ar+3e=ydAc=dV8o*MVo6LCraT# zn`>S#G8rW*WOK90w$9Mr2;lzPM^m3ZoZmMZT^-gqgl~F3#_lp<6YS{4 z|7o!E(Sqza$QoYs5gV1;#fNRQTW7{RDx(s8WZz+6az-|J{!e)5nX4am@c9%HabLfvyI}27}c+}aXbj4Yi#rl zM4!)_MY)LR?`(7t(cjwWYNF5C=mw&{vC;F1{@O-`w7;^^3yA)gjjkv9OB=nA=(8qT z=gBTFdd6nImgp~RRQU9B8y$gAMnALhy^23=U(`Gm*)B_+=BB$cJAskwf|L zt0wZJeE2mJ`C&fXXCnWR4=v&NK|bU`9lH4UeE67&{98WUZzBIXln=jdV&nPn8zyo? zIs7JXJ=*&n#;9a|K^d~f4fT}k0RJg5>$Jq|eed({SR-QA1~KD$JH*Vx3}*-$)R*bL zfS`@%t0m(yAd(ip{(65-F{O0XQhe?8ye+Gj%Y3;kHd8v;bQ*Nb5;~dejrHbmGWP$D za9sEY(=iS3CCL~UH{nkBVz6e>Q#XxqcPzeY#1HKB7@}y%A^#;<*7(B>mi6CE0$*Hh~LFxf_}>Ui97J0i1i?aLU==gOq*MSn+>>q&|}LbTULA0|3xjNYCuoP{=b4bSY_ z7{6@DT_;t-cK&cu@Yq_=Z!?dn1*h5A(Y0XE#xS=(LP3-enQMbNHg|C?IM&7%)q*$J z*kCRAyQInfT5!J2oL>uGV`J4?aDk0s)&Fu*kOy~bHnUF`$82y%;7nA6WTJWVLinjOFB+BXgV@VYA>HZ`N_x^ek<+mAvZzl1t5bv{Hznr&s zZ0xvNFvG?WP={6zm{nU)3pUuyBe>*cW7zi}N*dzrs=tqW-tT{?-fh2~SPPD}u@$x8 zL>qe<_gQRgAx2RfOn7ObQgRxrq9Gzm`!O-7UO zgxQ`dRx*B_B#UUD$!QXe$4tfxgr~iF+Bc&sLPjRldB+PTC^<8k;9uPAf00|PdI*n_ zA~SOOX$Wx=>PyW-m)uu6T<$9!F84M4D4rGS=zlc zSV=PG1E2Qi$Jwp^9W$FG?Q9}t$8Q_u>$kW1w{gV=4Ig%C5sMn?e9AiQ78}{YBed|uhURmq z?kB3x#3LymG3B_6#i7AGT9UXve!n9G7(L^M;Fiwdk zA~9kznur7s5KzJr2}2_$(Fxlcm?pJg!sJY$TJompA%JeFvj-bJ0?OIE%#nb89N+5C zEy3CK`5r#UW4HR3o@JjPkwMIYvDH(BGT0Wtg{6)Ge36h8!lprD3KM(GI>kTvxIZ!@ z(cNw50(O2->GrA4Z$Pz=SKRJj%5BW;xBI=mtQgmPl53VzfSaoJI~vQ+kJ+>sME{>7 zWGfo$E4)37k~Ih!gu*PWNSRhE6TdH>n%pfdOz!U3Oz!(TlgTX~^=B^DNuc-)76>zs zacn-*c#~EtA%hS)NVlb8yk%?%_#}NzBj6JzqY11InT#f|8aEkDV0F-BG=bFtlhH)L z_n8bqmHowI9E=SuAJgx8bA&SNCuEl+<4mdu=pc(@kc4_pnJ+lroo2ca_0+T}ZYbwV zm&9uTuNgzySrRVLHCZu4?f*YmjHPrlwHn6wI<*)H5b$h^GJ)RdsH$~%(0Dr$jHV^i5*lBld-5q!{U_bm}XRTmPbk@|JqBKxGoZcGm9s zJ8xpoZxYX5m&|srw;Vb4dhPkxg^Blkw`PhvKf+A7gq7ibE-Nk2p|znb!1>Suc&;kY z0!1v?B={?&=&rVs@PgpdBp~77i9{);t-B1N4;dTZs`Lsmj3Y5eu_H+>X zzKC{R*{<1rAVQ*hpJciH5*%Ioq}y+@&az|&Y~1eeml6`?jpMLZBy-**T}iUOvSix& zdn<}V0%SO%sp~Bz15K5si%EoiD1`0%P(#r3g^6HdU9EkEWSYQoLZZ(`lwkZVSdcHL zBzkR<5|rO#BT7*IIvY`f^4HjieF%we%ZKl(Vw2K-R|ODi(Kuja31L52aiM=}azIR1 zu=`bn>uzKBs|dSqW%sLyw)U1nM_D})*WM1BD2A(>idAUMxGKWcE0e?Gf+1MS3eHim zrB2++)tb|2mEBrsw90NR7%d`7_D2mCXM)0MCD34%-CAg{toTvP>DFMut11=}G49=H z9f0zwiO8`GheFnSjl}8yPlKELqw390k@U9L%Q81gBWf0F#S$LrIz^<6$Oki56A)`K9@~l z_VNSaX6BS%#irJcT zKY}L~V=?w0v_t{7H$4G&6^c@_1MZgVO3($S8#UHCGxZy`oMKA0l7GsTZeiT|4I2j+ zam%Gj6?4m*WopgQjHqEo@K724`o?5SjP&h6alu{5iw2jRlPUoSOBg~gJ~Xi+cp!n% z8M13{a-_8v{KS+GEhG>as`u{bIJWKXslfdSqqC^<58i zhgUuKAJZLIy%)<-bsg(}`t)^w{lzUWzTkQp^blLy@zt$={>6ilnNgIhGO2LD`ef^* z#*P=C-?R5?k-1UOLeNd}rjCE#{rH}{|FCJ}99QxxA_s;G)+hb*=`5*0FmenKuw3kg zHH_F!4)c+>y}iPJ?qs%GueJ7&wo|W8LvXuZotKlXdbMuLlYM#}82(~?a%`QIZJJD> z;Og3Y;QBixEdDKz* zKy1$f&SVUtt+fZ-T;OO(AV7x=t$;4?0LXE7VW9}iF!it9gZ&ydVaNFZX2hFd=a(^L z)Pt<7i^ZWh5@%)G6WGt(s~9LxxK}~xFdwO};MGqzV$L>}4#7^J_0d6n&4;QO@;vc$K-4p-7y-wowA@}O@inB76D~8bb z-K*}2A9Sx7ULSC;2cu9lInh^}$!Zj~)ar(qowydsIiBCEm3aE(#CK~Ym_9l2o!ZP8 zJvY^6j??qowHfY%CnvsDD{1!0iTi7jW4H8S4flaZv6iB=U*pwYic|+*;nm)XR0r4dYOk}ZgS&X;5<+Re&a1ubs19!8)!ueg z2RGz+x3@faTI3ZyY9+p%*CzM+RbK7gT6GZeYOip()IcFAl0(ESGJv&Md@ zgRUQx9++IR+*6AW6{8DIFt(6yF=P#AzG^Q&?KgBN3n59QVKB*x z=MBvwDIW`t`3QD#2LD)i>>h7S?qgPh4YyPX>8)syMX5Fq_u`(O%UTejpqy8SO5t(5 zx3VQOQSqx=xTPW7ZT*;Ik*XzwrevG9sRcn%p1myyXYkzIlF8_a8*^;Mw|Nh@INR~K zEgz%&PqbumI7V;twzUA~xCf?LABJdy=##Kv(*Lk++;*9F>oQ!+L1wyl>^~})_?2z< zt`?RP#q4fD@RjGD7M2xHe4blNS+BORKndge{CGW=4P%}2Ts|z5J4Y`ohM}>Ml)rA- zh@*qUG86P%H7s*vM^(mJc(sCMd+_z_B=E4Y0Ba+B4*tcpk=@$*I^h9OdZ z2(d+RDk9m*ua&W_l8H!E@*ArDwR6|iJ(0Nq0uAGms=_DIyN5+l{+Ds_G$VV%hNSPYM`|zip8k6wm z9YMbkf#tw3BEXJ;4i8wzy2OeW;kX3hei^5gPuoh(oPRt!4I^<$rZL3IVe1hUmpHXf zsqa~nRoV_Y4>z(|m8f+>szhp?HIh0+#V>qnF0R2xun2XY9b=n`AvW}#Wv`7&>ZrxH z{wXkcxN4L&8{(s4!*N9dpLJ{^!a_{4to*gxg1Gr+myI-QxrL1t=uv}Esp=`UGYz$} zBw35C4l8PVuMEFcfBa6?e(FvKN9*cj2Y_6AhT3m*n*gpHn94R;it_*-ZgbhMm}tal zZN`c_#dCH7;gvcKEyuK+gLpm&5b%@%jpV#XxA-F) zQ^KX+P7XUUhGxMkYFOEn1(Y`jS~nyzs%I@?h*D>vKvg{X?d14|WBgN=u|Va|m8fZ> zlg%@E!-6+cKP$1@UPb!Vjh<&TI?~{^i{swC(h=19=)kt|t6C>b;YVKfn!^umN)8D? z7fw~*_ps~7;1yOHHa_0!N^3q1i`*uo<;Eh5mRp5&_t}5BVM5ogwkGSrVvBXfia7=u ztt?$_UO3@9$&u!>;r#C;TQ4{w_TXaYuyjqN+?qhk+{3j)v?Qbf4cxxTlAvC@Ag;da z+6rsksy_VQQ_0p@#A{tZ5XS3560_)?LC!|&g5!C;$hK%*pq-#~k+SRJL(*>cO4o(m zU7~eC;XcH7iq+srtpP3o8D&$!qOL+81u**o`h8(Oy!pGy!6RbGmQ6!)-pj2xrBWH9 z-H(1ZIV86)L_0_Zs;e`JAW;XYwl6?Ca9);~h>nt#Sw$0vRlq3$#fw&}|BsF=TAn(o zmf-dyig-~bPnWfX7kv+PlRog|crC<m0{&A1F zoweU$*S?4wDoA4vC?P^zuq*pE5C)J-)?%m+el6K5QUOlJK&yva*nK!5?<UB+Hrz;)q1=}=VF>;SyD`a?W{<4#ra(-pxF?c_fXKUh$s6Ja62WUXHE)GcgJM!I`bd8{X zPT2?^H!=eScP!GA8X0e+ay&A|2Bf!$u^>=+p8HH4H|F>&!JjD1ojOq zQy}4=Y&iw7$(7~o?NUIM0kjQY@x}0gaLy>cnjfHe_kB6xT}#%Bl<7P1TSfP5_1xN)N@d7wqd|dSE62P zw%~MaRjtYltey^??P|6Grb5H-z98{tO+DE!ZJeeHUyurnt#Fi()BefnD?z~MN{o;; zVrkewY_kLGtP0Q@w zcnpc+1d3?oIKYdF)w=kc-NkL8^9qH|%W1gd;^t|%`h~08bbflMa7Ir3#Az5z6X*Q?0 zWRb2Md?3Oy*Cj9M>VPynI0e~VD{5Ed6N5^24|UY+6pi}sMorWd4XIT?vjK**I+~g( zKpYFl&8IVlO>-5t0H}RM3(3_%; z!9GC)_xd_mSSKM+AW0o88r-NrEunvkJ3);~sxJ8J&Ib~~gkbrzFhf3_$n}rVurV_j z^;^l@2GeBXd~)LRBZOA8s3gda-2*>)xVdVC|8+Cna1^<68AjSI654_w?FBJT2zohd zR{Cca!aerv8Jli<9}vmL=sLZIPmPRH9L5qjqf|sUKSlUB8GNx$j6I#n1pkl%*4j7k zQxjcvvrQknMetBoDIXeD*ieIBVq&z<5E6<9SAa?IeT8b*5Q^PkCxdQ6g(j)ORimgd zOYuKNr1x5B1s0lft+Oi9hdQ+Ay1F~9d%=`_a00`oNBYWTIuz;5Cxv;uuqEmLUO!u) zjkJfY!Yi-c#D*xH+r%y`?j#_yn!<>lObS7ZIu~0CV<+Q$A3>Gk?T+#eVgz^^duJ79 zDlt7cRKfEZQEW7YF_k$OyS)vWmDqBS(6~eE;}Z`YB-WMP-iDNJZ+15;43yp7THUsG ztlQ9%fp)h+Zd;qDZB4aNPY#=yDUgviX^}{=y}py(=0LK#!XESve-KSo)~WmMJ8E$7 z9YoEO!E252)K?eMUF?4_s^em<&JRk8Fd_^t7CQVOKZOJ=><_ZKqp*cEbZOb9wbm?= zRpS?@M08fFFeNa12*V4BnmVNfoJ4Qo6fF7z6)*ysS;)sYLPKUzkwCb(w+VdRRU3m= z4!#z8MYlwYh?H@~4tfi)Ea-)WA?O7^K4x^3s`0;&gPxpf`i6@2!Nej;Zx>M9^#E%+ z5GBA)wt#nJa))l%mK18nE{}yeR#PDk<303_0V#>wi-}*^X<}vBbP#4=Ff1dYSW<<> zq@|YCnMm;nDq(m(00YF|r2`8wQXI+7K`OS;*#HGtsxGW@>Aym(6o+ON;XxXBC(lQF zh?uK(H5b}%2c`+60@qZdx4Vf3@vcjWY==K2Zj4RGK~T5o*8j_Nq2Z4@W-YevY8_Wh zhH5Gu2f+CSfRf}U zytzzrCSlhtm_zg)`?RJB&mNk+%t3a=X97FG12bdEnBr36IG$tMImiKfQyJOWiQ{R| zkGUM~h=$!-_YAZVtD&G0`v!q%Eg)heG%|aLfyjdYAG9nijm31THyZz1dk)6wZ6r*| zhbJ0y*s0{DuQ5A4VkdB>A0V#XF*>z#X|HFLL`*uHi&?~(6$fqT5{uDAlE{-~BIdpg z2K`5D=34C$cBXKY@o?fpnO2{I;2Y1QkldzvoV69%zA3*oFdtXh*|Rvt&n^1~FPOL? zB6bImWC@CF19X3sIWRYBn5m|6@qDqc2z}XoV@6H;OQE=eRASN7IwQ- z^;EwH2=hsdy?X!D#TiL~SPvTPK($Omwh~Iic7ks-o8csb3>^TE%1!w;f8s#-Pilaj zO_P*gTq~78et8+ocu4v}1!ANa%p&Gqo!@!_={+@8Lv23J#i8yQ>C$3YGmROvmNPXp z5=RGMC{fTD%SmLAhoSUggcLPMR1?60u?sz7^}j*9L0*q!9cFTe`6KD6f>Gj1R}L=m zqKI0Td!&DuT{aw!{Xu%G)oEjp3K>Za(rB?EeH!GCbSXxbrHDc$DJpY7bg_`yI8>U1 zGk0l?pDoZh`EJYL|MJ{fulz2+3i9%(C99EVh3YDdpS(YLSm8F ziH5K&$-E_{4T(Z!DbUR;O%eyo(x7rBZ^v*7e7QG_gGy8BCJ;Y^iQE!Esm8)36p3&{ zT0jlqhPAL@+w)M)3&4;7qzhR8cmS*g)(lK7pWOwjx!(8NDU_uO+gOVtFei;jUuBm_%I`obp%hub z8q~cI$0!*JG?!#L$O63qf%9Hhs0OSmA?7a@H#ly@l`|8uZ#>VFl0W! z5lZ1L$qb8z8a#rH+)@jcCrkDZR)p!TUGB|dLY2U$PeQW`zp4D|&^jdQ~a(x1iT9*bmJ%w;BrWr`0 z$<+i_B5-C~vxH!+pFSZ1U$oPic3Qwj9=CwzAw6jgRQv5G+EC60Iit#C5FbIQlu4&5 zU29T$(2^6QI;k0ZReV=#2z*cjS*tcw`X{<@rJrVt(HfHM)gT(TLd!wRNV}U2Xc<*f zPC6%pSt^fiEup!?{RM7uj$~-x&3QfHABO2S$aGC;?qiFY$n<(|I2?LGE4B%#$tzev z+Y~K4t;k^c3&qu|7TKlHp#mDPw3hPE!dQ2%n0IxYSKACQ%=EJtYw4A&xL~b}ib2T@ zV5`MkS%8byo9q+wD9{NulN}Zo*p?PWU_njK=@^KN!kbzEeeoZXv(Co15+$+ZHTD)? zNS_p{GDynC*Z_d%8pB3kA<-F2{P_bUDd$K4OVv>P04}ve4FIL28!$PMs>{}dOCCqt z9IB*U@|$(k*q%wpYa8!4G(4+Yb3E=<>Zr*E?pA2ZeyR>C&J0oKCuWn~tCKeL0zv0laN_^-4Hb zS;SZPity_In-t1Si9KmkV3yCm52rvet{{oxh(<^Yr%;T0-o*@Dl#q=>J}xTkK(g)_ zE6@Z*A@$?o&u_8qDRyJ&0mGtzzZ1R+P>1_N-r?|KHM9B z@cMn|i9aOEKIAHbP4Smh*%#~bRA`Mm=h6?5tHHxhvyTl6cJk0Z7VP;g;c>z09Z|UJ zH-y`Q9v;@{U`lJbuzNx^+o3};|A)DR?K?g*Sd*NOz|2^awvZeVlv%ezY6ZIh60l2Q z*N9p~5#fh6d!T&~>J%HCRvMPNSRqzmv@XkB>PA^ZXE_7NBrs==we`cqzgVPVtp!sC z#erkkB^-$3W>(a1!YR*y``Fl4ArGkSRA&a3lubx7;g7mZy~y3`zWQJSKpKu6_-(=l z+_eo3w?CO2|L%4{ayz-$OUu$(tidiVt0N9$@U&BHosr57XR^4l(|G3$*`7hyd?h^Z z=gHQry!_SD+BIK!kOMrjDNSHUVFY`|**!2jB>d{nxmVqle=uR49g~|qV+G$``jS?=7GBIO(xV%#?EvQp|!IqB*UVH)Z$Y_A$%0N?5d2^Mt zmYuo0p$uPX>1w3wy){vqTG(^FN)$WSUs%jX`p0*0Km1Gj?G$2A^JPT}E)7FKiv-9IY_nV1}iZ zVZl;m_=jI4M~vM!8Jl|loZ0XvzkpwW`%2FZX|U!gkbocT*&ZQ*R)$1del#43IuJ+q z=K;ov!P-AWX{L*amS`0S~ zf*hCdv&}Mi1>zR;IuEB-1O+EEclzxv&%}<3Ou}!CEGp#2kY&L?scT# z5yKqlFIQ&S#;(2tS0^`%Z?n22)p2y`U!7VY)f7O9V>1X*1*hROC&a=LmX_-ts$-Dk zx+iUsPT&L|I&2y%{7~j~lQNZnUn^MG;W5y5)UpMM$ncxnl4DI}xNTc<%8W|2>;~u> zimK9S{RV5m&x%UIJyMLdPPdwg7Y@L?bkZLY&ii$8bPhvwyA;Gr^$tD9piQGE=0~^b zc`%$GJu^r^=!pw}K0UF_`>>uqq*~Hn5mkr~Bnw4YY=v#kcH5k*d54o!CHJGKa3twj z8`6&oViWHWZo*X3Bpk4V8vQvlyk&Y~MsK;E*ezY5=kb`#(-XswEA>2wj_A1vlX`k$ zqNF?Je^R5+YpeaFNMGx9HyVUbGM+^A6&iV!$yuf2e|T6D<*(q^*nNhw?@y)xdU7-Nt8?ImY!D-(W#d; zyu6BzO47fKx4n9~2mvoWZzp26UOIW%rI&@+CDs#W;xY|Z3x(*(V%w|L9keHYtnb`n ztW!Kk)3tg|#Gas@_%>O=6TQ2ny_@Jl8c-6^;>` zDvDxZyAqcZBE2}SiVw>nQU?Z3;5;oKwk}wOw@n=)9{a)*Nh^v~>&?Ssu8?|x4~vwa z_^?NCT2gu^x=67SqT#TksjH>nGSNkX5g!(kJe>V4L}>=HHx{K9_uxO6#l!yYfrV|s zoUSE6fNZPA3Q<=F1N1*w?#z5^X+nB1VtYLQr$+BTH9oYoExKG5ahDf~7%`06!@|3^ zC+l+uxpB)%{D782fYT%KH<2{6qKqC)QLRy3y)yhUZWR3?WT^W_E00B*L|sR9CZTHw z?jrXUNlZ|I6WxN$I+yxB&X((+tIC|Lq6vB#1bY~o=l@${(ldCbz|FS?<`cB7((+x| zBkDjZlLNaqPG)GFm-}S!3f-q?!LucIcC3k=kj#>g4VLp=H}reW;$WBCYmu6`l2|yC zPJ7tXOFiEw?xhe?m_@$5uFq3)DhugvT(`nu<=_BqS(%3h&uk)75n)6XhEf;GzsRRVJvC?9(d2l%Aii4yQPpFDL%fx2iNwVCt zM_8#y=-6jxQPg#MI`p2cIu#scS$Lj*4#atBDt%bt$Vh}c*=^ChZ^IpXn@ElB zBxgwhV7JY)h0)Myk82{E#?Dw002euHo4h4uS+RofpcCJ9$|_{0qu&6}W%Y^GN(y_Q zPBvA=0nFw!=WM(VU>d5+0a?3vL{{ymJc?s>qx?$fHGu2Y_Ofm3P1cvmp40@Y%=)`kkf&8{O310B4VxTG*+;2t*+ZlQF?^z)K zbd>dXFOdIrC~FggUW&5rElvV=B*nmiQPy&c`~M%x66zDd@=rxsn~O8x1>Y$K4vex^ z+UanF7-&4*ZV53dqKkRo0`K068RKEO1$N24R9d&_=qKVmz9-3!0s-sD1h>;$I;{da zO{ug~_gFeD-ch=f1v;&`$N$|6=AW^nl(nMg>l{lPL8Bm%WV!) znYLoPQF#w4D6a~C3Ovwdt>R(N0)-Kw^xWSlKB%^Z?lUZscKJKoS!`oalcE1y9oEt` zuFUIr02%d}HcReKHQn%1de}grePK^>NlA;182WYlfC4kPOcHX$C;F=->Q!AtS zv!2hg(j-g;cus08tW*}GJSUAirU6TYxp6*H1oljt%FKjVa>ffhzin2+5P~hj9l8#( zy5$|`&Ob9Fa@RFbq(7wg8l9|lK+yUp%=xTv>4z>kHNu{uE_jG;14j;LvCma&YH@GLG!$@6y)xXE%+Azv z4ag;Eu{T$?YdKFmFdgdPu7j5D95gMa@VLF z@P?MIH8nsv6_)W8l|w7Q+oFkdg0uXPbv6R~p$s=&V{+^#;pfQ-ST*WJ4@QCgnM{iK z9O?^6*D!<~;x(q*Ey;w5ge0s#2Cp{>v(KHHJO2}Q)yxKx>bRV#chhp4$&S0zuR5GE z(k5#(XAdk?SL8cg>B4&_@ z+0>oGa3R}NyfR7Qcq>~~=)*Tfyp#R@;7b)_<+STdQkyN>7mnSJ5y*U zn|75ZXB_~m8~L?*(gDG`4>}W9!*8?2cmMi4ZV z6-yxKdBuHVj_7>EhUuYX$0zFQ=2Y)4>Me3Sz`$lWPL>R-d>I_mfFxgGJr+1Go!p+V z2{3IbWXy{$)S(1wV^RBOMq990}k%CF={QQFjCH36OdLK8%es0~uVXWGXD(N;YM>_=(>1_oX5CIUie zhWTK2` zO=_y-x4`Fejb{?~+xV&uDHJ&ZW94((*o#UAz;R{{*o7_XLM2^rJ#lJ7iMLei2-@?|1zMHLdeB1+X2rCRP%VfnZ$S!a}NS(NNmE`;_i3>g7i*nk&v%%D_}En^y)pqB zYXx_HF`#L3O$5jV(S$nAGhlb|BZBQIBm@TNO~(}xJi4dXQG!>9Wqake3x#w?`Vk5F z=_n;jV{d-WiVjeKK<+kj^q{!?UtA;x6G;qO3;KjSSHxF_CF%B)>{Zg*o5eRzwkt~J zM1`>0#CYRxlvB0d!tWfCs_Izv#Ru(a1Zin&iz(QCx8w>iQfVj0QpxTn6Tuhg>#~cn zL|wWjv0>*Sm8LO1u7CnK8s=X;bkw*_bjCO7`LgP>^-6j-or&OvV8!Qnzn33%AzhOX zrjOnDu|re&(dLD)^>uU5khm;|I6U2V!PMM%*6SSoh9N2|buy}9(Whmavk#?8ldik| zQg5NDzkZu{pJ@pDQr^(;8^#+j7AUJI9^i||D)K?k&k$k?w%!mvWxS!aIzT~(5Wd0l z+NxtI;BcDfoi{QLWu#97YC2T}k-@5jvaj{LX)T;fbS$wg3bt$`u$7qffN+oJwVx*G zU#$0wp-OIFxh83tRr@ZgEo{#16r<6B^$Jh${cvpp&A@qp~FU#Xb9<_`MhYE2x!XH$4hwZ2FTWsU= zryf0d7~I0zAi<2d^7j?I!l z@4F#<^#L=!IPH@kH0KtlUAjOLa5Ta~0UoP9BUMxEe{#XjHBWj zY{pT1+h+5Ab6Gl+5HF1kV=+^ytW~)7f}IJOfWVgE`?IOrv&O3Tq~?dc zCwRjvt#(~yc-@8G@bE7uc-R^VnvcgMn1MWRM(&oG1Adw<+n|ECf@-{^kQ4Ro$pv~Q zGe}O#YE-443_oz9chEWHmMHK3iXqK@ic{w+vwd~yLYih*=<;m%!W;9C0-Iy!NJ{3V=M5VQ^*=;kUqquzQvlbWh;S;E8RTJ}(}iZg$An zR27U5JXL{rAPpytiZ|ipRC@v|)qbjD=JGj-X|De*Syo4zCV9M+ zS-FCkl@P&@>X^2CuEEqbZ|UVn8mXwIkgJ`eBE~0Mu$|s`s@GEIjAXu_rZk0zoa&7o zokTd1sb&{{$rKkCS>CVpRTZXB^@hB^B$pN76oJwuBVr$+8tFjsswEq2MMYXqR${iS zWECjI%);_bsd&bOtxF~!Tzq)mgwa zn#i$vOrq{;W004RH$HD(ppDdC=Wt=d@Fca{rV0tz_v<0jGGj-Kj`O&vEE}|hglV+4 z_wLv7XOA5fp79fNnfJKKuX^9~dbSfj$goD;>agncR9oc=IIiiL;f&K$GcRE4LBu^r@I@<2pC_CR2zIaCJlBtq- z9vRW|NzwDN+TzH0Sz@&GaFSjGj*btnKQq-U1j|vKtn5g1)OIA&HbS1zs!JoR7q~bg ztbW4$WiW~$qRLkE>D@S^ zn$QkJ3RgZZI`yWo^Jk{XTiqAl@H2C;cimUR@B9pks$%2Mer9}QW^eq{ljh5&b+)>= z3c*P`&nsb=OUimTjh6$nbz98@^H}(+t!9<$m7F(}^=e)JUUd^uSLoI4zc4Swy?VG` zujU@T-wv%hP={>Sv1mH5f-$1etcGA)UpVDg#qJHO$YM=fG)b+2Xm(xqKuPH=^t=r7foSkU6pc*5; zF`oG~L@@Kkh*d-|M#YHbL<}MVeq#hIFvk=@IWA&n6hS#IVrvvZIW7W@JB+r{LO6ul zh(aN2qKHBvtD=ZPA!5qG?JQ{s@Suo789SqhLK)(rDg|YD>3cSQ;n!xd=P}@adCIiU zvty0RjD5#?jf*TF>mCWU4LiKH~_Cgp92n|9Zj1Gl* zqXzU=W((1KA$7{iVQ;bBD5PN1DK9C9_;_5@mXe}+h%PG%BCEV8$fWY35IK)a>Z=s1 zI-2#Uq(a2bs3aRPP$0TBD$2%e9QYe^hUcw*EWG0xGdle24l}Z2ziSFaSHux4?ife( zTT-z8g0+u@zujTR43D=FuqApkyDK5?P;$_*J_!tkBHHxrAP{xiQf7q*ri$}Rd8PK0}2rwp(E-h8vV^T!}K zKYefb?WfIV&Pbg*%~4hr*VuZ69CmXhO)l&S6xEwKS+*|@eop4-9Bwcr6ZDma+fZI8 z4f~6O-qJ8<%bwCO0Omn#98Pl}?nWe(FGPAhG4t6{ij(XONnOK~${xd7&sbgFbYhL`U zIhhgO^elTuL-^}w&9H`5j}}_DD<3AGLj#8AvCo-FOZM5G?104WGY}cU&im}O zV*o0rT(J%6v-L1j2C~Z)8cOV#OszipnFa>32_XFSb7st`n<$Nmwx=*oifqdJ0e@2% zAw!y=YridkQ%wuP({`Ecv|_!!8Si0Y&3AzB%-0|eV0mb6l)PZaH^W$)AhHtTc?+gEvm5ce z!$1GXo4_oL`LTCoolPL3uc0o#@U(IDy_U&`*btTHuRX1~AyQ~warJ|62=ePso040J zIeGzq{gP6!GDO}7j>X~jcNjF^s}TX@!n zk1V>+nBnu(E30GXW`53vmz!h5^`pJ%$Eys_ z(Z-|cR`6V9(6yyE5iI*5Kg;!VP>>0-=?Am{@Zp`-y=jL0$j`1<&Bs#L{4YpOW#X zeBZ?_l(FI2i_bLP)~oXKKK#!7e=gl%Hsrtf(PPYg8@7J*Q$xpWTH#u*i3n@}?H{03 zZTEsrmgAYf@~VXZ)NikvYu=mBTzw+HAG&(Md!?90!pVvE`^m+#_~je~Cda|c7veHe zBy%CD6meiKezLkpHlh2(%2zG|&y7C>mgtE5x&`(7`K!xczTy!Yf7&&-4Q|AdWf)zfXFQ*IeXq7U4%u+k>#&S!3c9?WmQrDdohY>q8|dHLac zo_*$(mvAvcxdU#!!6ojD68GNPLY7hAP)RS{`l$OjE&t*jgNb6Qbi`*DebgyFD-tv67KdsVizR~6SPc+qTSRjf(`uv{e|%698_f#41hI;s zSVswVM?+^vN_5{@y8Kp>0)5)&N-CS*WDt*ep`>;FOWIyqP`+z+vpd;oZpxa_+dt#( zvW&l&t=W5Rww*oTnAxK{{bq049F6?(bIy%Uk5Qu6H~#sJyE~ux&&TbAse3Rz_LF_v z98K9PbH2v!>bX%(+o8+%t?ALZO8U&)XEm8gP_|7|UQzz}BZmrV*mw4w=n+SS$>bkv z>a>c6k>cJGY(Bqc{#%F5OYivLfnjszH?DbGu$lh%qrB#3_(ZT+(hvzgjMxFb znVcAqKl#oRa%ZzjxmO!(H{*o_uuzD3y|m&P#B9CaVBg`1VVD+)%QT62Hs~q7qs-Sv zp+Z3X*neTOJti| z93h}J+w9}f#$_gir)@t?DD5{#MFNM|+HF=TR65i?DTmk=QVp~@JE>_HhJ6MQ8o*O& zF`r?>k}?1q#wYW8?wW2E=G*R`!|z3R&*k@4`}<#aAH{oSo#Gd*n{Cd{-+a%MxA^*xSlcIy&xMWiYS*Kre^<9l2F&)8CoT z{2lrB?f1ShYSYkvuN=-slk79p5s*h zZ_k~u$WoD~rWY+u+t+mQwZ*++gp|X4dQtw4-~OvPKRNf7*`bq48mx zjo67*NDOZ04f)UP7-6qt#H1s5%Z{Q1MgH*}B?1&W=pp%H&9oa`#*q5t-*rU1xSf$; zjFqo|J5X}G=imY>?cqNaM@vi8Dco&AI|ZE#}${Q+_|gK;B*S^!$c(T^!a%1`qRqO>+O!BP*W>c|ASs^=mFa(Nu+BUUM&+!S8?t8H8p=X9hvX0B&-}yb9G7dk-%^u# zHc8F!-~5zhl9hi+Tu`yZ{F8q;%zPkU_rer4a`p=cnZ@~YUl=)OT zFSJ$me2M-Yp5OGsg=%DW_b2sOz55G#OnPxjE&0Tpp{@A~UtAm|-|)nXViZ_RD_*Yc#XlQBU z%=`_z|MD&~c;}sTKxh9{FfMR`{DN06Da6D5NdynCwKMe8tJ%s;?zQT*Q<7`$>J5Yc zlWGdT_mZi|fB5z00r%Vm?6Lw8+zm#ghvaW~?FjGZ>+_GjHr#u1eg1hK-@Ged^LnfI z-MjK_uTQLeNa-8GyI$geqx@HYk3mILM)@1bpMU*u<^R#^!{yB5~>z(gWr z{rA9uZ3=#>V#GMHf}4fU+>P4LJ2&MsZ?t+pzbikF$Bw)5=e}_PmlKbjm0FNqeh*ot zbkzy!Sd#XypJ?8_Ph{9tWtyA!(t>ox0!XI`qENUz1wZsd;g3@0L>@yc%&b99-qH6x zi9t@HHv_{9DoiG)C6heHvZ9hBbT~TYqbN0ax(pt}qnlpH$do6cPi1rL*fv-~LWB6i zLCn!$ds62n1T8CJ^T2Qz?~Q3s&J4COF9C-sJUIlr^!}-@ct_EeQ(o!8!xRk zUdr3n6JB0#mg!mDV5U~z%K%c@{P3s-Gp){z5_jL+DF2@ZGui|jKi+6QY`m_YhEESL zXX-gOZ88n_{y@OTxK{)#e;O`Hn^t|gDsA5Bt$aLuGHo7jS$TJ_6^)pcKz0x~sQ-8{ zU}>=O?(l(urm5s>t^L{*e|>(S@sIig#YZxc%9@Hk#s$h@l(#t8O{z%XDH$}%bhnAB zIB}4fGqda4jQFHrryiS4u=YDVA55k%#pS%9F!)0+5pBVDAK>v-FZlKMdEV`%FAaMK znUPhST@S+*15Im5YyU_3G9&!OVDs?cPI_Vuk8>#$*uadir^&oG=Hl-br}d(6bhDXj zJ`tYNY=+jaU=m^s6LvP6;SB&htVwa(P5Kkz?ak(0<}=}I&1O&K9^2#aPeU*iH!9pU z)Z~vV02J#f=)WwvcAy!OL-7-J=Q;)hG8|Uk*iWRFX&J#`F}#HjwV2UEAwiaAjISGl zELU^1glR}G+W6NNbE6lr&fxIC`0_35%<|BxI|g4G^Si|SvusJRp!k~3*6~%&A00nT zj5ofgv)p?+u3Ce>7p~YNW>|H$)_wQ!Im!2WLaiy(AvT|?w}qUr=Pkag3CtQ>NmS0v>Kz>^GCFCyvN1JL4iad9 z3j9lkP#Fp>7o@02#>nO9T}X_kwh+@zgz^+>Wv*3bT!yVN4p`2mQWvrdRFvFi-}qR@ ze9?>0C!iNJho>w^wVC;0IL3TxbkBOWP)NJdy|5nt6-FE7_tu9~4>lvbAFK~g)U$7W z_yIk)tq;xRslnq0Md`Mykb(UQ85n(h(Iu`;N8y*(hrd=KZ>$gh!gJ2&?;*43+LGP4 z9zx%?;5vO2o-O@|hPVB=@}BUtv6OW4J>iG+TxY|B!<)vM>i2v{IUD=sL{Qe|{INdP z^!te1p#AuSKK36Hgr4okU)>YFp}L-p>Y6&vRC|BACp==DnK9O0IB{{YK^Lha?0mOX zb6o{@k_DH%B-pee9N10wm@#i zB;vAf#>Esq#Ux_)zMo_a$d{AFLxOGFo#N1qm11FefZ~MluZGt(P ztKf?#n4z5N+~=Lq=i4@hUz=b$1`tsgnK8Le~Uo*R51Lo@RJkGA=LYg ziDqu=mLBy&@s1K&;G@`2h~)qgk(y-AobW`PL^RXFl9WhXSS!y~F5jlVe3EI4NHdBQ zX{PkU!cCLRsQT@cQz-fOlg#PIzV(t(qeKmCR^mj+Gyvl`LWnCI%>%;c(#dAXTdTEi zvN`3bW%u{*@;)7bh&*t0i>8>?lJU&!r+4e7m=oKrvW16clT647iO_HnnQQDGP6X2< z)AdN;@+Q;OK3^+hOtWyRIWDTkr!~XEyQi8%nYQ0x3uI9Da>*JH@5o<$UOCNV_6-_B*r0yr<=m& zL(LI|EHZ+lEQvyvdS&_Gp{Cue32!^p99ppkONTdwzdF>+A*N-znbx`nZJJ0d1hc%b z0BqqR6qNF!>E>S!23axe2?&Q3c0R1wuFmm=d`h}|x;c+RPdm(v&vh?TpX+s9YsanH zj#|(~v>i$JCaf2%{Op5?^ti$ub+AlfT~iyT!|8iL0ZCEH1U+^rTX=ITz~_x3x-bo| z3v7noO36;+x+P|vz>APw7a6&*WzsA)VO38!@^CZ8ObCO+&CiBLe3vFfB7i5IFL3@j-qouJzoR)tXIP7kcaMv9S$bTT+7%*-~^z3y*^XU#UJ zOrMXrOxn&R5_=822WcxDxGs=&lOH}Z%d~{MXPZI5TXK#$YQ!jejZWs`RsSekc_N$t z(1M}iC3DPS1LoUd2sWu*I-zWwW6pOPNuCrIMlw84d`p9Y40_NS9x~S)PWdP8hV0#2 zh-Y*@vfH$<6PWOn-KM#Il^s82i9V6%sk!FeBhRKAkP(tU;$q6uZQMZ_&UNXtH=cf^ zS!vAK8=pSPTvw4<$N-SkQA|mD3D(M85+ck?aQ4E5K=`F&%#&j~{|cuniB)dr;}S8l zH9Vw_fGRpQ@0Lb4wPhV%vVk@|7y&_jTI-E zGt88Q+~gTc2g6rGw0#ZvO5Vc?$gfKaV_v z_mf&b%6o6Cdp|XQ;iybmc*5wc4Y%i)jegGw+&c!M=?%`H^^TZpHXZDwy^(gj8)!e+ zUK+&>Wi}IREsg5?LcYyEy5elCfGo-{_1}NQB7Z=~Bl+-<)i;&!K^XO(;AyT=^eqO)ka*wGr*HkzOOt7HBv*J%>IWY8Z*#& zjTq=k;Ol1|O#%h9$(#YtEeX94r;-!MH@Rr&q|)e_Q8Z%2h3KUoYjynsGt!(JKC-}! zJ@l%-2+L_B(+UyMtL;DN-Xw(0*5=;qVhVPUHa#vJ`yO+mubXtP_EQ$8v1S$40W8gk zagTpa_{H~__n7;`UGFhVQ;X8~n2qPX*Q_?V^~T5bTF^=P>ATs5WPC|O!OI%Gs7Ji7 zD|zoJdGEDZw!^!!cMfVb63T_{S?9LI0sqCgdR~d!tGL!^)Tkz7Y^Q}&#BFXQDqOYD zoHzpcjG92_##&@FF2A|^_nW@B{NAtUIymixZ!9z|lJCF@L6qQ*ryjZE>tA{PyY@UC z9&!$B!F$3>&oNe3q{g1@@Lk=}`AXmCyS6{F!5-eP*{5x46vxbcvdc!2BFh@O@!WIG_dE(6{XW+r&8F62$JZYJ z@?~q^xbyjEtYl1h+WTzXA9$Z>)xbFQH4V(y_g?$-=l^rV*2i4Bo8M=KonaH~rVmQ6 z{Q3Xb*>mmne~pg0o<+81d_s(sM92C+Tz~g}Jn{RUr)_o>+{J+EOTU5}E^vc`yjPT< ztl)6o1?a5a6P`;liTD7iB!lnJuOs>*tf((#1#Db9uO*)}QotwM0OO8Kr= z*X;5s7*qy375h*j(H`a5;?<4Sv5C@`t$Z*MthpZQ?>mJwJE<|f*{i!PRg1h{)5as0 zn!Kqgpqq`q{3t5zZ1qoHVs@L&;nsgM-x$yXVuMkft@k;nr+dQdK5o9o;%xtfSzvm? zu1~PX-XH$z6Wn=`vKpb4`@_>dXAZBZM#QG<^)sI}=Y@MeiN>l;*zape9&?R(XMGbT z?JwPnSDE9&1^*{5<-paI6!=elwGG(&>9_%d%RBi`t?GYSVd{AuN*wUk5P|{}GMAgP zn0z%eyz_FjwR^%zSD1Gbwd@LWPjin6CS^==9`K`uB`9LBPFQB{69fQHZs?A1Md$&D zeB?misHG(3|7?hnIQ?z4lME-&p8*P6Y2dZo*}t65^axZH|&qH1KR(RVxVI&-RDyd|%T@x|6&@WsHO?{naS zhr+(=%%?$FCw;U-+p!mmCkTh}ij}aRVFDou3>p7CwoMu}$gp zMHga)biMhjC%j_blfQi8MaUfQoH*a^;Q2!*y?;yzwQBc*Qd-uk$wKLF`0gZQICbP7 zm^?TKF%Rlb%3+KVW$)zrWSrnGcBM>GcrLxcD*C5j#v_mp{b5tdJZXoigG#mn>p^cK z=oZzj=6V5COSd=};u2FPCRBcG><1JCQJ~Z<7zlKEg)|=?+_)nT*J@Ivk+5Yqak5zu zg?pl~Zn%S0j4(a{M{PX#26MJ&z8PM&%8Y1cXGuzqyaEA0NW^)cfztVAxPFxx?mn^c zN~WGqzWcO&mEAY@t}^HQduRjJSyY5O*7xcaNQ(6`KJ_K@v4+kosd$)+x@hB{zhsX0 z23x%iU4@wdS$lQm@bF!$&7{%aR0-M;sj(L=e?sVniO_+O44u-w+Du?q{PAit=Y&Eb zPDVBhVO-!ecT&Xg(g^L|LrNob{t+#0vur&5E9S6du~{{7voagM)D7;ajM~_Gi@CsT z2rs+Ee8xNyzHtkH`AGPYTg|M)H2?v(4>0xQ#@2@=1Eav&kg=w3Jh4JFugkD6;aF9i zrJuUhG`G%o?3)}}qp`5_-Dhu!LWbe^q9WIsz2?ed=r?=|n;tQjna9Hy|7Z@g8pon| z0F+0=Cm%5npklh}QFD%&UDU}~Fug*m%N$6VSf?fe2=ms!6(2L=%kJCo0v|>ZCF5qD0!hY8^$FPJH?9$tIFOsrpRHG#@` z5|)p#$6Dzr`B3CzZ0`Sw!XDaP%I{^BIy)~DUo_KezwbHzFW0kT4|nvT|7%&k5{gOG zjdN>p?USIeSM{?^KfCnvuzoi2lm50>S5LY&Pb2qKc)?5N7_aB5@Eb3gsYie7Du6~n z!Fgn2dPqj(JeG<_(2DQIpGGVWCh@7LkWLyWQv^>F6GLI^9yryp<{#lB_n2`-5V?3y zsa+N^AP_6EN-jnke5BvG$Bg!VbyZmNvN^nRGu5UChDX0_PS9n_buXKPy{E1UdtNqE zM@CJu=O~&KJfS95(bmc7Az|MuW>`4#6+q(W>%${oF%pyr-}{OgTe(BYD#J}Ln~9_Q z7xApVRnQOA{ie85Bxn)V{mBd;wXdXvks(QjvMG4ss_?phO*MxL{$$#Zlu;RYC6Q1R z4)_>q1Pm;Cq2M1~`(UCiVJ&7_;im@)kB^0+zIAQ*lRp`MK-3uz-S%KAEqV1%nBb)) zbM~4Rb>*zRW`b11Jg_B~wA?;jcFTEF_rPICop)+1Y4pSnWeLAp=iP7ChwrKPrt!F{ z-ut$BCOo#mJIOp3u59q$Yj%Y%Gl+Wd#$G z!b5TVR|a@za)a~70p7=FL_6Uc*>~2?xb44ab|&cjjSG_peJsbUwhY-biP`*L;Z138 zXx+o46?b}DbyK83R2%*-?d_qtzZ~ejkHMNd$Tt2%gS<9E;UI6US-L7+)eZ~Kd%DjlwyWt{VhD5#fWavP*N8bCRnQ4PZKOQ(o|fg&+K^S6g!_l&G~j7WN+EjR=2n zh&R!kA66Xdy(3qvosoQ)zRn^voZ?+`hw8OVk7{O8Y=PM-w#5#Usb#PPMF=Q$cJ?x7 z%$i&x(B#x6S zRSNd2PvA`!xT-Zpxb9rS2d*W_y5t0+Wf_K2s~Sng9!Jae5WY_rXXKU8I|bX_ zj2y4bQ?+#j$VVI)W-Aw0_;qzGY*`SZ?!|R-fqEPO;@2GC!1;l);O5pW&Qz;LzXpHc zKtF%@dS$g(ck~69pB#WyLo|Y(gswDruHm>vw3t|@G4xtRm;+F4^L zDYHIYdzd%+9hI_o?j|Pv7}aV*Xa&<_t?bnyD5UYmfCW(+B?}-`8oMgnZND;XJlq>n z9d|vPbhvlaVT^awpQs;65ovfk4Q^u)(ishyprQ75IIaG294n+h8GiL}Z)i#%YN3n?NzWBtztUHXUOs`Btw*F@(eFqCv!`-M2;-)o#BnByZ62*C8DgI zGrUn{DejozO*!@@+uKzpYXq8@C1ypIw;3RYh_?2#XwRK$>6Odclgr6C-dka^!*9>!`f?v1&qepX_g(M;gnCXtM~Zqh5jVyq7C6qB(m zJKroE1tx=tksYF9IwnEsWMDXv8R1fnj1%n%x3znR;hkpSOmA#$YcV>Q$wpljE++cO zQH5ygoSc}4P`mSmJ6gea*qN5)uB=Zy%I@Pm>yslHyi8msd}O9KF_*PvWUA<%x>&Jqie*%kwigt4lET8YxhPMQK3PO7k` z(iFf*P=&RYW{=s%A2q;Yh7!!zAh5J>f-{ux#v{Cu)=f{YrZLr!WWYtr@4-_m7yLrc z)QE>_FL0LAcIveW-UfS)A}bLb!IQP#>v+AFXwGa|!-L+i-VOy{MmE{v*1I0cYMMPZ zkPbNd>uU%_zjz_qJ3n0he^$Qoth1JXxZbO~Qmk4(6$PtP|l`}7rllXD}T zykeQ%>NRS0=z53iXL~If^H$iTZlG2{OX5jn4f7 zCcbO}>@wFkYJ(l%>`^L$Py-Ux>Iz$5gN#z9B|L>u9$eaL(gmTnXVSL0e;d};fmPgi z6sCp=u@2BJXcVTV8fe3`C(s14a#NFws`5+M6I4?FF?rJkMe7r|uM*5f|1T}P&78Mx{iCKL-Q@ zOG+77ETtyXvTrHi*qz}6bG_yT1A<{C`Gytq4MZKfT7}Ny zmuW8RYdGadZ%D37bEL68GI5>dq{xzaWXl!O3P1vj8R{{SrO|<4AwkH%$qA66Sc0iL zDL{n4N3K|gu}>U?MMEmA8DqtGiB(5mnd>7UQgVr%vgRIP3|Uv@@(3a8*1-gf)WGq*V6uZHYK<6V9^@D{0xSd+!OKlE-Pi1Fw^W#30f-ri`HCG644}+I zKJ6((eQ%NGyD-y=$2GWkawBXZiT|Jwv23-G3@xP7T>`m>#5D(Z5msy37qgy(&#ei9 z?AY+Sci_GI`_!x!9gW|$@VoEuj>r`V(jHTT~u?*Kd3#yj*{}zvlB1s*)S`yTTh0xmzjNr_h1(nE6nuw!wZk`4mn{+ zGz}?#;H<>0807H*y)WxIY$FV8iZ568`O79kE!GaefzNs1}M;15+gqVMGr=JZi7gkzsBZ1X~KS*+#96B*H#e)-8 zJr52l;&55T-9+$5E}2G0W5h{hs##`cCq4r2XgR}}iPl&mxFSW+56_?P4I87gIkYtE z#!+&GQ;q{l4kes!Zkq2+oO&qjkEj5hF*3DqFLWeI#Ld7tTctmU^9$bh>MO(GIB#ep zaTSHQ3L95XTq=$Wk2=m9rc}u|raoy?oll%shy&FY>as)~UrpgFJt%zDfT%;&uF^&P8#Rs^?wi=%uHe&lTLo1<^^yokDvc}Qv}ce>p2rP4QsM=$gyHr$Ap zPMSGrF_POihM!*OjXItvklf@1%k(($c@WifQS4ON#F4Nx6iEs2VNcZ-ZBb&i%&JFM z0ji-oedEU8F7(bZ%vi9grxB0!A|pT-#{y*317d!8)CMG%@@K+KR-P7 zLhti+Zq4hWuQ~kXg`D7vw!g!gi@cC@H(%sEYuC_dj#@CN~kD=Xu}n`~AKPH#_G{KXc~HnKR{L z&TJ0rBvp|tUELo*IMT(cE9pi!h)3QWT!!;aXe&&V;8L230bRj8KcumV>i&}31I(u4 zcSJhV)VR$*pzgJUC-6hd3QIjvYDnC%6gTsubD(Ri(~Z8tf<=(@s^_{&UBvuH?Oa4( z&_pe(pd7W7#q#@pFqx8yb`NwB!`{H~K@=eFD_2fPwOPoTso2&IkJV;KN?EFjA=um^ z$i|3ecj<}ms6rZ&EfxlKnxM$;h)>?&Iwd)LQir7Pq`XJgb*u}a@>p(#nZQtx=;s8D zDFMtQWKub0NpVuBWGXTS#Sazsc>bI5-StpwG=N5o`4q=yvS^hF4$7AiTtgli za!5A_t_5RFddmh3;Il!Akgn}0P94?h7FV+M%kkz z6Fk(Np)botBIBczQVrlnBjBcL`AH1OW?D(AuC0J1H5-AnxnM}P0vuo`lccf<9KfLo z${OHjjQ@_Z7Ql{POA|nYl9n1Y0coUW6MSnl2PSZhlrLoyHCMi%Ut2Sknu?l%+8?{y zJkJ0&+MpxGb)yYvP@`L`D>XtO!nmlGY`I1kLCeWW7RZKrQX`>Dx0d(L2ee@AadhmM z1cyfaPtAv!ghoGsK&nhUgImW$S1@3ViLT-!?t~C6d3nudgR&QHF-i}|k`P)e7s05mw&7>zMtLb<^>OJg!AG#LM-FoTN4g0I2gXAx!} z6KF~^+0o7d6Jo(F|CDE7Qj0)S-Z4N!gk_)@CqYqK?4W78Br*P;fbputXlG{nU=|IZ zGPwvhUAbOX3N#e*FM_;h!L68&F{vpNhp{D?zLj!N7>rF_*4j7DQQwe5-Oc-kk@Gu{ zGNkJe?;|rzXOX=cj4JC4$UjFtfj+zf6r*-VID^T9C-KF0njqeV^*fY8r}Y3Q8XU>w zAb3MXI!W?1l8QHs75-+*WheyHL(rIiz=_o_1VY!uYunZ*S*2#^DOU2m&_91oxJYUFK;pbW* ztEx5=>5~}U1>AHJyAMC_Phw5+)h4u{3l}5e$CDw6M>oP_E1IY=nWZ6E*U78_;x3xZ z1_SzRG8>McHdEMNYzq^?sjRBIOo7E-a@d!}^r>t=dFD=IPeXV6Y?{>FVyCl}Q0VTT z&iaS0{{`)YwpiLQe4ki|ZB7KZ(2Y!=V9_${MoJ29T@Fv^F^aP?CP3fHG=rn&G)XJp z-PaD7y-1jrqRcIAo!|l|99WouG=s=cF%|(0y%|d1iW#giig;`W`z9{R82aTZVOfB~ zGZ}rm>eHF5z76M&Pe!xAP;9(8JC{8cKKmus&0|024v}Usl@=(ln-=wy>m;~B zq#^4I%=Igz`_qwTT|7RYRfA%R1ruqK79Y=Nw=h&KZvk7R-Y@>OfaSEHO(}9oQwzR{ zw$_4cG0Yy7!+v>rv4|O7ekKcn3@O71UxAM7Tg41I061K%Jmx!piZn96=P)>72bWL5+cqv+~AuK*8VEBq5H z*^p@L_l7Ul1Tc(x4wNLW!iJ^55KLYOf7XE;{IPQzKRbPh?DEqDgokc~~?S68#2y=A+`iDj=aO7hP&>?7CeOTc!Q36ikkNmi!#Ce8^iFdQ#*)mm(UdP^#Sho*d zeyJ+ju4mms%cwXT#&K{Un(}Dosr&jKf6q{$VRC^*)l@!28gfOnOU9! zNV5&y@VzPn17XD79$_dt4#W2ZK=_$)FH2v#(O=duv>9`pD0qi6F2%wOEPVJjqV!(p zAm6f#!4xP~RIsk$3uYpf3OCjol#p>FaCN%jeUzq8h#W2#T1P5iGBPddQZ(`71~$GH z@&e5uCXfyfV9l5uWLA{**C39imljCyELO$hVtic}zeDd~d9_OS%Em7MDKo&xo z^*M?%LF+^c2~)sP&)vgz#x7Bj7u{$AM%&J0{}6>6St-sk4BW)lm*3KdvryPN$0!R;w)*WZG9^A}6P}x$oWX`>8nU+|_@95#dGjt%!;P;Zm2UtrD zwHUC24ee7OLpZkd(jQnQ>ADE~fh`85qCZe0=;9*$QKlGAd(RMOvC3e(qU(+GjYaxU z5DkII8Cr1l3h~|!*1~Dre?ZGWbwuieEIp>9Gaa-vHoz**Ke2tF*UM_WSewCmMB$rR zcH$q5i5SPf$zZi09etTBmqzYPmgTKvB-j?^6?HymiN*P=BQ$Jk5|bZf{YR1Bu9K>j z?tn{^E~xai6pjYR55oi=KPu+v_)*f`2o*u03>zBF3P}5FO#2Enb0G)Xx)ZBdwkYbw z6hzc|h>b~$kv5J1Z%Nd#ZZ*KZ+HrvxvHu}9B>ed`TF8hKsXJMAbsFce6(VeSHDoPj z%($>g>dnn@WC3bT(<7wv@1bfVKG`#3$lX*Sv4g7@U{;3$ z31!!~69cQ)#w85KHW^&P5PC4Rkm-jC(Q26@jT9rpCb)rN;(RB8j-6glH=h zmMQ=wn~|Kcmxk_iz%C0)2dUx|9$+ImnTVN&lSUPTb2O3gFsqqBYu7|5+GhhtbK*aI zUvEu^BAO&6+THAKC=lU}j%5KT5H%iUQ=vd?e-zvH zpg=sz=AdtMe2n$UFck>m-Co#F33^;r<(@?njgl@0A7e>3(<$)zI`P9}*z`P1w0j)J z!_#8=<7`JV6`0l~(<88=4Xpy3$iiBM$bSOb<1Eqf2{ty<*ds$sa-BMe!6jGWBrEc6 zm|r0P zBgE7v*#`WCp2Rw7%2RAQ-0PphRAGduwU<4GpHKI)wfLE^kIl1fUx!mtx0HOpk3FQ? z)=5B!r&;sxZKC4Cr&+TcwAFk+#unR| z%`(lGpJB18?WzRCXV0>hwo=0*ql6WzO?PjhP^43$x52~`++2ph77+9oQUFfK7&2#R z+@f9Tqe@O5V7S(F_fGNR3yd!A6yLl6QlHvgl75i2W9kGk>M->2apJziEKv7Gw{;>x z9m|YC^On!xC#ZR_YH-PVlxbeccZXS)s*Wo09z}!c6EJ1hMA0#98SQn9#lqwZUXoXy z%7#P9l^_^;{1cuDV!<)i&?f`nRzqiS#X~aL%{wVsaq<{S6}9O_X1UDvCc`9sTMz?X zpBJse1v~Z9#9$J~&MNm9B!Vl# z?brsZLg08BCFP)*p~=0e$SlA-&FBj_F{BBD{{yu23wiGRtN3WU@BOcV`Q;IRDav=t z9i8S{K=JBogWR;u8=^kw=^4dg#4WDV!4D)FI5zrWCvhW zul73A)$$o`B=$<idU06`|mGpg=wXBf&S!6DDyvI(26ShO= zrE>{bexZctzmLzsKU?DcfW=W_)y`mjV4v7{hIK_^=gzPf)v`~NOGN~q^`;HasL*pC zF`wGXdd1TNTECbv@SMW^O$J`*k`E!J%GaUQRqG|Jk=kX)q-S7wof>$Ig0-vy~ zsEP*}&V9*dtLw$!^DKy;`_Hq!=IxKhiOG_`&a;tBy(mU}&3;fXmh>%UZ!jB{z7@~q zl4{>FtbkQGQ7IZ+V*gh6i@z_iN4nq5(B<%Phjj3`v`&%<(lgF;Sq*Oe83l_8ZU-yU zcdg4R@>96r;Y2rw;RBKJnA-cq(djA|>rOJS==M2Ft=z~Zvp#42)Xeo@L^{-fRm$>x zz=U$8JHiwem(Q}VQQW9uN9`;PLq=VnX++TvfPw#nOB6%jJlpxXnpaB z!;8Yliqs!iOmTC5o)*bqWHlDaj0Hc-A{olriEQs>i)1>w#?!s^9xC;X9kwL;IcO`e zc9HrKSZw+^!XppL5Ph6aS9iKqOOWC=t|C_S%hShQjhObb!!2Sus8x#nZf^+2V`xt3 z+&DZyf`+~sj-$HLvQ36?+TSGmsm-&ILA`>|JYy_L<*>61$|weUG9?DjLlZa5dU8P~ z9m-X}hIEbp8K@BUv<7K)C>f#yE@?y$$&J!=_uMOG_AZu3E}wEdx^= zWR6WNFkV8%hTXt$(gnOlh7)$W>e^KXT^qlb8SMw7{XfQLH4q2%tFE1qi#jB;kD^&& zf0oKNrc`IhM1Z5q_zNO?vK6^s6i$4->-*o)Km5j;=_f25l9b49n3@k@>J3GR$Zi@< zS@3-{lcb$gcZqD6dBQ?X)FbPwv@u5+8*)HDGAAntMH|@;>dn-5;R+PD@_IrM!EMs^ zgs3PAGSbTtFC%C0iy0ZlB1%v$qf+`*TjT`W3A8%HN{4-{WeJkHsXPwPHW)kh%*Pzt zRer6{(#3|mI6vPOas~Ketg4a0I?8g_&{CkSAWh2=4s?WZ*m6yg3|D?@(Nfq`DIE6R zDY(u^-ZvzJ7%qHphI~(uFU%2(Gd$IBpum9ai)kg~fB{4<&cHJIUITD0&TtmbiESUk zH|C27h0miy+mlvm5|05l%=Pr^k;{WnEKpE_k%VE?mOBz&CzP`#Z88D@tAlopl&9ei zz5u&89TKKiQi#yz0(Beg;E{Y#9TtF$$PgL-!5@*2IQIn9J#uRw4wr!6(KRE$3|M90 z-OYVP9LYD4~; z`cg=8_~sgmx~8~W?IEo6O7Ro57nybKgL*E_%Tcq7pUg}6PsRB=@1t^JbV6PJXJSOx z=}iS2R;RC3`X8y2q;4wysNNlHBP(uNzm{RUq#GfxLudvsUMZege|6Z%{j|M-4R=}; zdm5&>3|saIY`z7_nl&W5qi&LHa>F0+rMh8_8aj%8*+xtAq{8iJ)SvRjp-bYNVzu$y zOlc+!(ub&_B!{i0!4=qe4%vhzq9LPrTjPO9>uTez`Oh*i>j?Yen>In5wpXqtf~0xJGP>3y>Rgq^lqH#L)nX#ycqs$Ves0os-^5LkpT&7W+ghM6wBE8@#{GscJ!njj+ z19xSefeYWdah}Z0$C>EVkJrPjV_rXAtp(O(o>z4k)553o%BC!=HS{oi)Hg$c#8KLy z&P5QDR0y0&$H<6N*$;@b{dkUJE8HwKmp`>J*WjeS)^`}0Zv z=a>FBxbbGVe^QxhOe94yPu(pp4&b-4%^O6Aft+r$oi&g*sBPShNm>&ws?5SqRxmR_ z&6J_Y4U;VRD&2*#6Mq6qiddI}t$M!=-E!)vjve~8nQd3`lT3>wQjr+u>TCMacO}|DiaZYI@E6+ATG%MhC zads@vq{8D;>LgKR9FMlt;I>7)3nly1IPRxp-WZ3%zHlcW#)>`?Rffx2{xzJZh=Sof zKYSTOoY+5{$Fi-~0OmyvapH~<+@BJQNeFhoLWa-)fkBQ6^$9K%2>L{DBx*!IQT#rR$I}HCp7A`-diw-c9uk6Q>8>)ez>{i^J0FIO$4&&xoez7*bH8X; zz#feYugg;cM zWs!Mg)isegnWx&q_Zl{v%v(i6m_z)d+ELC@#>B#CoTJ|{8SHSj2#h{XGP-Sw(E)Jl zN8H%*uaaD$l{VOKtgQtm1N|j$T_bNgxol}ncw;|-^j=Pq*x1-p*4FSwFSWE@oH##) z4@O^WJ+&fvpP0(;Vnv6<-dVWpqsKHJ$yOY~M^IwL_-Wk1)*cc+&fu|P)ifT*?mZ;7 zPXqmT9sbiKSR6z=9W}$nr_GdBF&~5$olLd;hMo~fHP!qy3OEz*Ja9yR`Z-@PHTWBj(U6smlvYE zI)mr1C5Nfhc{d2&ekS+duwb~)-Yi&XCX!kwlZX{h&IIFaJuF(!L3$4#4rlK&v-i(3 zvTr(z-{?A(NIGRF8|hIaJy)bW# zkGBs?0t9A*C}$3fnzMoX>agfKn^$AM9v0JP^LqZpw01>W4QcpjlJMtoEQI=Gwo%e+ zvkgKe%mJa+A2BL+A4R8iP<%?qXu*5t+=Ni35J3yRIfrMlZAT0aiI~g%{>Leuh;q*O z7r{8GZE}XZdS=dCqXe7g@@)3P5gKn2#Jh8O6>#UzbIUu3=sORzKXpVD%)4P*zBtdQ z(b;)Mde`aKxV9!%)R+%ynLwBMd?I^IO4bC?dLsAB(dhg6mb`i{K={&+#oz@z+x7>%CONR^QKN2o;SR3!i15-r;RNvn3^|X*qqsSOdK|;_LQjz z?7_e8_{~-o`i4#!8~~gz=3nP`hcHK#=~CL!c&%{bN^nXgxJf10SqX-vyFz-{b6o+( zN@b`5qD7?uEdjT}>%vO~q&ip&*t!xN1UzlZu!57_GJf!F;`L*-w)Tx_~M}eKAoGxwQCPGgbd#9x9P=t2?w*+rh zye3>!37!Qw)8c;{+&bQNcq8#rmD(#xg34sn8gC2Ov-BWzBm^X^*1t&d3n%T$#(mfHwFO4Won0j?+1-*_|exvtxzrcRrZH?eR8k>$E3PA}nWnSZPpwv@N5TOoBM z;UDW!2IBWayu@iHyaGukNm!g-%71EeM)xSg0dK+^kN4^w9%TrAA451If(gF~*k^$+ z08Wsw(3kTmEmqr%bT$A->9ihhr1M}n9TR>foQ?^9O6iCL%X!--R(jryzLO$uL|@Nx z-cBsIhkHfO)jYyKY1ovhBPH)Vf#7$dG$--q;(a1Q>{)>^d9qV{xB^Plo|uxDmAr?R zJ{kAo4?_TAOzK!BObk^;2`=0$idOS(Arrj|XyQc^ejn~+3w#0YSPT4ZCHP7uSOpcV z*~S5`f-uj;dX$m)orf1)tEB51K1gGc9`W!xUYq53#QW=bT{h4o9P3f5)gIAsJ+G2{ zz~hl^gU&g2!q|e*fw2XXrcYCpGagZ}o@eWq;U5s?MEhYqPL3u*!CucFAd)<=f!kPy znK#joIKhOeJS3Z*i!FKO9)3;jem35tP-i1qK{CUHn}^|^o!bQ_4x2ElaLUAyBf{a# z4ta|u{6x5ujAHHDgc)sE0IW_B1%hWJTum^#W#LjoywpZ;zfU|YVBB5h^C;-0N`9TX z_3Afh*r;)nVRsH6F>(~i#7iqZ_^T*85e8%}`?dC%KBD(#jCZGeV$^2d)KQ9{0R)Xq z6psP+)=nhZt|%?>OP!!!qWE<)&uC!cXNCP&;Fly|zwj?n#FR9B36HnG=)Q&Lu}>4l zf-U?bZy~ZGYBoST;-EsG7X^UkKzT~0lyjeh5Q{o zGBC1W1R8n7$XS6|z*A8>B#VUmc&=kJVCtt&CW|ij@w$%J0aHF-C5vT%BO_BhvS)~6 z_wkTjRf2z&U=*w;dT)g|9J-a;#PF>=IpW{}U=M8&OSbar$uPG|nbH%#Bvt$1m+~C$ z7q4#R9Ws^xCT_kPzuEYG2EWwchwy7N!sXr1t9Z?Dmw=}-BK{rs^9<%s6^rlZ4ZWRG zJ+hZkc^l&Glv;A~e*Q1r>&o;fIY3Yo(yuF1-2D(bMBmJkBMdkzTiS?jP*iJ}?-5SHp2Chv_?PbWT17 z9sjLHtx}d4JcztPA3^PFeyezden;FY-a=k%^{wLDk9h6SZiKIfbcauwJ!x8DVC0l3 zg;NFqCTUIYAiTGRU&{Y~5sS)TLo8A^ydAF`cJND*{EqND6u-kr2f!aG6p1^{en>-r z6{dblxPjq>snc5%9tAiKFE#&YyjFNj7i5m>c|m+J7+g-PX5SW zsuR5ckT?k7p@huZ%z%3)UbHMCcPkt|jC8%Gj#(5LIN~{aDDG^$bMVf^Yb^|k>-l&W zgz?t2b(y$G{*^;x>i@(nD_|UAO9?8;8;h&Ay zgh{2d!rcKAbxeQica(<-6L;6Mz|;k-;i>D8EHJ}UH)=qA)I?Av>skU(pCnOe`V)s) zVd6S#hPwgNm~4iBq!RpaCHNu0b*PV;5gr7BB%}#%hud0%2LO|PY5H%5+Zuio;KmmJ zjg{b60b42d3gDW=8D@en!|kxZFI9q%0k+oo9l%zKy$!eyUNgOma9b--3fNkKuK@SA zBFaI`F0+P~jQo`)G0HYiKW*MjINz_3$t7^-F=TImXc8THu#x z4E(fG-qJe(uoLi1{8lya#}U58LBIhCdz2>lb%L0r4cxFOz*}kFoBDryG3^_k5He>! zZy`Q0hc*6+N-&X&a7!?QqW)ZYOq~GSe;d7yBCcih%8&pJUjy*248N=JTQ<;(8W;Iu z@6d6^L~s^OCyrj^Jwkf{w+GxAZ;TS8JA$3tyjDh`K4yi9x>oouz}8xmP_X*1 zt^~KX!dU=VBm&EpMCKMK8~Fz6typ-1D$|BDT`Y%3nB#<_yw1F566;89;Jl= zzkeB;2Jns_dBa*}ct5g(Xh8UIe01(i3;YS-EF-@jm$4qQ=|}FRL>~JQy&b>bBQg5T z{>k_q@)K_pWhNF0LPZ&oU;B|qi&H=G$g+~!$b8SWW%9RD+7wR$_{x;VN3`UVKMnHp1a@6J*F+YK4-^zm z!xSnoY0B7%BWo2-83CPZ>hwEB)UP~Qod1Kbj#hRd!cZi58#0U*b$;SCMEyT`O+9hC zN9jY#LzYg{HDK+~uz+^0{1Gq&8o zBmVl`W*a}XaLP1ky;u!jN~jqcAsW9X+zGJX0(S;X(!%ub0+^(z39kj*+5+zfOzEzk zAwIi;>8%;A7C3>J0r=csc*`gge;;rw4)A9^=lLZ5UxZnpDR{t808|n>|2IMWj6loEA>N6b1jO zna76pJ*nIM!*dc$n!q zBK;5Eveuh(J+fTig@5}MjGQ$I24YM(W(=D$c38o*0F>IM0slOR_A(XInJrsbX%6f# zl;#E0j+9Lkei3kQz+(R|yrG!%7stj}QD*}W3ddhOF~&LHu2%Ik^i*Lc;?W`M{-zBJxj;d=oSE^&cJp{C!8_kO(OCUwsmc3UNQJ7B`~ z4aeO9H}QrUH%#@8Qi`lCF#4uG)v#$m!1z=Ti>?*peB;e$~d1f6VjX8062<}f1a3Fq< zQDD5msRc3&dWtOSPVw^Jyj8okMTXVoGnA0V;&>=6M1K>m2AK4VUSxr6Pc!?9t*{Gz zqU!Iwx_Fc6T}6-Ic?Z!{(=&RfE`(74Zymf%@dojB#@h?;AiQ2gq}LPv9Ru7PysPnU z#cOS9s<3L<(N1f6T58rJLvB%9&|JZUsZHz^g^weG*b*m}#hqNApPjVWFybzuka$g) zl!z z$x&8rswocG^k!_*(vn|n`d2Kp6{fzOc=zLd8SlGzzr_0sUfVKI1#b%88h9JxZH>1( z-T`<=;GKkbA>LxVJMfm^eF^WIc;CnSIo^wSf5Gcoj{b=^3vWZbEtg}TX*W0q;vJ25 z4&J4B*WtY%?{2(%@Lm!{4!s5otQ313dTrKirTEmLXR~=LOLV91Q2+m~MCSiciIROT z{ofJn&AY_BI6bLJovJFv7d49n1QsJmTH?71y?}WEutDe*tR+SYZRcR<@Q~IlsH$2E zA>NGBQ<{IdT2a2iw2Hm0a*G=s>D&H_@;&*ys3NxUM0|(%5E_P`s2Z==RL6*0;`K(M zQQ-E|=M*L40HWMZnU268ZQK#qlzzsZkAbTX{uBnXW-Chimo%m4s={U>SuOMy zECXKvwJSo^c4L&(!vIoyoGNo4G$0&`Kwgpy(fO{*t3uv{-i-yY6)9~;7(Twa zqPz-B^;=b*RSjYhMtbK=__jx6Ew44EnS@!P0%R^C%4|yYEX1J-kH80n_d{T7^Rg7> z?QhYA`Y}~|65=KS{@Fij6hIbd0H{MZQ<`b~fH_actLXhmkxheW=XV^3VEGN0eiUgu z<#v*CTeKf`J6%L7bR3&DUUDf`^Og-`s_>6X{tPd(B%cPC3q9~sdx*$ZeJ)m1b zwlgm^!Sa4sgz~CN<^@&z*IDi{9*c=;k({$5A`T_(v%I2C6hspS?~^ z(0Cr^w@5PTS=n4$;HxdhIP)|bIBK7a%Gz9jimF||Ls3?teWG)0sy49&)@80h8~X$N zZintaBn*&rl#j{I*+E5d<69(&dO^qqw)+lA#Cy1^Spg)!tG=QXQSvi%Rr`L7qWtg; zl8@6>_BnDl_$79ofzJ31@jt!YNG#)V$uF~zGmhRMr9aZ4AR#5c22QsEA%!hODDAa+ zit;P!=l6i^x?p6Q7!Tk@Ms#9tQ`;V+bZWc-Z4x~%@eS%kXg)<-hQD9Cpp?Chn8_os zG)h2g05wW569hryf(Wdz-9)h?;I-~3NWwgaN}06Gy;)>>tUqeD_@X=B6DcS~w1Om>zZ&|{*Im3%n5^|mpXf%}k++LYPA_GOPY zE?Z&0Avm8nR8ju*6+(QkR&s;>rpNEQV88hqfl5?GW0oY&H%wO_5LB* z0b0@MxNsoGQgDj>u*yfwhPWj6UKE%{TCEU*!iyeZ*T9XZKENs`B!&I5%7YJM7$NsH zwEB8<@n=v<`*M{pUacsUy8Skl?+>7D17%ZYqYlwadg&}z> zyfX(W%I{|lK+!rBLTH{d0#_)rw(Xn7(4vrfa)Ih-hw5xr712FgKk|Q7hZ5Am9~rvZ zLQzUVo#ch8FD?=VHJ_?oShTliI}^mzt~-nJ-ey5$r#P#5t92+3k$o>qj%d}T91-~5 zkDR^-DAPXEPWI`{6t=4N#1yCDSNZ{ zl-p4U^GP~nj~Hu#b8w#z9&e8xW(efnl_kanB?9u5p13y5XSkHtT7i9~o2!O0|3{Xp zoCmj>%KWS>^yPA8{yYNviy_#F)70kFD??yRe?EqzmrzqR*{1qu)r0|>{Odpy>NOe4 zGTKq~M>JKnS@&Vh11+F_39n1zpmKwC)rOE!E{KYUP<(ZTu9k7HDRngwTSu=RIt|6d zd{kXea4)eSQtR5?vn;SbV0XtgH-n>$%e%SvTHuts>bRC!;N*JIcK5|*>!6Wmu( z74u2zFyHRJZV3=yn&Qs2q>$9l@7`!BLCT5*TMz@2`S_Qofj5Lu>+019yl!bXwJJU< zv>?w6hk<^Jmf*b0inv#^E3OZ%akBMv=VogVinB{BtgB~awzmen7yBU2TZ)-jUyF9$ zFJ7suXNCTUfY7hZC-FmumQ);P(P)j)>}q{I#&HYs>P5hl-0S!e`Ah;pc|<>ksOtzo zG}ptHA|-ypGHnlIgb-uv>FFVLsiLe$l4^h5Rmai+)PcIsnPou)Rx!8Yy5B-lb(mh& zwa$XbXsXq7EzdDaOo#QfYOXsVB+ZBaq}gpVYnvd{o~$7~339z=ArcbKbRDuFlGmtq z#~rA|Dby%=km_~(1>e$eU7aQWAd~A43o9hws(Rd?S`ev+lib~navR+;wU*b73tG#b zgzRYBK}++-*NjtagDf>osOwFXFck8HCb80;NbX=&+emA;+7Qi(tE+{MD2?CQ(nL`H zlCnz0BCl2LH!HF%<^NJ*S*rYSsVbDdNvv5LqyDH4rRsXtQgn5c9_#wUg2*_4MtBT} zUPv;to$B)4lLI{%K*ncj(Y=RmW{0FbudK>}qnO`p}N zdZv%0$zA}EB)$jXJ^{Bs!|vXZW2OVRX*K6WOI!0ecbe-3O8}neuIhZ!is%vHQ=BKP zm?Sr@bt#ioRDXZR`2UZE)qK0{-xb-%mTM_@d}&)B$+EMNn07&s`ACp?ptwMY`ACSh zh8qGbkp$QzxTWw)6bQ9lhKg5gPg^J)e+dHxJwB?}DT>gsfsf>Gsf^ssCuJ{`2gqFq z$=yV_N#^=OBy{ruLE!pG;C6yLX}(>SZz$Ym@rno^F5Y&y&Eg$}yS#X3WvFUHz z`qWhl(8cR|x=t92pRAYk6xS|782m@pBOd82wgidj2cfID?A0x?jzivDx3(o(*SW+Z zP56@$tr6b+gC&r^XHC}^7MN%1dCvDt1m(|(Nfe(4h4o+%^!G|id>^fs12=X2#3!jm z$fIwH`+2y{$A7EWcGS`gekkQK@E8g_BE{!NiYv%HJUQ`8q_{ntq9qmN?;n-$k7O(y ziroIcA5Fsze87nknk}4z;l*s>&2XD7jKivC3ya5_=vDvk#w{0(TjryN^msXTokiMZ zW7kyxWn-5o`etL7`mpHLRF4}o_5>DTp)IMnuU?Cr3_TY!6SX7qCsbm*ftr>QXh?_#z(qNi`ScCR9|AMZ3w7ViL|?ZvM_7z zGPRoPMG@0XZy0*%4sbJRSz{yA)K=&URj7_yq^A0#27|(sY*7SsH))$~wGcykhN`Gt zQ3=yftCFGC3MecSDjmZfM}^ztpjV=DQSTy+)}~`;Nffi`?5{66awGGKSoJ z<86DavHIoM{-^-^ZI>-tp0{6u?MKUy;~fy^YYyfwqO@v{^*P?E0eJ)<=Qh>u>j>Z| z0GrqC>I=8|*uGSgoT(zvT8|IW_oWr;#NK1^zLUuMD!Ic&^nf#ORKzsIF&}R~zwOWe zQpC@$)@ET~+>Tq+`VptsWFLP<5vmUDk#B@jw_HxV-x zsrO0x2l3?GFM8rH;xumnG zr2cB{Uo{H}&KZtu1KRv@3>H0{n7hwgEQYtnB2{XUxT`f(^{TrS<*ibPk6$AdX#q$x zNo!8ko?mAqqQZKq^@qe78j0WwAnPV=!?YO8s}}R}8J?5|6H&2Z5K6s;JFjBMH6OJP zPja*a>(7EDRtND^w=bs_<`jUc=Ec}9=ZU64J+(zIgg8neM)Oo(eFQiY4)AB5nUXP# zDlTlPE_>8TJlQo`+!NF@)X|~@KbZ-ZpdT z^6cob2owMy^-hHiMTcW?_`_S}W8>E6I8g$rn7BpsY@_>HJo+TI{GxohOWa4xBtF2U z(e5*r_RU?N>1rQao_S;zrn|3;huUD(%PUT`(eu;+o7(DW>O2u>tNXnBEMe6{+-n;t zI=9v9ssqG4gi2Uz@y-?Lly2awC&j+D=v8?`Afu?1>%j|l(-@^!H4(8!USOzDtuJ6*JI zr+Y#zS{Vj}+%$JBXq@Jg8}PcKEO6>%-R_ElLCJh@O{UwCPptuB)_6dz8{G&!Apiia zfhqG>U!mry~o*KgDPitR5;{F>dW*bbCML5u+@ zZif}VEy4DYMH#CZ7ok)PF!-MV5-EpXAu7?>gi85U!D=(r&3tN!)u&y!2DW$An$DLkrL|2{WuC4WOC^zT ztgDqJlu=2su>%I_NAJes4Hz`HrowWgAj6!U73ICpfVc%J&1i6K9b&+BY9%4$xhLE) zJFBj)3FQ>c?V?7zE?9EU?XPCKwpw&C>YJ{C;V7bMM?J@zW?_TeJJlHHQ({_2J^i+M z=!d7!*15NX6>DNzQc6Iw-SyA@YniL&CTF8O(N9sHUW6Zl1Ur?NP|3Xmhk+=)O-%yl029`ey9j8~KmM1}r@r*5vjKPcQ8yAX;ZX>&8fD6FFZeEbM@8$rL# z&4HzPoEX|kPqD@ARFuVEiiMr@#H?yvFuV8?V(>-VH48!155Qt_HNjtXgE-VluM*mW zR+cuyOuMsbVtLusEW6!7mXbtNOwG3Yd}Jlb2LKC+uR9pB7rASBePkS&1o!`J7+FDq z!-kR2b^?M`E1FnZr@6;l`geTyYPLoY+2#{Ju>m?o1*1&kl>Z*%2G$p6XiK`8Dymt_ z9_MI}v=5+AYHfRluNr_;05V&+-6?RJPv!-;kIWsVg@7X*_p5Yxa9GY;TJVU0^7D0_C5DSZba<*|7-1dNY99J=8|N2`x|& z0C}V098S1T!7ZmyS?DvR1d#df0DR0xn*Wy7H9>?p<_F-LLM$IgDWyd3LlyzJ1Nl4^ z)05B<!8O-Ph?e5vns8Hyvt9Kp zwW-j%fpDAJhZ(0a>0P0lE;})qOUgSXKdScX##IVj@QN6A~7E!{EtNE_qt^BeN zGuR^#L^%jv`~G;}ywY1%=!$3+pJF!+~xJzEq$C0s?Jfk%c^q*fLWc(dRy#9Cigk`%8gT3N^l?;vzXf0Nv0TkpsG$V_UhE`MyNulQ2rlVE6Oj>KWoE+ z=8rmw-9y0EZUo))gAp+CK!nw90pQjK=znbqsz*`R!xd-)I*>WB_I)b2ZY5BxAttPB z6DE`X$?Ftl2GZ3!WAR-&I^Ja{Yg!M8xJMBe<~T*`Nt;plJb^ty37CT89N9o9d;X7l zD9T?Hy(MKg9A)CGLWgi>mI8HE`ADqbb|Uuxm2dwLQ#+r5Puv71Avf@^qqcp+_>WQk zRt8?-pZ3F!F-T#piORoN044yHv<51_@+jmk1!}3@C`^a~vF@m68GD*$gx<%(#Xal$ zH@f->^givJ-TgWGmHChqeA@y`DR{|>ASt+}vJ{L(2MRwSMT$YkVnjNN9HbO1vPc#w z1+Q479!bFy7PwprUZwa_3eNhc6nxK;CP~5Hk)fvw_>aWk7L+ij3SvtsSRp8+;2Y@W zt>H^jaJ3~FDFx425QY>?u?Pc_g3%V(kb=uvV9cik2S14XwZ2ib(1Eo6~RD345pt42-up3#Jc{3~z61C)Sl!=?Xh!;Pqe`A{3y zLZMDkozR5);4Y=evI#4ML=%4067kZECfsQ$S|CIt*$!*EawKbDX)|iW91Co;VKJI5 zky6A+Ru}GkxMdT5cB3X#7|EHy>-E7&aMY=0ZmLH8&HncJBy1jYz>~Y*sby9ZU_`q6 zfTc@iB}cm!Tm0o>`X!5AVXUS5x+Ba1Xc!d#ZcBKYVht%PBl4#?md#g&0RqcizQ1dL zwiMk_!#4zjN-5knuiZ5sZu7CNfnoS}OV(jiL4^rh9mju~u!Zwi+%H?ISSKOXb)Tik zvWhkf!^tZ8#xzGkQc=XjfSj5p!rdUp{$Hnu)?4xA{v1fUqh%Yg?l ze~!aOHS9ju+C8Djed(O`rSHZn&!68?9Ji`{(N$62r(oN$?A5p%c9{{-1;ChUigJ#E z9moFeU#ehP;v(dq3^(Cl*a`9tyeQm?Sr{@B5h;LOjNx!M2sRNM(fne#aD%pBqdx&u zo(s=H8T>5_tQ&$+ii*$%n;By}U^Bi00M%~G9E`u$uz{Bk1Jvzp2(WNn*^@+yfn6X_ z$a@sLdqTMFiw*<#BkYTA15mW-^RU~FVp86V7b!~13jkW9l7AN<9PFHuWEJT5Bg)$n zo7O|JBxmn|#zyH?E=a|+FAu{Yg-9|^%JaakoXIB zBwp2hIM?KNfI0R=yA!J-Sevueho+Nxax!N898g!aOg74Og3h;tyC{XATM-Wr)-yt} zVCO~HscriVDpjka*g#Cc+W>Cvb8#`Yx1shjSV97}Ph#7xOyIkJL+qSK`e%^9)HiS< zjexIjo?3`>DY+keOCOZ^SA@nkajSbe=Iukm_(Lz?$Yj_(8@l9$e}*@f6*cjCxF%zf zcLgBuq|tP?e(;GHwK-fB3U@ddF8yo((GXa&UbG=P!Wzt)oRYa?ExIyPPTR0bw5~Z6 zsxZ-dkb9Y^c{^y`<`iW5FTnUw>0W3+?YCv&O!DOh=<*XX^{Xn%{zx2TzzLb-h(-rw z;c2WumV~MM< ziz=#fe+{EVSx0bOgtaHs(Y=tu;Fb6lxN|w&)FX;eYf3N)Ay^@FYTo8qOatJknW6@U zbcA;_+|Y&lqNAm`;LcY0TpzC0Avem%VxS<)^)r5GtD?M)C6rp7&GavWWsVUB%P1B% z1}lZpNfXO~kI4%`&lD`KQ;_O|DIWbD7 z_krlG;6p&@#%cfhAlA4b8f4m+o8pKgNb}DkR7kEu94i2dcNw54AAv1gI7Z_^mE}WP z3e|sGs{q&^%;EY&WgieNy|*yb2y|oD1Mq9p4DCb! z9in*+g*|vS>{I_S+|WY+d|3He8Eq9B5Gki?5!h~TfmH48)6luV%kYA%Ka2xXD76H< z|HjTHq$PpRkZ>%rlt6_9o__?J<3TL)QH+x}t577WNcw0DCKZ5Z+M@n+C?}l`E#6bTjPlZ;k$t*2?NWVHP{C{>p{v(ZTi74bWp7hFE588H#cZ+ zcCnN;WE=^2Z;B;l9Oa>?h3&$)5vuw>0*W@OFE-eq$s%xkiKU^#YHPsC`v}t;=V-Jp z&xN59I^J|U2mOs>g%^9{2*$@S4V8lUukOTN{qF%}fO;EZ6s5We&z}xEmb;#S<^xsi%gPy<(iOa#&ATIjkqF z@QDdkdmoC(R5y6{0w`jhy&|8m-GXkeDD$rXeCJh^TkME=9?6(TywX4;354jtmxTHw zg}*3=K4{KYYeQ)xADo2IeyIxE83COT>E^+1>SGKpyg@9kGX&$N-2;YV?@(uN9w9y( zu1AJ`u4!1Fos-F{0hl-NEZXjx3rf*4m2Z3pSA`*_KUrmjVIJVNq@#+)BmP z2Uf#@Hh#GUgyVTcegJg&WQd}yBw#-JDT5}bXr+g644hDt&`rXJ&&)5bQ# ztOR8v_mU0JR_V}MC67vH)-tVU)-rKs4F}jn;wWe_FJ@sZtb)_I1AUV*Z0rW3R=?G0 zd!uf+&*3NGy;`;hz|rQD@M(41e1tWhg!@5p2m{dE7=Jdcfk_2auW=vMG?Hkvy#SgS zq0Z5nyymEqw!09h5aEI(sH>XEnN#r7FJTu9dR|696w!F309Xq_br^)o81Fu1DMiM_ zcvtIMW=MG3VJiLt0UDzz&z}JRGk`M@!=(Ax>a*x*vJqS+&-z%>Q~pVoqo7k{ z638+-q*hIIX#RWDd2a@w7a}mPd&US{ zH&Zp;8HxoUHxeElg+Ct1d+n~}igKHHU>x={h?jBy;y2>RI6XJ!3k+y1Nd3AS{_w-E ziEHEZwA|$Z?C3v>mT!>D|zizYr6~<3m8B`ir&W z_3V~Ts0I(6LlQ5knGd03ya6HXeoW1@;2~#?2T3X0eqM;1b1Bb%Y3{D#r}28@s_jSN zs?gYPYnIK0j8O2kT>R;|BmmWkNplr17v7D^Y464QU1ON zWtqWzSq;q?MCeGB^X zO~d`uCwz$gzGKETFtHv6%$ox><7>dc}n19FGXpAA_ul3PR;S)V3aLzYYS+&rzWD`6GUD&FV9=($+yGO zCX@Scu%Z;W#jrv>zRKa&m?wi~ffpiNt(t*m;U{n~LU-2__Y~rM;ET`TKsDwcIfFrs zn*!J#fVf)c4{-YrBv{TU6>Y+NQTX!;x&;O>yF!^) z)A8pMc(FQg4;IRWqR_D4JY~$Y6tT1`!Q*!g-iYi%`kQYrG4_E7&2K;s2R5cSwni% zh{P7M#$fYg0)E2u7A>6(+lBPlGyA;5{0!}B0c4VfkWn99=R zMwk&HcC|-rv?beosy&ICVy=r?V77oiev}0ZyeQhQS(tAjs22@jsH6V-HXHMZn1@J7 zI|e-OxCd3CT`e)iKv65Fn`uKVvu(cx!rqK)S`_)>$f5F`g?#LhQz8!FnA@?(# zZFZON&(KrV1krScKJ@m97+=jN@e#k%hM?w?l0~Ja$97xTK|s=J33xqu1Q`zzE#+Ky z6NC}v*J|FbB55`e@wZM9xqge+Z`A;}R$6k7c}z=k&9fjPa1)DT6A(oxwgm1*JvWB| z=X?O>li+39u1_q*PKad*wr|Q1V#7?`kB_Q9g`bhJ*pH1GjKWB~7m39@q2(mKi(;Pu z5Q{T8Nju?l7JwbQMB^%ydW9Na5wS{cNv+5`d$ihN-pP@?Q;|sU{XZnq0ZSK-d0eaN zx~!V@hE4F-$d)zFfOW4@Rf-+nqbR)j=qn;pDv}+%T%kDnT@iM(IFLhl${Q`_AB`qD z%+}KrX2yp*)KjrU91mtHF;DqKt2uhI|21^ptd^SQ{jR_)1Q4yXq=>TxV)`6Ckd_I- z^gjA|R&VCaENdXZOIVzQ)w{?!L|Ly2_lt2GI z46HzB2|y5Z#$!sk8eJh*$@eqn+KOtLPu2yN;5rHFm`~OUHP88+1>qRzNLE~A^HanJ zb9MjVCoR!yM5~@?7edd0_5!HB6#@je6)GnP_)xV+KZHu1 z1yGZYqZ^NFwq5j?r>Di$v*ex|he?F%tq8Gj9?pRstCY2?yCrDWy_j#dwjd~1*9uD` zP|nW1$k}`>xr6jCBQ6F%{E=hd8wa50mf`~)rzBtF?gFsz4Q?XCR7TZiCgQ9V1_%8y zY947JePJFHtqWq(eBBq_qdlm1MRp74bg$SrUk{|eP#6B^B+j_|pzPeAZk+oQ@6XpW zL+?VK&@hsE$QF;XnNRBB1Y3p`KAIX)m}B}P_)GCMX&E91-5~Wye9ay3YfbKLF|k7+ zya$rIZ+yhv);RrgB0jAE`y&RvJ;;)1zl4Z)x>?`>aW$(zwVFhUjrAq%>xj_;WE;_us15BTHQN75K7V*Zdt^(selFoqB8cXTt=5D3h8;F=TYHjFGx|Zy|mD{ znk#~?=K2*LV=lpi)&Sz3#~AjD0az9kB6=HW_iG4qFMy8`fdKU1>lmvEr1+Mln39m9 zjD&uhk|smlR&*y8{=hpl)M#&yLiBIK!0eZxA(}p&+M)kL(UGOG0@(kBF2=S&CK$@* zBj6_P%TknnKZsRDxDBQqI-Xg5zAT=FD`7$VD(4&_xjD!*}o;XUKwZG{^F;>n96_ zKg6p$FIW(sH`FvN_E<3d5uWLM!h(>gD6WpyBItTmXMZ5jipJx9%T!g+0BccQ&9#gS zJFp!7Npr1M=syI!4{->>c4KtWa5Safl7!obNs4xe)w!tEh{cfG+5Qa!A zMTxbS({XzWHeWg3b*b#u0G1^NJ%vS3csXvxl}dG>dTfI@an!}O1}*j-STZPZlPFco zLb$Yx@cIQYuRD>}j`f(SQOy1bj9lbRZWw*BTGJm_*~7@4V>v2rU(W+JNQ2DnzKne`XRqs~$6T7g2Lil@SelsJH+A93f2EU9h zbQbDA=7PY{wZ+=Q&C%Oa)R2;sO0rnafOkQv5Rq%r4A=@FlDvM2{&4fUq)1$sB=2J~ z?_i4Di-b-hhq7yzB601Kc?A=>%`2E9uc?Z0lM-=Th9+ASCUZrg;8e_S7w^TnUJ5|#a|<5AbpCGu>1b8CZpD^rh%x0OXucF95_lv- z_oo@RqP?&Z`u^A8zl0K%Kj$+p21~$=XCuK|?ysPM|BaMrPN|K!AF`J)m9HK9EZd0Y z!+l~%SKeB=4%}hTVC~0fnNkpW2kLqn(n$gZNL8+(HhBdj&DY@n#Qk96=eOh1M9icU zac5I_{qBbjxXKX%F0r9(G`S!{<>6m7!KKS+@+2B@nY{dq7)M)^rYPso-0`mw&rBpn z`{YgR0|YO|uOO7X&+FhO5FBKP$Msx@e#>H{#CB8QwaMU06xcsg5=(w01x;Mdr$41T$yxBhVF}Lfp^rOV7|8B=Mk*Kg76<7F@MePe6~R^!+NM zUoCDV+L!L1VpY(~+CYu6!QD{RTGqgl6uEQIi)U|yazR9xr?UU2ud5HLqKe|X`|jQM zp1jBRL%tM52UJR-zZgpcOH=a$XADh5L`Q~7$HYR$RG7>Xl+S<*=7?oxX?~@$$c&~< z&M0D{V~tU!ls0INPE!`lINI;*zJ<3)?jP^nk8{pFd+)jDoW1w#UHZ4BuEFjmVHIO# z=>&FW2(li>N}|p8C|wGycXmS)6HuqD(7$aCtY<3~{D#FNh+{NC-oXO~>x3TYpc;QX zk#AfnCY11K*d9i2iDOJ=#~_>x7G&YFcApKuYw z7iK_z2@zO1Ox&r!!_p=%G>5Pr?1fH)G2Rz$K-(}n!(zpRN{N+fY$S(BWu@IEevtDy4N!etH&@-Xj95z<2NhyeSwbey!vq)v*`wPEMF!KD_8@ z7)@g+9G^MNfdGXjjQZt5sDp3=unV)VA-nlE4r0J~q1q-%qXmX@r8qcv1)ui_Dt z^n=NCJ9Y~5keS=nDz>Xx`)b6pfnRkcm#sDZz{G3aGh&BXG9j4%#5HdRZw-eGuF*%b6)bcP;-)acD3*s^!7!RpKI~i6?10qkH5Sr+ zIOCs$q>I4}{4$9?QD7?7eUOur(0ISZia+bPN1XnUKNV=`>)Ua%bkC@gYj|YQIdX+e zX)-ko70Q3fta>d(2~sNmEeG+9$OMi;Bg>uYa-_|R1$yj@eDca{Ii-I=q`k`uk$~f& zsieqDg#{&9i3WN855>d?_E<532f*OXXoSv8XPywJ1M;4VG}&{t)!@fypG8DE?)x$b zi)2FR&9TU+#{r18d9etmgXo)=-iD=@LIz+D55@JhJlJbWKOHVhB`}p_3JnwZ9L7|S zaq+83h91&6X`@IT?S+(Qc5;{T*tmZC?tFh1!mrTcQbY>XmD>6&}1;? zmj$(T2`W(5UzDMV?wYz6Uy>tNP2K}2@?l-EyN$RNc|QOU4FH2{c(}Cp?w9tyQOmlS zY{?itmEVnXAP9Qwb4KX;7X_F!5k|}uPHBj&hq#y)ADz%s$KB#+U9XV7gr1=DaSZuQ z^Y%{*5NSEXL<}EoVNZ9!*PS(DU099GX5~pzHn|!r6r4T`r;S2xn104G9m~tQV|;l_B(muT1o@z%Qul17t z=ggir3QG8}9Oc>yUN<4bs(F~?DnpK5EkISG>j*Mkl+olUB#rM5GAR>w(;NT~j6*i8 z8G08gz%y(<`barNqM{?~_5PY;=4+M(qKfQu}EK zz1s#2!puC52dfUuei_RquI5qTQPp<{H9ogRr|3lJVji5N!ugwPT+o|3b!(hZXR0Au z*z@XDhxlN!I#^4cqALEvK2IH?oy|~lnO2>thN#-39QDm$5xhqoqxHO{CXEo2Bh^r? zutB|*>t6i!Ys>I;qE+;z4pzI|9&69fS)E<5bV+TsJR diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 237ebdf5ce..39801653fb 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -107,10 +107,15 @@ pub mod main { let shielded_token_kd_gain_key = token::storage_key::masp_kd_gain_key(&ibc_token); - let token_map = token::conversion::token_map_handle(); - token_map.insert(ctx, ibc_denom, ibc_token)?; + let token_map_key = token::storage_key::masp_token_map_key(); + let mut token_map: masp::TokenMap = ctx.read(&token_map_key)?.unwrap_or_default(); + token_map.insert(ibc_denom, ibc_token); + ctx.write(&token_map_key, token_map)?; - ctx.write(&shielded_token_last_inflation_key, token::Amount::zero())?; + ctx.write( + &shielded_token_last_inflation_key, + token::Amount::zero(), + )?; ctx.write( &shielded_token_last_locked_amount_key, token::Amount::zero(), From 42aa4f55f28d6021d65afbc88e760a0ab6b0d3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 7 Mar 2024 12:32:43 +0000 Subject: [PATCH 23/96] state/write_log: clear IBC events in `fn drop_tx` --- crates/state/src/write_log.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/state/src/write_log.rs b/crates/state/src/write_log.rs index 6ce1f31d54..a3f25797eb 100644 --- a/crates/state/src/write_log.rs +++ b/crates/state/src/write_log.rs @@ -473,12 +473,13 @@ impl WriteLog { self.take_ibc_events(); } - /// Drop the current transaction's write log and precommit when it's - /// declined by any of the triggered validity predicates. Starts a new - /// transaction write log. + /// Drop the current transaction's write log and IBC events and precommit + /// when it's declined by any of the triggered validity predicates. + /// Starts a new transaction write log. pub fn drop_tx(&mut self) { self.tx_precommit_write_log.clear(); self.tx_write_log.clear(); + self.ibc_events.clear(); } /// Drop the current transaction's write log but keep the precommit one. From 97178fc2f355df1385bb5c5cbd25c3224ad56cd0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Feb 2024 22:43:15 -0500 Subject: [PATCH 24/96] add parentheses --- wasm/wasm_source/src/vp_user.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 655d73ccd6..5110580d13 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -225,15 +225,15 @@ fn validate_pos_changes( Consensus | BelowCapacity | BelowThreshold ) && post == Inactive) // Reactivation case - || pre == Inactive && post != Inactive + || (pre == Inactive && post != Inactive) // Unjail case - || pre == Jailed + || (pre == Jailed && matches!( post, Consensus | BelowCapacity | BelowThreshold - ) + )) { address == owner && **valid_sig } else if From bfb69176366da0035b4098b0b475f2be364eedf6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 27 Feb 2024 13:32:29 -0500 Subject: [PATCH 25/96] fix unjail SE bug + test --- crates/proof_of_stake/src/lib.rs | 2 +- wasm/wasm_source/src/vp_user.rs | 150 ++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/crates/proof_of_stake/src/lib.rs b/crates/proof_of_stake/src/lib.rs index 4eaf8fc281..02b9ada912 100644 --- a/crates/proof_of_stake/src/lib.rs +++ b/crates/proof_of_stake/src/lib.rs @@ -2680,7 +2680,7 @@ where /// Jail a validator by removing it from and updating the validator sets and /// changing a its state to `Jailed`. Validators are jailed for liveness and for /// misbehaving. -fn jail_validator( +pub fn jail_validator( storage: &mut S, params: &PosParams, validator: &Address, diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 5110580d13..d752d60415 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -235,7 +235,7 @@ fn validate_pos_changes( | BelowThreshold )) { - address == owner && **valid_sig + if address == owner { **valid_sig } else { true } } else if // Bonding and unbonding may affect validator sets matches!( @@ -347,6 +347,7 @@ mod tests { use namada_tx_prelude::{StorageWrite, TxEnv}; use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; + use proof_of_stake::jail_validator; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -628,6 +629,153 @@ mod tests { ); } + /// Test unjailing of a validator that causes a consensus validator to be + /// demoted to the below-capacity set. Probing a bug as seen in the SE. + #[test] + fn test_unjail_with_demotion() { + // Genesis validators + let mut pos_params = PosParams::default(); + pos_params.owned.max_validator_slots = 2; + + // Common + let protocol_key = key::testing::keypair_1().ref_to(); + let eth_cold_key = key::testing::keypair_1().ref_to(); + let eth_hot_key = key::testing::keypair_1().ref_to(); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); + + // Unique + let (validator1, validator2, validator3) = ( + address::testing::established_address_1(), + address::testing::established_address_2(), + address::testing::established_address_3(), + ); + let (stake1, stake2, stake3) = ( + token::Amount::native_whole(1), + token::Amount::native_whole(2), + token::Amount::native_whole(3), + ); + let (ck1, ck2, ck3) = ( + key::testing::keypair_2().ref_to(), + key::testing::keypair_3().ref_to(), + key::testing::keypair_4().ref_to(), + ); + let genesis_validators = [ + GenesisValidator { + address: validator1.clone(), + tokens: stake1, + consensus_key: ck1.clone(), + protocol_key: protocol_key.clone(), + commission_rate, + max_commission_rate_change, + eth_hot_key: eth_hot_key.clone(), + eth_cold_key: eth_cold_key.clone(), + metadata: Default::default(), + }, + GenesisValidator { + address: validator3.clone(), + tokens: stake3, + consensus_key: ck3.clone(), + protocol_key: protocol_key.clone(), + commission_rate, + max_commission_rate_change, + eth_hot_key: eth_hot_key.clone(), + eth_cold_key: eth_cold_key.clone(), + metadata: Default::default(), + }, + GenesisValidator { + address: validator2.clone(), + tokens: stake2, + consensus_key: ck2, + protocol_key, + commission_rate, + max_commission_rate_change, + eth_hot_key, + eth_cold_key, + metadata: Default::default(), + }, + ]; + + println!("\nValidator1: {}", &validator1); + println!("Validator2: {}", &validator2); + println!("Validator3: {}\n", &validator3); + + // Init PoS storage + init_pos(&genesis_validators[..], &pos_params, Epoch(0)); + + // Initialize a tx environment + let mut tx_env = tx_host_env::take(); + let token = address::testing::nam(); + + // write the denomination of NAM into storage + token::write_denom( + &mut tx_env.state, + &token, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + + // Jail validator3 + jail_validator( + &mut tx_env.state, + &pos_params, + &validator3, + Epoch(0), + Epoch(0), + ) + .unwrap(); + + // Initialize VP environment + vp_host_env::init_from_tx(validator3.clone(), tx_env, |_address| { + // Unjail validator3 + tx::ctx().unjail_validator(&validator3).unwrap() + }); + + let pks_map = AccountPublicKeysMap::from_iter(vec![ck3]); + + let mut vp_env = vp_host_env::take(); + let mut tx_data = Tx::from_type(TxType::Raw); + tx_data.set_data(Data::new(vec![])); + tx_data.set_code(Code::new(vec![], None)); + tx_data.add_section(Section::Signature(Signature::new( + vec![tx_data.raw_header_hash()], + pks_map.index_secret_keys(vec![key::testing::keypair_4()]), + None, + ))); + let signed_tx = tx_data.clone(); + vp_env.tx = signed_tx.clone(); + + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + // dbg!(&keys_changed); + // let verifiers: BTreeSet

= BTreeSet::default(); + let verifiers: BTreeSet
= vp_env.get_verifiers(); + dbg!(&verifiers); + vp_host_env::set(vp_env); + // for verifier in verifiers.clone() { + // dbg!(&verifier); + // assert!( + // validate_tx( + // &CTX, + // signed_tx.clone(), + // validator1, + // keys_changed.clone(), + // verifiers.clone() + // ) + // .unwrap() + // ); + assert!( + validate_tx( + &CTX, + signed_tx.clone(), + validator1, + keys_changed.clone(), + verifiers.clone() + ) + .unwrap() + ); + } + /// Test that a PoS action to become validator that must be authorized is /// rejected without a valid signature. #[test] From 4031b3acd89758694a74d4aa820fb65e8f682e27 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 3 Mar 2024 11:12:50 +0100 Subject: [PATCH 26/96] changelog: add #2617 --- .changelog/unreleased/bug-fixes/2617-fix-vp-user.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2617-fix-vp-user.md diff --git a/.changelog/unreleased/bug-fixes/2617-fix-vp-user.md b/.changelog/unreleased/bug-fixes/2617-fix-vp-user.md new file mode 100644 index 0000000000..d5ea51e5b9 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2617-fix-vp-user.md @@ -0,0 +1,2 @@ +- Fix a bug preventing unjailing when it involves demotion of a validator out of + the consensus set. ([\#2617](https://github.com/anoma/namada/pull/2617)) \ No newline at end of file From 9341be2c93c9429f323dd4910b75844722b0c5f7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 12 Feb 2024 21:14:33 -0500 Subject: [PATCH 27/96] clean up pos VP --- crates/namada/src/ledger/pos/vp.rs | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/namada/src/ledger/pos/vp.rs b/crates/namada/src/ledger/pos/vp.rs index 9a0de30c83..28ac392a43 100644 --- a/crates/namada/src/ledger/pos/vp.rs +++ b/crates/namada/src/ledger/pos/vp.rs @@ -2,23 +2,17 @@ use std::collections::BTreeSet; -// use borsh::BorshDeserialize; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; // use namada_proof_of_stake::validation::validate; use namada_proof_of_stake::storage::read_pos_params; use namada_proof_of_stake::storage_key::is_params_key; pub use namada_proof_of_stake::types; -// use crate::ledger::pos::{ -// is_validator_address_raw_hash_key, -// is_validator_max_commission_rate_change_key, -// }; use namada_state::StateRead; -use namada_state::StorageRead; use namada_tx::Tx; use thiserror::Error; -use crate::address::{Address, InternalAddress}; +use crate::address::{self, Address}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::storage::{Key, KeySeg}; use crate::vm::WasmCacheAccess; @@ -71,11 +65,10 @@ where // use validation::DataUpdate::{self, *}; // use validation::ValidatorUpdate::*; - let addr = Address::Internal(InternalAddress::PoS); // let mut changes: Vec = vec![]; - let _current_epoch = self.ctx.pre().get_block_epoch()?; + // let _current_epoch = self.ctx.pre().get_block_epoch()?; - tracing::debug!("\nValidating PoS Tx\n"); + tracing::debug!("\nValidating PoS storage changes\n"); for key in keys_changed { if is_params_key(key) { @@ -92,22 +85,24 @@ where { return Ok(false); } - } else if key.segments.first() == Some(&addr.to_db_key()) { - // Unknown changes to this address space are disallowed - // tracing::info!("PoS unrecognized key change {} rejected", - // key); + let params = read_pos_params(&self.ctx.post())?.owned; + if !params.validate().is_empty() { + return Ok(false); + } + } else if key.segments.first() == Some(&address::POS.to_db_key()) { + // No VP logic applied to all other PoS keys for now, as PoS txs + // are all whitelisted tracing::debug!( "PoS key change {} - No action is taken currently.", key ); - // return Ok(false); } else { - tracing::debug!("PoS unrecognized key change {}", key); // Unknown changes anywhere else are permitted + tracing::debug!("PoS unrecognized key change {}", key); } } - let _params = read_pos_params(&self.ctx.pre())?; + // let _params = read_pos_params(&self.ctx.pre())?; // let errors = validate(¶ms, changes, current_epoch); // Ok(if errors.is_empty() { // true From 6a6aea612e55edef1d067c6ed3583061325e4b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Feb 2024 12:16:33 +0000 Subject: [PATCH 28/96] changelog: add #2604 --- .changelog/unreleased/improvements/2604-prep-pos-vp.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2604-prep-pos-vp.md diff --git a/.changelog/unreleased/improvements/2604-prep-pos-vp.md b/.changelog/unreleased/improvements/2604-prep-pos-vp.md new file mode 100644 index 0000000000..c1d31ffd71 --- /dev/null +++ b/.changelog/unreleased/improvements/2604-prep-pos-vp.md @@ -0,0 +1,2 @@ +- In PoS VP validate governance proposal changes in PoS parameters. + ([\#2604](https://github.com/anoma/namada/pull/2604)) \ No newline at end of file From a92df3713abb018a6a2130002655f73358ebd753 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 7 Mar 2024 19:46:47 +0100 Subject: [PATCH 29/96] clarify SK in test --- wasm/wasm_source/src/vp_user.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index d752d60415..fd4166ec3c 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -655,11 +655,12 @@ mod tests { token::Amount::native_whole(2), token::Amount::native_whole(3), ); - let (ck1, ck2, ck3) = ( - key::testing::keypair_2().ref_to(), - key::testing::keypair_3().ref_to(), - key::testing::keypair_4().ref_to(), + let (sk1, sk2, sk3) = ( + key::testing::keypair_2(), + key::testing::keypair_3(), + key::testing::keypair_4(), ); + let (ck1, ck2, ck3) = (sk1.ref_to(), sk2.ref_to(), sk3.ref_to()); let genesis_validators = [ GenesisValidator { address: validator1.clone(), @@ -739,7 +740,7 @@ mod tests { tx_data.set_code(Code::new(vec![], None)); tx_data.add_section(Section::Signature(Signature::new( vec![tx_data.raw_header_hash()], - pks_map.index_secret_keys(vec![key::testing::keypair_4()]), + pks_map.index_secret_keys(vec![sk3]), None, ))); let signed_tx = tx_data.clone(); From 49a6bd8fd83bfbd8c07972134d28d1e029f942fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 8 Mar 2024 09:49:23 +0000 Subject: [PATCH 30/96] changelog: add #2850 --- .changelog/unreleased/bug-fixes/2850-clear-ibc-events.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2850-clear-ibc-events.md diff --git a/.changelog/unreleased/bug-fixes/2850-clear-ibc-events.md b/.changelog/unreleased/bug-fixes/2850-clear-ibc-events.md new file mode 100644 index 0000000000..72fefc4300 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2850-clear-ibc-events.md @@ -0,0 +1,2 @@ +- Clear IBC events emitted from rejected txs. + ([\#2850](https://github.com/anoma/namada/pull/2850)) \ No newline at end of file From bcca08e2caa04dd47dcd7453dc4a03abe23f5aef Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 15 Feb 2024 11:52:48 -0500 Subject: [PATCH 31/96] fix valid validator voting period computation --- crates/core/src/storage.rs | 8 ++++++++ crates/governance/src/storage/proposal.rs | 8 +++++--- crates/governance/src/utils.rs | 11 ++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index dddb367f7c..ed11d64737 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -1171,6 +1171,14 @@ impl Mul for Epoch { } } +impl Mul for u64 { + type Output = Epoch; + + fn mul(self, rhs: Epoch) -> Self::Output { + Epoch(self * rhs.0) + } +} + impl Div for Epoch { type Output = Epoch; diff --git a/crates/governance/src/storage/proposal.rs b/crates/governance/src/storage/proposal.rs index c5f527f64b..557b27db50 100644 --- a/crates/governance/src/storage/proposal.rs +++ b/crates/governance/src/storage/proposal.rs @@ -535,9 +535,11 @@ impl StorageProposal { is_validator: bool, ) -> bool { if is_validator { - self.voting_start_epoch <= current_epoch - && current_epoch * 3 - <= self.voting_start_epoch + self.voting_end_epoch * 2 + crate::utils::is_valid_validator_voting_period( + current_epoch, + self.voting_start_epoch, + self.voting_end_epoch, + ) } else { let valid_start_epoch = current_epoch >= self.voting_start_epoch; let valid_end_epoch = current_epoch <= self.voting_end_epoch; diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index 4619c90473..35dc364434 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -432,8 +432,8 @@ pub fn compute_proposal_result( } } -/// Calculate the valid voting window for validator given a proposal epoch -/// details +/// Calculate the valid voting window for a validator given proposal epoch +/// details. The valid window is within 2/3 of the voting period. pub fn is_valid_validator_voting_period( current_epoch: Epoch, voting_start_epoch: Epoch, @@ -442,9 +442,10 @@ pub fn is_valid_validator_voting_period( if voting_start_epoch >= voting_end_epoch { false } else { - let duration = voting_end_epoch - voting_start_epoch; - let two_third_duration = (duration / 3) * 2; - current_epoch <= voting_start_epoch + two_third_duration + // From e_cur <= e_start + 2/3 * (e_end - e_start) + let is_within_two_thirds = + 3 * current_epoch <= voting_start_epoch + 2 * voting_end_epoch; + current_epoch >= voting_start_epoch && is_within_two_thirds } } From 260af423e83a54069343c9cef98fbb8c2eb0406b Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 15 Feb 2024 11:53:09 -0500 Subject: [PATCH 32/96] improve client logging for invalid vote --- crates/sdk/src/tx.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 15b69575d0..694936e98b 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -1994,9 +1994,15 @@ pub async fn build_vote_proposal( let is_validator = rpc::is_validator(context.client(), voter).await?; if !proposal.can_be_voted(epoch, is_validator) { - if tx.force { - eprintln!("Invalid proposal {} vote period.", proposal_id); - } else { + eprintln!("Proposal {} cannot be voted on anymore.", proposal_id); + if is_validator { + eprintln!( + "NB: voter address {} is a validator, and validators can only \ + vote on proposals within the first 2/3 of the voting period.", + voter + ); + } + if !tx.force { return Err(Error::from( TxSubmitError::InvalidProposalVotingPeriod(proposal_id), )); From fa96e2d426d215deb902ccbce279459cd662e9b5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 15 Feb 2024 11:59:06 -0500 Subject: [PATCH 33/96] changelog: add #2628 --- .../unreleased/bug-fixes/2628-fix-validator-voting-period.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/2628-fix-validator-voting-period.md diff --git a/.changelog/unreleased/bug-fixes/2628-fix-validator-voting-period.md b/.changelog/unreleased/bug-fixes/2628-fix-validator-voting-period.md new file mode 100644 index 0000000000..e38e345e89 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/2628-fix-validator-voting-period.md @@ -0,0 +1,2 @@ +- Fixes the computation of the valid validator voting period. + ([\#2628](https://github.com/anoma/namada/pull/2628)) \ No newline at end of file From dafd8fa9b965af85f5995e165595932cc35eed3e Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 15 Feb 2024 23:17:22 -0500 Subject: [PATCH 34/96] bug fix: log result if epoch is after voting_end --- crates/apps/src/lib/client/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/apps/src/lib/client/rpc.rs b/crates/apps/src/lib/client/rpc.rs index 52f2c56ba4..6ae61f5534 100644 --- a/crates/apps/src/lib/client/rpc.rs +++ b/crates/apps/src/lib/client/rpc.rs @@ -1271,7 +1271,7 @@ pub async fn query_proposal_result( (proposal_result, proposal_query) { display_line!(context.io(), "Proposal Id: {} ", proposal_id); - if current_epoch >= proposal_query.voting_end_epoch { + if current_epoch > proposal_query.voting_end_epoch { display_line!(context.io(), "{:4}{}", "", proposal_result); } else { display_line!( From 48d403a18891701ee833182acefc34daf1b3654b Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 23 Feb 2024 21:15:54 -0600 Subject: [PATCH 35/96] test --- crates/governance/src/utils.rs | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index 35dc364434..e2162b75cf 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -1348,7 +1348,7 @@ mod test { } #[test] - fn test_proposal_fifthteen() { + fn test_proposal_fifteen() { let mut proposal_votes = ProposalVotes::default(); let validator_address = address::testing::established_address_1(); @@ -1402,4 +1402,44 @@ mod test { assert!(!proposal_result.two_thirds_nay_over_two_thirds_total()) } + + #[test] + fn test_validator_voting_period() { + assert!(!is_valid_validator_voting_period( + 0.into(), + 2.into(), + 4.into() + )); + assert!(is_valid_validator_voting_period( + 2.into(), + 2.into(), + 4.into() + )); + assert!(is_valid_validator_voting_period( + 3.into(), + 2.into(), + 4.into() + )); + assert!(!is_valid_validator_voting_period( + 4.into(), + 2.into(), + 4.into() + )); + + assert!(is_valid_validator_voting_period( + 3.into(), + 2.into(), + 5.into() + )); + assert!(is_valid_validator_voting_period( + 4.into(), + 2.into(), + 5.into() + )); + assert!(!is_valid_validator_voting_period( + 5.into(), + 2.into(), + 5.into() + )); + } } From d27ad0b7a9a1ad4986de141fa5d96ca94de527b1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 23 Feb 2024 21:20:11 -0600 Subject: [PATCH 36/96] add to docstring --- crates/governance/src/utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index e2162b75cf..7f86850aa8 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -434,6 +434,8 @@ pub fn compute_proposal_result( /// Calculate the valid voting window for a validator given proposal epoch /// details. The valid window is within 2/3 of the voting period. +/// NOTE: technically the window can be more generous than 2/3 since the end +/// epoch is a valid epoch for voting too. pub fn is_valid_validator_voting_period( current_epoch: Epoch, voting_start_epoch: Epoch, From 795a735c58ee28c9937b1d909674a55ff35670ac Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Mar 2024 16:35:30 +0100 Subject: [PATCH 37/96] validate start epoch in VP --- crates/namada/src/ledger/governance/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/namada/src/ledger/governance/mod.rs b/crates/namada/src/ledger/governance/mod.rs index 3ab335f8bb..b549cfb201 100644 --- a/crates/namada/src/ledger/governance/mod.rs +++ b/crates/namada/src/ledger/governance/mod.rs @@ -545,6 +545,12 @@ where return Ok(false); } + // TODO: HACK THAT NEEDS TO BE PROPERLY FIXED WITH PARAM + let latency = 30u64; + if start_epoch.0 - current_epoch.0 > latency { + return Ok(false); + } + Ok((end_epoch - start_epoch) % min_period == 0 && (end_epoch - start_epoch).0 >= min_period) } From 1548393f344dcaf7e0b6e74654a631a1e0bd0491 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 9 Feb 2024 14:46:49 +0100 Subject: [PATCH 38/96] Started adding tooling for updating the ledger db during a hard fork --- Cargo.lock | 18 +++ Cargo.toml | 1 + crates/apps/src/lib/cli.rs | 55 +++++++++ crates/apps/src/lib/node/ledger/mod.rs | 50 +++++++- .../src/lib/node/ledger/shell/init_chain.rs | 19 +++ .../src/lib/node/ledger/storage/rocksdb.rs | 103 ++++++++++++++++ .../src/lib/node/ledger/tendermint_node.rs | 6 + crates/sdk/Cargo.toml | 2 + crates/sdk/src/lib.rs | 3 + crates/sdk/src/migrations.rs | 116 ++++++++++++++++++ crates/storage/src/db.rs | 10 ++ crates/storage/src/mockdb.rs | 10 ++ 12 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 crates/sdk/src/migrations.rs diff --git a/Cargo.lock b/Cargo.lock index efd89f312d..c38867bde9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -677,6 +677,22 @@ dependencies = [ "borsh", ] +[[package]] +name = "borsh-serde-adapter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db8a3a1f8dad7e23888d6943cb08a1dd01a0d9865c9a286938a5e3fc4f410f8" +dependencies = [ + "anyhow", + "borsh", + "borsh-derive", + "log", + "serde 1.0.193", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "bs58" version = "0.5.0" @@ -4560,11 +4576,13 @@ dependencies = [ "bls12_381", "borsh", "borsh-ext", + "borsh-serde-adapter", "circular-queue", "data-encoding", "derivation-path", "ethbridge-bridge-contract", "ethers", + "eyre", "fd-lock", "futures", "itertools 0.10.5", diff --git a/Cargo.toml b/Cargo.toml index 1e02a8eda6..2f8efb0543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ byte-unit = "4.0.13" byteorder = "1.4.2" borsh = {version = "1.2.0", features = ["unstable__schema", "derive"]} borsh-ext = { git = "https://github.com/heliaxdev/borsh-ext", tag = "v1.2.0" } +borsh-serde-adapter = "1.0.1" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} circular-queue = "0.2.6" clap = "4.3.4" diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 109c0d78af..91bff71dfc 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -839,6 +839,7 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), + UpdateDB(LedgerUpdateDb), RollBack(LedgerRollBack), } @@ -850,10 +851,12 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); + let update_db = SubCmd::parse(matches).map(Self::UpdateDB); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) + .or(update_db) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -873,6 +876,7 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) + .subcommand(LedgerUpdateDb::def()) .subcommand(LedgerRollBack::def()) } } @@ -955,6 +959,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerUpdateDB (args::LedgerUpdateDb); + + impl SubCmd for LedgerUpdateDB { + const CMD: &'static str = "update_db"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerUpdateDb::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD).about( + "Applies a set of updates to the DB for hard forking. The input should be a path \ + to a file dictating a set of keys and their new values. Can be dry-run for testing.", + ) + } + } + #[derive(Clone, Debug)] pub struct LedgerRollBack; @@ -3375,6 +3398,38 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerUpdateDb { + pub updates: PathBuf, + pub dry_run: bool, + pub force: bool, + } + + impl Args for LedgerUpdateDb { + fn parse(matches: &ArgMatches) -> Self { + let updates = PATH.parse(matches); + let dry_run = DRY_RUN_TX.parse(matches); + let force = FORCE.parse(matches); + Self { + updates, + dry_run, + force, + } + } + + fn def(app: App) -> App { + app.arg(PATH.def().help( + "The path to a json of key-value pairs to update the DB with.", + )) + .arg(DRY_RUN_TX.def().help( + "If set, applies the updates but does not persist them. Using for testing and debugging.", + )) + .arg(FORCE.def().help( + "Apply changes even if validation does not pass.", + )) + } + } + #[derive(Clone, Debug)] pub struct UpdateLocalConfig { pub config_path: PathBuf, diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 5c21f687e5..cb416e0d29 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -14,7 +14,7 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; -use namada::core::storage::Key; +use namada::core::storage::{BlockHeight, Key}; use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::governance::storage::keys as governance_storage; @@ -163,6 +163,13 @@ impl Shell { } } +/// Determine if the ledger is migrating state. +pub fn migrating_state() -> Option { + const ENV_INITIAL_HEIGHT: &str = "NAMADA_INITIAL_HEIGHT"; + let height = std::env::var(ENV_INITIAL_HEIGHT).ok()?; + height.parse::().ok().map(BlockHeight) +} + /// Run the ledger with an async runtime pub fn run(config: config::Ledger, wasm_dir: PathBuf) { let logical_cores = num_cpus::get(); @@ -224,6 +231,47 @@ pub fn dump_db( db.dump_block(out_file_path, historic, block_height); } +/// Change the funds of an account in-place. Use with +/// caution, as this modifies state in storage without +/// going through the consensus protocol. +pub fn update_db_keys( + config: config::Ledger, + updates: PathBuf, + dry_run: bool, +) { + use std::io::Read; + use data_encoding::HEXUPPER; + use namada::ledger::storage::DB; + + let mut update_json = String::new(); + let mut file = std::fs::File::open(updates); + file.read_to_string(&mut update_json).expect("Unable to read the updates json file"); + + let cometbft_path = config.cometbft_dir(); + let chain_id = config.chain_id; + let db_path = config.shell.db_dir(&chain_id); + + let mut db = storage::PersistentDB::open(db_path, None); + let mut batch = Default::default(); + + for (key, value) in updates { + tracing::debug!("Overwriting key <{}> with value: {}...", key, value); + let data = HEXUPPER.decode(value.as_bytes()) + .expect("Could not decode a hex value into bytes"); + db.overwrite_entry(&mut batch, None, &key, data) + .expect("Failed to overwrite a key in storage"); + } + if !dry_run { + tracing::debug!("Persisting DB changes..."); + db.exec_batch(batch).expect("Failed to execute write batch"); + db.flush(true).expect("Failed to flush data to disk"); + + // reset CometBFT's state, such that we can resume with a different app hash + tendermint_node::reset(cometbft_path) + .expect("Failed to reset CometBFT state"); + } +} + /// Roll Namada state back to the previous height pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { shell::rollback(config) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index 8982367b78..ca03ae9a39 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -27,6 +27,7 @@ use crate::config::genesis::transactions::{ BondTx, EstablishedAccountTx, Signed as SignedTx, ValidatorAccountTx, }; use crate::facade::tendermint_proto::google::protobuf; +use crate::node::ledger; use crate::wasm_loader; /// Errors that represent panics in normal flow but get demoted to errors @@ -94,6 +95,24 @@ where chain_id, init.chain_id ))); } + if ledger::migrating_state().is_some() { + let rsp = response::InitChain { + validators: self + .get_abci_validator_updates(true, |pk, power| { + let pub_key: crate::facade::tendermint::PublicKey = pk.into(); + let power = + crate::facade::tendermint::vote::Power::try_from(power) + .unwrap(); + validator::Update { pub_key, power } + }) + .expect("Must be able to set genesis validator set"), + app_hash: self.wl_storage.storage.merkle_root().0.to_vec().try_into().expect("Infallible"), + ..Default::default() + }; + debug_assert!(!rsp.validators.is_empty()); + debug_assert!(!rsp.app_hash.iter().all(|&b| b == 0)); + return Ok(rsp); + } // Read the genesis files #[cfg(any( diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 18e2fa13f6..e46b35d146 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -70,6 +70,7 @@ use rocksdb::{ DBCompressionType, Direction, FlushOptions, IteratorMode, Options, ReadOptions, WriteBatch, }; +use namada_sdk::migrations::DBUpdateVisitor; use crate::config::utils::num_of_threads; @@ -1533,6 +1534,108 @@ impl DB for RocksDB { Ok(()) } + + #[inline] + fn overwrite_entry( + &self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()> { + let last_height: BlockHeight = { + let state_cf = self.get_column_family(STATE_CF)?; + + types::decode( + self.0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + })?, + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + })? + }; + let desired_height = height.unwrap_or(last_height); + + if desired_height != last_height { + todo!( + "Overwriting values at heights different than the last \ + committed height hast yet to be implemented" + ); + } + // NB: the following code only updates values + // written to at the last committed height + + let val = new_value.as_ref(); + + // update subspace value + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; + let subspace_key = key.to_string(); + + batch.0.put_cf(subspace_cf, subspace_key, val); + + // update value stored in diffs + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let diffs_key = Key::from(last_height.to_db_key()) + .with_segment("new".to_owned()) + .join(key) + .to_string(); + + batch.0.put_cf(diffs_cf, diffs_key, val); + + Ok(()) + } +} + +/// A struct that can visit a set of updates, +/// registering them all in the batch +pub struct RocksDBUpdateVisitor<'db> { + db: &'db RocksDB, + batch: RocksDBWriteBatch, +} + +impl<'db> RocksDBUpdateVisitor<'db> { + pub fn new(db: &'db RocksDB) -> Self { + Self { + db, + batch: Default::default(), + } + } +} + +impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { + + fn read(&self, key: &Key) -> Option> { + self.db.read_subspace_val(key).expect("Failed to read from storage") + } + + fn write(&mut self, key: &Key, value: impl AsRef) { + self.db.overwrite_entry(&mut self.batch, None, key, value) + .expect("Failed to overwrite a key in storage") + } + + fn delete(&mut self, key: &Key) { + let last_height: BlockHeight = { + let state_cf = self.db.get_column_family(STATE_CF)?; + + types::decode( + self.db.0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + })?, + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + })? + }; + self.db.batch_delete_subspace_val(&mut self.batch, last_height, key, true) + .expect("Failed to delet key from storage"); + } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/crates/apps/src/lib/node/ledger/tendermint_node.rs b/crates/apps/src/lib/node/ledger/tendermint_node.rs index b08dce9ca6..31626e2226 100644 --- a/crates/apps/src/lib/node/ledger/tendermint_node.rs +++ b/crates/apps/src/lib/node/ledger/tendermint_node.rs @@ -452,6 +452,12 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); + if let Some(height) = super::migrating_state() { + genesis.initial_height = height + .0 + .try_into() + .expect("Failed to convert initial genesis height"); + } let size = block::Size { // maximum size of a serialized Tendermint block. // on Namada, we have a hard-cap of 16 MiB (6 MiB max diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 0c620e3d92..7bfbd1e2b4 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -73,11 +73,13 @@ bimap.workspace = true bls12_381 = { workspace = true, optional = true } borsh.workspace = true borsh-ext.workspace = true +borsh-serde-adapter.workspace = true circular-queue.workspace = true data-encoding.workspace = true derivation-path.workspace = true ethbridge-bridge-contract.workspace = true ethers.workspace = true +eyre.workspace = true fd-lock = { workspace = true, optional = true } futures.workspace = true itertools.workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 71dc57aa46..8a2b7bcce7 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,4 +1,6 @@ extern crate alloc; +extern crate core; +extern crate core; pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] @@ -24,6 +26,7 @@ pub mod error; pub mod events; pub(crate) mod internal_macros; pub mod io; +pub mod migrations; pub mod queries; pub mod wallet; diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs new file mode 100644 index 0000000000..50418836f5 --- /dev/null +++ b/crates/sdk/src/migrations.rs @@ -0,0 +1,116 @@ +use core::fmt::{Formatter, Write}; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use borsh::schema::BorshSchemaContainer; +use borsh_ext::BorshSerializeExt; +use borsh_serde_adapter::deserialize_adapter::deserialize_from_schema; +use data_encoding::HEXUPPER; +use namada_core::types::storage::Key; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::de::Visitor; + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +/// A DB serialized value and schema. +pub struct DbSerializedValue { + /// The schema of the type + container: BorshSchemaContainer, + /// The serialized bytes + data: Vec, +} + +impl DbSerializedValue { + pub fn new(data: T) -> Self + where + T: BorshSerialize + BorshDeserialize + BorshSchema + { + Self { + container: BorshSchemaContainer::for_type::(), + data: data.serialize_to_vec(), + } + } + + /// Check if the schema can deserialize the data. If so, outputs + /// a json representation of the data. + /// + /// NB: If custom Borsh serializations or schemas are implemented for + /// a type, this validation may not pass. This works best for types + /// where the serialization and schema are derived. + pub fn validate(&self) -> std::io::Result { + deserialize_from_schema(&mut self.data.as_slice(), &self.container) + } + + /// If the value under a key should have the same type as the existing value, + /// check that the contained schema also deserializes the old data. + pub fn is_same_type(&self, other: &mut &[u8]) -> bool { + deserialize_from_schema(other, &self.container).is_ok() + + } +} + +impl serde::Serialize for DbSerializedValue { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + let bytes = self.serialize_to_vec(); + serializer.serialize_str(&HEXUPPER.encode(&bytes)) + } +} + +struct DbValueVisitor; + +impl<'de> Visitor<'de> for DbValueVisitor { + type Value = DbSerializedValue; + + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { + formatter.write_str("Hex encoded bytes of a DbSerializeValue struct.") + } + + fn visit_str(self, v: &str) -> Result + where E: serde::de::Error + { + let bytes = HEXUPPER.decode(v.as_bytes()) + .map_err(|e| E::custom(format!("Could not parse {} as hex with error: {}", v, e)))?; + Self::Value::try_from_slice(&bytes).map_err(|e| E::custom(e.to_string())) + } +} + +impl<'de> serde::Deserialize<'de> for DbSerializedValue { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + deserializer.deserialize_any(DbValueVisitor { }) + } +} + +pub trait DBUpdateVisitor { + fn read(&self, key: &Key) -> Option>; + fn write(&mut self, key: &Key, value: impl AsRef); + fn delete(&mut self, key: &Key); +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DbUpdateType { + Add{key: Key, value: DbSerializedValue, fixed_type: bool}, + Delete(Key), +} + +impl DbUpdateType { + fn update(&self, db: &mut DB) -> eyre::Result<()>{ + match self { + Self::Add{key, value, fixed_type} => { + let json_value = value + .validate() + .map_err(|e| eyre::eyre!( + "The updated DB value could not be deserialized with the provided schema due to: {}", + e, + ))?; + if let Some(val) = db.read(&key) { + if *fixed_type && !value.is_same_type(&mut val.as_slice()) { + eyre::eyre!("The value under the key <{}> does not have the same type as {}", key, json_value)?; + } + } + db.write(key, &value.data); + } + Self::Delete(key) => db.delete(&key), + } + Ok(()) + } +} \ No newline at end of file diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 5ce22e85db..3ffbe52742 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -260,6 +260,16 @@ pub trait DB: Debug { batch: &mut Self::WriteBatch, key: &Key, ) -> Result<()>; + + /// Overwrite a new value in storage, taking into + /// account values stored at a previous height + fn overwrite_entry( + &mut self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()>; } /// A database prefix iterator. diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 4128321e54..b69df7fb33 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -674,6 +674,16 @@ impl DB for MockDB { Ok(()) } + + fn overwrite_entry( + &mut self, + _batch: &mut Self::WriteBatch, + _height: Option, + _key: &Key, + _new_value: impl AsRef<[u8]> + ) -> Result<()> { + unimplemented!() + } } impl<'iter> DBIter<'iter> for MockDB { From c11dfb393443dc0972d5f78dbce0bb01892a8a87 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 12 Feb 2024 10:02:12 +0100 Subject: [PATCH 39/96] fixing compile issues and clippy --- crates/apps/src/bin/namada-node/cli.rs | 2 ++ crates/apps/src/lib/cli.rs | 4 ++-- crates/apps/src/lib/node/ledger/mod.rs | 5 +++-- .../src/lib/node/ledger/shell/init_chain.rs | 2 +- .../src/lib/node/ledger/storage/rocksdb.rs | 18 ++++++++++-------- crates/macros/Cargo.toml | 2 ++ crates/sdk/src/lib.rs | 1 - crates/sdk/src/migrations.rs | 11 ++++++----- crates/storage/src/db.rs | 2 +- crates/storage/src/mockdb.rs | 2 +- 10 files changed, 28 insertions(+), 21 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index a0d00d48dc..b12980b1f7 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -3,6 +3,7 @@ use eyre::{Context, Result}; use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; +use namada_apps::cli::cmds::Ledger; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; @@ -38,6 +39,7 @@ pub fn main() -> Result<()> { ledger::rollback(chain_ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; } + Ledger::UpdateDB(_) => {} }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 91bff71dfc..940de746e2 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -839,7 +839,7 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), - UpdateDB(LedgerUpdateDb), + UpdateDB(LedgerUpdateDB), RollBack(LedgerRollBack), } @@ -876,7 +876,7 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) - .subcommand(LedgerUpdateDb::def()) + .subcommand(LedgerUpdateDB::def()) .subcommand(LedgerRollBack::def()) } } diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index cb416e0d29..80e63a0b44 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -244,9 +244,10 @@ pub fn update_db_keys( use namada::ledger::storage::DB; let mut update_json = String::new(); - let mut file = std::fs::File::open(updates); + let mut file = std::fs::File::open(updates).expect("Could not fine updates file at the specified path."); file.read_to_string(&mut update_json).expect("Unable to read the updates json file"); - + let updates: std::collections::HashMap = serde_json::from_str(&update_json) + .expect("Could not parse the updates file as json"); let cometbft_path = config.cometbft_dir(); let chain_id = config.chain_id; let db_path = config.shell.db_dir(&chain_id); diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index ca03ae9a39..dee11e1c1a 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -110,7 +110,7 @@ where ..Default::default() }; debug_assert!(!rsp.validators.is_empty()); - debug_assert!(!rsp.app_hash.iter().all(|&b| b == 0)); + debug_assert!(!Vec::::from(rsp.app_hash.clone()).iter().all(|&b| b == 0)); return Ok(rsp); } diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index e46b35d146..cd91212c08 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1598,6 +1598,7 @@ pub struct RocksDBUpdateVisitor<'db> { } impl<'db> RocksDBUpdateVisitor<'db> { + #[allow(dead_code)] pub fn new(db: &'db RocksDB) -> Self { Self { db, @@ -1612,29 +1613,30 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { self.db.read_subspace_val(key).expect("Failed to read from storage") } - fn write(&mut self, key: &Key, value: impl AsRef) { + fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { self.db.overwrite_entry(&mut self.batch, None, key, value) .expect("Failed to overwrite a key in storage") } fn delete(&mut self, key: &Key) { let last_height: BlockHeight = { - let state_cf = self.db.get_column_family(STATE_CF)?; + let state_cf = self.db.get_column_family(STATE_CF).unwrap(); types::decode( self.db.0 .get_cf(state_cf, "height") - .map_err(|e| Error::DBError(e.to_string()))? + .map_err(|e| Error::DBError(e.to_string())) + .unwrap() .ok_or_else(|| { Error::DBError("No block height found".to_string()) - })?, + }).unwrap(), ) - .map_err(|e| { - Error::DBError(format!("Unable to decode block height: {e}")) - })? + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + }).unwrap() }; self.db.batch_delete_subspace_val(&mut self.batch, last_height, key, true) - .expect("Failed to delet key from storage"); + .expect("Failed to delete key from storage"); } } diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index cad5691947..05e51ea5ec 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -19,6 +19,8 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = {version="1.0", features = ["full", "extra-traits"]} +lazy_static = "1.4.0" +borsh-derive = "1.2.0" [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 8a2b7bcce7..081865d26e 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,6 +1,5 @@ extern crate alloc; extern crate core; -extern crate core; pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 50418836f5..9a8a3c49f8 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -1,4 +1,4 @@ -use core::fmt::{Formatter, Write}; +use core::fmt::Formatter; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::schema::BorshSchemaContainer; @@ -82,7 +82,7 @@ impl<'de> serde::Deserialize<'de> for DbSerializedValue { pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; - fn write(&mut self, key: &Key, value: impl AsRef); + fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); fn delete(&mut self, key: &Key); } @@ -93,6 +93,7 @@ pub enum DbUpdateType { } impl DbUpdateType { + #[allow(dead_code)] fn update(&self, db: &mut DB) -> eyre::Result<()>{ match self { Self::Add{key, value, fixed_type} => { @@ -102,14 +103,14 @@ impl DbUpdateType { "The updated DB value could not be deserialized with the provided schema due to: {}", e, ))?; - if let Some(val) = db.read(&key) { + if let Some(val) = db.read(key) { if *fixed_type && !value.is_same_type(&mut val.as_slice()) { - eyre::eyre!("The value under the key <{}> does not have the same type as {}", key, json_value)?; + return Err(eyre::eyre!("The value under the key <{}> does not have the same type as {}", key, json_value)); } } db.write(key, &value.data); } - Self::Delete(key) => db.delete(&key), + Self::Delete(key) => db.delete(key), } Ok(()) } diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 3ffbe52742..11e79b3716 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -264,7 +264,7 @@ pub trait DB: Debug { /// Overwrite a new value in storage, taking into /// account values stored at a previous height fn overwrite_entry( - &mut self, + &self, batch: &mut Self::WriteBatch, height: Option, key: &Key, diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index b69df7fb33..71b6f87bb4 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -676,7 +676,7 @@ impl DB for MockDB { } fn overwrite_entry( - &mut self, + &self, _batch: &mut Self::WriteBatch, _height: Option, _key: &Key, From 08ae8df5015bb02b6cb7a5f91f03f7db26e16821 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 4 Mar 2024 12:08:21 +0100 Subject: [PATCH 40/96] rebased --- Cargo.toml | 1 - .../src/lib/node/ledger/shell/init_chain.rs | 2 +- .../src/lib/node/ledger/storage/rocksdb.rs | 4 +- crates/sdk/Cargo.toml | 1 - crates/sdk/src/migrations.rs | 97 ++----------------- 5 files changed, 11 insertions(+), 94 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f8efb0543..1e02a8eda6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,6 @@ byte-unit = "4.0.13" byteorder = "1.4.2" borsh = {version = "1.2.0", features = ["unstable__schema", "derive"]} borsh-ext = { git = "https://github.com/heliaxdev/borsh-ext", tag = "v1.2.0" } -borsh-serde-adapter = "1.0.1" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} circular-queue = "0.2.6" clap = "4.3.4" diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index dee11e1c1a..4c8e8322c5 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -106,7 +106,7 @@ where validator::Update { pub_key, power } }) .expect("Must be able to set genesis validator set"), - app_hash: self.wl_storage.storage.merkle_root().0.to_vec().try_into().expect("Infallible"), + app_hash: self.state.in_mem().merkle_root().0.to_vec().try_into().expect("Infallible"), ..Default::default() }; debug_assert!(!rsp.validators.is_empty()); diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index cd91212c08..4da43752ad 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1546,7 +1546,7 @@ impl DB for RocksDB { let last_height: BlockHeight = { let state_cf = self.get_column_family(STATE_CF)?; - types::decode( + decode( self.0 .get_cf(state_cf, "height") .map_err(|e| Error::DBError(e.to_string()))? @@ -1622,7 +1622,7 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { let last_height: BlockHeight = { let state_cf = self.db.get_column_family(STATE_CF).unwrap(); - types::decode( + decode( self.db.0 .get_cf(state_cf, "height") .map_err(|e| Error::DBError(e.to_string())) diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 7bfbd1e2b4..f86490b7cb 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -73,7 +73,6 @@ bimap.workspace = true bls12_381 = { workspace = true, optional = true } borsh.workspace = true borsh-ext.workspace = true -borsh-serde-adapter.workspace = true circular-queue.workspace = true data-encoding.workspace = true derivation-path.workspace = true diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 9a8a3c49f8..c269454115 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -3,83 +3,11 @@ use core::fmt::Formatter; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::schema::BorshSchemaContainer; use borsh_ext::BorshSerializeExt; -use borsh_serde_adapter::deserialize_adapter::deserialize_from_schema; use data_encoding::HEXUPPER; -use namada_core::types::storage::Key; +use namada_core::storage::Key; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::Visitor; -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] -/// A DB serialized value and schema. -pub struct DbSerializedValue { - /// The schema of the type - container: BorshSchemaContainer, - /// The serialized bytes - data: Vec, -} - -impl DbSerializedValue { - pub fn new(data: T) -> Self - where - T: BorshSerialize + BorshDeserialize + BorshSchema - { - Self { - container: BorshSchemaContainer::for_type::(), - data: data.serialize_to_vec(), - } - } - - /// Check if the schema can deserialize the data. If so, outputs - /// a json representation of the data. - /// - /// NB: If custom Borsh serializations or schemas are implemented for - /// a type, this validation may not pass. This works best for types - /// where the serialization and schema are derived. - pub fn validate(&self) -> std::io::Result { - deserialize_from_schema(&mut self.data.as_slice(), &self.container) - } - - /// If the value under a key should have the same type as the existing value, - /// check that the contained schema also deserializes the old data. - pub fn is_same_type(&self, other: &mut &[u8]) -> bool { - deserialize_from_schema(other, &self.container).is_ok() - - } -} - -impl serde::Serialize for DbSerializedValue { - fn serialize(&self, serializer: S) -> Result where S: Serializer { - let bytes = self.serialize_to_vec(); - serializer.serialize_str(&HEXUPPER.encode(&bytes)) - } -} - -struct DbValueVisitor; - -impl<'de> Visitor<'de> for DbValueVisitor { - type Value = DbSerializedValue; - - fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { - formatter.write_str("Hex encoded bytes of a DbSerializeValue struct.") - } - - fn visit_str(self, v: &str) -> Result - where E: serde::de::Error - { - let bytes = HEXUPPER.decode(v.as_bytes()) - .map_err(|e| E::custom(format!("Could not parse {} as hex with error: {}", v, e)))?; - Self::Value::try_from_slice(&bytes).map_err(|e| E::custom(e.to_string())) - } -} - -impl<'de> serde::Deserialize<'de> for DbSerializedValue { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - deserializer.deserialize_any(DbValueVisitor { }) - } -} - pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); @@ -88,7 +16,7 @@ pub trait DBUpdateVisitor { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DbUpdateType { - Add{key: Key, value: DbSerializedValue, fixed_type: bool}, + Add{key: Key, value: Vec}, Delete(Key), } @@ -96,22 +24,13 @@ impl DbUpdateType { #[allow(dead_code)] fn update(&self, db: &mut DB) -> eyre::Result<()>{ match self { - Self::Add{key, value, fixed_type} => { - let json_value = value - .validate() - .map_err(|e| eyre::eyre!( - "The updated DB value could not be deserialized with the provided schema due to: {}", - e, - ))?; - if let Some(val) = db.read(key) { - if *fixed_type && !value.is_same_type(&mut val.as_slice()) { - return Err(eyre::eyre!("The value under the key <{}> does not have the same type as {}", key, json_value)); - } - } - db.write(key, &value.data); + Self::Add{key, value} => { + db.write(key, &value); + } + Self::Delete(key) =>{ + db.delete(key); } - Self::Delete(key) => db.delete(key), } Ok(()) } -} \ No newline at end of file +} From 383a7f354b850f6e3bba714e35ed5359b8d23731 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 13:35:53 +0100 Subject: [PATCH 41/96] [feat]: Added a new derive macro to register deserializers of types to a global map --- Cargo.lock | 91 ++++++++++++--- Cargo.toml | 4 +- crates/account/Cargo.toml | 2 + crates/account/src/lib.rs | 10 +- crates/account/src/types.rs | 4 + crates/apps/Cargo.toml | 4 + crates/apps/src/bin/namada-node/cli.rs | 2 - crates/apps/src/lib/cli.rs | 55 --------- crates/apps/src/lib/config/genesis.rs | 26 ++++- crates/apps/src/lib/config/genesis/chain.rs | 8 ++ .../apps/src/lib/config/genesis/templates.rs | 15 +++ .../src/lib/config/genesis/transactions.rs | 4 + crates/apps/src/lib/node/ledger/mod.rs | 51 +-------- .../src/lib/node/ledger/shell/init_chain.rs | 19 ---- .../src/lib/node/ledger/storage/rocksdb.rs | 105 ------------------ .../src/lib/node/ledger/tendermint_node.rs | 6 - crates/core/Cargo.toml | 3 +- crates/core/src/account.rs | 3 + crates/core/src/address.rs | 15 ++- crates/core/src/chain.rs | 12 +- crates/core/src/dec.rs | 3 + crates/core/src/eth_bridge_pool.rs | 7 +- crates/core/src/ethereum_events.rs | 8 ++ crates/core/src/ethereum_structs.rs | 4 + crates/core/src/event.rs | 32 +++++- crates/core/src/hash.rs | 3 + crates/core/src/ibc.rs | 6 +- crates/core/src/internal.rs | 4 +- crates/core/src/keccak.rs | 3 + crates/core/src/key/common.rs | 14 ++- crates/core/src/key/ed25519.rs | 15 ++- crates/core/src/key/mod.rs | 3 + crates/core/src/key/secp256k1.rs | 11 +- crates/core/src/masp.rs | 10 +- crates/core/src/parameters.rs | 5 + crates/core/src/sign.rs | 3 + crates/core/src/storage.rs | 40 ++++++- crates/core/src/time.rs | 6 + crates/core/src/token.rs | 7 ++ crates/core/src/uint.rs | 4 + crates/core/src/voting_power.rs | 7 +- crates/ethereum_bridge/Cargo.toml | 2 + .../transactions/ethereum_events/eth_msgs.rs | 12 +- .../src/protocol/transactions/votes.rs | 11 +- .../src/storage/eth_bridge_queries.rs | 4 + .../ethereum_bridge/src/storage/parameters.rs | 8 ++ .../src/storage/vote_tallies.rs | 5 +- crates/gas/Cargo.toml | 4 +- crates/gas/src/lib.rs | 4 + crates/governance/Cargo.toml | 2 + crates/governance/src/cli/offline.rs | 26 ++++- crates/governance/src/cli/onchain.rs | 66 +++++++++-- crates/governance/src/parameters.rs | 3 + crates/governance/src/pgf/parameters.rs | 3 + crates/governance/src/pgf/storage/steward.rs | 6 +- crates/governance/src/storage/proposal.rs | 21 +++- crates/governance/src/storage/vote.rs | 3 + crates/governance/src/utils.rs | 14 ++- crates/macros/Cargo.toml | 4 +- crates/macros/src/lib.rs | 81 +++++++++++++- crates/merkle_tree/Cargo.toml | 3 + crates/merkle_tree/src/eth_bridge_pool.rs | 10 +- crates/merkle_tree/src/lib.rs | 3 + crates/migrations/Cargo.toml | 23 ++++ crates/migrations/src/lib.rs | 19 ++++ crates/namada/Cargo.toml | 2 + crates/proof_of_stake/Cargo.toml | 3 + crates/proof_of_stake/src/epoched.rs | 14 +++ crates/proof_of_stake/src/parameters.rs | 6 +- crates/proof_of_stake/src/types/mod.rs | 48 +++++++- crates/sdk/Cargo.toml | 4 +- crates/sdk/src/lib.rs | 2 - crates/sdk/src/masp.rs | 12 +- crates/sdk/src/migrations.rs | 36 ------ crates/sdk/src/queries/shell/eth_bridge.rs | 22 +++- crates/sdk/src/wallet/alias.rs | 4 +- crates/state/Cargo.toml | 3 + crates/state/src/in_memory.rs | 4 +- crates/storage/Cargo.toml | 3 + crates/storage/src/conversion_state.rs | 6 +- crates/storage/src/db.rs | 10 -- crates/storage/src/mockdb.rs | 10 -- crates/storage/src/tx_queue.rs | 14 ++- crates/tx/Cargo.toml | 3 + crates/tx/src/data/decrypted.rs | 3 + crates/tx/src/data/eval_vp.rs | 10 +- crates/tx/src/data/mod.rs | 5 + crates/tx/src/data/pgf.rs | 3 + crates/tx/src/data/pos.rs | 10 ++ crates/tx/src/data/protocol.rs | 4 + crates/tx/src/data/wrapper.rs | 5 + crates/tx/src/types.rs | 21 +++- crates/vote_ext/Cargo.toml | 3 + crates/vote_ext/src/bridge_pool_roots.rs | 5 + crates/vote_ext/src/ethereum_events.rs | 38 ++++++- crates/vote_ext/src/lib.rs | 13 ++- crates/vote_ext/src/validator_set_update.rs | 31 +++++- wasm/Cargo.lock | 70 ++++++++++++ wasm_for_tests/wasm_source/Cargo.lock | 70 ++++++++++++ 99 files changed, 1067 insertions(+), 401 deletions(-) create mode 100644 crates/migrations/Cargo.toml create mode 100644 crates/migrations/src/lib.rs delete mode 100644 crates/sdk/src/migrations.rs diff --git a/Cargo.lock b/Cargo.lock index c38867bde9..bae1259c73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -677,22 +677,6 @@ dependencies = [ "borsh", ] -[[package]] -name = "borsh-serde-adapter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db8a3a1f8dad7e23888d6943cb08a1dd01a0d9865c9a286938a5e3fc4f410f8" -dependencies = [ - "anyhow", - "borsh", - "borsh-derive", - "log", - "serde 1.0.193", - "serde_derive", - "serde_json", - "thiserror", -] - [[package]] name = "bs58" version = "0.5.0" @@ -3825,6 +3809,26 @@ dependencies = [ "serde 1.0.193", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -4149,6 +4153,8 @@ dependencies = [ "namada_gas", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -4206,8 +4212,10 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde 1.0.193", @@ -4252,9 +4260,12 @@ dependencies = [ "ledger-transport-hid", "libc", "libloading", + "linkme", "masp_primitives", "masp_proofs", "namada", + "namada_macros", + "namada_migrations", "namada_sdk", "namada_test_utils", "num-rational 0.4.1", @@ -4338,8 +4349,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits 0.2.17", @@ -4390,9 +4403,11 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4429,7 +4444,10 @@ version = "0.31.9" dependencies = [ "assert_matches", "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "proptest", "serde 1.0.193", "thiserror", @@ -4441,8 +4459,10 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -4496,9 +4516,13 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "lazy_static", + "namada_migrations", + "paste", "pretty_assertions", "proc-macro2", "quote", + "sha2 0.9.9", "syn 1.0.109", ] @@ -4511,13 +4535,28 @@ dependencies = [ "eyre", "ics23", "itertools 0.10.5", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "proptest", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde 1.0.193", +] + [[package]] name = "namada_parameters" version = "0.31.9" @@ -4538,9 +4577,12 @@ dependencies = [ "data-encoding", "derivative", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_governance", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_state", "namada_storage", @@ -4576,18 +4618,17 @@ dependencies = [ "bls12_381", "borsh", "borsh-ext", - "borsh-serde-adapter", "circular-queue", "data-encoding", "derivation-path", "ethbridge-bridge-contract", "ethers", - "eyre", "fd-lock", "futures", "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -4595,6 +4636,8 @@ dependencies = [ "namada_ethereum_bridge", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4657,9 +4700,12 @@ dependencies = [ "chrono", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -4681,9 +4727,12 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -4777,9 +4826,12 @@ dependencies = [ "assert_matches", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", + "namada_macros", + "namada_migrations", "num-derive", "num-traits 0.2.17", "proptest", @@ -4838,7 +4890,10 @@ version = "0.31.9" dependencies = [ "borsh", "data-encoding", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "namada_tx", "serde 1.0.193", ] diff --git a/Cargo.toml b/Cargo.toml index 1e02a8eda6..3681402671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ "crates/ibc", "crates/light_sdk", "crates/macros", - "crates/macros", + "crates/migrations", "crates/merkle_tree", "crates/parameters", "crates/proof_of_stake", @@ -114,6 +114,7 @@ ledger-namada-rs = { git = "https://github.com/Zondax/ledger-namada", tag = "v0. ledger-transport-hid = "0.10.0" libc = "0.2.97" libloading = "0.7.2" +linkme = "0.3" # branch = "murisi/namada-integration" masp_primitives = { git = "https://github.com/anoma/masp", rev = "30492323d98b0531fd18b6285cd94afcaa4066d2" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "30492323d98b0531fd18b6285cd94afcaa4066d2", default-features = false, features = ["local-prover"] } @@ -146,6 +147,7 @@ serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" serde_json = "1.0.62" sha2 = "0.9.3" +sha2-const = "0.1.2" signal-hook = "0.3.9" slip10_ed25519 = "0.1.3" # sysinfo with disabled multithread feature diff --git a/crates/account/Cargo.toml b/crates/account/Cargo.toml index c81ee1370a..1f329cade4 100644 --- a/crates/account/Cargo.toml +++ b/crates/account/Cargo.toml @@ -19,9 +19,11 @@ testing = ["namada_core/testing", "proptest"] [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } namada_storage = { path = "../storage" } borsh.workspace = true +linkme.workspace = true proptest = { workspace = true, optional = true } serde.workspace = true diff --git a/crates/account/src/lib.rs b/crates/account/src/lib.rs index 890cf942a0..00e79371c9 100644 --- a/crates/account/src/lib.rs +++ b/crates/account/src/lib.rs @@ -10,13 +10,21 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub use namada_core::account::AccountPublicKeysMap; use namada_core::address::Address; use namada_core::key::common; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; pub use storage::*; pub use storage_key::*; pub use types::*; #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] /// Account data pub struct Account { diff --git a/crates/account/src/types.rs b/crates/account/src/types.rs index 332c3e8764..6d73177a9a 100644 --- a/crates/account/src/types.rs +++ b/crates/account/src/types.rs @@ -2,6 +2,8 @@ use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::hash::Hash; use namada_core::key::common; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; /// A tx data type to initialize a new established account @@ -11,6 +13,7 @@ use serde::{Deserialize, Serialize}; PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -33,6 +36,7 @@ pub struct InitAccount { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/apps/Cargo.toml b/crates/apps/Cargo.toml index 72b1161e6a..fae2ca3d09 100644 --- a/crates/apps/Cargo.toml +++ b/crates/apps/Cargo.toml @@ -62,9 +62,12 @@ jemalloc = ["rocksdb/jemalloc"] [dependencies] namada = {path = "../namada", features = ["multicore", "http-client", "tendermint-rpc", "std"]} +namada_macros = {path = "../macros"} +namada_migrations = {path = "../migrations"} namada_sdk = {path = "../sdk", default-features = false, features = ["wasm-runtime", "download-params", "std", "rand"]} namada_test_utils = {path = "../test_utils", optional = true} + ark-serialize.workspace = true ark-std.workspace = true arse-merkle-tree = { workspace = true, features = ["blake2b"] } @@ -98,6 +101,7 @@ ledger-namada-rs.workspace = true ledger-transport-hid.workspace = true libc.workspace = true libloading.workspace = true +linkme.workspace = true masp_primitives = { workspace = true, features = ["transparent-inputs"] } masp_proofs = { workspace = true, features = ["bundled-prover", "download-params"] } num_cpus.workspace = true diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index b12980b1f7..a0d00d48dc 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -3,7 +3,6 @@ use eyre::{Context, Result}; use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; -use namada_apps::cli::cmds::Ledger; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; @@ -39,7 +38,6 @@ pub fn main() -> Result<()> { ledger::rollback(chain_ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; } - Ledger::UpdateDB(_) => {} }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 940de746e2..109c0d78af 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -839,7 +839,6 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), - UpdateDB(LedgerUpdateDB), RollBack(LedgerRollBack), } @@ -851,12 +850,10 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); - let update_db = SubCmd::parse(matches).map(Self::UpdateDB); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) - .or(update_db) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -876,7 +873,6 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) - .subcommand(LedgerUpdateDB::def()) .subcommand(LedgerRollBack::def()) } } @@ -959,25 +955,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub struct LedgerUpdateDB (args::LedgerUpdateDb); - - impl SubCmd for LedgerUpdateDB { - const CMD: &'static str = "update_db"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD) - .map(|matches| Self(args::LedgerUpdateDb::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD).about( - "Applies a set of updates to the DB for hard forking. The input should be a path \ - to a file dictating a set of keys and their new values. Can be dry-run for testing.", - ) - } - } - #[derive(Clone, Debug)] pub struct LedgerRollBack; @@ -3398,38 +3375,6 @@ pub mod args { } } - #[derive(Clone, Debug)] - pub struct LedgerUpdateDb { - pub updates: PathBuf, - pub dry_run: bool, - pub force: bool, - } - - impl Args for LedgerUpdateDb { - fn parse(matches: &ArgMatches) -> Self { - let updates = PATH.parse(matches); - let dry_run = DRY_RUN_TX.parse(matches); - let force = FORCE.parse(matches); - Self { - updates, - dry_run, - force, - } - } - - fn def(app: App) -> App { - app.arg(PATH.def().help( - "The path to a json of key-value pairs to update the DB with.", - )) - .arg(DRY_RUN_TX.def().help( - "If set, applies the updates but does not persist them. Using for testing and debugging.", - )) - .arg(FORCE.def().help( - "Apply changes even if validation does not pass.", - )) - } - } - #[derive(Clone, Debug)] pub struct UpdateLocalConfig { pub config_path: PathBuf, diff --git a/crates/apps/src/lib/config/genesis.rs b/crates/apps/src/lib/config/genesis.rs index 8ab5fcb1d4..30994510ce 100644 --- a/crates/apps/src/lib/config/genesis.rs +++ b/crates/apps/src/lib/config/genesis.rs @@ -25,6 +25,8 @@ use namada::ledger::eth_bridge::EthereumBridgeParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, OwnedPosParams}; use namada::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; #[cfg(all(any(test, feature = "benches"), not(feature = "integration")))] @@ -35,6 +37,7 @@ use crate::config::genesis::chain::{Finalized, FinalizedEstablishedAccountTx}; Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, Ord, @@ -144,7 +147,7 @@ impl FromStr for GenesisAddress { } } -#[derive(Debug, BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] #[borsh(init=init)] pub struct Genesis { pub genesis_time: DateTimeUtc, @@ -176,6 +179,7 @@ impl Genesis { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -200,7 +204,14 @@ pub struct Validator { } #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Derivative, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + PartialEq, + Eq, + Derivative, )] #[derivative(PartialOrd, Ord)] pub struct EstablishedAccount { @@ -218,7 +229,14 @@ pub struct EstablishedAccount { } #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Derivative, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + PartialEq, + Eq, + Derivative, )] #[derivative(PartialOrd, Ord)] pub struct TokenAccount { @@ -242,6 +260,7 @@ pub struct TokenAccount { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -267,6 +286,7 @@ pub struct ImplicitAccount { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct Parameters { /// Max payload size, in bytes, for a tx batch proposal. diff --git a/crates/apps/src/lib/config/genesis/chain.rs b/crates/apps/src/lib/config/genesis/chain.rs index a17e6fe34b..8877fd6ec2 100644 --- a/crates/apps/src/lib/config/genesis/chain.rs +++ b/crates/apps/src/lib/config/genesis/chain.rs @@ -15,6 +15,8 @@ use namada::core::key::{common, RefTo}; use namada::core::time::{DateTimeUtc, DurationNanos, Rfc3339String}; use namada::core::token::Amount; use namada::ledger::parameters::EpochDuration; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_sdk::wallet::store::AddressVpType; use namada_sdk::wallet::{pre_genesis, Wallet}; use serde::{Deserialize, Serialize}; @@ -589,6 +591,7 @@ pub struct Chain { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -619,6 +622,7 @@ impl FinalizedTokens { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -640,6 +644,7 @@ impl DeriveEstablishedAddress for (&Alias, &templates::TokenConfig) { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -696,6 +701,7 @@ impl FinalizedTransactions { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -741,6 +747,7 @@ impl FinalizedParameters { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, )] @@ -757,6 +764,7 @@ pub struct FinalizedEstablishedAccountTx { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, )] diff --git a/crates/apps/src/lib/config/genesis/templates.rs b/crates/apps/src/lib/config/genesis/templates.rs index 696880552a..6aa645083b 100644 --- a/crates/apps/src/lib/config/genesis/templates.rs +++ b/crates/apps/src/lib/config/genesis/templates.rs @@ -16,6 +16,8 @@ use namada::eth_bridge::storage::parameters::{ Contracts, Erc20WhitelistEntry, MinimumConfirmations, }; use namada::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::transactions::{self, Transactions}; @@ -66,6 +68,7 @@ pub fn read_transactions( Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -116,6 +119,7 @@ impl UndenominatedBalances { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -131,6 +135,7 @@ pub struct DenominatedBalances { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -146,6 +151,7 @@ pub struct RawTokenBalances( Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -161,6 +167,7 @@ pub struct TokenBalances( Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -176,6 +183,7 @@ pub struct ValidityPredicates { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -190,6 +198,7 @@ pub struct WasmVpConfig { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -204,6 +213,7 @@ pub struct Tokens { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -366,6 +376,7 @@ impl ChainParams { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -417,6 +428,7 @@ pub struct PosParams { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -469,6 +481,7 @@ pub struct PgfParams { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -498,6 +511,7 @@ impl TokenBalances { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -512,6 +526,7 @@ pub struct Unvalidated {} Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, diff --git a/crates/apps/src/lib/config/genesis/transactions.rs b/crates/apps/src/lib/config/genesis/transactions.rs index a1b4213b38..c65ffde72d 100644 --- a/crates/apps/src/lib/config/genesis/transactions.rs +++ b/crates/apps/src/lib/config/genesis/transactions.rs @@ -27,6 +27,8 @@ use namada::tx::data::{pos, Fee, TxType}; use namada::tx::{ verify_standalone_sig, Code, Commitment, Data, Section, SignatureIndex, Tx, }; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_sdk::args::Tx as TxArgs; use namada_sdk::signing::{sign_tx, SigningTxData}; use namada_sdk::tx::{TX_BECOME_VALIDATOR_WASM, TX_BOND_WASM}; @@ -671,6 +673,7 @@ impl TxToSign for ValidatorAccountTx { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -981,6 +984,7 @@ impl From> for SignedBondTx { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 80e63a0b44..5c21f687e5 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -14,7 +14,7 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; -use namada::core::storage::{BlockHeight, Key}; +use namada::core::storage::Key; use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::governance::storage::keys as governance_storage; @@ -163,13 +163,6 @@ impl Shell { } } -/// Determine if the ledger is migrating state. -pub fn migrating_state() -> Option { - const ENV_INITIAL_HEIGHT: &str = "NAMADA_INITIAL_HEIGHT"; - let height = std::env::var(ENV_INITIAL_HEIGHT).ok()?; - height.parse::().ok().map(BlockHeight) -} - /// Run the ledger with an async runtime pub fn run(config: config::Ledger, wasm_dir: PathBuf) { let logical_cores = num_cpus::get(); @@ -231,48 +224,6 @@ pub fn dump_db( db.dump_block(out_file_path, historic, block_height); } -/// Change the funds of an account in-place. Use with -/// caution, as this modifies state in storage without -/// going through the consensus protocol. -pub fn update_db_keys( - config: config::Ledger, - updates: PathBuf, - dry_run: bool, -) { - use std::io::Read; - use data_encoding::HEXUPPER; - use namada::ledger::storage::DB; - - let mut update_json = String::new(); - let mut file = std::fs::File::open(updates).expect("Could not fine updates file at the specified path."); - file.read_to_string(&mut update_json).expect("Unable to read the updates json file"); - let updates: std::collections::HashMap = serde_json::from_str(&update_json) - .expect("Could not parse the updates file as json"); - let cometbft_path = config.cometbft_dir(); - let chain_id = config.chain_id; - let db_path = config.shell.db_dir(&chain_id); - - let mut db = storage::PersistentDB::open(db_path, None); - let mut batch = Default::default(); - - for (key, value) in updates { - tracing::debug!("Overwriting key <{}> with value: {}...", key, value); - let data = HEXUPPER.decode(value.as_bytes()) - .expect("Could not decode a hex value into bytes"); - db.overwrite_entry(&mut batch, None, &key, data) - .expect("Failed to overwrite a key in storage"); - } - if !dry_run { - tracing::debug!("Persisting DB changes..."); - db.exec_batch(batch).expect("Failed to execute write batch"); - db.flush(true).expect("Failed to flush data to disk"); - - // reset CometBFT's state, such that we can resume with a different app hash - tendermint_node::reset(cometbft_path) - .expect("Failed to reset CometBFT state"); - } -} - /// Roll Namada state back to the previous height pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { shell::rollback(config) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index 4c8e8322c5..8982367b78 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -27,7 +27,6 @@ use crate::config::genesis::transactions::{ BondTx, EstablishedAccountTx, Signed as SignedTx, ValidatorAccountTx, }; use crate::facade::tendermint_proto::google::protobuf; -use crate::node::ledger; use crate::wasm_loader; /// Errors that represent panics in normal flow but get demoted to errors @@ -95,24 +94,6 @@ where chain_id, init.chain_id ))); } - if ledger::migrating_state().is_some() { - let rsp = response::InitChain { - validators: self - .get_abci_validator_updates(true, |pk, power| { - let pub_key: crate::facade::tendermint::PublicKey = pk.into(); - let power = - crate::facade::tendermint::vote::Power::try_from(power) - .unwrap(); - validator::Update { pub_key, power } - }) - .expect("Must be able to set genesis validator set"), - app_hash: self.state.in_mem().merkle_root().0.to_vec().try_into().expect("Infallible"), - ..Default::default() - }; - debug_assert!(!rsp.validators.is_empty()); - debug_assert!(!Vec::::from(rsp.app_hash.clone()).iter().all(|&b| b == 0)); - return Ok(rsp); - } // Read the genesis files #[cfg(any( diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 4da43752ad..18e2fa13f6 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -70,7 +70,6 @@ use rocksdb::{ DBCompressionType, Direction, FlushOptions, IteratorMode, Options, ReadOptions, WriteBatch, }; -use namada_sdk::migrations::DBUpdateVisitor; use crate::config::utils::num_of_threads; @@ -1534,110 +1533,6 @@ impl DB for RocksDB { Ok(()) } - - #[inline] - fn overwrite_entry( - &self, - batch: &mut Self::WriteBatch, - height: Option, - key: &Key, - new_value: impl AsRef<[u8]>, - ) -> Result<()> { - let last_height: BlockHeight = { - let state_cf = self.get_column_family(STATE_CF)?; - - decode( - self.0 - .get_cf(state_cf, "height") - .map_err(|e| Error::DBError(e.to_string()))? - .ok_or_else(|| { - Error::DBError("No block height found".to_string()) - })?, - ) - .map_err(|e| { - Error::DBError(format!("Unable to decode block height: {e}")) - })? - }; - let desired_height = height.unwrap_or(last_height); - - if desired_height != last_height { - todo!( - "Overwriting values at heights different than the last \ - committed height hast yet to be implemented" - ); - } - // NB: the following code only updates values - // written to at the last committed height - - let val = new_value.as_ref(); - - // update subspace value - let subspace_cf = self.get_column_family(SUBSPACE_CF)?; - let subspace_key = key.to_string(); - - batch.0.put_cf(subspace_cf, subspace_key, val); - - // update value stored in diffs - let diffs_cf = self.get_column_family(DIFFS_CF)?; - let diffs_key = Key::from(last_height.to_db_key()) - .with_segment("new".to_owned()) - .join(key) - .to_string(); - - batch.0.put_cf(diffs_cf, diffs_key, val); - - Ok(()) - } -} - -/// A struct that can visit a set of updates, -/// registering them all in the batch -pub struct RocksDBUpdateVisitor<'db> { - db: &'db RocksDB, - batch: RocksDBWriteBatch, -} - -impl<'db> RocksDBUpdateVisitor<'db> { - #[allow(dead_code)] - pub fn new(db: &'db RocksDB) -> Self { - Self { - db, - batch: Default::default(), - } - } -} - -impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { - - fn read(&self, key: &Key) -> Option> { - self.db.read_subspace_val(key).expect("Failed to read from storage") - } - - fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { - self.db.overwrite_entry(&mut self.batch, None, key, value) - .expect("Failed to overwrite a key in storage") - } - - fn delete(&mut self, key: &Key) { - let last_height: BlockHeight = { - let state_cf = self.db.get_column_family(STATE_CF).unwrap(); - - decode( - self.db.0 - .get_cf(state_cf, "height") - .map_err(|e| Error::DBError(e.to_string())) - .unwrap() - .ok_or_else(|| { - Error::DBError("No block height found".to_string()) - }).unwrap(), - ) - .map_err(|e| { - Error::DBError(format!("Unable to decode block height: {e}")) - }).unwrap() - }; - self.db.batch_delete_subspace_val(&mut self.batch, last_height, key, true) - .expect("Failed to delete key from storage"); - } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/crates/apps/src/lib/node/ledger/tendermint_node.rs b/crates/apps/src/lib/node/ledger/tendermint_node.rs index 31626e2226..b08dce9ca6 100644 --- a/crates/apps/src/lib/node/ledger/tendermint_node.rs +++ b/crates/apps/src/lib/node/ledger/tendermint_node.rs @@ -452,12 +452,6 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); - if let Some(height) = super::migrating_state() { - genesis.initial_height = height - .0 - .try_into() - .expect("Failed to convert initial genesis height"); - } let size = block::Size { // maximum size of a serialized Tendermint block. // on Namada, we have a hard-cap of 16 MiB (6 MiB max diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index bb4437f87d..a319f7290d 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -28,7 +28,7 @@ testing = [ [dependencies] namada_macros = {path = "../macros"} - +namada_migrations = {path = "../migrations"} arse-merkle-tree.workspace = true bech32.workspace = true borsh.workspace = true @@ -44,6 +44,7 @@ ics23.workspace = true impl-num-traits = "0.1.2" index-set.workspace = true k256.workspace = true +linkme.workspace = true masp_primitives.workspace = true num256.workspace = true num_enum = "0.7.0" diff --git a/crates/core/src/account.rs b/crates/core/src/account.rs index 10c7d936ca..908ed94788 100644 --- a/crates/core/src/account.rs +++ b/crates/core/src/account.rs @@ -3,6 +3,8 @@ use std::collections::{BTreeMap, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::key::{common, RefTo}; @@ -13,6 +15,7 @@ use crate::hints; Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, Default, diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index 1a6b524dba..2277161a85 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -10,6 +10,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -72,7 +74,14 @@ pub type Result = std::result::Result; /// An account's address #[derive( - Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, Hash, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, + PartialEq, + Eq, + Hash, )] pub enum Address { /// An established address is generated on-chain @@ -379,6 +388,7 @@ impl TryFrom for Address { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -438,6 +448,7 @@ impl_display_and_from_str_via_format!(EstablishedAddress); Eq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -478,6 +489,7 @@ impl EstablishedAddressGen { Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -507,6 +519,7 @@ impl From<&key::common::PublicKey> for Address { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, diff --git a/crates/core/src/chain.rs b/crates/core/src/chain.rs index 2c7c815a14..9830f9c4ee 100644 --- a/crates/core/src/chain.rs +++ b/crates/core/src/chain.rs @@ -6,6 +6,8 @@ use std::num::NonZeroU64; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -30,6 +32,7 @@ pub const CHAIN_ID_PREFIX_SEP: char = '.'; Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct ProposalBytes { inner: NonZeroU64, @@ -183,6 +186,7 @@ pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialOrd, Ord, @@ -305,7 +309,13 @@ impl FromStr for ChainId { /// Chain ID prefix #[derive( - Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, + Debug, + Clone, + Serialize, + Deserialize, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, )] #[serde(transparent)] pub struct ChainIdPrefix(String); diff --git a/crates/core/src/dec.rs b/crates/core/src/dec.rs index 858e94f327..11184d27d9 100644 --- a/crates/core/src/dec.rs +++ b/crates/core/src/dec.rs @@ -10,6 +10,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use num_traits::CheckedMul; use serde::{Deserialize, Serialize}; @@ -38,6 +40,7 @@ pub type Result = std::result::Result; Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Serialize, diff --git a/crates/core/src/eth_bridge_pool.rs b/crates/core/src/eth_bridge_pool.rs index 86fa7daaea..11301ca77b 100644 --- a/crates/core/src/eth_bridge_pool.rs +++ b/crates/core/src/eth_bridge_pool.rs @@ -6,7 +6,8 @@ use std::borrow::Cow; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use ethabi::token::Token; -use namada_macros::StorageKeys; +use namada_macros::{BorshDeserializer, StorageKeys}; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::address::InternalAddress; @@ -81,6 +82,7 @@ const NAMESPACE: &str = "transfer"; Ord, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -179,6 +181,7 @@ impl<'transfer> PendingTransferAppendix<'transfer> { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct TransferToEthereum { @@ -208,6 +211,7 @@ pub struct TransferToEthereum { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct PendingTransfer { @@ -345,6 +349,7 @@ impl From<&PendingTransfer> for Key { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct GasFee { diff --git a/crates/core/src/ethereum_events.rs b/crates/core/src/ethereum_events.rs index 3a381c401a..9c1f5bfb6e 100644 --- a/crates/core/src/ethereum_events.rs +++ b/crates/core/src/ethereum_events.rs @@ -10,6 +10,8 @@ use borsh_ext::BorshSerializeExt; use ethabi::ethereum_types::{H160, U256 as ethUint}; use ethabi::Token; use eyre::{eyre, Context}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use crate::address::Address; @@ -33,6 +35,7 @@ use crate::token::Amount; Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct Uint(pub [u64; 4]); @@ -137,6 +140,7 @@ impl Sub for Uint { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] #[serde(try_from = "String")] @@ -229,6 +233,7 @@ pub trait GetEventNonce { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct TransfersToNamada { @@ -264,6 +269,7 @@ impl From for EthereumEvent { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub enum EthereumEvent { @@ -325,6 +331,7 @@ impl EthereumEvent { Ord, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct TransferToNamada { @@ -347,6 +354,7 @@ pub struct TransferToNamada { Ord, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/core/src/ethereum_structs.rs b/crates/core/src/ethereum_structs.rs index e2bd8a5ce0..0856a7c01f 100644 --- a/crates/core/src/ethereum_structs.rs +++ b/crates/core/src/ethereum_structs.rs @@ -6,6 +6,8 @@ use std::ops::{Add, AddAssign, Deref}; use borsh::{BorshDeserialize, BorshSerialize}; pub use ethbridge_structs::*; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use num256::Uint256; use serde::{Deserialize, Serialize}; @@ -22,6 +24,7 @@ use crate::keccak::KeccakHash; PartialOrd, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -43,6 +46,7 @@ pub enum BpTransferStatus { PartialOrd, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] diff --git a/crates/core/src/event.rs b/crates/core/src/event.rs index d82121de50..18f562a493 100644 --- a/crates/core/src/event.rs +++ b/crates/core/src/event.rs @@ -5,6 +5,8 @@ use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; use std::str::FromStr; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use thiserror::Error; use crate::borsh::{BorshDeserialize, BorshSerialize}; @@ -25,7 +27,15 @@ impl EmitEvents for Vec { /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block -#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] pub enum EventLevel { /// Indicates an event is to do with a finalized block. Block, @@ -35,7 +45,15 @@ pub enum EventLevel { /// Custom events that can be queried from Tendermint /// using a websocket client -#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] pub struct Event { /// The type of event. pub event_type: EventType, @@ -47,7 +65,15 @@ pub struct Event { } /// The two types of custom events we currently use -#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] pub enum EventType { /// The transaction was accepted to be included in a block Accepted, diff --git a/crates/core/src/hash.rs b/crates/core/src/hash.rs index 59c6dd279f..4fe0251871 100644 --- a/crates/core/src/hash.rs +++ b/crates/core/src/hash.rs @@ -7,6 +7,8 @@ use arse_merkle_tree::traits::Hasher; use arse_merkle_tree::H256; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -43,6 +45,7 @@ pub type HashResult = std::result::Result; Eq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/core/src/ibc.rs b/crates/core/src/ibc.rs index d3d5995b09..4537bb9ffb 100644 --- a/crates/core/src/ibc.rs +++ b/crates/core/src/ibc.rs @@ -8,6 +8,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::{DecodePartial, HEXLOWER, HEXLOWER_PERMISSIVE, HEXUPPER}; pub use ibc::*; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -35,6 +37,7 @@ pub const EVENT_TYPE_DENOM_TRACE: &str = "denomination_trace"; Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -68,6 +71,7 @@ impl FromStr for IbcTokenHash { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -147,7 +151,7 @@ impl BorshDeserialize for MsgShieldedTransfer { } /// IBC shielded transfer -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct IbcShieldedTransfer { /// The IBC event type pub transfer: Transfer, diff --git a/crates/core/src/internal.rs b/crates/core/src/internal.rs index 31401f2cce..353ef568da 100644 --- a/crates/core/src/internal.rs +++ b/crates/core/src/internal.rs @@ -1,6 +1,8 @@ //! Shared internal types between the host env and guest (wasm). use borsh::{BorshDeserialize, BorshSerialize}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; /// A result of a wasm call to host functions that may fail. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -13,7 +15,7 @@ pub enum HostEnvResult { /// Key-value pair represents data from account's subspace. /// It is used for prefix iterator's WASM host_env functions. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct KeyVal { /// The storage key pub key: String, diff --git a/crates/core/src/keccak.rs b/crates/core/src/keccak.rs index ba73bff5b1..a9a862987a 100644 --- a/crates/core/src/keccak.rs +++ b/crates/core/src/keccak.rs @@ -6,6 +6,8 @@ use std::fmt; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use ethabi::Token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; pub use tiny_keccak::{Hasher, Keccak}; @@ -37,6 +39,7 @@ pub enum TryFromError { Ord, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct KeccakHash(pub [u8; 32]); diff --git a/crates/core/src/key/common.rs b/crates/core/src/key/common.rs index aa58c8700b..1b5f94ac6a 100644 --- a/crates/core/src/key/common.rs +++ b/crates/core/src/key/common.rs @@ -5,6 +5,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXLOWER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -30,6 +32,7 @@ use crate::{impl_display_and_from_str_via_format, string_encoding}; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub enum PublicKey { @@ -168,7 +171,14 @@ impl TryFrom<&PublicKey> for EthAddress { } /// Secret key -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, +)] #[allow(clippy::large_enum_variant)] pub enum SecretKey { /// Encapsulate Ed25519 secret keys @@ -297,6 +307,7 @@ impl FromStr for SecretKey { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub enum Signature { @@ -372,6 +383,7 @@ impl super::Signature for Signature { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, diff --git a/crates/core/src/key/ed25519.rs b/crates/core/src/key/ed25519.rs index a2b00ace80..35c94361c0 100644 --- a/crates/core/src/key/ed25519.rs +++ b/crates/core/src/key/ed25519.rs @@ -10,6 +10,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXLOWER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -26,7 +28,9 @@ const SECRET_KEY_LENGTH: usize = 32; const SIGNATURE_LENGTH: usize = 64; /// Ed25519 public key -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive( + Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshDeserializer, +)] pub struct PublicKey(pub ed25519_consensus::VerificationKey); impl super::PublicKey for PublicKey { @@ -134,7 +138,9 @@ impl FromStr for PublicKey { } /// Ed25519 secret key -#[derive(Debug, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] +#[derive( + Debug, Serialize, Deserialize, Zeroize, ZeroizeOnDrop, BorshDeserializer, +)] pub struct SecretKey(pub Box); impl super::SecretKey for SecretKey { @@ -239,7 +245,9 @@ impl FromStr for SecretKey { } /// Ed25519 signature -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive( + Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshDeserializer, +)] pub struct Signature(pub ed25519_consensus::Signature); impl super::Signature for Signature { @@ -332,6 +340,7 @@ impl Ord for Signature { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, diff --git a/crates/core/src/key/mod.rs b/crates/core/src/key/mod.rs index dee77713f4..f70e6017ae 100644 --- a/crates/core/src/key/mod.rs +++ b/crates/core/src/key/mod.rs @@ -11,6 +11,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -278,6 +280,7 @@ pub trait SigScheme: Eq + Ord + Debug + Serialize + Default { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, diff --git a/crates/core/src/key/secp256k1.rs b/crates/core/src/key/secp256k1.rs index cacef20680..c1a4a65f82 100644 --- a/crates/core/src/key/secp256k1.rs +++ b/crates/core/src/key/secp256k1.rs @@ -14,6 +14,8 @@ use data_encoding::HEXLOWER; use ethabi::Token; use k256::ecdsa::RecoveryId; use k256::elliptic_curve::sec1::ToEncodedPoint; +use namada_macros::BorshDeserializer; +use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -34,7 +36,9 @@ use crate::key::StorageHasher; pub const SIGNATURE_SIZE: usize = 64 + 1; /// secp256k1 public key -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive( + Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshDeserializer, +)] pub struct PublicKey(pub k256::PublicKey); /// Size of a compressed public key bytes @@ -168,7 +172,7 @@ impl From<&PublicKey> for EthAddress { } /// Secp256k1 secret key -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshDeserializer)] pub struct SecretKey(pub Box); impl super::SecretKey for SecretKey { @@ -288,7 +292,7 @@ impl RefTo for SecretKey { } /// Secp256k1 signature -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, BorshDeserializer)] pub struct Signature(pub k256::ecdsa::Signature, pub RecoveryId); impl super::Signature for Signature { @@ -530,6 +534,7 @@ impl TryFrom<&[u8; 65]> for Signature { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index ad37985461..07583cd261 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -6,6 +6,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -22,6 +24,7 @@ use crate::token::{Denomination, MaspDigitPos}; #[derive( BorshSerialize, BorshDeserialize, + BorshDeserializer, Clone, Debug, PartialOrd, @@ -101,6 +104,7 @@ const PAYMENT_ADDRESS_SIZE: usize = 43 + 1; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, Eq, PartialEq, PartialOrd, @@ -245,6 +249,7 @@ impl<'de> serde::Deserialize<'de> for ExtendedViewingKey { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct PaymentAddress(masp_primitives::sapling::PaymentAddress, bool); @@ -306,7 +311,9 @@ impl<'de> serde::Deserialize<'de> for PaymentAddress { } /// Wrapper for masp_primitive's ExtendedSpendingKey -#[derive(Clone, Debug, Copy, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, Debug, Copy, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub struct ExtendedSpendingKey(masp_primitives::zip32::ExtendedSpendingKey); impl string_encoding::Format for ExtendedSpendingKey { @@ -469,6 +476,7 @@ impl Display for TransferTarget { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, diff --git a/crates/core/src/parameters.rs b/crates/core/src/parameters.rs index ee2b9ecf6c..1b3333b26e 100644 --- a/crates/core/src/parameters.rs +++ b/crates/core/src/parameters.rs @@ -2,6 +2,9 @@ use std::collections::BTreeMap; +use namada_macros::BorshDeserializer; +use namada_migrations::*; + use super::address::Address; use super::chain::ProposalBytes; use super::dec::Dec; @@ -21,6 +24,7 @@ use crate::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct Parameters { @@ -68,6 +72,7 @@ pub struct Parameters { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct EpochDuration { diff --git a/crates/core/src/sign.rs b/crates/core/src/sign.rs index 7b3e0d91ec..b4860d1adc 100644 --- a/crates/core/src/sign.rs +++ b/crates/core/src/sign.rs @@ -3,6 +3,8 @@ use std::cmp::Ordering; use data_encoding::HEXUPPER; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -28,6 +30,7 @@ pub enum SigIndexDecodeError { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index dddb367f7c..e800b1634b 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -11,6 +11,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::{BASE32HEX_NOPAD, HEXUPPER}; use index_set::vec::VecIndexSet; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -83,6 +85,7 @@ pub const WASM_HASH_PREFIX: &str = "hash"; Copy, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -128,6 +131,7 @@ impl From for u32 { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, Default, )] pub struct BlockResults(VecIndexSet); @@ -166,6 +170,7 @@ impl BlockResults { Copy, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -238,6 +243,7 @@ impl FromStr for BlockHeight { Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -347,7 +353,9 @@ impl core::fmt::Debug for BlockHash { /// The data from Tendermint header /// relevant for Namada storage -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Default)] +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, Default, +)] pub struct Header { /// Merkle root hash of block pub hash: Hash, @@ -370,6 +378,7 @@ impl Header { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Debug, Default, @@ -410,7 +419,7 @@ impl FromStr for Key { } /// Storage keys that are utf8 encoded strings -#[derive(Eq, PartialEq, Copy, Clone, Hash)] +#[derive(Eq, PartialEq, Copy, Clone, Hash, BorshDeserializer)] pub struct StringKey { /// The original key string, in bytes pub original: [u8; IBC_KEY_LIMIT], @@ -497,7 +506,15 @@ impl arse_merkle_tree::Key for StringKey { /// A wrapper around raw bytes to be stored as values /// in a merkle tree -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] pub struct TreeBytes(pub Vec); impl arse_merkle_tree::traits::Value for TreeBytes { @@ -794,6 +811,7 @@ pub trait KeySeg { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Debug, Eq, @@ -1038,6 +1056,7 @@ impl KeySeg for common::PublicKey { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -1215,6 +1234,7 @@ impl Mul for Epoch { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct Epochs { /// The block heights of the first block of each known epoch. @@ -1282,7 +1302,14 @@ impl Epochs { } /// A value of a storage prefix iterator. -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, +)] pub struct PrefixValue { /// Storage key pub key: Key, @@ -1291,7 +1318,9 @@ pub struct PrefixValue { } /// Container of all Ethereum event queues. -#[derive(Default, Debug, BorshSerialize, BorshDeserialize)] +#[derive( + Default, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub struct EthEventsQueue { /// Queue of transfer to Namada events. pub transfers_to_namada: InnerEthEventsQueue, @@ -1457,6 +1486,7 @@ impl GetEventNonce for InnerEthEventsQueue { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, Eq, PartialEq, Ord, diff --git a/crates/core/src/time.rs b/crates/core/src/time.rs index 3a5e1d617f..70e14471a1 100644 --- a/crates/core/src/time.rs +++ b/crates/core/src/time.rs @@ -9,6 +9,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use chrono::ParseError; pub use chrono::{DateTime, Duration, TimeZone, Utc}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; /// Check if the given `duration` has passed since the given `start. @@ -34,6 +36,7 @@ pub fn duration_passed( Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct DurationSecs(pub u64); @@ -79,6 +82,7 @@ impl Display for DurationSecs { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct DurationNanos { @@ -110,6 +114,7 @@ impl From for std::time::Duration { Deserialize, Serialize, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -132,6 +137,7 @@ pub struct Rfc3339String(pub String); Hash, serde::Serialize, serde::Deserialize, + BorshDeserializer, )] #[serde(try_from = "Rfc3339String", into = "Rfc3339String")] pub struct DateTimeUtc(pub DateTime); diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 5f6c9c6c2a..c6e2125cc3 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -9,6 +9,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use ethabi::ethereum_types::U256; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -28,6 +30,7 @@ use crate::uint::{self, Uint, I256}; Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, PartialEq, Eq, @@ -308,6 +311,7 @@ impl Display for Amount { Ord, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -337,6 +341,7 @@ impl From for u8 { Eq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct DenominatedAmount { @@ -914,6 +919,7 @@ impl From for Uint { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -982,6 +988,7 @@ impl From for IbcAmount { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, diff --git a/crates/core/src/uint.rs b/crates/core/src/uint.rs index 63d732cf7c..13c60715b0 100644 --- a/crates/core/src/uint.rs +++ b/crates/core/src/uint.rs @@ -7,6 +7,8 @@ use std::ops::{Add, AddAssign, BitAnd, Div, Mul, Neg, Rem, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use num_integer::Integer; use num_traits::{CheckedAdd, CheckedMul, CheckedSub}; use uint::construct_uint; @@ -300,6 +302,7 @@ construct_uint! { #[derive( BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] @@ -482,6 +485,7 @@ const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct I256(pub Uint); diff --git a/crates/core/src/voting_power.rs b/crates/core/src/voting_power.rs index b4164b58f0..4cb3fed917 100644 --- a/crates/core/src/voting_power.rs +++ b/crates/core/src/voting_power.rs @@ -9,6 +9,8 @@ use std::ops::{Add, AddAssign, Mul}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::ethereum_types as ethereum; use eyre::{eyre, Result}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use num_rational::Ratio; use num_traits::ops::checked::CheckedAdd; use serde::de::Visitor; @@ -21,6 +23,7 @@ use crate::uint::Uint; #[derive( BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Default, Copy, @@ -96,7 +99,9 @@ impl From for u128 { /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. -#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] +#[derive( + Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, BorshDeserializer, +)] pub struct FractionalVotingPower(Ratio); impl FractionalVotingPower { diff --git a/crates/ethereum_bridge/Cargo.toml b/crates/ethereum_bridge/Cargo.toml index f99338d22d..b5d8e8ef10 100644 --- a/crates/ethereum_bridge/Cargo.toml +++ b/crates/ethereum_bridge/Cargo.toml @@ -24,6 +24,7 @@ testing = [ namada_account = {path = "../account", optional = true} namada_core = {path = "../core", default-features = false, features = ["ethers-derive"]} namada_macros = {path = "../macros"} +namada_migrations = {path = "../migrations"} namada_parameters = {path = "../parameters"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_state = {path = "../state"} @@ -37,6 +38,7 @@ ethabi.workspace = true ethers.workspace = true eyre.workspace = true itertools.workspace = true +linkme.workspace = true serde.workspace = true serde_json.workspace = true rand.workspace = true diff --git a/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs b/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs index 3267912131..fb9a6bfb21 100644 --- a/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,5 +1,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ethereum_events::EthereumEvent; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_vote_ext::ethereum_events::MultiSignedEthEvent; use crate::protocol::transactions::votes::{dedupe, Tally, Votes}; @@ -15,6 +17,7 @@ use crate::protocol::transactions::votes::{dedupe, Tally, Votes}; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct EthMsgUpdate { /// The event being seen. @@ -38,7 +41,14 @@ impl From for EthMsgUpdate { /// Represents an event stored under `eth_msgs` #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct EthMsg { /// The event being stored diff --git a/crates/ethereum_bridge/src/protocol/transactions/votes.rs b/crates/ethereum_bridge/src/protocol/transactions/votes.rs index be8a097200..79c7abbc47 100644 --- a/crates/ethereum_bridge/src/protocol/transactions/votes.rs +++ b/crates/ethereum_bridge/src/protocol/transactions/votes.rs @@ -9,6 +9,8 @@ use namada_core::address::Address; use namada_core::storage::{BlockHeight, Epoch}; use namada_core::token; use namada_core::voting_power::FractionalVotingPower; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_proof_of_stake::pos_queries::PosQueries; use namada_state::{DBIter, StorageHasher, WlState, DB}; @@ -116,7 +118,14 @@ impl EpochedVotingPowerExt for EpochedVotingPower { } #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] /// Represents all the information needed to tally a piece of data that may be /// voted for over multiple epochs diff --git a/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs b/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs index 903cad06d6..ae40f8227b 100644 --- a/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs +++ b/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs @@ -9,6 +9,8 @@ use namada_core::keccak::KeccakHash; use namada_core::storage::{BlockHeight, Epoch, Key as StorageKey}; use namada_core::voting_power::{EthBridgeVotingPower, FractionalVotingPower}; use namada_core::{hints, token}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_proof_of_stake::pos_queries::{ConsensusValidators, PosQueries}; use namada_proof_of_stake::storage::{ validator_eth_cold_key_handle, validator_eth_hot_key_handle, @@ -37,6 +39,7 @@ pub enum SendValsetUpd { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, @@ -54,6 +57,7 @@ pub enum EthBridgeStatus { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, PartialEq, Eq, diff --git a/crates/ethereum_bridge/src/storage/parameters.rs b/crates/ethereum_bridge/src/storage/parameters.rs index c30321d1fa..07e94d3777 100644 --- a/crates/ethereum_bridge/src/storage/parameters.rs +++ b/crates/ethereum_bridge/src/storage/parameters.rs @@ -7,6 +7,8 @@ use namada_core::ethereum_events::EthAddress; use namada_core::ethereum_structs; use namada_core::storage::Key; use namada_core::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_state::{DBIter, StorageHasher, WlState, DB}; use namada_storage::{StorageRead, StorageWrite}; use serde::{Deserialize, Serialize}; @@ -29,6 +31,7 @@ use crate::storage::vp; Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct Erc20WhitelistEntry { /// The address of the whitelisted ERC20 token. @@ -49,6 +52,7 @@ pub struct Erc20WhitelistEntry { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] #[repr(transparent)] pub struct MinimumConfirmations(NonZeroU64); @@ -85,6 +89,7 @@ impl From for NonZeroU64 { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] #[repr(transparent)] pub struct ContractVersion(NonZeroU64); @@ -109,6 +114,7 @@ impl Default for ContractVersion { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct UpgradeableContract { /// The Ethereum address of the contract. @@ -129,6 +135,7 @@ pub struct UpgradeableContract { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's @@ -148,6 +155,7 @@ pub struct Contracts { Serialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub struct EthereumBridgeParams { /// Initial Ethereum block height when events will first be extracted from. diff --git a/crates/ethereum_bridge/src/storage/vote_tallies.rs b/crates/ethereum_bridge/src/storage/vote_tallies.rs index 7ed38aa409..ebebfbe4d0 100644 --- a/crates/ethereum_bridge/src/storage/vote_tallies.rs +++ b/crates/ethereum_bridge/src/storage/vote_tallies.rs @@ -9,7 +9,8 @@ use namada_core::ethereum_events::{EthereumEvent, Uint}; use namada_core::hash::Hash; use namada_core::keccak::{keccak_hash, KeccakHash}; use namada_core::storage::{BlockHeight, DbKeySeg, Epoch, Key}; -use namada_macros::StorageKeys; +use namada_macros::{BorshDeserializer, StorageKeys}; +use namada_migrations::*; use namada_vote_ext::validator_set_update::VotingPowersMap; use crate::storage::proof::{BridgePoolRootProof, EthereumProof}; @@ -189,7 +190,7 @@ impl From<&Hash> for Keys { /// A wrapper struct for managing keys related to /// tracking signatures over bridge pool roots and nonces. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, BorshDeserializer)] pub struct BridgePoolRoot(pub BridgePoolRootProof); impl BorshSerialize for BridgePoolRoot { diff --git a/crates/gas/Cargo.toml b/crates/gas/Cargo.toml index 578ba43112..1d9db8ca9b 100644 --- a/crates/gas/Cargo.toml +++ b/crates/gas/Cargo.toml @@ -14,8 +14,10 @@ version.workspace = true [dependencies] namada_core = { path = "../core" } - +namada_macros = {path = "../macros"} +namada_migrations = {path = "../migrations"} borsh.workspace = true +linkme.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index 6b1b94e761..aa064cbcab 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -5,6 +5,8 @@ use std::fmt::Display; use std::ops::Div; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -72,6 +74,7 @@ const SCALE: u64 = 10_000; PartialEq, PartialOrd, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, Serialize, @@ -205,6 +208,7 @@ pub struct VpGasMeter { Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/governance/Cargo.toml b/crates/governance/Cargo.toml index 82cbb75c1b..477ada2fa8 100644 --- a/crates/governance/Cargo.toml +++ b/crates/governance/Cargo.toml @@ -18,12 +18,14 @@ testing = ["proptest"] [dependencies] namada_core = { path = "../core" } namada_macros = {path = "../macros"} +namada_migrations = { path= "../migrations" } namada_parameters = {path = "../parameters"} namada_storage = {path = "../storage"} namada_trans_token = {path = "../trans_token"} borsh.workspace = true itertools.workspace = true +linkme.workspace = true proptest = { workspace = true, optional = true } serde_json.workspace = true serde.workspace = true diff --git a/crates/governance/src/cli/offline.rs b/crates/governance/src/cli/offline.rs index f55a3e0104..f92b4eb064 100644 --- a/crates/governance/src/cli/offline.rs +++ b/crates/governance/src/cli/offline.rs @@ -9,13 +9,21 @@ use namada_core::hash::Hash; use namada_core::key::{common, RefTo, SigScheme}; use namada_core::sign::SignatureIndex; use namada_core::storage::Epoch; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::validation::{is_valid_tally_epoch, ProposalValidation}; use crate::storage::vote::ProposalVote; #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] /// The offline proposal structure pub struct OfflineProposal { @@ -89,7 +97,13 @@ impl TryFrom<&[u8]> for OfflineProposal { } #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] /// The signed offline proposal structure pub struct OfflineSignedProposal { @@ -173,7 +187,13 @@ impl OfflineSignedProposal { } #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] /// The offline proposal structure pub struct OfflineVote { diff --git a/crates/governance/src/cli/onchain.rs b/crates/governance/src/cli/onchain.rs index f4c405af13..bcf0049bc4 100644 --- a/crates/governance/src/cli/onchain.rs +++ b/crates/governance/src/cli/onchain.rs @@ -5,6 +5,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::Address; use namada_core::storage::Epoch; use namada_core::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::validation::{ @@ -17,7 +19,13 @@ use crate::parameters::GovernanceParameters; use crate::storage::proposal::PGFTarget; #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] /// The proposal structure pub struct OnChainProposal { @@ -37,7 +45,13 @@ pub struct OnChainProposal { /// Pgf default proposal #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct DefaultProposal { /// The proposal data @@ -183,7 +197,13 @@ impl TryFrom<&[u8]> for PgfStewardProposal { /// Pgf funding proposal #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct PgfFundingProposal { /// The proposal data @@ -246,7 +266,13 @@ impl TryFrom<&[u8]> for PgfFundingProposal { /// Pgf stewards #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct PgfSteward { /// Pgf action @@ -257,7 +283,13 @@ pub struct PgfSteward { /// Pgf action #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub enum PgfAction { /// Add action @@ -275,7 +307,13 @@ impl PgfAction { /// Pgf funding #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct PgfFunding { /// Pgf continuous funding @@ -304,7 +342,13 @@ impl Display for PgfFunding { /// Pgf continuous funding #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct PgfContinuous { /// Pgf target @@ -315,7 +359,13 @@ pub struct PgfContinuous { /// Pgf retro funding #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct PgfRetro { /// Pgf retro target diff --git a/crates/governance/src/parameters.rs b/crates/governance/src/parameters.rs index 5318ec2880..caf28a7ebb 100644 --- a/crates/governance/src/parameters.rs +++ b/crates/governance/src/parameters.rs @@ -1,5 +1,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_storage::{Result, StorageRead, StorageWrite}; use super::storage::keys as goverance_storage; @@ -14,6 +16,7 @@ use super::storage::keys as goverance_storage; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] /// Governance parameter structure pub struct GovernanceParameters { diff --git a/crates/governance/src/pgf/parameters.rs b/crates/governance/src/pgf/parameters.rs index 69e6fea9d3..b80d842a3d 100644 --- a/crates/governance/src/pgf/parameters.rs +++ b/crates/governance/src/pgf/parameters.rs @@ -3,6 +3,8 @@ use std::collections::BTreeSet; use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::dec::Dec; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_storage::{Result, StorageRead, StorageWrite}; use serde::{Deserialize, Serialize}; @@ -19,6 +21,7 @@ use super::storage::steward::StewardDetail; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] diff --git a/crates/governance/src/pgf/storage/steward.rs b/crates/governance/src/pgf/storage/steward.rs index 973c33ed78..804133a3db 100644 --- a/crates/governance/src/pgf/storage/steward.rs +++ b/crates/governance/src/pgf/storage/steward.rs @@ -3,10 +3,14 @@ use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::Address; use namada_core::dec::Dec; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use crate::pgf::REWARD_DISTRIBUTION_LIMIT; -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, PartialEq, +)] /// Struct holding data about a pgf steward pub struct StewardDetail { /// The steward address diff --git a/crates/governance/src/storage/proposal.rs b/crates/governance/src/storage/proposal.rs index c5f527f64b..13117b1c18 100644 --- a/crates/governance/src/storage/proposal.rs +++ b/crates/governance/src/storage/proposal.rs @@ -7,6 +7,8 @@ use namada_core::address::Address; use namada_core::hash::Hash; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::storage::Epoch; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_trans_token::Amount; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -32,6 +34,7 @@ pub enum ProposalError { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -69,6 +72,7 @@ impl InitProposalData { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -167,6 +171,7 @@ impl TryFrom for InitProposalData { PartialOrd, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -191,6 +196,7 @@ impl StoragePgfFunding { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -243,6 +249,7 @@ where PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, Ord, @@ -294,6 +301,7 @@ impl Display for PGFTarget { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, Ord, @@ -309,7 +317,15 @@ pub struct PGFInternalTarget { /// The target of a PGF payment #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Ord, Eq, PartialOrd, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Ord, + Eq, + PartialOrd, + BorshDeserializer, )] pub struct PGFIbcTarget { /// The target address on the target chain @@ -393,6 +409,7 @@ impl borsh::BorshSchema for PGFIbcTarget { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, Eq, @@ -508,7 +525,7 @@ impl Display for PGFAction { } } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer)] /// Proposal representation when fetched from the storage pub struct StorageProposal { /// The proposal id diff --git a/crates/governance/src/storage/vote.rs b/crates/governance/src/storage/vote.rs index 62487c78ba..b09be21f73 100644 --- a/crates/governance/src/storage/vote.rs +++ b/crates/governance/src/storage/vote.rs @@ -1,6 +1,8 @@ use std::fmt::Display; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; #[derive( @@ -9,6 +11,7 @@ use serde::{Deserialize, Serialize}; PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Eq, Serialize, Deserialize, diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index 4619c90473..186cd740e8 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -6,6 +6,8 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::dec::Dec; use namada_core::storage::Epoch; use namada_core::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use super::cli::offline::OfflineVote; use super::storage::proposal::ProposalType; @@ -35,7 +37,7 @@ impl Display for ProposalStatus { pub type VotePower = token::Amount; /// Structure rappresenting a proposal vote -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct Vote { /// Field holding the address of the validator pub validator: Address, @@ -60,7 +62,9 @@ impl Vote { } /// Represent a tally type -#[derive(Copy, Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub enum TallyType { /// Represent a tally type for proposal requiring 2/3 of the total voting /// power to be yay @@ -90,7 +94,9 @@ impl TallyType { } /// The result of a proposal -#[derive(Copy, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive( + Copy, Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub enum TallyResult { /// Proposal was accepted with the associated value Passed, @@ -173,7 +179,7 @@ impl TallyResult { } /// The result with votes of a proposal -#[derive(Clone, Copy, BorshDeserialize, BorshSerialize)] +#[derive(Clone, Copy, BorshDeserialize, BorshSerialize, BorshDeserializer)] pub struct ProposalResult { /// The result of a proposal pub result: TallyResult, diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 05e51ea5ec..3522a78f31 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -16,11 +16,13 @@ version.workspace = true proc-macro = true [dependencies] +namada_migrations = {path = "../migrations"} +paste.workspace = true proc-macro2 = "1.0" quote = "1.0" syn = {version="1.0", features = ["full", "extra-traits"]} +sha2.workspace = true lazy_static = "1.4.0" -borsh-derive = "1.2.0" [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index a328ed250c..151cb206e0 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -7,10 +7,14 @@ #![deny(rustdoc::private_intra_doc_links)] use proc_macro::TokenStream; -use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use proc_macro2::{Span as Span2, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; +use sha2::Digest; use syn::punctuated::Punctuated; -use syn::{parse_macro_input, ExprAssign, FnArg, ItemFn, ItemStruct, Pat}; +use syn::{ + parse_macro_input, ExprAssign, FnArg, ItemEnum, ItemFn, ItemStruct, + LitByte, Pat, +}; /// Generate WASM binding for a transaction main entrypoint function. /// @@ -332,6 +336,79 @@ where }) } +#[proc_macro_derive(BorshDeserializer)] +pub fn derive_borsh_deserializer(struct_def: TokenStream) -> TokenStream { + derive_borsh_deserializer_inner(struct_def.into()).into() +} + +#[inline] +fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { + let mut hasher = sha2::Sha256::new(); + let (ident, generics) = syn::parse2::(item_def.clone()) + .map(|def| { + hasher.update(def.to_token_stream().to_string().as_bytes()); + (def.ident, def.generics) + }) + .unwrap_or_else(|_| { + let def = syn::parse2::(item_def).expect( + "BorshDeserializer expected to be derived on a struct or enum", + ); + hasher.update(def.to_token_stream().to_string().as_bytes()); + (def.ident, def.generics) + }); + let type_hash: [u8; 32] = hasher.finalize().into(); + let hash = syn::ExprArray { + attrs: vec![], + bracket_token: Default::default(), + elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { + syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Byte(LitByte::new( + b, + proc_macro2::Span::call_site(), + )), + }) + })), + }; + + if !generics.params.is_empty() { + panic!( + "Cannot derive BorshDeserializer on a parameterized type. This \ + can be done manually for concrete instantiations via the \ + derive_borshdeserializer! macro." + ); + } + + let deserializer_ident = + syn::Ident::new(&format!("{}_DESERIALIZER", ident), Span::call_site()); + quote!( + #[namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] + static #deserializer_ident: fn() = || { + let mut locked = namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); + locked.insert(#hash, |bytes| {#ident::try_from_slice(&bytes).is_ok()}); + }; + impl namada_migrations::TypeHash for #ident { + const HASH: [u8; 32] = #hash; + } + ) +} + +#[allow(unused_macros)] +macro_rules! derive_borshdeserializer { + ($name: ty) => { + #[namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] + static [<$name_ DESERIALIZER>]: fn() = || { + let mut locked = ::namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); + locked.insert($name::HASH, |bytes| {#ident::try_from_slice(&bytes).is_ok()}); + }; + impl ::namada_migrations::TypeHash for $name { + const HASH: [u8; 32] = ::sha2_const::Sha256::new() + .update(std::any::type_name::<$name>()) + .finalize(); + } + }; +} + #[cfg(test)] mod test_proc_macros { use syn::File; diff --git a/crates/merkle_tree/Cargo.toml b/crates/merkle_tree/Cargo.toml index 4fc284deb9..f7e4cefbd8 100644 --- a/crates/merkle_tree/Cargo.toml +++ b/crates/merkle_tree/Cargo.toml @@ -14,11 +14,14 @@ version.workspace = true [dependencies] namada_core = { path = "../core" } +namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } arse-merkle-tree.workspace = true borsh.workspace = true eyre.workspace = true ics23.workspace = true +linkme.workspace = true prost.workspace = true thiserror.workspace = true diff --git a/crates/merkle_tree/src/eth_bridge_pool.rs b/crates/merkle_tree/src/eth_bridge_pool.rs index 12f0e9303c..468b9a382d 100644 --- a/crates/merkle_tree/src/eth_bridge_pool.rs +++ b/crates/merkle_tree/src/eth_bridge_pool.rs @@ -10,6 +10,8 @@ use namada_core::hash::Hash; use namada_core::keccak::{keccak_hash, KeccakHash}; use namada_core::storage; use namada_core::storage::{BlockHeight, DbKeySeg}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; #[derive(thiserror::Error, Debug)] #[error(transparent)] @@ -28,7 +30,13 @@ const POOL_ROOT_PREFIX_NON_LEAF: u8 = 0xff; /// /// Note that an empty tree has root [0u8; 20] by definition. #[derive( - Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, + Debug, + Default, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct BridgePoolTree { /// Root of the tree diff --git a/crates/merkle_tree/src/lib.rs b/crates/merkle_tree/src/lib.rs index 45fd45653c..3c80df75d9 100644 --- a/crates/merkle_tree/src/lib.rs +++ b/crates/merkle_tree/src/lib.rs @@ -26,6 +26,8 @@ use namada_core::storage::{ StringKey, TreeBytes, TreeKeyError, IBC_KEY_LIMIT, }; use namada_core::{decode, DecodeError}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use thiserror::Error; /// Trait for reading from a merkle tree that is a sub-tree @@ -133,6 +135,7 @@ pub type Amt = PartialOrd, BorshSerialize, BorshDeserialize, + BorshDeserializer, )] pub enum StoreType { /// Base tree, which has roots of the subtrees diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml new file mode 100644 index 0000000000..e6eee355b0 --- /dev/null +++ b/crates/migrations/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "namada_migrations" +description = "Namada DB migration tooling" +resolver = "2" +authors.workspace = true +edition.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[lib] + +[dependencies] +borsh.workspace = true +borsh-ext.workspace = true +data-encoding.workspace = true +lazy_static.workspace = true +linkme.workspace = true +serde.workspace = true diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs new file mode 100644 index 0000000000..cd33c9a371 --- /dev/null +++ b/crates/migrations/src/lib.rs @@ -0,0 +1,19 @@ +#![allow(clippy::type_complexity)] + +use std::collections::HashMap; +use std::sync::Mutex; + +use lazy_static::lazy_static; +pub use linkme::distributed_slice; + +lazy_static! { + pub static ref TYPE_DESERIALIZERS: Mutex) -> bool>> = + Mutex::new(HashMap::new()); +} + +#[distributed_slice] +pub static REGISTER_DESERIALIZERS: [fn()]; + +pub trait TypeHash { + const HASH: [u8; 32]; +} diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index b2d9681245..ee7262bc29 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -81,6 +81,8 @@ namada_ethereum_bridge = { path = "../ethereum_bridge", default-features = false namada_gas = { path = "../gas" } namada_governance = { path = "../governance" } namada_ibc = { path = "../ibc" } +namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } namada_parameters = { path = "../parameters" } namada_proof_of_stake = { path = "../proof_of_stake" } namada_replay_protection = { path = "../replay_protection" } diff --git a/crates/proof_of_stake/Cargo.toml b/crates/proof_of_stake/Cargo.toml index acf4e86b27..063a2fc6f9 100644 --- a/crates/proof_of_stake/Cargo.toml +++ b/crates/proof_of_stake/Cargo.toml @@ -21,6 +21,8 @@ testing = ["proptest"] namada_account = { path = "../account" } namada_core = { path = "../core" } namada_governance = { path = "../governance" } +namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } namada_storage = { path = "../storage" } namada_parameters = { path = "../parameters" } namada_trans_token = { path = "../trans_token" } @@ -28,6 +30,7 @@ namada_trans_token = { path = "../trans_token" } borsh.workspace = true data-encoding.workspace = true derivative.workspace = true +linkme.workspace = true num-traits.workspace = true once_cell.workspace = true proptest = { workspace = true, optional = true } diff --git a/crates/proof_of_stake/src/epoched.rs b/crates/proof_of_stake/src/epoched.rs index f218c74e5a..23c4fc55d3 100644 --- a/crates/proof_of_stake/src/epoched.rs +++ b/crates/proof_of_stake/src/epoched.rs @@ -8,6 +8,8 @@ use std::{cmp, ops}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::storage::{self, Epoch}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_storage::collections::lazy_map::{LazyMap, NestedMap}; use namada_storage::collections::{self, LazyCollection}; use namada_storage::{StorageRead, StorageWrite}; @@ -759,6 +761,7 @@ where Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -782,6 +785,7 @@ impl EpochOffset for OffsetZero { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -805,6 +809,7 @@ impl EpochOffset for OffsetDefaultNumPastEpochs { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -828,6 +833,7 @@ impl EpochOffset for OffsetPipelineLen { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -851,6 +857,7 @@ impl EpochOffset for OffsetUnbondingLen { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -874,6 +881,7 @@ impl EpochOffset for OffsetPipelinePlusUnbondingLen { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -897,6 +905,7 @@ impl EpochOffset for OffsetSlashProcessingLen { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -920,6 +929,7 @@ impl EpochOffset for OffsetSlashProcessingLenPlus { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -943,6 +953,7 @@ impl EpochOffset for OffsetMaxU64 { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -966,6 +977,7 @@ impl EpochOffset for OffsetMaxProposalPeriod { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -990,6 +1002,7 @@ impl EpochOffset for OffsetMaxProposalPeriodPlus { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -1017,6 +1030,7 @@ impl EpochOffset for OffsetMaxProposalPeriodOrSlashProcessingLen { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, diff --git a/crates/proof_of_stake/src/parameters.rs b/crates/proof_of_stake/src/parameters.rs index 30d60b2b3e..0b25e67d86 100644 --- a/crates/proof_of_stake/src/parameters.rs +++ b/crates/proof_of_stake/src/parameters.rs @@ -8,11 +8,13 @@ use namada_core::storage::Epoch; use namada_core::token; use namada_core::uint::Uint; use namada_governance::parameters::GovernanceParameters; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use thiserror::Error; /// Proof-of-Stake system parameters. This includes parameters that are used in /// PoS but are read from other accounts storage (governance). -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Clone, BorshDeserialize, BorshDeserializer, BorshSerialize)] pub struct PosParams { /// PoS-owned params pub owned: OwnedPosParams, @@ -23,7 +25,7 @@ pub struct PosParams { /// Proof-of-Stake system parameters owned by the PoS address, set at genesis /// and can only be changed via governance -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Clone, BorshDeserialize, BorshDeserializer, BorshSerialize)] pub struct OwnedPosParams { /// A maximum number of consensus validators pub max_validator_slots: u64, diff --git a/crates/proof_of_stake/src/types/mod.rs b/crates/proof_of_stake/src/types/mod.rs index f0567a3aae..e4502ad021 100644 --- a/crates/proof_of_stake/src/types/mod.rs +++ b/crates/proof_of_stake/src/types/mod.rs @@ -15,6 +15,8 @@ use namada_core::key::common; use namada_core::storage::{Epoch, KeySeg}; use namada_core::token; use namada_core::token::Amount; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_storage::collections::lazy_map::NestedMap; use namada_storage::collections::{LazyMap, LazySet, LazyVec}; pub use rev_order::ReverseOrdTokenAmount; @@ -260,7 +262,14 @@ pub type LivenessMissedVotes = NestedMap>; pub type LivenessSumMissedVotes = LazyMap; #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Eq, Hash, PartialEq, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Eq, + Hash, + PartialEq, )] /// Slashed amount of tokens. pub struct SlashedAmount { @@ -270,7 +279,7 @@ pub struct SlashedAmount { pub epoch: Epoch, } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer)] /// Commission rate and max commission rate change per epoch for a validator pub struct CommissionPair { /// Validator commission rate @@ -309,6 +318,7 @@ pub struct Redelegation { BorshSerialize, BorshSchema, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, PartialOrd, @@ -343,6 +353,7 @@ pub struct GenesisValidator { BorshSerialize, BorshSchema, BorshDeserialize, + BorshDeserializer, Deserialize, Serialize, Eq, @@ -406,6 +417,7 @@ pub struct ConsensusValidator { Ord, Hash, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, )] @@ -426,6 +438,7 @@ pub struct BondId { PartialOrd, Ord, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, )] @@ -462,6 +475,7 @@ impl Display for WeightedValidator { Clone, Copy, BorshDeserialize, + BorshDeserializer, BorshSchema, BorshSerialize, )] @@ -509,6 +523,7 @@ impl Position { Clone, Copy, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -537,6 +552,7 @@ pub enum ValidatorState { Debug, Clone, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -566,6 +582,7 @@ pub type Slashes = LazyVec; Clone, Copy, BorshDeserialize, + BorshDeserializer, BorshSerialize, BorshSchema, PartialEq, @@ -583,7 +600,7 @@ pub enum SlashType { /// VoteInfo inspired from tendermint for validators whose signature was /// included in the last block -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshDeserializer)] pub struct VoteInfo { /// Validator address pub validator_address: Address, @@ -607,7 +624,14 @@ pub struct ResultSlashing { pub type BondsAndUnbondsDetails = HashMap; /// Bonds and unbonds with all details (slashes and rewards, if any) -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshDeserializer, + BorshSchema, +)] pub struct BondsAndUnbondsDetail { /// Bonds pub bonds: Vec, @@ -619,7 +643,13 @@ pub struct BondsAndUnbondsDetail { /// Bond with all its details #[derive( - Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema, PartialEq, + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshDeserializer, + BorshSchema, + PartialEq, )] pub struct BondDetails { /// The first epoch in which this bond contributed to a stake @@ -632,7 +662,13 @@ pub struct BondDetails { /// Unbond with all its details #[derive( - Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema, PartialEq, + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshDeserializer, + BorshSchema, + PartialEq, )] pub struct UnbondDetails { /// The first epoch in which the source bond of this unbond contributed to diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index f86490b7cb..c466bd384a 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -60,6 +60,8 @@ namada_core = { path = "../core" } namada_ethereum_bridge = { path = "../ethereum_bridge", default-features = false } namada_governance = { path = "../governance" } namada_ibc = { path = "../ibc" } +namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } namada_parameters = { path = "../parameters" } namada_proof_of_stake = { path = "../proof_of_stake" } namada_state = { path = "../state" } @@ -78,12 +80,12 @@ data-encoding.workspace = true derivation-path.workspace = true ethbridge-bridge-contract.workspace = true ethers.workspace = true -eyre.workspace = true fd-lock = { workspace = true, optional = true } futures.workspace = true itertools.workspace = true jubjub = { workspace = true, optional = true } lazy_static.workspace = true +linkme.workspace = true masp_primitives.workspace = true masp_proofs.workspace = true num256.workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 081865d26e..71dc57aa46 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,5 +1,4 @@ extern crate alloc; -extern crate core; pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] @@ -25,7 +24,6 @@ pub mod error; pub mod events; pub(crate) mod internal_macros; pub mod io; -pub mod migrations; pub mod queries; pub mod wallet; diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 35315ea39a..95c6e9989d 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -60,6 +60,8 @@ use namada_core::storage::{BlockHeight, Epoch, IndexedTx, TxIndex}; use namada_core::time::{DateTimeUtc, DurationSecs}; use namada_core::uint::Uint; use namada_ibc::IbcMessage; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_token::{self as token, Denomination, MaspDigitPos, Transfer}; use namada_tx::data::{TxResult, WrapperTx}; use namada_tx::Tx; @@ -116,7 +118,7 @@ pub type IndexedNoteEntry = ( ); /// Shielded transfer -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct ShieldedTransfer { /// Shielded transfer builder pub builder: Builder<(), (), ExtendedFullViewingKey, ()>, @@ -129,7 +131,7 @@ pub struct ShieldedTransfer { } /// Shielded pool data for a token -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct MaspTokenRewardData { pub name: String, pub address: Address, @@ -499,7 +501,7 @@ pub fn is_amount_required(src: I128Sum, dest: I128Sum, delta: I128Sum) -> bool { } /// a masp change -#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +#[derive(BorshSerialize, BorshDeserialize, BorshDeserializer, Debug, Clone)] pub struct MaspChange { /// the token address pub asset: Address, @@ -528,7 +530,9 @@ pub type TransactionDelta = HashMap; /// /// The cache is designed so that it either contains /// all transactions from a given height, or none. -#[derive(BorshSerialize, BorshDeserialize, Debug, Default, Clone)] +#[derive( + BorshSerialize, BorshDeserialize, BorshDeserializer, Debug, Default, Clone, +)] pub struct Unscanned { txs: IndexedNoteData, } diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs deleted file mode 100644 index c269454115..0000000000 --- a/crates/sdk/src/migrations.rs +++ /dev/null @@ -1,36 +0,0 @@ -use core::fmt::Formatter; - -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use borsh::schema::BorshSchemaContainer; -use borsh_ext::BorshSerializeExt; -use data_encoding::HEXUPPER; -use namada_core::storage::Key; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde::de::Visitor; - -pub trait DBUpdateVisitor { - fn read(&self, key: &Key) -> Option>; - fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); - fn delete(&mut self, key: &Key); -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DbUpdateType { - Add{key: Key, value: Vec}, - Delete(Key), -} - -impl DbUpdateType { - #[allow(dead_code)] - fn update(&self, db: &mut DB) -> eyre::Result<()>{ - match self { - Self::Add{key, value} => { - db.write(key, &value); - } - Self::Delete(key) =>{ - db.delete(key); - } - } - Ok(()) - } -} diff --git a/crates/sdk/src/queries/shell/eth_bridge.rs b/crates/sdk/src/queries/shell/eth_bridge.rs index 766d191427..65834b2201 100644 --- a/crates/sdk/src/queries/shell/eth_bridge.rs +++ b/crates/sdk/src/queries/shell/eth_bridge.rs @@ -28,6 +28,8 @@ use namada_ethereum_bridge::storage::vote_tallies::{eth_msgs_prefix, Keys}; use namada_ethereum_bridge::storage::{ bridge_contract_key, native_erc20_key, vote_tallies, }; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_proof_of_stake::pos_queries::PosQueries; use namada_state::MembershipProof::BridgePool; use namada_state::{DBIter, StorageHasher, StoreRef, StoreType, DB}; @@ -50,6 +52,7 @@ use crate::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -78,7 +81,14 @@ pub struct TransferToEthereumStatus { /// Contains information about the flow control of some ERC20 /// wrapped asset. #[derive( - Debug, Copy, Clone, Eq, PartialEq, BorshSerialize, BorshDeserialize, + Debug, + Copy, + Clone, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, )] pub struct Erc20FlowControl { /// Whether the wrapped asset is whitelisted. @@ -117,7 +127,15 @@ pub type TransferToErcArgs = ( ); /// Response data returned by `generate_bridge_pool_proof`. -#[derive(Debug, Clone, Eq, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, + Clone, + Eq, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, +)] pub struct GenBridgePoolProofRsp { /// Ethereum ABI encoded arguments to pass to `transfer_to_erc`. pub abi_encoded_args: Vec, diff --git a/crates/sdk/src/wallet/alias.rs b/crates/sdk/src/wallet/alias.rs index b154b7e13a..1eac1f0a30 100644 --- a/crates/sdk/src/wallet/alias.rs +++ b/crates/sdk/src/wallet/alias.rs @@ -8,12 +8,14 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::{Address, InternalAddress}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; /// Aliases created from raw strings are kept in-memory as given, but their /// `Serialize` and `Display` instance converts them to lowercase. Their /// `PartialEq` instance is case-insensitive. -#[derive(Clone, Debug, Default, Eq)] +#[derive(Clone, Debug, Default, Eq, BorshDeserializer)] pub struct Alias(String); impl Alias { diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index df16bc3d7e..b0fd8570e7 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -21,7 +21,9 @@ testing = ["proptest", "namada_core/testing"] [dependencies] namada_core = { path = "../core", default-features = false } namada_gas = { path = "../gas" } +namada_macros = { path = "../macros" } namada_merkle_tree = { path = "../merkle_tree" } +namada_migrations = { path = "../migrations" } namada_parameters = { path = "../parameters" } namada_replay_protection = { path = "../replay_protection" } namada_storage = { path = "../storage" } @@ -32,6 +34,7 @@ arse-merkle-tree.workspace = true borsh.workspace = true ics23.workspace = true itertools.workspace = true +linkme.workspace = true sha2.workspace = true thiserror.workspace = true tiny-keccak.workspace = true diff --git a/crates/state/src/in_memory.rs b/crates/state/src/in_memory.rs index 2d53b92b7c..a38c1d6e8b 100644 --- a/crates/state/src/in_memory.rs +++ b/crates/state/src/in_memory.rs @@ -4,7 +4,9 @@ use namada_core::chain::{ChainId, CHAIN_ID_LENGTH}; use namada_core::time::DateTimeUtc; use namada_core::{encode, ethereum_structs}; use namada_gas::MEMORY_ACCESS_GAS_PER_BYTE; +use namada_macros::BorshDeserializer; use namada_merkle_tree::{MerkleRoot, MerkleTree}; +use namada_migrations::*; use namada_parameters::{EpochDuration, Parameters}; use namada_storage::conversion_state::ConversionState; use namada_storage::tx_queue::{ExpiredTxsQueue, TxQueue}; @@ -72,7 +74,7 @@ where } /// Last committed block -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct LastBlock { /// Block height pub height: BlockHeight, diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 12f75ba063..e94662c5e7 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -21,12 +21,15 @@ testing = [ [dependencies] namada_core = { path = "../core" } namada_gas = { path = "../gas" } +namada_macros = { path = "../macros" } namada_merkle_tree = { path = "../merkle_tree" } +namada_migrations = {path = "../migrations" } namada_replay_protection = { path = "../replay_protection" } namada_tx = { path = "../tx" } borsh.workspace = true itertools.workspace = true +linkme.workspace = true thiserror.workspace = true tracing.workspace = true diff --git a/crates/storage/src/conversion_state.rs b/crates/storage/src/conversion_state.rs index 135b63bab5..7590f2a40c 100644 --- a/crates/storage/src/conversion_state.rs +++ b/crates/storage/src/conversion_state.rs @@ -10,9 +10,13 @@ use namada_core::masp_primitives::merkle_tree::FrozenCommitmentTree; use namada_core::masp_primitives::sapling; use namada_core::storage::Epoch; use namada_core::token::{Denomination, MaspDigitPos}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; /// A representation of the conversion state -#[derive(Debug, Default, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Default, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub struct ConversionState { /// The last amount of the native token distributed pub normed_inflation: Option, diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 11e79b3716..5ce22e85db 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -260,16 +260,6 @@ pub trait DB: Debug { batch: &mut Self::WriteBatch, key: &Key, ) -> Result<()>; - - /// Overwrite a new value in storage, taking into - /// account values stored at a previous height - fn overwrite_entry( - &self, - batch: &mut Self::WriteBatch, - height: Option, - key: &Key, - new_value: impl AsRef<[u8]>, - ) -> Result<()>; } /// A database prefix iterator. diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 71b6f87bb4..4128321e54 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -674,16 +674,6 @@ impl DB for MockDB { Ok(()) } - - fn overwrite_entry( - &self, - _batch: &mut Self::WriteBatch, - _height: Option, - _key: &Key, - _new_value: impl AsRef<[u8]> - ) -> Result<()> { - unimplemented!() - } } impl<'iter> DBIter<'iter> for MockDB { diff --git a/crates/storage/src/tx_queue.rs b/crates/storage/src/tx_queue.rs index 3d5d9c7d87..be36bd4629 100644 --- a/crates/storage/src/tx_queue.rs +++ b/crates/storage/src/tx_queue.rs @@ -1,11 +1,13 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ethereum_events::EthereumEvent; use namada_gas::Gas; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_tx::Tx; /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally /// add `has_valid_pow` flag for only used in testnets. -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshDeserializer)] pub struct TxInQueue { /// Wrapper tx pub tx: Tx, @@ -15,7 +17,9 @@ pub struct TxInQueue { pub gas: Gas, } -#[derive(Default, Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive( + Default, Debug, Clone, BorshDeserialize, BorshSerialize, BorshDeserializer, +)] /// Wrapper txs to be decrypted in the next block proposal pub struct TxQueue(std::collections::VecDeque); @@ -49,14 +53,16 @@ impl TxQueue { } /// Expired transaction kinds. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub enum ExpiredTx { /// Broadcast the given Ethereum event. EthereumEvent(EthereumEvent), } /// Queue of expired transactions that need to be retransmitted. -#[derive(Default, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive( + Default, Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] pub struct ExpiredTxsQueue { inner: Vec, } diff --git a/crates/tx/Cargo.toml b/crates/tx/Cargo.toml index bc7e00bc04..6f1adf49a4 100644 --- a/crates/tx/Cargo.toml +++ b/crates/tx/Cargo.toml @@ -19,10 +19,13 @@ testing = ["proptest", "namada_core/testing"] [dependencies] namada_core = { path = "../core" } namada_gas = { path = "../gas" } +namada_macros = { path = "../macros" } +namada_migrations = {path = "../migrations" } ark-bls12-381.workspace = true borsh.workspace = true data-encoding.workspace = true +linkme.workspace = true masp_primitives.workspace = true num-derive.workspace = true num-traits.workspace = true diff --git a/crates/tx/src/data/decrypted.rs b/crates/tx/src/data/decrypted.rs index d764f03d00..71a4f520dd 100644 --- a/crates/tx/src/data/decrypted.rs +++ b/crates/tx/src/data/decrypted.rs @@ -6,6 +6,8 @@ pub mod decrypted_tx { use namada_core::borsh::{ BorshDeserialize, BorshSchema, BorshSerialize, BorshSerializeExt, }; + use namada_macros::BorshDeserializer; + use namada_migrations::*; use sha2::{Digest, Sha256}; #[derive( @@ -13,6 +15,7 @@ pub mod decrypted_tx { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, serde::Serialize, serde::Deserialize, diff --git a/crates/tx/src/data/eval_vp.rs b/crates/tx/src/data/eval_vp.rs index 69562734fa..f0d2e06e26 100644 --- a/crates/tx/src/data/eval_vp.rs +++ b/crates/tx/src/data/eval_vp.rs @@ -1,5 +1,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::hash::Hash; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use crate::Tx; @@ -7,7 +9,13 @@ use crate::Tx; /// A validity predicate with an input that is intended to be invoked via `eval` /// host function. #[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, )] pub struct EvalVp { /// The VP code hash to `eval` diff --git a/crates/tx/src/data/mod.rs b/crates/tx/src/data/mod.rs index e187bdd9fb..5280300e51 100644 --- a/crates/tx/src/data/mod.rs +++ b/crates/tx/src/data/mod.rs @@ -27,6 +27,8 @@ use namada_core::hash::Hash; use namada_core::ibc::IbcEvent; use namada_core::storage; use namada_gas::{Gas, VpsGas}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use serde::{Deserialize, Serialize}; @@ -169,6 +171,7 @@ pub fn hash_tx(tx_bytes: &[u8]) -> Hash { Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -202,6 +205,7 @@ impl TxResult { Default, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] @@ -290,6 +294,7 @@ fn iterable_to_string( Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/tx/src/data/pgf.rs b/crates/tx/src/data/pgf.rs index 5343ae4daa..07739e272e 100644 --- a/crates/tx/src/data/pgf.rs +++ b/crates/tx/src/data/pgf.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::dec::Dec; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,6 +22,7 @@ pub enum PgfError { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, Serialize, Deserialize, )] diff --git a/crates/tx/src/data/pos.rs b/crates/tx/src/data/pos.rs index 3805556780..6d997592ee 100644 --- a/crates/tx/src/data/pos.rs +++ b/crates/tx/src/data/pos.rs @@ -5,6 +5,8 @@ use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::dec::Dec; use namada_core::key::{common, secp256k1}; use namada_core::token; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; /// A tx data type to become a validator account. @@ -14,6 +16,7 @@ use serde::{Deserialize, Serialize}; PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -56,6 +59,7 @@ pub struct BecomeValidator { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -82,6 +86,7 @@ pub type Unbond = Bond; PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -103,6 +108,7 @@ pub struct Withdraw { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -124,6 +130,7 @@ pub struct ClaimRewards { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -148,6 +155,7 @@ pub struct Redelegation { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -168,6 +176,7 @@ pub struct CommissionChange { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, @@ -198,6 +207,7 @@ pub struct MetaDataChange { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Hash, Eq, diff --git a/crates/tx/src/data/protocol.rs b/crates/tx/src/data/protocol.rs index f9244e62d2..fb42317b4b 100644 --- a/crates/tx/src/data/protocol.rs +++ b/crates/tx/src/data/protocol.rs @@ -5,6 +5,8 @@ use namada_core::borsh::{ BorshDeserialize, BorshSchema, BorshSerialize, BorshSerializeExt, }; use namada_core::key::*; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -15,6 +17,7 @@ use crate::TxError; Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -55,6 +58,7 @@ impl ProtocolTx { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/tx/src/data/wrapper.rs b/crates/tx/src/data/wrapper.rs index a70ebaa39d..c4612d8c3f 100644 --- a/crates/tx/src/data/wrapper.rs +++ b/crates/tx/src/data/wrapper.rs @@ -18,6 +18,8 @@ pub mod wrapper_tx { use namada_core::token::{Amount, DenominatedAmount, Transfer}; use namada_core::uint::Uint; use namada_gas::Gas; + use namada_macros::BorshDeserializer; + use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -61,6 +63,7 @@ pub mod wrapper_tx { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -89,6 +92,7 @@ pub mod wrapper_tx { PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -182,6 +186,7 @@ pub mod wrapper_tx { Clone, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index 9533209425..7e5770cfda 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -21,6 +21,8 @@ use namada_core::masp::AssetData; use namada_core::sign::SignatureIndex; use namada_core::storage::Epoch; use namada_core::time::DateTimeUtc; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use serde::de::Error as SerdeError; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -70,7 +72,14 @@ pub enum DecodeError { /// Because the signature is not checked by the ledger, we don't inline it into /// the `Tx` type directly. Instead, the signature is attached to the `tx.data`, /// which can then be checked by a validity predicate wasm. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, +)] pub struct SignedTxData { /// The original tx data bytes, if any pub data: Option>, @@ -208,6 +217,7 @@ pub fn verify_standalone_sig>( Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -242,6 +252,7 @@ pub struct CommitmentError; Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -301,6 +312,7 @@ impl Commitment { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -353,6 +365,7 @@ pub type Memo = Vec; Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -370,6 +383,7 @@ pub enum Signer { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -515,6 +529,7 @@ impl Signature { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -561,6 +576,7 @@ impl CompressedSignature { Deserialize, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct Ciphertext { @@ -704,6 +720,7 @@ impl borsh::BorshSchema for MaspBuilder { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -851,6 +868,7 @@ impl Section { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, @@ -943,6 +961,7 @@ pub enum TxError { Debug, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, Serialize, Deserialize, diff --git a/crates/vote_ext/Cargo.toml b/crates/vote_ext/Cargo.toml index d20c591fc3..2aee50fea3 100644 --- a/crates/vote_ext/Cargo.toml +++ b/crates/vote_ext/Cargo.toml @@ -14,9 +14,12 @@ version.workspace = true [dependencies] namada_core = { path = "../core" } +namada_macros = { path = "../macros" } +namada_migrations = { path = "../migrations" } namada_tx = { path = "../tx" } borsh.workspace = true +linkme.workspace = true serde.workspace = true [dev-dependencies] diff --git a/crates/vote_ext/src/bridge_pool_roots.rs b/crates/vote_ext/src/bridge_pool_roots.rs index 41b1f85b54..d48b4d6e9e 100644 --- a/crates/vote_ext/src/bridge_pool_roots.rs +++ b/crates/vote_ext/src/bridge_pool_roots.rs @@ -10,6 +10,8 @@ use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::key::common; use namada_core::key::common::Signature; use namada_core::storage::BlockHeight; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_tx::Signed; /// A vote extension containing a validator's signature @@ -25,6 +27,7 @@ use namada_tx::Signed; Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct BridgePoolRootVext { @@ -58,6 +61,7 @@ pub type Vext = BridgePoolRootVext; BorshSerialize, BorshSchema, BorshDeserialize, + BorshDeserializer, PartialEq, Eq, Hash, @@ -98,6 +102,7 @@ impl Vext { Eq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct MultiSignedVext(pub HashSet); diff --git a/crates/vote_ext/src/ethereum_events.rs b/crates/vote_ext/src/ethereum_events.rs index 05fa0c3b3a..e2da2cbabf 100644 --- a/crates/vote_ext/src/ethereum_events.rs +++ b/crates/vote_ext/src/ethereum_events.rs @@ -9,6 +9,8 @@ use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ethereum_events::EthereumEvent; use namada_core::key::common::{self, Signature}; use namada_core::storage::BlockHeight; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_tx::Signed; /// Type alias for an [`EthereumEventsVext`]. @@ -16,7 +18,14 @@ pub type Vext = EthereumEventsVext; /// Represents a [`Vext`] signed by some validator, with /// a Namada protocol key. -#[derive(Clone, Debug, BorshSerialize, BorshSchema, BorshDeserialize)] +#[derive( + Clone, + Debug, + BorshSerialize, + BorshSchema, + BorshDeserialize, + BorshDeserializer, +)] pub struct SignedVext(pub Signed); impl Deref for SignedVext { @@ -39,7 +48,14 @@ impl From> for SignedVext { /// to be included as a vote extension at the end of a Tendermint PreCommit /// phase. #[derive( - Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Debug, + Clone, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct EthereumEventsVext { /// The block height for which this [`Vext`] was made. @@ -73,7 +89,14 @@ impl Vext { /// Aggregates an Ethereum event with the corresponding /// validators who saw this event. #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct MultiSignedEthEvent { /// The Ethereum event that was signed. @@ -89,7 +112,14 @@ pub type VextDigest = EthereumEventsVextDigest; /// Compresses a set of signed [`Vext`] instances, to save /// space on a block. #[derive( - Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Debug, + Clone, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct EthereumEventsVextDigest { /// The signatures, signing address, and signing block height diff --git a/crates/vote_ext/src/lib.rs b/crates/vote_ext/src/lib.rs index 72fdbb21cd..9c50a5e055 100644 --- a/crates/vote_ext/src/lib.rs +++ b/crates/vote_ext/src/lib.rs @@ -9,6 +9,8 @@ use namada_core::borsh::{ }; use namada_core::chain::ChainId; use namada_core::key::common; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_tx::data::protocol::{ProtocolTx, ProtocolTxType}; use namada_tx::data::TxType; use namada_tx::{Signature, Signed, Tx, TxError}; @@ -16,7 +18,14 @@ use namada_tx::{Signature, Signed, Tx, TxError}; /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. #[derive( - Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Debug, + Clone, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct VoteExtension { /// Vote extension data related with Ethereum events. @@ -89,7 +98,7 @@ macro_rules! ethereum_tx_data_declare { ethereum_tx_data_declare! { /// Data associated with Ethereum protocol transactions. - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, BorshSchema)] { /// Ethereum events contained in vote extensions that /// are compressed before being included on chain diff --git a/crates/vote_ext/src/validator_set_update.rs b/crates/vote_ext/src/validator_set_update.rs index 7c997f39a1..04b0eff34a 100644 --- a/crates/vote_ext/src/validator_set_update.rs +++ b/crates/vote_ext/src/validator_set_update.rs @@ -13,6 +13,8 @@ use namada_core::key::common::{self, Signature}; use namada_core::storage::Epoch; use namada_core::voting_power::{EthBridgeVotingPower, FractionalVotingPower}; use namada_core::{ethereum_structs, token}; +use namada_macros::BorshDeserializer; +use namada_migrations::*; use namada_tx::Signed; // the contract versions and namespaces plugged into validator set hashes @@ -28,7 +30,14 @@ pub type VextDigest = ValidatorSetUpdateVextDigest; /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct ValidatorSetUpdateVextDigest { /// A mapping from a consensus validator address to a [`Signature`]. @@ -76,7 +85,14 @@ impl VextDigest { /// Represents a [`Vext`] signed by some validator, with /// an Ethereum key. #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, + PartialEq, + Eq, )] pub struct SignedVext(pub Signed); @@ -93,7 +109,14 @@ pub type Vext = ValidatorSetUpdateVext; /// Represents a validator set update, for some new [`Epoch`]. #[derive( - Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, + Eq, + PartialEq, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, )] pub struct ValidatorSetUpdateVext { /// The addresses of the validators in the new [`Epoch`], @@ -149,6 +172,7 @@ impl Vext { Hash, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] pub struct EthAddrBook { @@ -330,6 +354,7 @@ fn encode_validator_data( PartialEq, BorshSerialize, BorshDeserialize, + BorshDeserializer, BorshSchema, )] // TODO: find a new home for this type diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 53a6cdf415..b41132c7ed 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3079,6 +3079,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3341,6 +3361,8 @@ dependencies = [ "namada_gas", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3393,8 +3415,10 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde", @@ -3418,8 +3442,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3453,9 +3479,11 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3478,7 +3506,10 @@ name = "namada_gas" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "serde", "thiserror", ] @@ -3489,8 +3520,10 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3530,8 +3563,12 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "lazy_static", + "namada_migrations", + "paste", "proc-macro2", "quote", + "sha2 0.9.9", "syn 1.0.109", ] @@ -3542,12 +3579,27 @@ dependencies = [ "borsh", "eyre", "ics23", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde", +] + [[package]] name = "namada_parameters" version = "0.31.9" @@ -3566,9 +3618,12 @@ dependencies = [ "borsh", "data-encoding", "derivative", + "linkme", "namada_account", "namada_core", "namada_governance", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3606,6 +3661,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3613,6 +3669,8 @@ dependencies = [ "namada_ethereum_bridge", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3668,9 +3726,12 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3690,9 +3751,12 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3767,9 +3831,12 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", + "namada_macros", + "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3826,7 +3893,10 @@ name = "namada_vote_ext" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "namada_tx", "serde", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 025fa4c4d8..e538e63160 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3079,6 +3079,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3341,6 +3361,8 @@ dependencies = [ "namada_gas", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3393,8 +3415,10 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde", @@ -3418,8 +3442,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3453,9 +3479,11 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3478,7 +3506,10 @@ name = "namada_gas" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "serde", "thiserror", ] @@ -3489,8 +3520,10 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3530,8 +3563,12 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "lazy_static", + "namada_migrations", + "paste", "proc-macro2", "quote", + "sha2 0.9.9", "syn 1.0.109", ] @@ -3542,12 +3579,27 @@ dependencies = [ "borsh", "eyre", "ics23", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde", +] + [[package]] name = "namada_parameters" version = "0.31.9" @@ -3566,9 +3618,12 @@ dependencies = [ "borsh", "data-encoding", "derivative", + "linkme", "namada_account", "namada_core", "namada_governance", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3606,6 +3661,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3613,6 +3669,8 @@ dependencies = [ "namada_ethereum_bridge", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3668,9 +3726,12 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3690,9 +3751,12 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3767,9 +3831,12 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", + "namada_macros", + "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3826,7 +3893,10 @@ name = "namada_vote_ext" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "namada_tx", "serde", ] From 8988219284e3945844225f43cb4d879ee6682903 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 14:45:30 +0100 Subject: [PATCH 42/96] [feat]: Featured gated migrations code --- Cargo.lock | 1 + crates/account/Cargo.toml | 3 +- crates/account/src/lib.rs | 1 + crates/account/src/types.rs | 1 + crates/apps/Cargo.toml | 9 ++++- crates/apps/src/lib/config/genesis.rs | 1 + crates/apps/src/lib/config/genesis/chain.rs | 1 + .../apps/src/lib/config/genesis/templates.rs | 1 + .../src/lib/config/genesis/transactions.rs | 1 + crates/core/Cargo.toml | 7 +++- crates/core/src/account.rs | 1 + crates/core/src/address.rs | 1 + crates/core/src/chain.rs | 1 + crates/core/src/dec.rs | 1 + crates/core/src/eth_bridge_pool.rs | 1 + crates/core/src/ethereum_events.rs | 1 + crates/core/src/ethereum_structs.rs | 1 + crates/core/src/event.rs | 1 + crates/core/src/hash.rs | 1 + crates/core/src/ibc.rs | 1 + crates/core/src/internal.rs | 1 + crates/core/src/keccak.rs | 1 + crates/core/src/key/common.rs | 1 + crates/core/src/key/ed25519.rs | 1 + crates/core/src/key/mod.rs | 1 + crates/core/src/key/secp256k1.rs | 1 + crates/core/src/masp.rs | 1 + crates/core/src/parameters.rs | 1 + crates/core/src/sign.rs | 1 + crates/core/src/storage.rs | 3 +- crates/core/src/time.rs | 1 + crates/core/src/token.rs | 1 + crates/core/src/uint.rs | 1 + crates/core/src/voting_power.rs | 1 + crates/ethereum_bridge/Cargo.toml | 7 +++- .../transactions/ethereum_events/eth_msgs.rs | 1 + .../src/protocol/transactions/votes.rs | 1 + .../src/storage/eth_bridge_queries.rs | 1 + .../ethereum_bridge/src/storage/parameters.rs | 1 + .../src/storage/vote_tallies.rs | 1 + crates/gas/Cargo.toml | 9 ++++- crates/gas/src/lib.rs | 1 + crates/governance/Cargo.toml | 7 +++- crates/governance/src/cli/offline.rs | 1 + crates/governance/src/cli/onchain.rs | 1 + crates/governance/src/parameters.rs | 1 + crates/governance/src/pgf/parameters.rs | 1 + crates/governance/src/pgf/storage/steward.rs | 1 + crates/governance/src/storage/proposal.rs | 1 + crates/governance/src/storage/vote.rs | 1 + crates/governance/src/utils.rs | 3 +- crates/macros/Cargo.toml | 3 ++ crates/macros/src/lib.rs | 6 +++- crates/merkle_tree/Cargo.toml | 9 ++++- crates/merkle_tree/src/eth_bridge_pool.rs | 1 + crates/merkle_tree/src/lib.rs | 1 + crates/migrations/Cargo.toml | 5 ++- crates/migrations/src/lib.rs | 15 ++++++++- crates/namada/Cargo.toml | 6 ++++ crates/proof_of_stake/Cargo.toml | 7 +++- crates/proof_of_stake/src/epoched.rs | 1 + crates/proof_of_stake/src/parameters.rs | 1 + crates/proof_of_stake/src/types/mod.rs | 1 + crates/sdk/Cargo.toml | 16 ++++++++- crates/sdk/src/masp.rs | 3 +- crates/sdk/src/queries/shell/eth_bridge.rs | 1 + crates/sdk/src/wallet/alias.rs | 1 + crates/state/Cargo.toml | 7 +++- crates/state/src/in_memory.rs | 1 + crates/storage/Cargo.toml | 7 +++- crates/storage/src/conversion_state.rs | 1 + crates/storage/src/tx_queue.rs | 1 + crates/tx/Cargo.toml | 7 +++- crates/tx/src/data/decrypted.rs | 1 + crates/tx/src/data/eval_vp.rs | 1 + crates/tx/src/data/mod.rs | 1 + crates/tx/src/data/pgf.rs | 1 + crates/tx/src/data/pos.rs | 1 + crates/tx/src/data/protocol.rs | 1 + crates/tx/src/data/wrapper.rs | 1 + crates/tx/src/types.rs | 1 + crates/vote_ext/Cargo.toml | 9 ++++- crates/vote_ext/src/bridge_pool_roots.rs | 1 + crates/vote_ext/src/ethereum_events.rs | 1 + crates/vote_ext/src/lib.rs | 1 + crates/vote_ext/src/validator_set_update.rs | 1 + wasm/Cargo.lock | 33 ------------------- wasm_for_tests/wasm_source/Cargo.lock | 33 ------------------- 88 files changed, 194 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bae1259c73..b0f2dc26a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4144,6 +4144,7 @@ dependencies = [ "ibc-testkit", "itertools 0.10.5", "k256", + "linkme", "loupe", "masp_primitives", "masp_proofs", diff --git a/crates/account/Cargo.toml b/crates/account/Cargo.toml index 1f329cade4..4f525d2ae3 100644 --- a/crates/account/Cargo.toml +++ b/crates/account/Cargo.toml @@ -15,6 +15,7 @@ version.workspace = true [features] default = [] testing = ["namada_core/testing", "proptest"] +migrations = ["namada_migrations/migrations", "namada_macros/migrations", "linkme"] [dependencies] namada_core = { path = "../core" } @@ -23,7 +24,7 @@ namada_migrations = { path = "../migrations" } namada_storage = { path = "../storage" } borsh.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true } proptest = { workspace = true, optional = true } serde.workspace = true diff --git a/crates/account/src/lib.rs b/crates/account/src/lib.rs index 00e79371c9..b1eda546ab 100644 --- a/crates/account/src/lib.rs +++ b/crates/account/src/lib.rs @@ -11,6 +11,7 @@ pub use namada_core::account::AccountPublicKeysMap; use namada_core::address::Address; use namada_core::key::common; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; pub use storage::*; diff --git a/crates/account/src/types.rs b/crates/account/src/types.rs index 6d73177a9a..f0b355b387 100644 --- a/crates/account/src/types.rs +++ b/crates/account/src/types.rs @@ -3,6 +3,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::hash::Hash; use namada_core::key::common; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/apps/Cargo.toml b/crates/apps/Cargo.toml index fae2ca3d09..8b2eab33e4 100644 --- a/crates/apps/Cargo.toml +++ b/crates/apps/Cargo.toml @@ -59,6 +59,13 @@ testing = ["namada_test_utils"] benches = ["testing", "namada_test_utils"] integration = [] jemalloc = ["rocksdb/jemalloc"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "namada_sdk/migrations", + "namada/migrations", + "linkme", +] [dependencies] namada = {path = "../namada", features = ["multicore", "http-client", "tendermint-rpc", "std"]} @@ -101,7 +108,7 @@ ledger-namada-rs.workspace = true ledger-transport-hid.workspace = true libc.workspace = true libloading.workspace = true -linkme.workspace = true +linkme = { workspace = true, optional = true } masp_primitives = { workspace = true, features = ["transparent-inputs"] } masp_proofs = { workspace = true, features = ["bundled-prover", "download-params"] } num_cpus.workspace = true diff --git a/crates/apps/src/lib/config/genesis.rs b/crates/apps/src/lib/config/genesis.rs index 30994510ce..62b31afa52 100644 --- a/crates/apps/src/lib/config/genesis.rs +++ b/crates/apps/src/lib/config/genesis.rs @@ -26,6 +26,7 @@ use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, OwnedPosParams}; use namada::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/apps/src/lib/config/genesis/chain.rs b/crates/apps/src/lib/config/genesis/chain.rs index 8877fd6ec2..cfe4659bb6 100644 --- a/crates/apps/src/lib/config/genesis/chain.rs +++ b/crates/apps/src/lib/config/genesis/chain.rs @@ -16,6 +16,7 @@ use namada::core::time::{DateTimeUtc, DurationNanos, Rfc3339String}; use namada::core::token::Amount; use namada::ledger::parameters::EpochDuration; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_sdk::wallet::store::AddressVpType; use namada_sdk::wallet::{pre_genesis, Wallet}; diff --git a/crates/apps/src/lib/config/genesis/templates.rs b/crates/apps/src/lib/config/genesis/templates.rs index 6aa645083b..217871138c 100644 --- a/crates/apps/src/lib/config/genesis/templates.rs +++ b/crates/apps/src/lib/config/genesis/templates.rs @@ -17,6 +17,7 @@ use namada::eth_bridge::storage::parameters::{ }; use namada::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/apps/src/lib/config/genesis/transactions.rs b/crates/apps/src/lib/config/genesis/transactions.rs index c65ffde72d..1f00d62a8d 100644 --- a/crates/apps/src/lib/config/genesis/transactions.rs +++ b/crates/apps/src/lib/config/genesis/transactions.rs @@ -28,6 +28,7 @@ use namada::tx::{ verify_standalone_sig, Code, Commitment, Data, Section, SignatureIndex, Tx, }; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_sdk::args::Tx as TxArgs; use namada_sdk::signing::{sign_tx, SigningTxData}; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index a319f7290d..e1753a27ec 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -25,6 +25,11 @@ testing = [ "rand", "proptest", ] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_macros = {path = "../macros"} @@ -44,7 +49,7 @@ ics23.workspace = true impl-num-traits = "0.1.2" index-set.workspace = true k256.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} masp_primitives.workspace = true num256.workspace = true num_enum = "0.7.0" diff --git a/crates/core/src/account.rs b/crates/core/src/account.rs index 908ed94788..3d0eedf35a 100644 --- a/crates/core/src/account.rs +++ b/crates/core/src/account.rs @@ -4,6 +4,7 @@ use std::collections::{BTreeMap, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index 2277161a85..71d66ac664 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -11,6 +11,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/core/src/chain.rs b/crates/core/src/chain.rs index 9830f9c4ee..511c23d785 100644 --- a/crates/core/src/chain.rs +++ b/crates/core/src/chain.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/core/src/dec.rs b/crates/core/src/dec.rs index 11184d27d9..485d5226e3 100644 --- a/crates/core/src/dec.rs +++ b/crates/core/src/dec.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use num_traits::CheckedMul; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/eth_bridge_pool.rs b/crates/core/src/eth_bridge_pool.rs index 11301ca77b..704e37253f 100644 --- a/crates/core/src/eth_bridge_pool.rs +++ b/crates/core/src/eth_bridge_pool.rs @@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use ethabi::token::Token; use namada_macros::{BorshDeserializer, StorageKeys}; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/ethereum_events.rs b/crates/core/src/ethereum_events.rs index 9c1f5bfb6e..61d42c6f15 100644 --- a/crates/core/src/ethereum_events.rs +++ b/crates/core/src/ethereum_events.rs @@ -11,6 +11,7 @@ use ethabi::ethereum_types::{H160, U256 as ethUint}; use ethabi::Token; use eyre::{eyre, Context}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/ethereum_structs.rs b/crates/core/src/ethereum_structs.rs index 0856a7c01f..3236dfc95d 100644 --- a/crates/core/src/ethereum_structs.rs +++ b/crates/core/src/ethereum_structs.rs @@ -7,6 +7,7 @@ use std::ops::{Add, AddAssign, Deref}; use borsh::{BorshDeserialize, BorshSerialize}; pub use ethbridge_structs::*; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use num256::Uint256; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/event.rs b/crates/core/src/event.rs index 18f562a493..3ae864aa7b 100644 --- a/crates/core/src/event.rs +++ b/crates/core/src/event.rs @@ -6,6 +6,7 @@ use std::ops::{Index, IndexMut}; use std::str::FromStr; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use thiserror::Error; diff --git a/crates/core/src/hash.rs b/crates/core/src/hash.rs index 4fe0251871..bb49edd06d 100644 --- a/crates/core/src/hash.rs +++ b/crates/core/src/hash.rs @@ -8,6 +8,7 @@ use arse_merkle_tree::H256; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/core/src/ibc.rs b/crates/core/src/ibc.rs index 4537bb9ffb..42d97f014d 100644 --- a/crates/core/src/ibc.rs +++ b/crates/core/src/ibc.rs @@ -9,6 +9,7 @@ use borsh_ext::BorshSerializeExt; use data_encoding::{DecodePartial, HEXLOWER, HEXLOWER_PERMISSIVE, HEXUPPER}; pub use ibc::*; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/core/src/internal.rs b/crates/core/src/internal.rs index 353ef568da..3399823f4b 100644 --- a/crates/core/src/internal.rs +++ b/crates/core/src/internal.rs @@ -2,6 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; /// A result of a wasm call to host functions that may fail. diff --git a/crates/core/src/keccak.rs b/crates/core/src/keccak.rs index a9a862987a..70ee83799b 100644 --- a/crates/core/src/keccak.rs +++ b/crates/core/src/keccak.rs @@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use ethabi::Token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; diff --git a/crates/core/src/key/common.rs b/crates/core/src/key/common.rs index 1b5f94ac6a..f73e99a3e8 100644 --- a/crates/core/src/key/common.rs +++ b/crates/core/src/key/common.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXLOWER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; diff --git a/crates/core/src/key/ed25519.rs b/crates/core/src/key/ed25519.rs index 35c94361c0..d746f16179 100644 --- a/crates/core/src/key/ed25519.rs +++ b/crates/core/src/key/ed25519.rs @@ -11,6 +11,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXLOWER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; diff --git a/crates/core/src/key/mod.rs b/crates/core/src/key/mod.rs index f70e6017ae..23af1c05a6 100644 --- a/crates/core/src/key/mod.rs +++ b/crates/core/src/key/mod.rs @@ -12,6 +12,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; diff --git a/crates/core/src/key/secp256k1.rs b/crates/core/src/key/secp256k1.rs index c1a4a65f82..fbc878f05a 100644 --- a/crates/core/src/key/secp256k1.rs +++ b/crates/core/src/key/secp256k1.rs @@ -15,6 +15,7 @@ use ethabi::Token; use k256::ecdsa::RecoveryId; use k256::elliptic_curve::sec1::ToEncodedPoint; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; #[cfg(any(test, feature = "rand"))] use rand::{CryptoRng, RngCore}; diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 07583cd261..2d2e86ac83 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -7,6 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/core/src/parameters.rs b/crates/core/src/parameters.rs index 1b3333b26e..38840e3fd3 100644 --- a/crates/core/src/parameters.rs +++ b/crates/core/src/parameters.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use super::address::Address; diff --git a/crates/core/src/sign.rs b/crates/core/src/sign.rs index b4860d1adc..a456247302 100644 --- a/crates/core/src/sign.rs +++ b/crates/core/src/sign.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use data_encoding::HEXUPPER; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index e800b1634b..15e0eeec5d 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -12,6 +12,7 @@ use borsh_ext::BorshSerializeExt; use data_encoding::{BASE32HEX_NOPAD, HEXUPPER}; use index_set::vec::VecIndexSet; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -419,7 +420,7 @@ impl FromStr for Key { } /// Storage keys that are utf8 encoded strings -#[derive(Eq, PartialEq, Copy, Clone, Hash, BorshDeserializer)] +#[derive(Eq, Debug, PartialEq, Copy, Clone, Hash, BorshDeserializer)] pub struct StringKey { /// The original key string, in bytes pub original: [u8; IBC_KEY_LIMIT], diff --git a/crates/core/src/time.rs b/crates/core/src/time.rs index 70e14471a1..2cffc8188f 100644 --- a/crates/core/src/time.rs +++ b/crates/core/src/time.rs @@ -10,6 +10,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use chrono::ParseError; pub use chrono::{DateTime, Duration, TimeZone, Utc}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index c6e2125cc3..9765a017bc 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -10,6 +10,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use ethabi::ethereum_types::U256; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/core/src/uint.rs b/crates/core/src/uint.rs index 13c60715b0..60cfd14952 100644 --- a/crates/core/src/uint.rs +++ b/crates/core/src/uint.rs @@ -8,6 +8,7 @@ use std::ops::{Add, AddAssign, BitAnd, Div, Mul, Neg, Rem, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use num_integer::Integer; use num_traits::{CheckedAdd, CheckedMul, CheckedSub}; diff --git a/crates/core/src/voting_power.rs b/crates/core/src/voting_power.rs index 4cb3fed917..abe7cb49ed 100644 --- a/crates/core/src/voting_power.rs +++ b/crates/core/src/voting_power.rs @@ -10,6 +10,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::ethereum_types as ethereum; use eyre::{eyre, Result}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use num_rational::Ratio; use num_traits::ops::checked::CheckedAdd; diff --git a/crates/ethereum_bridge/Cargo.toml b/crates/ethereum_bridge/Cargo.toml index b5d8e8ef10..556f15064c 100644 --- a/crates/ethereum_bridge/Cargo.toml +++ b/crates/ethereum_bridge/Cargo.toml @@ -19,6 +19,11 @@ testing = [ "namada_core/testing", "namada_state/testing", ] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_account = {path = "../account", optional = true} @@ -38,7 +43,7 @@ ethabi.workspace = true ethers.workspace = true eyre.workspace = true itertools.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} serde.workspace = true serde_json.workspace = true rand.workspace = true diff --git a/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs b/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs index fb9a6bfb21..3a59d08f93 100644 --- a/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/crates/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,6 +1,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ethereum_events::EthereumEvent; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_vote_ext::ethereum_events::MultiSignedEthEvent; diff --git a/crates/ethereum_bridge/src/protocol/transactions/votes.rs b/crates/ethereum_bridge/src/protocol/transactions/votes.rs index 79c7abbc47..3e49cba9d3 100644 --- a/crates/ethereum_bridge/src/protocol/transactions/votes.rs +++ b/crates/ethereum_bridge/src/protocol/transactions/votes.rs @@ -10,6 +10,7 @@ use namada_core::storage::{BlockHeight, Epoch}; use namada_core::token; use namada_core::voting_power::FractionalVotingPower; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_proof_of_stake::pos_queries::PosQueries; use namada_state::{DBIter, StorageHasher, WlState, DB}; diff --git a/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs b/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs index ae40f8227b..5875078638 100644 --- a/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs +++ b/crates/ethereum_bridge/src/storage/eth_bridge_queries.rs @@ -10,6 +10,7 @@ use namada_core::storage::{BlockHeight, Epoch, Key as StorageKey}; use namada_core::voting_power::{EthBridgeVotingPower, FractionalVotingPower}; use namada_core::{hints, token}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_proof_of_stake::pos_queries::{ConsensusValidators, PosQueries}; use namada_proof_of_stake::storage::{ diff --git a/crates/ethereum_bridge/src/storage/parameters.rs b/crates/ethereum_bridge/src/storage/parameters.rs index 07e94d3777..36121b7a6b 100644 --- a/crates/ethereum_bridge/src/storage/parameters.rs +++ b/crates/ethereum_bridge/src/storage/parameters.rs @@ -8,6 +8,7 @@ use namada_core::ethereum_structs; use namada_core::storage::Key; use namada_core::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_state::{DBIter, StorageHasher, WlState, DB}; use namada_storage::{StorageRead, StorageWrite}; diff --git a/crates/ethereum_bridge/src/storage/vote_tallies.rs b/crates/ethereum_bridge/src/storage/vote_tallies.rs index ebebfbe4d0..b4cdb9b208 100644 --- a/crates/ethereum_bridge/src/storage/vote_tallies.rs +++ b/crates/ethereum_bridge/src/storage/vote_tallies.rs @@ -10,6 +10,7 @@ use namada_core::hash::Hash; use namada_core::keccak::{keccak_hash, KeccakHash}; use namada_core::storage::{BlockHeight, DbKeySeg, Epoch, Key}; use namada_macros::{BorshDeserializer, StorageKeys}; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_vote_ext::validator_set_update::VotingPowersMap; diff --git a/crates/gas/Cargo.toml b/crates/gas/Cargo.toml index 1d9db8ca9b..35c4ad5f11 100644 --- a/crates/gas/Cargo.toml +++ b/crates/gas/Cargo.toml @@ -12,12 +12,19 @@ readme.workspace = true repository.workspace = true version.workspace = true +[features] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme" +] + [dependencies] namada_core = { path = "../core" } namada_macros = {path = "../macros"} namada_migrations = {path = "../migrations"} borsh.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} serde.workspace = true thiserror.workspace = true diff --git a/crates/gas/src/lib.rs b/crates/gas/src/lib.rs index aa064cbcab..f950209b96 100644 --- a/crates/gas/src/lib.rs +++ b/crates/gas/src/lib.rs @@ -6,6 +6,7 @@ use std::ops::Div; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/governance/Cargo.toml b/crates/governance/Cargo.toml index 477ada2fa8..7c0c28e243 100644 --- a/crates/governance/Cargo.toml +++ b/crates/governance/Cargo.toml @@ -14,6 +14,11 @@ version.workspace = true [features] testing = ["proptest"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_core = { path = "../core" } @@ -25,7 +30,7 @@ namada_trans_token = {path = "../trans_token"} borsh.workspace = true itertools.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} proptest = { workspace = true, optional = true } serde_json.workspace = true serde.workspace = true diff --git a/crates/governance/src/cli/offline.rs b/crates/governance/src/cli/offline.rs index f92b4eb064..f508fef4ca 100644 --- a/crates/governance/src/cli/offline.rs +++ b/crates/governance/src/cli/offline.rs @@ -10,6 +10,7 @@ use namada_core::key::{common, RefTo, SigScheme}; use namada_core::sign::SignatureIndex; use namada_core::storage::Epoch; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/governance/src/cli/onchain.rs b/crates/governance/src/cli/onchain.rs index bcf0049bc4..b56169e3f0 100644 --- a/crates/governance/src/cli/onchain.rs +++ b/crates/governance/src/cli/onchain.rs @@ -6,6 +6,7 @@ use namada_core::address::Address; use namada_core::storage::Epoch; use namada_core::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/governance/src/parameters.rs b/crates/governance/src/parameters.rs index caf28a7ebb..23d923cedb 100644 --- a/crates/governance/src/parameters.rs +++ b/crates/governance/src/parameters.rs @@ -1,6 +1,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_storage::{Result, StorageRead, StorageWrite}; diff --git a/crates/governance/src/pgf/parameters.rs b/crates/governance/src/pgf/parameters.rs index b80d842a3d..171bc91200 100644 --- a/crates/governance/src/pgf/parameters.rs +++ b/crates/governance/src/pgf/parameters.rs @@ -4,6 +4,7 @@ use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::dec::Dec; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_storage::{Result, StorageRead, StorageWrite}; use serde::{Deserialize, Serialize}; diff --git a/crates/governance/src/pgf/storage/steward.rs b/crates/governance/src/pgf/storage/steward.rs index 804133a3db..3b5c3648f6 100644 --- a/crates/governance/src/pgf/storage/steward.rs +++ b/crates/governance/src/pgf/storage/steward.rs @@ -4,6 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::Address; use namada_core::dec::Dec; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use crate::pgf::REWARD_DISTRIBUTION_LIMIT; diff --git a/crates/governance/src/storage/proposal.rs b/crates/governance/src/storage/proposal.rs index 13117b1c18..e4fad9e7a3 100644 --- a/crates/governance/src/storage/proposal.rs +++ b/crates/governance/src/storage/proposal.rs @@ -8,6 +8,7 @@ use namada_core::hash::Hash; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::storage::Epoch; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_trans_token::Amount; use serde::{Deserialize, Serialize}; diff --git a/crates/governance/src/storage/vote.rs b/crates/governance/src/storage/vote.rs index b09be21f73..2f5d6aa443 100644 --- a/crates/governance/src/storage/vote.rs +++ b/crates/governance/src/storage/vote.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use borsh::{BorshDeserialize, BorshSerialize}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index 186cd740e8..1f5d17c8d7 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -7,6 +7,7 @@ use namada_core::dec::Dec; use namada_core::storage::Epoch; use namada_core::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use super::cli::offline::OfflineVote; @@ -179,7 +180,7 @@ impl TallyResult { } /// The result with votes of a proposal -#[derive(Clone, Copy, BorshDeserialize, BorshSerialize, BorshDeserializer)] +#[derive(Clone, Debug, Copy, BorshDeserialize, BorshSerialize, BorshDeserializer)] pub struct ProposalResult { /// The result of a proposal pub result: TallyResult, diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 3522a78f31..0a01c560f9 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -15,6 +15,9 @@ version.workspace = true [lib] proc-macro = true +[features] +migrations = ["namada_migrations/migrations"] + [dependencies] namada_migrations = {path = "../migrations"} paste.workspace = true diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 151cb206e0..345d26fa4f 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -382,11 +382,15 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { let deserializer_ident = syn::Ident::new(&format!("{}_DESERIALIZER", ident), Span::call_site()); quote!( + #[cfg(feature = "migrations")] #[namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] static #deserializer_ident: fn() = || { let mut locked = namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); - locked.insert(#hash, |bytes| {#ident::try_from_slice(&bytes).is_ok()}); + locked.insert(#hash, |bytes| { + #ident::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() + }); }; + impl namada_migrations::TypeHash for #ident { const HASH: [u8; 32] = #hash; } diff --git a/crates/merkle_tree/Cargo.toml b/crates/merkle_tree/Cargo.toml index f7e4cefbd8..3b9797d153 100644 --- a/crates/merkle_tree/Cargo.toml +++ b/crates/merkle_tree/Cargo.toml @@ -12,6 +12,13 @@ readme.workspace = true repository.workspace = true version.workspace = true +[features] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] + [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } @@ -21,7 +28,7 @@ arse-merkle-tree.workspace = true borsh.workspace = true eyre.workspace = true ics23.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} prost.workspace = true thiserror.workspace = true diff --git a/crates/merkle_tree/src/eth_bridge_pool.rs b/crates/merkle_tree/src/eth_bridge_pool.rs index 468b9a382d..cc56a7333c 100644 --- a/crates/merkle_tree/src/eth_bridge_pool.rs +++ b/crates/merkle_tree/src/eth_bridge_pool.rs @@ -11,6 +11,7 @@ use namada_core::keccak::{keccak_hash, KeccakHash}; use namada_core::storage; use namada_core::storage::{BlockHeight, DbKeySeg}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; #[derive(thiserror::Error, Debug)] diff --git a/crates/merkle_tree/src/lib.rs b/crates/merkle_tree/src/lib.rs index 3c80df75d9..85e1603937 100644 --- a/crates/merkle_tree/src/lib.rs +++ b/crates/merkle_tree/src/lib.rs @@ -27,6 +27,7 @@ use namada_core::storage::{ }; use namada_core::{decode, DecodeError}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use thiserror::Error; diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index e6eee355b0..25ae4fb7e1 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -14,10 +14,13 @@ version.workspace = true [lib] +[features] +migrations = ["linkme"] + [dependencies] borsh.workspace = true borsh-ext.workspace = true data-encoding.workspace = true lazy_static.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} serde.workspace = true diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index cd33c9a371..c27f514c45 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -4,16 +4,29 @@ use std::collections::HashMap; use std::sync::Mutex; use lazy_static::lazy_static; +#[cfg(feature = "migrations")] pub use linkme::distributed_slice; lazy_static! { - pub static ref TYPE_DESERIALIZERS: Mutex) -> bool>> = + pub static ref TYPE_DESERIALIZERS: Mutex) -> Option>> = Mutex::new(HashMap::new()); } +#[cfg(feature = "migrations")] #[distributed_slice] pub static REGISTER_DESERIALIZERS: [fn()]; + + pub trait TypeHash { const HASH: [u8; 32]; } + +/// Calls all of the regeistered callbacks which place type +/// deserializes into [`TYPE_DESERIALIZERS`]. +#[cfg(feature = "migrations")] +pub fn initialize_deserializers() { + for func in REGISTER_DESERIALIZERS { + func(); + } +} \ No newline at end of file diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index ee7262bc29..4c3ba9f979 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -73,6 +73,11 @@ multicore = [ # Download MASP params if they're not present download-params = ["namada_sdk/download-params"] rand = ["namada_sdk/rand"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_account = { path = "../account" } @@ -108,6 +113,7 @@ ethers.workspace = true eyre.workspace = true futures.workspace = true itertools.workspace = true +linkme = {workspace = true, optional = true} loupe = { version = "0.1.3", optional = true } masp_primitives.workspace = true masp_proofs.workspace = true diff --git a/crates/proof_of_stake/Cargo.toml b/crates/proof_of_stake/Cargo.toml index 063a2fc6f9..afeccf07b2 100644 --- a/crates/proof_of_stake/Cargo.toml +++ b/crates/proof_of_stake/Cargo.toml @@ -16,6 +16,11 @@ version.workspace = true default = [] # testing helpers testing = ["proptest"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_account = { path = "../account" } @@ -30,7 +35,7 @@ namada_trans_token = { path = "../trans_token" } borsh.workspace = true data-encoding.workspace = true derivative.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} num-traits.workspace = true once_cell.workspace = true proptest = { workspace = true, optional = true } diff --git a/crates/proof_of_stake/src/epoched.rs b/crates/proof_of_stake/src/epoched.rs index 23c4fc55d3..44bf869f49 100644 --- a/crates/proof_of_stake/src/epoched.rs +++ b/crates/proof_of_stake/src/epoched.rs @@ -9,6 +9,7 @@ use std::{cmp, ops}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::storage::{self, Epoch}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_storage::collections::lazy_map::{LazyMap, NestedMap}; use namada_storage::collections::{self, LazyCollection}; diff --git a/crates/proof_of_stake/src/parameters.rs b/crates/proof_of_stake/src/parameters.rs index 0b25e67d86..df0be06c71 100644 --- a/crates/proof_of_stake/src/parameters.rs +++ b/crates/proof_of_stake/src/parameters.rs @@ -9,6 +9,7 @@ use namada_core::token; use namada_core::uint::Uint; use namada_governance::parameters::GovernanceParameters; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use thiserror::Error; diff --git a/crates/proof_of_stake/src/types/mod.rs b/crates/proof_of_stake/src/types/mod.rs index e4502ad021..8b07dcba6e 100644 --- a/crates/proof_of_stake/src/types/mod.rs +++ b/crates/proof_of_stake/src/types/mod.rs @@ -16,6 +16,7 @@ use namada_core::storage::{Epoch, KeySeg}; use namada_core::token; use namada_core::token::Amount; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_storage::collections::lazy_map::NestedMap; use namada_storage::collections::{LazyMap, LazySet, LazyVec}; diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index c466bd384a..6c521e39e8 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -53,6 +53,20 @@ testing = [ # Download MASP params if they're not present download-params = ["masp_proofs/download-params"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "namada_account/migrations", + "namada_core/migrations", + "namada_ethereum_bridge/migrations", + "namada_governance/migrations", + "namada_proof_of_stake/migrations", + "namada_state/migrations", + "namada_storage/migrations", + "namada_tx/migrations", + "namada_vote_ext/migrations", + "linkme", +] [dependencies] namada_account = { path = "../account" } @@ -85,7 +99,7 @@ futures.workspace = true itertools.workspace = true jubjub = { workspace = true, optional = true } lazy_static.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} masp_primitives.workspace = true masp_proofs.workspace = true num256.workspace = true diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 95c6e9989d..835a5724af 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -61,6 +61,7 @@ use namada_core::time::{DateTimeUtc, DurationSecs}; use namada_core::uint::Uint; use namada_ibc::IbcMessage; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_token::{self as token, Denomination, MaspDigitPos, Transfer}; use namada_tx::data::{TxResult, WrapperTx}; @@ -131,7 +132,7 @@ pub struct ShieldedTransfer { } /// Shielded pool data for a token -#[derive(BorshSerialize, BorshDeserialize, BorshDeserializer)] +#[derive(Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] pub struct MaspTokenRewardData { pub name: String, pub address: Address, diff --git a/crates/sdk/src/queries/shell/eth_bridge.rs b/crates/sdk/src/queries/shell/eth_bridge.rs index 65834b2201..9df529df89 100644 --- a/crates/sdk/src/queries/shell/eth_bridge.rs +++ b/crates/sdk/src/queries/shell/eth_bridge.rs @@ -29,6 +29,7 @@ use namada_ethereum_bridge::storage::{ bridge_contract_key, native_erc20_key, vote_tallies, }; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_proof_of_stake::pos_queries::PosQueries; use namada_state::MembershipProof::BridgePool; diff --git a/crates/sdk/src/wallet/alias.rs b/crates/sdk/src/wallet/alias.rs index 1eac1f0a30..d2dfef6cab 100644 --- a/crates/sdk/src/wallet/alias.rs +++ b/crates/sdk/src/wallet/alias.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::{Address, InternalAddress}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index b0fd8570e7..2a9fa1cc45 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -17,6 +17,11 @@ default = [] # for integration tests and test utilities testing = ["proptest", "namada_core/testing"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_core = { path = "../core", default-features = false } @@ -34,7 +39,7 @@ arse-merkle-tree.workspace = true borsh.workspace = true ics23.workspace = true itertools.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} sha2.workspace = true thiserror.workspace = true tiny-keccak.workspace = true diff --git a/crates/state/src/in_memory.rs b/crates/state/src/in_memory.rs index a38c1d6e8b..ad814bcd12 100644 --- a/crates/state/src/in_memory.rs +++ b/crates/state/src/in_memory.rs @@ -6,6 +6,7 @@ use namada_core::{encode, ethereum_structs}; use namada_gas::MEMORY_ACCESS_GAS_PER_BYTE; use namada_macros::BorshDeserializer; use namada_merkle_tree::{MerkleRoot, MerkleTree}; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_parameters::{EpochDuration, Parameters}; use namada_storage::conversion_state::ConversionState; diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index e94662c5e7..c7c943d48f 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -17,6 +17,11 @@ default = [] testing = [ "namada_core/testing", ] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_core = { path = "../core" } @@ -29,7 +34,7 @@ namada_tx = { path = "../tx" } borsh.workspace = true itertools.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} thiserror.workspace = true tracing.workspace = true diff --git a/crates/storage/src/conversion_state.rs b/crates/storage/src/conversion_state.rs index 7590f2a40c..839378b873 100644 --- a/crates/storage/src/conversion_state.rs +++ b/crates/storage/src/conversion_state.rs @@ -11,6 +11,7 @@ use namada_core::masp_primitives::sapling; use namada_core::storage::Epoch; use namada_core::token::{Denomination, MaspDigitPos}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; /// A representation of the conversion state diff --git a/crates/storage/src/tx_queue.rs b/crates/storage/src/tx_queue.rs index be36bd4629..7a02a31ebb 100644 --- a/crates/storage/src/tx_queue.rs +++ b/crates/storage/src/tx_queue.rs @@ -2,6 +2,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ethereum_events::EthereumEvent; use namada_gas::Gas; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_tx::Tx; diff --git a/crates/tx/Cargo.toml b/crates/tx/Cargo.toml index 6f1adf49a4..59d47d75cd 100644 --- a/crates/tx/Cargo.toml +++ b/crates/tx/Cargo.toml @@ -15,6 +15,11 @@ version.workspace = true [features] default = [] testing = ["proptest", "namada_core/testing"] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] [dependencies] namada_core = { path = "../core" } @@ -25,7 +30,7 @@ namada_migrations = {path = "../migrations" } ark-bls12-381.workspace = true borsh.workspace = true data-encoding.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} masp_primitives.workspace = true num-derive.workspace = true num-traits.workspace = true diff --git a/crates/tx/src/data/decrypted.rs b/crates/tx/src/data/decrypted.rs index 71a4f520dd..f9f48db492 100644 --- a/crates/tx/src/data/decrypted.rs +++ b/crates/tx/src/data/decrypted.rs @@ -7,6 +7,7 @@ pub mod decrypted_tx { BorshDeserialize, BorshSchema, BorshSerialize, BorshSerializeExt, }; use namada_macros::BorshDeserializer; + #[cfg(feature = "migrations")] use namada_migrations::*; use sha2::{Digest, Sha256}; diff --git a/crates/tx/src/data/eval_vp.rs b/crates/tx/src/data/eval_vp.rs index f0d2e06e26..7011a4ab1f 100644 --- a/crates/tx/src/data/eval_vp.rs +++ b/crates/tx/src/data/eval_vp.rs @@ -1,6 +1,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::hash::Hash; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/tx/src/data/mod.rs b/crates/tx/src/data/mod.rs index 5280300e51..65e92d939a 100644 --- a/crates/tx/src/data/mod.rs +++ b/crates/tx/src/data/mod.rs @@ -28,6 +28,7 @@ use namada_core::ibc::IbcEvent; use namada_core::storage; use namada_gas::{Gas, VpsGas}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; diff --git a/crates/tx/src/data/pgf.rs b/crates/tx/src/data/pgf.rs index 07739e272e..fc36642693 100644 --- a/crates/tx/src/data/pgf.rs +++ b/crates/tx/src/data/pgf.rs @@ -4,6 +4,7 @@ use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::dec::Dec; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/tx/src/data/pos.rs b/crates/tx/src/data/pos.rs index 6d997592ee..f8af08fd9d 100644 --- a/crates/tx/src/data/pos.rs +++ b/crates/tx/src/data/pos.rs @@ -6,6 +6,7 @@ use namada_core::dec::Dec; use namada_core::key::{common, secp256k1}; use namada_core::token; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; diff --git a/crates/tx/src/data/protocol.rs b/crates/tx/src/data/protocol.rs index fb42317b4b..dc3fdaccf0 100644 --- a/crates/tx/src/data/protocol.rs +++ b/crates/tx/src/data/protocol.rs @@ -6,6 +6,7 @@ use namada_core::borsh::{ }; use namada_core::key::*; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/tx/src/data/wrapper.rs b/crates/tx/src/data/wrapper.rs index c4612d8c3f..13327b5854 100644 --- a/crates/tx/src/data/wrapper.rs +++ b/crates/tx/src/data/wrapper.rs @@ -19,6 +19,7 @@ pub mod wrapper_tx { use namada_core::uint::Uint; use namada_gas::Gas; use namada_macros::BorshDeserializer; + #[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index 7e5770cfda..1847b577ff 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -22,6 +22,7 @@ use namada_core::sign::SignatureIndex; use namada_core::storage::Epoch; use namada_core::time::DateTimeUtc; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use serde::de::Error as SerdeError; use serde::{Deserialize, Serialize}; diff --git a/crates/vote_ext/Cargo.toml b/crates/vote_ext/Cargo.toml index 2aee50fea3..ffb0efdb31 100644 --- a/crates/vote_ext/Cargo.toml +++ b/crates/vote_ext/Cargo.toml @@ -12,6 +12,13 @@ readme.workspace = true repository.workspace = true version.workspace = true +[features] +migrations = [ + "namada_migrations/migrations", + "namada_macros/migrations", + "linkme", +] + [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } @@ -19,7 +26,7 @@ namada_migrations = { path = "../migrations" } namada_tx = { path = "../tx" } borsh.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional=true} serde.workspace = true [dev-dependencies] diff --git a/crates/vote_ext/src/bridge_pool_roots.rs b/crates/vote_ext/src/bridge_pool_roots.rs index d48b4d6e9e..e7b3d8ee6c 100644 --- a/crates/vote_ext/src/bridge_pool_roots.rs +++ b/crates/vote_ext/src/bridge_pool_roots.rs @@ -11,6 +11,7 @@ use namada_core::key::common; use namada_core::key::common::Signature; use namada_core::storage::BlockHeight; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_tx::Signed; diff --git a/crates/vote_ext/src/ethereum_events.rs b/crates/vote_ext/src/ethereum_events.rs index e2da2cbabf..4b930659e4 100644 --- a/crates/vote_ext/src/ethereum_events.rs +++ b/crates/vote_ext/src/ethereum_events.rs @@ -10,6 +10,7 @@ use namada_core::ethereum_events::EthereumEvent; use namada_core::key::common::{self, Signature}; use namada_core::storage::BlockHeight; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_tx::Signed; diff --git a/crates/vote_ext/src/lib.rs b/crates/vote_ext/src/lib.rs index 9c50a5e055..b64695d147 100644 --- a/crates/vote_ext/src/lib.rs +++ b/crates/vote_ext/src/lib.rs @@ -10,6 +10,7 @@ use namada_core::borsh::{ use namada_core::chain::ChainId; use namada_core::key::common; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_tx::data::protocol::{ProtocolTx, ProtocolTxType}; use namada_tx::data::TxType; diff --git a/crates/vote_ext/src/validator_set_update.rs b/crates/vote_ext/src/validator_set_update.rs index 04b0eff34a..21ca96b220 100644 --- a/crates/vote_ext/src/validator_set_update.rs +++ b/crates/vote_ext/src/validator_set_update.rs @@ -14,6 +14,7 @@ use namada_core::storage::Epoch; use namada_core::voting_power::{EthBridgeVotingPower, FractionalVotingPower}; use namada_core::{ethereum_structs, token}; use namada_macros::BorshDeserializer; +#[cfg(feature = "migrations")] use namada_migrations::*; use namada_tx::Signed; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b41132c7ed..ab5f537aff 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3079,26 +3079,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "linkme" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3415,7 +3395,6 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3442,7 +3421,6 @@ dependencies = [ "impl-num-traits", "index-set", "k256", - "linkme", "masp_primitives", "namada_macros", "namada_migrations", @@ -3479,7 +3457,6 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_macros", @@ -3506,7 +3483,6 @@ name = "namada_gas" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3520,7 +3496,6 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3579,7 +3554,6 @@ dependencies = [ "borsh", "eyre", "ics23", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3596,7 +3570,6 @@ dependencies = [ "borsh-ext", "data-encoding", "lazy_static", - "linkme", "serde", ] @@ -3618,7 +3591,6 @@ dependencies = [ "borsh", "data-encoding", "derivative", - "linkme", "namada_account", "namada_core", "namada_governance", @@ -3661,7 +3633,6 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", - "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3726,7 +3697,6 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3751,7 +3721,6 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3831,7 +3800,6 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", - "linkme", "masp_primitives", "namada_core", "namada_gas", @@ -3893,7 +3861,6 @@ name = "namada_vote_ext" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index e538e63160..b923798a72 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3079,26 +3079,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "linkme" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3415,7 +3395,6 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3442,7 +3421,6 @@ dependencies = [ "impl-num-traits", "index-set", "k256", - "linkme", "masp_primitives", "namada_macros", "namada_migrations", @@ -3479,7 +3457,6 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_macros", @@ -3506,7 +3483,6 @@ name = "namada_gas" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3520,7 +3496,6 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3579,7 +3554,6 @@ dependencies = [ "borsh", "eyre", "ics23", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3596,7 +3570,6 @@ dependencies = [ "borsh-ext", "data-encoding", "lazy_static", - "linkme", "serde", ] @@ -3618,7 +3591,6 @@ dependencies = [ "borsh", "data-encoding", "derivative", - "linkme", "namada_account", "namada_core", "namada_governance", @@ -3661,7 +3633,6 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", - "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3726,7 +3697,6 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3751,7 +3721,6 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3831,7 +3800,6 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", - "linkme", "masp_primitives", "namada_core", "namada_gas", @@ -3893,7 +3861,6 @@ name = "namada_vote_ext" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", From 4e07860484f6c80062298574c3b409408cb40567 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 15:21:15 +0100 Subject: [PATCH 43/96] [chore]: Added changelog --- .../unreleased/improvements/2814-global-deserializer-map.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2814-global-deserializer-map.md diff --git a/.changelog/unreleased/improvements/2814-global-deserializer-map.md b/.changelog/unreleased/improvements/2814-global-deserializer-map.md new file mode 100644 index 0000000000..6d801ec912 --- /dev/null +++ b/.changelog/unreleased/improvements/2814-global-deserializer-map.md @@ -0,0 +1,2 @@ + - This PR adds a proc macro that registers the deserializer of a type in a hashmap. This allows us to verify that + data blob deserializes as correctly if where are in possession of the hash map key. ([\#2814](https://github.com/anoma/namada/pull/2814)) \ No newline at end of file From a52e20b815a5ce721ef1cec9309fad2b8f662bce Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 15:32:15 +0100 Subject: [PATCH 44/96] [chore]: Formatting --- crates/governance/src/utils.rs | 4 +++- crates/migrations/src/lib.rs | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/governance/src/utils.rs b/crates/governance/src/utils.rs index 1f5d17c8d7..aa71909217 100644 --- a/crates/governance/src/utils.rs +++ b/crates/governance/src/utils.rs @@ -180,7 +180,9 @@ impl TallyResult { } /// The result with votes of a proposal -#[derive(Clone, Debug, Copy, BorshDeserialize, BorshSerialize, BorshDeserializer)] +#[derive( + Clone, Debug, Copy, BorshDeserialize, BorshSerialize, BorshDeserializer, +)] pub struct ProposalResult { /// The result of a proposal pub result: TallyResult, diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index c27f514c45..71d980c634 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -16,8 +16,6 @@ lazy_static! { #[distributed_slice] pub static REGISTER_DESERIALIZERS: [fn()]; - - pub trait TypeHash { const HASH: [u8; 32]; } @@ -29,4 +27,4 @@ pub fn initialize_deserializers() { for func in REGISTER_DESERIALIZERS { func(); } -} \ No newline at end of file +} From cac418d5ae27ab0f11c32bbf330ac37a73373adf Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 6 Mar 2024 08:36:23 +0100 Subject: [PATCH 45/96] Update crates/migrations/src/lib.rs Co-authored-by: Tiago Carvalho --- crates/migrations/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 71d980c634..3c4bf9a46c 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -6,9 +6,13 @@ use std::sync::Mutex; use lazy_static::lazy_static; #[cfg(feature = "migrations")] pub use linkme::distributed_slice; +/// Predicate that checks if an arbitrary byte array deserializes as some type `T` +/// erased inside of the callback. If the serialization is correct, the full path of `T` +/// is returned as a string (via [`std::any::type_name`]). +type CbFromByteArrayToTypeName = fn(Vec) -> Option; lazy_static! { - pub static ref TYPE_DESERIALIZERS: Mutex) -> Option>> = + pub static ref TYPE_DESERIALIZERS: Mutex> = Mutex::new(HashMap::new()); } From 8f6940e2afa67386d7625eb661462159cfb96ffd Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 6 Mar 2024 08:36:35 +0100 Subject: [PATCH 46/96] Update crates/migrations/Cargo.toml Co-authored-by: Tiago Carvalho --- crates/migrations/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index 25ae4fb7e1..9cc93e1e7b 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -22,5 +22,5 @@ borsh.workspace = true borsh-ext.workspace = true data-encoding.workspace = true lazy_static.workspace = true -linkme = {workspace = true, optional = true} +linkme.workspace = true serde.workspace = true From 0e1ee60031b5636a0ca37e4c83271a49356c47ad Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 6 Mar 2024 10:39:12 +0100 Subject: [PATCH 47/96] Addressing review comments --- crates/account/Cargo.toml | 4 +- crates/apps/Cargo.toml | 5 +- crates/core/Cargo.toml | 5 +- crates/ethereum_bridge/Cargo.toml | 5 +- crates/gas/Cargo.toml | 5 +- crates/governance/Cargo.toml | 5 +- crates/macros/Cargo.toml | 5 +- crates/macros/src/lib.rs | 75 ++++++++++++++++----------- crates/merkle_tree/Cargo.toml | 5 +- crates/migrations/Cargo.toml | 5 -- crates/migrations/src/lib.rs | 15 ++---- crates/namada/Cargo.toml | 5 +- crates/proof_of_stake/Cargo.toml | 5 +- crates/sdk/Cargo.toml | 5 +- crates/state/Cargo.toml | 5 +- crates/storage/Cargo.toml | 5 +- crates/tx/Cargo.toml | 5 +- crates/vote_ext/Cargo.toml | 5 +- wasm/Cargo.lock | 26 +--------- wasm_for_tests/wasm_source/Cargo.lock | 26 +--------- 20 files changed, 80 insertions(+), 141 deletions(-) diff --git a/crates/account/Cargo.toml b/crates/account/Cargo.toml index 4f525d2ae3..3ed213ba62 100644 --- a/crates/account/Cargo.toml +++ b/crates/account/Cargo.toml @@ -15,12 +15,12 @@ version.workspace = true [features] default = [] testing = ["namada_core/testing", "proptest"] -migrations = ["namada_migrations/migrations", "namada_macros/migrations", "linkme"] +migrations = ["namada_migrations", "linkme"] [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_storage = { path = "../storage" } borsh.workspace = true diff --git a/crates/apps/Cargo.toml b/crates/apps/Cargo.toml index 8b2eab33e4..6b2dff74a3 100644 --- a/crates/apps/Cargo.toml +++ b/crates/apps/Cargo.toml @@ -60,8 +60,7 @@ benches = ["testing", "namada_test_utils"] integration = [] jemalloc = ["rocksdb/jemalloc"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "namada_sdk/migrations", "namada/migrations", "linkme", @@ -70,7 +69,7 @@ migrations = [ [dependencies] namada = {path = "../namada", features = ["multicore", "http-client", "tendermint-rpc", "std"]} namada_macros = {path = "../macros"} -namada_migrations = {path = "../migrations"} +namada_migrations = {path = "../migrations", optional = true} namada_sdk = {path = "../sdk", default-features = false, features = ["wasm-runtime", "download-params", "std", "rand"]} namada_test_utils = {path = "../test_utils", optional = true} diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index e1753a27ec..f6a7a4ff5f 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -26,14 +26,13 @@ testing = [ "proptest", ] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] [dependencies] namada_macros = {path = "../macros"} -namada_migrations = {path = "../migrations"} +namada_migrations = {path = "../migrations", optional = true} arse-merkle-tree.workspace = true bech32.workspace = true borsh.workspace = true diff --git a/crates/ethereum_bridge/Cargo.toml b/crates/ethereum_bridge/Cargo.toml index 556f15064c..c74d8a3e8f 100644 --- a/crates/ethereum_bridge/Cargo.toml +++ b/crates/ethereum_bridge/Cargo.toml @@ -20,8 +20,7 @@ testing = [ "namada_state/testing", ] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -29,7 +28,7 @@ migrations = [ namada_account = {path = "../account", optional = true} namada_core = {path = "../core", default-features = false, features = ["ethers-derive"]} namada_macros = {path = "../macros"} -namada_migrations = {path = "../migrations"} +namada_migrations = {path = "../migrations", optional = true} namada_parameters = {path = "../parameters"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_state = {path = "../state"} diff --git a/crates/gas/Cargo.toml b/crates/gas/Cargo.toml index 35c4ad5f11..70e4799370 100644 --- a/crates/gas/Cargo.toml +++ b/crates/gas/Cargo.toml @@ -14,15 +14,14 @@ version.workspace = true [features] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme" ] [dependencies] namada_core = { path = "../core" } namada_macros = {path = "../macros"} -namada_migrations = {path = "../migrations"} +namada_migrations = {path = "../migrations", optional = true} borsh.workspace = true linkme = {workspace = true, optional = true} serde.workspace = true diff --git a/crates/governance/Cargo.toml b/crates/governance/Cargo.toml index 7c0c28e243..3f48aef3cd 100644 --- a/crates/governance/Cargo.toml +++ b/crates/governance/Cargo.toml @@ -15,15 +15,14 @@ version.workspace = true [features] testing = ["proptest"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] [dependencies] namada_core = { path = "../core" } namada_macros = {path = "../macros"} -namada_migrations = { path= "../migrations" } +namada_migrations = { path= "../migrations", optional = true } namada_parameters = {path = "../parameters"} namada_storage = {path = "../storage"} namada_trans_token = {path = "../trans_token"} diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 0a01c560f9..4f45eb14e6 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -15,11 +15,8 @@ version.workspace = true [lib] proc-macro = true -[features] -migrations = ["namada_migrations/migrations"] - [dependencies] -namada_migrations = {path = "../migrations"} +data-encoding.workspace = true paste.workspace = true proc-macro2 = "1.0" quote = "1.0" diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 345d26fa4f..d0e68b8ca9 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -357,6 +357,43 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { (def.ident, def.generics) }); let type_hash: [u8; 32] = hasher.finalize().into(); + + if !generics.params.is_empty() { + panic!( + "Cannot derive BorshDeserializer on a parameterized type. This \ + can be done manually for concrete instantiations via the \ + derive_borshdeserializer! macro." + ); + } + impl_borsh_deserializer(type_hash, ident) +} + +#[proc_macro] +pub fn derive_borshdeserializer(item: TokenStream) -> TokenStream { + derive_borshdeserializer_inner(item.into()).into() +} + +fn derive_borshdeserializer_inner(item: TokenStream2) -> TokenStream2 { + let type_def = syn::parse2::(item).expect( + "Could not parse input to `derive_borshdesrializer` as a type.", + ); + match type_def { + syn::Type::Array(_) | syn::Type::Tuple(_) | syn::Type::Path(_) => {} + _ => panic!( + "The `borsh_derserializer!` macro may only be called on arrays, \ + tuples, structs, and enums." + ), + } + let mut hasher = sha2::Sha256::new(); + hasher.update(type_def.to_token_stream().to_string().as_bytes()); + let type_hash: [u8; 32] = hasher.finalize().into(); + impl_borsh_deserializer(type_hash, type_def) +} + +fn impl_borsh_deserializer( + type_hash: [u8; 32], + type_def: T, +) -> TokenStream2 { let hash = syn::ExprArray { attrs: vec![], bracket_token: Default::default(), @@ -370,49 +407,25 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { }) })), }; - - if !generics.params.is_empty() { - panic!( - "Cannot derive BorshDeserializer on a parameterized type. This \ - can be done manually for concrete instantiations via the \ - derive_borshdeserializer! macro." - ); - } - + let hex = data_encoding::HEXUPPER.encode(&type_hash); let deserializer_ident = - syn::Ident::new(&format!("{}_DESERIALIZER", ident), Span::call_site()); + syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); quote!( #[cfg(feature = "migrations")] - #[namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] + #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] static #deserializer_ident: fn() = || { - let mut locked = namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); + let mut locked = ::namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); locked.insert(#hash, |bytes| { - #ident::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() + #type_def::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() }); }; - - impl namada_migrations::TypeHash for #ident { + #[cfg(feature = "migrations")] + impl ::namada_migrations::TypeHash for #type_def { const HASH: [u8; 32] = #hash; } ) } -#[allow(unused_macros)] -macro_rules! derive_borshdeserializer { - ($name: ty) => { - #[namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] - static [<$name_ DESERIALIZER>]: fn() = || { - let mut locked = ::namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); - locked.insert($name::HASH, |bytes| {#ident::try_from_slice(&bytes).is_ok()}); - }; - impl ::namada_migrations::TypeHash for $name { - const HASH: [u8; 32] = ::sha2_const::Sha256::new() - .update(std::any::type_name::<$name>()) - .finalize(); - } - }; -} - #[cfg(test)] mod test_proc_macros { use syn::File; diff --git a/crates/merkle_tree/Cargo.toml b/crates/merkle_tree/Cargo.toml index 3b9797d153..56657bd76f 100644 --- a/crates/merkle_tree/Cargo.toml +++ b/crates/merkle_tree/Cargo.toml @@ -14,15 +14,14 @@ version.workspace = true [features] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } arse-merkle-tree.workspace = true borsh.workspace = true diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index 9cc93e1e7b..1ad4c63df6 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -14,12 +14,7 @@ version.workspace = true [lib] -[features] -migrations = ["linkme"] - [dependencies] -borsh.workspace = true -borsh-ext.workspace = true data-encoding.workspace = true lazy_static.workspace = true linkme.workspace = true diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 3c4bf9a46c..badda3459c 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -1,14 +1,11 @@ -#![allow(clippy::type_complexity)] - use std::collections::HashMap; use std::sync::Mutex; use lazy_static::lazy_static; -#[cfg(feature = "migrations")] pub use linkme::distributed_slice; -/// Predicate that checks if an arbitrary byte array deserializes as some type `T` -/// erased inside of the callback. If the serialization is correct, the full path of `T` -/// is returned as a string (via [`std::any::type_name`]). +/// Predicate that checks if an arbitrary byte array deserializes as some type +/// `T` erased inside of the callback. If the serialization is correct, the full +/// path of `T` is returned as a string (via [`std::any::type_name`]). type CbFromByteArrayToTypeName = fn(Vec) -> Option; lazy_static! { @@ -16,7 +13,6 @@ lazy_static! { Mutex::new(HashMap::new()); } -#[cfg(feature = "migrations")] #[distributed_slice] pub static REGISTER_DESERIALIZERS: [fn()]; @@ -24,9 +20,8 @@ pub trait TypeHash { const HASH: [u8; 32]; } -/// Calls all of the regeistered callbacks which place type -/// deserializes into [`TYPE_DESERIALIZERS`]. -#[cfg(feature = "migrations")] +/// Calls all of the registered callbacks which place type +/// deserializers into [`TYPE_DESERIALIZERS`]. pub fn initialize_deserializers() { for func in REGISTER_DESERIALIZERS { func(); diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index 4c3ba9f979..34101670be 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -74,8 +74,7 @@ multicore = [ download-params = ["namada_sdk/download-params"] rand = ["namada_sdk/rand"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -87,7 +86,7 @@ namada_gas = { path = "../gas" } namada_governance = { path = "../governance" } namada_ibc = { path = "../ibc" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_parameters = { path = "../parameters" } namada_proof_of_stake = { path = "../proof_of_stake" } namada_replay_protection = { path = "../replay_protection" } diff --git a/crates/proof_of_stake/Cargo.toml b/crates/proof_of_stake/Cargo.toml index afeccf07b2..ae04753520 100644 --- a/crates/proof_of_stake/Cargo.toml +++ b/crates/proof_of_stake/Cargo.toml @@ -17,8 +17,7 @@ default = [] # testing helpers testing = ["proptest"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -27,7 +26,7 @@ namada_account = { path = "../account" } namada_core = { path = "../core" } namada_governance = { path = "../governance" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_storage = { path = "../storage" } namada_parameters = { path = "../parameters" } namada_trans_token = { path = "../trans_token" } diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 6c521e39e8..00d567f6d0 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -54,8 +54,7 @@ testing = [ # Download MASP params if they're not present download-params = ["masp_proofs/download-params"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "namada_account/migrations", "namada_core/migrations", "namada_ethereum_bridge/migrations", @@ -75,7 +74,7 @@ namada_ethereum_bridge = { path = "../ethereum_bridge", default-features = false namada_governance = { path = "../governance" } namada_ibc = { path = "../ibc" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_parameters = { path = "../parameters" } namada_proof_of_stake = { path = "../proof_of_stake" } namada_state = { path = "../state" } diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index 2a9fa1cc45..c0e4e7e55d 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -18,8 +18,7 @@ default = [] # for integration tests and test utilities testing = ["proptest", "namada_core/testing"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -28,7 +27,7 @@ namada_core = { path = "../core", default-features = false } namada_gas = { path = "../gas" } namada_macros = { path = "../macros" } namada_merkle_tree = { path = "../merkle_tree" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_parameters = { path = "../parameters" } namada_replay_protection = { path = "../replay_protection" } namada_storage = { path = "../storage" } diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index c7c943d48f..fa75a91e67 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -18,8 +18,7 @@ testing = [ "namada_core/testing", ] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -28,7 +27,7 @@ namada_core = { path = "../core" } namada_gas = { path = "../gas" } namada_macros = { path = "../macros" } namada_merkle_tree = { path = "../merkle_tree" } -namada_migrations = {path = "../migrations" } +namada_migrations = {path = "../migrations", optional = true } namada_replay_protection = { path = "../replay_protection" } namada_tx = { path = "../tx" } diff --git a/crates/tx/Cargo.toml b/crates/tx/Cargo.toml index 59d47d75cd..cf522e03b0 100644 --- a/crates/tx/Cargo.toml +++ b/crates/tx/Cargo.toml @@ -16,8 +16,7 @@ version.workspace = true default = [] testing = ["proptest", "namada_core/testing"] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] @@ -25,7 +24,7 @@ migrations = [ namada_core = { path = "../core" } namada_gas = { path = "../gas" } namada_macros = { path = "../macros" } -namada_migrations = {path = "../migrations" } +namada_migrations = {path = "../migrations", optional = true } ark-bls12-381.workspace = true borsh.workspace = true diff --git a/crates/vote_ext/Cargo.toml b/crates/vote_ext/Cargo.toml index ffb0efdb31..6bf6e1532b 100644 --- a/crates/vote_ext/Cargo.toml +++ b/crates/vote_ext/Cargo.toml @@ -14,15 +14,14 @@ version.workspace = true [features] migrations = [ - "namada_migrations/migrations", - "namada_macros/migrations", + "namada_migrations", "linkme", ] [dependencies] namada_core = { path = "../core" } namada_macros = { path = "../macros" } -namada_migrations = { path = "../migrations" } +namada_migrations = { path = "../migrations", optional = true } namada_tx = { path = "../tx" } borsh.workspace = true diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index ab5f537aff..9b5beb53fc 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3342,7 +3342,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3397,7 +3396,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_storage", "proptest", "serde", @@ -3423,7 +3421,6 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", - "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3460,7 +3457,6 @@ dependencies = [ "namada_account", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3485,7 +3481,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "serde", "thiserror", ] @@ -3498,7 +3493,6 @@ dependencies = [ "itertools 0.10.5", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3538,8 +3532,8 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "data-encoding", "lazy_static", - "namada_migrations", "paste", "proc-macro2", "quote", @@ -3556,23 +3550,11 @@ dependencies = [ "ics23", "namada_core", "namada_macros", - "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] -[[package]] -name = "namada_migrations" -version = "0.31.8" -dependencies = [ - "borsh", - "borsh-ext", - "data-encoding", - "lazy_static", - "serde", -] - [[package]] name = "namada_parameters" version = "0.31.9" @@ -3595,7 +3577,6 @@ dependencies = [ "namada_core", "namada_governance", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3641,7 +3622,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3701,7 +3681,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3725,7 +3704,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3804,7 +3782,6 @@ dependencies = [ "namada_core", "namada_gas", "namada_macros", - "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3863,7 +3840,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_tx", "serde", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index b923798a72..37ecb71b65 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3342,7 +3342,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3397,7 +3396,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_storage", "proptest", "serde", @@ -3423,7 +3421,6 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", - "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3460,7 +3457,6 @@ dependencies = [ "namada_account", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3485,7 +3481,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "serde", "thiserror", ] @@ -3498,7 +3493,6 @@ dependencies = [ "itertools 0.10.5", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3538,8 +3532,8 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "data-encoding", "lazy_static", - "namada_migrations", "paste", "proc-macro2", "quote", @@ -3556,23 +3550,11 @@ dependencies = [ "ics23", "namada_core", "namada_macros", - "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] -[[package]] -name = "namada_migrations" -version = "0.31.8" -dependencies = [ - "borsh", - "borsh-ext", - "data-encoding", - "lazy_static", - "serde", -] - [[package]] name = "namada_parameters" version = "0.31.9" @@ -3595,7 +3577,6 @@ dependencies = [ "namada_core", "namada_governance", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3641,7 +3622,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3701,7 +3681,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3725,7 +3704,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3804,7 +3782,6 @@ dependencies = [ "namada_core", "namada_gas", "namada_macros", - "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3863,7 +3840,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_tx", "serde", ] From e0500592a03d0f4f5fbf78e8fecea22e5ab5baba Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 7 Mar 2024 10:02:37 +0100 Subject: [PATCH 48/96] Update crates/migrations/src/lib.rs Co-authored-by: Tiago Carvalho --- crates/migrations/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index badda3459c..72f2b7780d 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -3,6 +3,7 @@ use std::sync::Mutex; use lazy_static::lazy_static; pub use linkme::distributed_slice; + /// Predicate that checks if an arbitrary byte array deserializes as some type /// `T` erased inside of the callback. If the serialization is correct, the full /// path of `T` is returned as a string (via [`std::any::type_name`]). From 1b14d6e7a15f3dc11e985cfe56403d21af44be2c Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 7 Mar 2024 13:53:35 +0100 Subject: [PATCH 49/96] Update .changelog/unreleased/improvements/2814-global-deserializer-map.md Co-authored-by: Tomas Zemanovic --- .../unreleased/improvements/2814-global-deserializer-map.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/unreleased/improvements/2814-global-deserializer-map.md b/.changelog/unreleased/improvements/2814-global-deserializer-map.md index 6d801ec912..6ab34bc8d5 100644 --- a/.changelog/unreleased/improvements/2814-global-deserializer-map.md +++ b/.changelog/unreleased/improvements/2814-global-deserializer-map.md @@ -1,2 +1,2 @@ - This PR adds a proc macro that registers the deserializer of a type in a hashmap. This allows us to verify that - data blob deserializes as correctly if where are in possession of the hash map key. ([\#2814](https://github.com/anoma/namada/pull/2814)) \ No newline at end of file + data blob deserializes correctly if we are in possession of the hash map key. ([\#2814](https://github.com/anoma/namada/pull/2814)) \ No newline at end of file From 7f787e023cce3ef9701c5c75115091dc243aa388 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 8 Mar 2024 10:52:04 +0100 Subject: [PATCH 50/96] rebase --- Cargo.lock | 74 ------------------------------------------------------ 1 file changed, 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0f2dc26a5..efd89f312d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3809,26 +3809,6 @@ dependencies = [ "serde 1.0.193", ] -[[package]] -name = "linkme" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -4144,7 +4124,6 @@ dependencies = [ "ibc-testkit", "itertools 0.10.5", "k256", - "linkme", "loupe", "masp_primitives", "masp_proofs", @@ -4154,8 +4133,6 @@ dependencies = [ "namada_gas", "namada_governance", "namada_ibc", - "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -4213,10 +4190,8 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", - "namada_migrations", "namada_storage", "proptest", "serde 1.0.193", @@ -4261,12 +4236,9 @@ dependencies = [ "ledger-transport-hid", "libc", "libloading", - "linkme", "masp_primitives", "masp_proofs", "namada", - "namada_macros", - "namada_migrations", "namada_sdk", "namada_test_utils", "num-rational 0.4.1", @@ -4350,10 +4322,8 @@ dependencies = [ "impl-num-traits", "index-set", "k256", - "linkme", "masp_primitives", "namada_macros", - "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits 0.2.17", @@ -4404,11 +4374,9 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4445,10 +4413,7 @@ version = "0.31.9" dependencies = [ "assert_matches", "borsh", - "linkme", "namada_core", - "namada_macros", - "namada_migrations", "proptest", "serde 1.0.193", "thiserror", @@ -4460,10 +4425,8 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -4517,13 +4480,9 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ - "lazy_static", - "namada_migrations", - "paste", "pretty_assertions", "proc-macro2", "quote", - "sha2 0.9.9", "syn 1.0.109", ] @@ -4536,28 +4495,13 @@ dependencies = [ "eyre", "ics23", "itertools 0.10.5", - "linkme", "namada_core", - "namada_macros", - "namada_migrations", "proptest", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] -[[package]] -name = "namada_migrations" -version = "0.31.8" -dependencies = [ - "borsh", - "borsh-ext", - "data-encoding", - "lazy_static", - "linkme", - "serde 1.0.193", -] - [[package]] name = "namada_parameters" version = "0.31.9" @@ -4578,12 +4522,9 @@ dependencies = [ "data-encoding", "derivative", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_governance", - "namada_macros", - "namada_migrations", "namada_parameters", "namada_state", "namada_storage", @@ -4629,7 +4570,6 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", - "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -4637,8 +4577,6 @@ dependencies = [ "namada_ethereum_bridge", "namada_governance", "namada_ibc", - "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4701,12 +4639,9 @@ dependencies = [ "chrono", "ics23", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", - "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -4728,12 +4663,9 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", - "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -4827,12 +4759,9 @@ dependencies = [ "assert_matches", "borsh", "data-encoding", - "linkme", "masp_primitives", "namada_core", "namada_gas", - "namada_macros", - "namada_migrations", "num-derive", "num-traits 0.2.17", "proptest", @@ -4891,10 +4820,7 @@ version = "0.31.9" dependencies = [ "borsh", "data-encoding", - "linkme", "namada_core", - "namada_macros", - "namada_migrations", "namada_tx", "serde 1.0.193", ] From 3875b39c41bfc153463980d8e5d68f8afd1b2c52 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 15:01:29 +0100 Subject: [PATCH 51/96] [feat]: Added a pattern matching iterator to the db --- Cargo.lock | 2 + .../src/lib/node/ledger/storage/rocksdb.rs | 67 ++++++++++++++++++- crates/namada/Cargo.toml | 1 + crates/state/src/lib.rs | 2 +- crates/storage/Cargo.toml | 1 + crates/storage/src/db.rs | 10 +++ crates/storage/src/mockdb.rs | 37 +++++++++- crates/storage/src/types.rs | 30 +++++++++ wasm/Cargo.lock | 2 + 9 files changed, 149 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efd89f312d..3ecef2a2a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4157,6 +4157,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "regex", "ripemd", "serde 1.0.193", "serde_json", @@ -4668,6 +4669,7 @@ dependencies = [ "namada_merkle_tree", "namada_replay_protection", "namada_tx", + "regex", "thiserror", "tracing", ] diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 18e2fa13f6..a6d1523b7d 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -61,10 +61,11 @@ use namada::replay_protection; use namada::state::merkle_tree::{base_tree_key_prefix, subtree_key_prefix}; use namada::state::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, DbError as Error, - DbResult as Result, MerkleTreeStoresRead, PrefixIterator, StoreType, DB, + DbResult as Result, MerkleTreeStoresRead, PrefixIterator, PatternIterator, StoreType, DB, }; use namada::token::ConversionState; use rayon::prelude::*; +use regex::Regex; use rocksdb::{ BlockBasedOptions, ColumnFamily, ColumnFamilyDescriptor, DBCompactionStyle, DBCompressionType, Direction, FlushOptions, IteratorMode, Options, @@ -1537,6 +1538,7 @@ impl DB for RocksDB { impl<'iter> DBIter<'iter> for RocksDB { type PrefixIter = PersistentPrefixIterator<'iter>; + type PatternIter = PersistentPatternIterator<'iter>; fn iter_prefix( &'iter self, @@ -1545,6 +1547,14 @@ impl<'iter> DBIter<'iter> for RocksDB { iter_subspace_prefix(self, prefix) } + fn iter_pattern( + &'iter self, + prefix: Option<&Key>, + pattern: Regex, + ) -> PersistentPatternIterator<'iter> { + iter_subspace_pattern(self, prefix, pattern) + } + fn iter_results(&'iter self) -> PersistentPrefixIterator<'iter> { let db_prefix = "results/".to_owned(); let prefix = "results".to_owned(); @@ -1598,6 +1608,18 @@ fn iter_subspace_prefix<'iter>( iter_prefix(db, subspace_cf, stripped_prefix, prefix) } +fn iter_subspace_pattern<'iter>( + db: &'iter RocksDB, + prefix: Option<&Key>, + pattern: Regex, +) -> PersistentPatternIterator<'iter> { + let subspace_cf = db + .get_column_family(SUBSPACE_CF) + .expect("{SUBSPACE_CF} column family should exist"); + let stripped_prefix = None; + iter_pattern(db, subspace_cf, stripped_prefix, prefix, pattern) +} + fn iter_diffs_prefix<'a>( db: &'a RocksDB, height: BlockHeight, @@ -1650,6 +1672,24 @@ fn iter_prefix<'a>( PersistentPrefixIterator(PrefixIterator::new(iter, stripped_prefix)) } +/// Create an iterator over key-vals in the given CF matching the given +/// pattern(s). +fn iter_pattern<'a>( + db: &'a RocksDB, + cf: &'a ColumnFamily, + stripped_prefix: Option<&Key>, + prefix: Option<&Key>, + pattern: Regex, +) -> PersistentPatternIterator<'a> { + PersistentPatternIterator { + inner: PatternIterator { + iter: iter_prefix(db, cf, stripped_prefix, prefix), + pattern, + }, + finished: false, + } +} + #[derive(Debug)] pub struct PersistentPrefixIterator<'a>( PrefixIterator>, @@ -1684,6 +1724,31 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { } } +#[derive(Debug)] +pub struct PersistentPatternIterator<'a> { + inner: PatternIterator >, + finished: bool, +} + +impl<'a> Iterator for PersistentPatternIterator<'a> { + type Item = (String, Vec, u64); + + /// Returns the next pair and the gas cost + fn next(&mut self) -> Option<(String, Vec, u64)> { + if self.finished { + return None; + } + loop { + let next_result = self.inner.iter.next()?; + if self.inner.pattern.is_match(&next_result.0) { + return Some(next_result) + } else { + self.finished = true; + } + } + } +} + /// Make read options for RocksDB iterator with the given prefix fn make_iter_read_opts(prefix: Option) -> ReadOptions { let mut read_opts = ReadOptions::default(); diff --git a/crates/namada/Cargo.toml b/crates/namada/Cargo.toml index 34101670be..c09842abb3 100644 --- a/crates/namada/Cargo.toml +++ b/crates/namada/Cargo.toml @@ -128,6 +128,7 @@ prost.workspace = true rand.workspace = true rand_core.workspace = true rayon = { version = "=1.5.3", optional = true } +regex.workspace = true ripemd.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index afe728c4fa..6788dd699c 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -33,7 +33,7 @@ pub use namada_storage as storage; pub use namada_storage::conversion_state::{ ConversionState, WithConversionState, }; -pub use namada_storage::types::{KVBytes, PrefixIterator}; +pub use namada_storage::types::{KVBytes, PrefixIterator, PatternIterator}; pub use namada_storage::{ collections, iter_prefix, iter_prefix_bytes, iter_prefix_with_filter, mockdb, tx_queue, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index fa75a91e67..7889d95251 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -36,6 +36,7 @@ itertools.workspace = true linkme = {workspace = true, optional = true} thiserror.workspace = true tracing.workspace = true +regex = "1.10.2" [dev-dependencies] namada_core = { path = "../core", features = ["testing"] } diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 5ce22e85db..e9c4ab0aad 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use regex::Regex; use namada_core::address::EstablishedAddressGen; use namada_core::hash::{Error as HashError, Hash}; @@ -266,6 +267,7 @@ pub trait DB: Debug { pub trait DBIter<'iter> { /// The concrete type of the iterator type PrefixIter: Debug + Iterator, u64)>; + type PatternIter: Debug + Iterator, u64)>; /// WARNING: This only works for values that have been committed to DB. /// To be able to see values written or deleted, but not yet committed, @@ -275,6 +277,14 @@ pub trait DBIter<'iter> { /// ordered by the storage keys. fn iter_prefix(&'iter self, prefix: Option<&Key>) -> Self::PrefixIter; + /// WARNING: This only works for values that have been committed to DB. + /// To be able to see values written or deleted, but not yet committed, + /// use the `StorageWithWriteLog`. + /// + /// Read account subspace key value pairs with the given pattern from the DB, + /// ordered by the storage keys. + fn iter_pattern(&'iter self, prefix: Option<&Key>, pattern: Regex) -> Self::PatternIter; + /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 4128321e54..d84efd2a4d 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -7,6 +7,7 @@ use std::path::Path; use std::str::FromStr; use itertools::Either; +use regex::Regex; use namada_core::borsh::{BorshDeserialize, BorshSerializeExt}; use namada_core::hash::Hash; use namada_core::storage::{ @@ -25,7 +26,7 @@ use crate::db::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; use crate::tx_queue::TxQueue; -use crate::types::{KVBytes, PrefixIterator}; +use crate::types::{KVBytes, PatternIterator, PrefixIterator}; const SUBSPACE_CF: &str = "subspace"; @@ -678,6 +679,7 @@ impl DB for MockDB { impl<'iter> DBIter<'iter> for MockDB { type PrefixIter = MockPrefixIterator; + type PatternIter = MockPatternIterator; fn iter_prefix(&'iter self, prefix: Option<&Key>) -> MockPrefixIterator { let stripped_prefix = "subspace/".to_owned(); @@ -699,6 +701,14 @@ impl<'iter> DBIter<'iter> for MockDB { MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) } + fn iter_pattern(&'iter self, prefix: Option<&Key>, pattern: Regex) -> Self::PatternIter { + MockPatternIterator { + inner: PatternIterator {iter: self.iter_prefix(prefix), pattern}, + finished: false, + } + } + + fn iter_results(&'iter self) -> MockPrefixIterator { let stripped_prefix = "results/".to_owned(); let prefix = "results".to_owned(); @@ -808,6 +818,31 @@ impl Iterator for PrefixIterator { } } +#[derive(Debug)] +pub struct MockPatternIterator { + inner: PatternIterator, + finished: bool, +} + +impl Iterator for MockPatternIterator { + type Item = (String, Vec, u64); + + /// Returns the next pair and the gas cost + fn next(&mut self) -> Option<(String, Vec, u64)> { + if self.finished { + return None; + } + loop { + let next_result = self.inner.iter.next()?; + if self.inner.pattern.is_match(&next_result.0) { + return Some(next_result) + } else { + self.finished = true; + } + } + } +} + impl DBWriteBatch for MockDBWriteBatch {} fn unknown_key_error(key: &str) -> Result<()> { diff --git a/crates/storage/src/types.rs b/crates/storage/src/types.rs index 52fc61c4eb..0b5407b3d7 100644 --- a/crates/storage/src/types.rs +++ b/crates/storage/src/types.rs @@ -1,5 +1,7 @@ //! The key and values that may be persisted in a DB. +use regex::Regex; + /// A key-value pair as raw bytes pub type KVBytes = (Box<[u8]>, Box<[u8]>); @@ -31,3 +33,31 @@ impl std::fmt::Debug for PrefixIterator { f.write_str("PrefixIterator") } } + +/// Storage prefix iterator generic wrapper type. +pub struct PatternIterator { + /// The concrete iterator implementation + pub iter: I, + /// The pattern we are matching keys against. + pub pattern: Regex, +} + +impl PatternIterator { + /// Initialize a new prefix iterator + pub fn new(iter: I, pattern: Regex) -> Self + where + E: std::error::Error, + I: Iterator>, + { + Self { + iter, + pattern, + } + } +} + +impl std::fmt::Debug for PatternIterator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PatternIterator") + } +} \ No newline at end of file diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9b5beb53fc..e3d3d6997e 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3364,6 +3364,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "regex", "ripemd", "serde", "serde_json", @@ -3706,6 +3707,7 @@ dependencies = [ "namada_merkle_tree", "namada_replay_protection", "namada_tx", + "regex", "thiserror", "tracing", ] From d72292dbdef4975d68ce0dde08ce5480308f3ee8 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 15:04:37 +0100 Subject: [PATCH 52/96] [chore]: Added changelog --- .changelog/unreleased/improvements/2839-db-patter-iter.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/2839-db-patter-iter.md diff --git a/.changelog/unreleased/improvements/2839-db-patter-iter.md b/.changelog/unreleased/improvements/2839-db-patter-iter.md new file mode 100644 index 0000000000..92958259e9 --- /dev/null +++ b/.changelog/unreleased/improvements/2839-db-patter-iter.md @@ -0,0 +1 @@ + - When iterating over key prefixes, we can additionally filter out keys based on a regex. ([\#2839](https://github.com/anoma/namada/pull/2839)) \ No newline at end of file From 1242c11f42db8c992d4e439afc87abecc6bcee72 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Mar 2024 19:30:57 +0100 Subject: [PATCH 53/96] fmt --- .../src/lib/node/ledger/storage/rocksdb.rs | 9 +++++---- crates/state/src/lib.rs | 2 +- crates/storage/src/db.rs | 12 ++++++++---- crates/storage/src/mockdb.rs | 18 ++++++++++++------ crates/storage/src/types.rs | 13 +++++-------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index a6d1523b7d..8706dacf24 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -61,7 +61,8 @@ use namada::replay_protection; use namada::state::merkle_tree::{base_tree_key_prefix, subtree_key_prefix}; use namada::state::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, DbError as Error, - DbResult as Result, MerkleTreeStoresRead, PrefixIterator, PatternIterator, StoreType, DB, + DbResult as Result, MerkleTreeStoresRead, PatternIterator, PrefixIterator, + StoreType, DB, }; use namada::token::ConversionState; use rayon::prelude::*; @@ -1537,8 +1538,8 @@ impl DB for RocksDB { } impl<'iter> DBIter<'iter> for RocksDB { - type PrefixIter = PersistentPrefixIterator<'iter>; type PatternIter = PersistentPatternIterator<'iter>; + type PrefixIter = PersistentPrefixIterator<'iter>; fn iter_prefix( &'iter self, @@ -1726,7 +1727,7 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { #[derive(Debug)] pub struct PersistentPatternIterator<'a> { - inner: PatternIterator >, + inner: PatternIterator>, finished: bool, } @@ -1741,7 +1742,7 @@ impl<'a> Iterator for PersistentPatternIterator<'a> { loop { let next_result = self.inner.iter.next()?; if self.inner.pattern.is_match(&next_result.0) { - return Some(next_result) + return Some(next_result); } else { self.finished = true; } diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 6788dd699c..c7f112c500 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -33,7 +33,7 @@ pub use namada_storage as storage; pub use namada_storage::conversion_state::{ ConversionState, WithConversionState, }; -pub use namada_storage::types::{KVBytes, PrefixIterator, PatternIterator}; +pub use namada_storage::types::{KVBytes, PatternIterator, PrefixIterator}; pub use namada_storage::{ collections, iter_prefix, iter_prefix_bytes, iter_prefix_with_filter, mockdb, tx_queue, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index e9c4ab0aad..072baa1f3d 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -1,5 +1,4 @@ use std::fmt::Debug; -use regex::Regex; use namada_core::address::EstablishedAddressGen; use namada_core::hash::{Error as HashError, Hash}; @@ -13,6 +12,7 @@ use namada_merkle_tree::{ Error as MerkleTreeError, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, }; +use regex::Regex; use thiserror::Error; use crate::conversion_state::ConversionState; @@ -281,9 +281,13 @@ pub trait DBIter<'iter> { /// To be able to see values written or deleted, but not yet committed, /// use the `StorageWithWriteLog`. /// - /// Read account subspace key value pairs with the given pattern from the DB, - /// ordered by the storage keys. - fn iter_pattern(&'iter self, prefix: Option<&Key>, pattern: Regex) -> Self::PatternIter; + /// Read account subspace key value pairs with the given pattern from the + /// DB, ordered by the storage keys. + fn iter_pattern( + &'iter self, + prefix: Option<&Key>, + pattern: Regex, + ) -> Self::PatternIter; /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index d84efd2a4d..78dbb1b736 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -7,7 +7,6 @@ use std::path::Path; use std::str::FromStr; use itertools::Either; -use regex::Regex; use namada_core::borsh::{BorshDeserialize, BorshSerializeExt}; use namada_core::hash::Hash; use namada_core::storage::{ @@ -20,6 +19,7 @@ use namada_merkle_tree::{ base_tree_key_prefix, subtree_key_prefix, MerkleTreeStoresRead, StoreType, }; use namada_replay_protection as replay_protection; +use regex::Regex; use crate::conversion_state::ConversionState; use crate::db::{ @@ -678,8 +678,8 @@ impl DB for MockDB { } impl<'iter> DBIter<'iter> for MockDB { - type PrefixIter = MockPrefixIterator; type PatternIter = MockPatternIterator; + type PrefixIter = MockPrefixIterator; fn iter_prefix(&'iter self, prefix: Option<&Key>) -> MockPrefixIterator { let stripped_prefix = "subspace/".to_owned(); @@ -701,14 +701,20 @@ impl<'iter> DBIter<'iter> for MockDB { MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) } - fn iter_pattern(&'iter self, prefix: Option<&Key>, pattern: Regex) -> Self::PatternIter { + fn iter_pattern( + &'iter self, + prefix: Option<&Key>, + pattern: Regex, + ) -> Self::PatternIter { MockPatternIterator { - inner: PatternIterator {iter: self.iter_prefix(prefix), pattern}, + inner: PatternIterator { + iter: self.iter_prefix(prefix), + pattern, + }, finished: false, } } - fn iter_results(&'iter self) -> MockPrefixIterator { let stripped_prefix = "results/".to_owned(); let prefix = "results".to_owned(); @@ -835,7 +841,7 @@ impl Iterator for MockPatternIterator { loop { let next_result = self.inner.iter.next()?; if self.inner.pattern.is_match(&next_result.0) { - return Some(next_result) + return Some(next_result); } else { self.finished = true; } diff --git a/crates/storage/src/types.rs b/crates/storage/src/types.rs index 0b5407b3d7..7f66bdb782 100644 --- a/crates/storage/src/types.rs +++ b/crates/storage/src/types.rs @@ -45,14 +45,11 @@ pub struct PatternIterator { impl PatternIterator { /// Initialize a new prefix iterator pub fn new(iter: I, pattern: Regex) -> Self - where - E: std::error::Error, - I: Iterator>, + where + E: std::error::Error, + I: Iterator>, { - Self { - iter, - pattern, - } + Self { iter, pattern } } } @@ -60,4 +57,4 @@ impl std::fmt::Debug for PatternIterator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("PatternIterator") } -} \ No newline at end of file +} From 0c8b43402565c7bcb6d8d5baf0f725f575914ceb Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 13:35:53 +0100 Subject: [PATCH 54/96] [feat]: Added a new derive macro to register deserializers of types to a global map --- Cargo.lock | 73 ++++++++++++++++ wasm/Cargo.lock | 121 +++++++++++++++++++------- wasm_for_tests/wasm_source/Cargo.lock | 115 ++++++++++++++++++------ 3 files changed, 247 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ecef2a2a9..759b8b38b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3809,6 +3809,26 @@ dependencies = [ "serde 1.0.193", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -4133,6 +4153,8 @@ dependencies = [ "namada_gas", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -4191,8 +4213,10 @@ name = "namada_account" version = "0.31.9" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde 1.0.193", @@ -4237,9 +4261,12 @@ dependencies = [ "ledger-transport-hid", "libc", "libloading", + "linkme", "masp_primitives", "masp_proofs", "namada", + "namada_macros", + "namada_migrations", "namada_sdk", "namada_test_utils", "num-rational 0.4.1", @@ -4323,8 +4350,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits 0.2.17", @@ -4375,9 +4404,11 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4414,7 +4445,10 @@ version = "0.31.9" dependencies = [ "assert_matches", "borsh", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "proptest", "serde 1.0.193", "thiserror", @@ -4426,8 +4460,10 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -4481,9 +4517,13 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "lazy_static", + "namada_migrations", + "paste", "pretty_assertions", "proc-macro2", "quote", + "sha2 0.9.9", "syn 1.0.109", ] @@ -4496,13 +4536,28 @@ dependencies = [ "eyre", "ics23", "itertools 0.10.5", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "proptest", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde 1.0.193", +] + [[package]] name = "namada_parameters" version = "0.31.9" @@ -4523,9 +4578,12 @@ dependencies = [ "data-encoding", "derivative", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_governance", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_state", "namada_storage", @@ -4571,6 +4629,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -4578,6 +4637,8 @@ dependencies = [ "namada_ethereum_bridge", "namada_governance", "namada_ibc", + "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -4640,9 +4701,12 @@ dependencies = [ "chrono", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -4664,9 +4728,12 @@ version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", + "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "regex", @@ -4761,9 +4828,12 @@ dependencies = [ "assert_matches", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", + "namada_macros", + "namada_migrations", "num-derive", "num-traits 0.2.17", "proptest", @@ -4822,7 +4892,10 @@ version = "0.31.9" dependencies = [ "borsh", "data-encoding", + "linkme", "namada_core", + "namada_macros", + "namada_migrations", "namada_tx", "serde 1.0.193", ] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index e3d3d6997e..e3f06e478a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3079,6 +3079,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3316,7 +3336,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "bimap", @@ -3342,6 +3362,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3364,7 +3385,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "regex", "ripemd", "serde", "serde_json", @@ -3392,11 +3412,13 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde", @@ -3404,7 +3426,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.31.9" +version = "0.31.8" dependencies = [ "bech32 0.8.1", "borsh", @@ -3420,8 +3442,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3448,16 +3472,18 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ethabi", "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3477,23 +3503,27 @@ dependencies = [ [[package]] name = "namada_gas" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "serde", "thiserror", ] [[package]] name = "namada_governance" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3506,7 +3536,7 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ibc", @@ -3531,10 +3561,10 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.31.9" +version = "0.31.8" dependencies = [ - "data-encoding", "lazy_static", + "namada_migrations", "paste", "proc-macro2", "quote", @@ -3544,21 +3574,35 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "eyre", "ics23", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde", +] + [[package]] name = "namada_parameters" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_core", @@ -3569,15 +3613,17 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "data-encoding", "derivative", + "linkme", "namada_account", "namada_core", "namada_governance", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3591,14 +3637,14 @@ dependencies = [ [[package]] name = "namada_replay_protection" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "bimap", @@ -3615,6 +3661,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3623,6 +3670,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3659,7 +3707,7 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3673,15 +3721,17 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3697,24 +3747,25 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", - "regex", "thiserror", "tracing", ] [[package]] name = "namada_test_utils" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_core", @@ -3723,7 +3774,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "chrono", @@ -3757,7 +3808,7 @@ dependencies = [ [[package]] name = "namada_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_shielded_token", @@ -3767,7 +3818,7 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_storage", @@ -3775,15 +3826,17 @@ dependencies = [ [[package]] name = "namada_tx" -version = "0.31.9" +version = "0.31.8" dependencies = [ "ark-bls12-381", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", "namada_macros", + "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3798,7 +3851,7 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_storage", @@ -3806,7 +3859,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3828,7 +3881,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3837,18 +3890,20 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_tx", "serde", ] [[package]] name = "namada_vp_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "derivative", "masp_primitives", @@ -3860,7 +3915,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_account", @@ -3881,7 +3936,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.31.9" +version = "0.31.8" dependencies = [ "getrandom 0.2.11", "namada", @@ -6252,7 +6307,7 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tx_template" -version = "0.31.9" +version = "0.31.8" dependencies = [ "getrandom 0.2.11", "namada_tests", @@ -6406,7 +6461,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.31.9" +version = "0.31.8" dependencies = [ "getrandom 0.2.11", "namada_tests", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 37ecb71b65..8b3f31c739 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3079,6 +3079,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "linkme" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3316,7 +3336,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "bimap", @@ -3342,6 +3362,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3391,11 +3412,13 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde", @@ -3403,7 +3426,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.31.9" +version = "0.31.8" dependencies = [ "bech32 0.8.1", "borsh", @@ -3419,8 +3442,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3447,16 +3472,18 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ethabi", "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3476,23 +3503,27 @@ dependencies = [ [[package]] name = "namada_gas" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "serde", "thiserror", ] [[package]] name = "namada_governance" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3505,7 +3536,7 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ibc", @@ -3530,10 +3561,10 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.31.9" +version = "0.31.8" dependencies = [ - "data-encoding", "lazy_static", + "namada_migrations", "paste", "proc-macro2", "quote", @@ -3543,21 +3574,35 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "eyre", "ics23", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "borsh", + "borsh-ext", + "data-encoding", + "lazy_static", + "linkme", + "serde", +] + [[package]] name = "namada_parameters" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_core", @@ -3568,15 +3613,17 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "data-encoding", "derivative", + "linkme", "namada_account", "namada_core", "namada_governance", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3590,14 +3637,14 @@ dependencies = [ [[package]] name = "namada_replay_protection" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "bimap", @@ -3614,6 +3661,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3622,6 +3670,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3658,7 +3707,7 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3672,15 +3721,17 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3696,14 +3747,16 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3712,7 +3765,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_core", @@ -3721,7 +3774,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.31.9" +version = "0.31.8" dependencies = [ "async-trait", "chrono", @@ -3755,7 +3808,7 @@ dependencies = [ [[package]] name = "namada_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_shielded_token", @@ -3765,7 +3818,7 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_storage", @@ -3773,15 +3826,17 @@ dependencies = [ [[package]] name = "namada_tx" -version = "0.31.9" +version = "0.31.8" dependencies = [ "ark-bls12-381", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", "namada_macros", + "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3796,7 +3851,7 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "namada_core", "namada_storage", @@ -3804,7 +3859,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3826,7 +3881,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "masp_primitives", @@ -3835,18 +3890,20 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_tx", "serde", ] [[package]] name = "namada_vp_env" -version = "0.31.9" +version = "0.31.8" dependencies = [ "derivative", "masp_primitives", @@ -3858,7 +3915,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.31.9" +version = "0.31.8" dependencies = [ "borsh", "namada_account", @@ -3879,7 +3936,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.31.9" +version = "0.31.8" dependencies = [ "getrandom 0.2.11", "namada_test_utils", From f75a00d708ac23427334c86c0c0f8dc59c0659bf Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 14:45:30 +0100 Subject: [PATCH 55/96] [feat]: Featured gated migrations code --- Cargo.lock | 1 + crates/macros/Cargo.toml | 3 +++ crates/migrations/Cargo.toml | 5 +++- wasm/Cargo.lock | 33 --------------------------- wasm_for_tests/wasm_source/Cargo.lock | 33 --------------------------- 5 files changed, 8 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 759b8b38b7..bf5fd075a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4144,6 +4144,7 @@ dependencies = [ "ibc-testkit", "itertools 0.10.5", "k256", + "linkme", "loupe", "masp_primitives", "masp_proofs", diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 4f45eb14e6..f99842d4cf 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -15,6 +15,9 @@ version.workspace = true [lib] proc-macro = true +[features] +migrations = ["namada_migrations/migrations"] + [dependencies] data-encoding.workspace = true paste.workspace = true diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index 1ad4c63df6..e99a8ae19d 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -14,8 +14,11 @@ version.workspace = true [lib] +[features] +migrations = ["linkme"] + [dependencies] data-encoding.workspace = true lazy_static.workspace = true -linkme.workspace = true +linkme = {workspace = true, optional = true} serde.workspace = true diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index e3f06e478a..70d7710679 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3079,26 +3079,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "linkme" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3415,7 +3395,6 @@ name = "namada_account" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3442,7 +3421,6 @@ dependencies = [ "impl-num-traits", "index-set", "k256", - "linkme", "masp_primitives", "namada_macros", "namada_migrations", @@ -3479,7 +3457,6 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_macros", @@ -3506,7 +3483,6 @@ name = "namada_gas" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3520,7 +3496,6 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3579,7 +3554,6 @@ dependencies = [ "borsh", "eyre", "ics23", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3596,7 +3570,6 @@ dependencies = [ "borsh-ext", "data-encoding", "lazy_static", - "linkme", "serde", ] @@ -3618,7 +3591,6 @@ dependencies = [ "borsh", "data-encoding", "derivative", - "linkme", "namada_account", "namada_core", "namada_governance", @@ -3661,7 +3633,6 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", - "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3726,7 +3697,6 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3751,7 +3721,6 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3831,7 +3800,6 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", - "linkme", "masp_primitives", "namada_core", "namada_gas", @@ -3893,7 +3861,6 @@ name = "namada_vote_ext" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8b3f31c739..ff062b3ba1 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3079,26 +3079,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "linkme" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3415,7 +3395,6 @@ name = "namada_account" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3442,7 +3421,6 @@ dependencies = [ "impl-num-traits", "index-set", "k256", - "linkme", "masp_primitives", "namada_macros", "namada_migrations", @@ -3479,7 +3457,6 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", - "linkme", "namada_account", "namada_core", "namada_macros", @@ -3506,7 +3483,6 @@ name = "namada_gas" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3520,7 +3496,6 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3579,7 +3554,6 @@ dependencies = [ "borsh", "eyre", "ics23", - "linkme", "namada_core", "namada_macros", "namada_migrations", @@ -3596,7 +3570,6 @@ dependencies = [ "borsh-ext", "data-encoding", "lazy_static", - "linkme", "serde", ] @@ -3618,7 +3591,6 @@ dependencies = [ "borsh", "data-encoding", "derivative", - "linkme", "namada_account", "namada_core", "namada_governance", @@ -3661,7 +3633,6 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", - "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3726,7 +3697,6 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3751,7 +3721,6 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", - "linkme", "namada_core", "namada_gas", "namada_macros", @@ -3831,7 +3800,6 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", - "linkme", "masp_primitives", "namada_core", "namada_gas", @@ -3893,7 +3861,6 @@ name = "namada_vote_ext" version = "0.31.8" dependencies = [ "borsh", - "linkme", "namada_core", "namada_macros", "namada_migrations", From cae053cdb61a934445cff82feb5272849adf2c04 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 6 Mar 2024 10:39:12 +0100 Subject: [PATCH 56/96] Addressing review comments --- Cargo.lock | 6 ++---- crates/macros/Cargo.toml | 3 --- crates/migrations/Cargo.toml | 3 --- wasm/Cargo.lock | 26 +------------------------- wasm_for_tests/wasm_source/Cargo.lock | 26 +------------------------- 5 files changed, 4 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf5fd075a9..4018f1c951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4518,8 +4518,8 @@ dependencies = [ name = "namada_macros" version = "0.31.9" dependencies = [ + "data-encoding", "lazy_static", - "namada_migrations", "paste", "pretty_assertions", "proc-macro2", @@ -4549,10 +4549,8 @@ dependencies = [ [[package]] name = "namada_migrations" -version = "0.31.8" +version = "0.31.9" dependencies = [ - "borsh", - "borsh-ext", "data-encoding", "lazy_static", "linkme", diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index f99842d4cf..4f45eb14e6 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -15,9 +15,6 @@ version.workspace = true [lib] proc-macro = true -[features] -migrations = ["namada_migrations/migrations"] - [dependencies] data-encoding.workspace = true paste.workspace = true diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index e99a8ae19d..1f0ae72bbc 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -14,9 +14,6 @@ version.workspace = true [lib] -[features] -migrations = ["linkme"] - [dependencies] data-encoding.workspace = true lazy_static.workspace = true diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 70d7710679..439a84b197 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3342,7 +3342,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3397,7 +3396,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_storage", "proptest", "serde", @@ -3423,7 +3421,6 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", - "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3460,7 +3457,6 @@ dependencies = [ "namada_account", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3485,7 +3481,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "serde", "thiserror", ] @@ -3498,7 +3493,6 @@ dependencies = [ "itertools 0.10.5", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3538,8 +3532,8 @@ dependencies = [ name = "namada_macros" version = "0.31.8" dependencies = [ + "data-encoding", "lazy_static", - "namada_migrations", "paste", "proc-macro2", "quote", @@ -3556,23 +3550,11 @@ dependencies = [ "ics23", "namada_core", "namada_macros", - "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] -[[package]] -name = "namada_migrations" -version = "0.31.8" -dependencies = [ - "borsh", - "borsh-ext", - "data-encoding", - "lazy_static", - "serde", -] - [[package]] name = "namada_parameters" version = "0.31.8" @@ -3595,7 +3577,6 @@ dependencies = [ "namada_core", "namada_governance", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3641,7 +3622,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3701,7 +3681,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3725,7 +3704,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3804,7 +3782,6 @@ dependencies = [ "namada_core", "namada_gas", "namada_macros", - "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3863,7 +3840,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_tx", "serde", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index ff062b3ba1..c869c732b3 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3342,7 +3342,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3397,7 +3396,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_storage", "proptest", "serde", @@ -3423,7 +3421,6 @@ dependencies = [ "k256", "masp_primitives", "namada_macros", - "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3460,7 +3457,6 @@ dependencies = [ "namada_account", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3485,7 +3481,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "serde", "thiserror", ] @@ -3498,7 +3493,6 @@ dependencies = [ "itertools 0.10.5", "namada_core", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3538,8 +3532,8 @@ dependencies = [ name = "namada_macros" version = "0.31.8" dependencies = [ + "data-encoding", "lazy_static", - "namada_migrations", "paste", "proc-macro2", "quote", @@ -3556,23 +3550,11 @@ dependencies = [ "ics23", "namada_core", "namada_macros", - "namada_migrations", "prost 0.12.3", "sparse-merkle-tree", "thiserror", ] -[[package]] -name = "namada_migrations" -version = "0.31.8" -dependencies = [ - "borsh", - "borsh-ext", - "data-encoding", - "lazy_static", - "serde", -] - [[package]] name = "namada_parameters" version = "0.31.8" @@ -3595,7 +3577,6 @@ dependencies = [ "namada_core", "namada_governance", "namada_macros", - "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3641,7 +3622,6 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", - "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3701,7 +3681,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3725,7 +3704,6 @@ dependencies = [ "namada_gas", "namada_macros", "namada_merkle_tree", - "namada_migrations", "namada_replay_protection", "namada_tx", "thiserror", @@ -3804,7 +3782,6 @@ dependencies = [ "namada_core", "namada_gas", "namada_macros", - "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3863,7 +3840,6 @@ dependencies = [ "borsh", "namada_core", "namada_macros", - "namada_migrations", "namada_tx", "serde", ] From 07da03a61217012a89feafd137b8f2f943b02791 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 18:02:47 +0100 Subject: [PATCH 57/96] [feat]: Rebased on the serializers branch and added cli command to apply db changes from a json file --- Cargo.lock | 1 + crates/apps/src/bin/namada-node/cli.rs | 8 + crates/apps/src/lib/cli.rs | 49 +++++ crates/apps/src/lib/node/ledger/mod.rs | 67 ++++++- .../src/lib/node/ledger/shell/init_chain.rs | 33 ++++ .../apps/src/lib/node/ledger/storage/mod.rs | 1 + .../src/lib/node/ledger/storage/rocksdb.rs | 114 +++++++++++ .../src/lib/node/ledger/tendermint_node.rs | 6 + crates/sdk/Cargo.toml | 1 + crates/sdk/src/lib.rs | 1 + crates/sdk/src/migrations.rs | 181 ++++++++++++++++++ crates/storage/src/db.rs | 10 + crates/storage/src/mockdb.rs | 10 + wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 15 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 crates/sdk/src/migrations.rs diff --git a/Cargo.lock b/Cargo.lock index 4018f1c951..496f8f364e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4623,6 +4623,7 @@ dependencies = [ "derivation-path", "ethbridge-bridge-contract", "ethers", + "eyre", "fd-lock", "futures", "itertools 0.10.5", diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index a0d00d48dc..0a79d8515f 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -38,6 +38,14 @@ pub fn main() -> Result<()> { ledger::rollback(chain_ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; } + cmds::Ledger::UpdateDB(cmds::LedgerUpdateDB(args)) => { + let chain_ctx = ctx.take_chain_or_exit(); + ledger::update_db_keys( + chain_ctx.config.ledger, + args.updates, + args.dry_run, + ); + } }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 109c0d78af..8363f77a75 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -839,6 +839,7 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), + UpdateDB(LedgerUpdateDB), RollBack(LedgerRollBack), } @@ -850,10 +851,12 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); + let update_db = SubCmd::parse(matches).map(Self::UpdateDB); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) + .or(update_db) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -873,6 +876,7 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) + .subcommand(LedgerUpdateDB::def()) .subcommand(LedgerRollBack::def()) } } @@ -955,6 +959,27 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerUpdateDB(pub args::LedgerUpdateDb); + + impl SubCmd for LedgerUpdateDB { + const CMD: &'static str = "update_db"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerUpdateDb::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD).about( + "Applies a set of updates to the DB for hard forking. The \ + input should be a path to a file dictating a set of keys and \ + their new values. Can be dry-run for testing.", + ) + } + } + #[derive(Clone, Debug)] pub struct LedgerRollBack; @@ -3375,6 +3400,30 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerUpdateDb { + pub updates: PathBuf, + pub dry_run: bool, + } + + impl Args for LedgerUpdateDb { + fn parse(matches: &ArgMatches) -> Self { + let updates = PATH.parse(matches); + let dry_run = DRY_RUN_TX.parse(matches); + Self { updates, dry_run } + } + + fn def(app: App) -> App { + app.arg(PATH.def().help( + "The path to a json of key-value pairs to update the DB with.", + )) + .arg(DRY_RUN_TX.def().help( + "If set, applies the updates but does not persist them. Using \ + for testing and debugging.", + )) + } + } + #[derive(Clone, Debug)] pub struct UpdateLocalConfig { pub config_path: PathBuf, diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 5c21f687e5..c6a6ff3baa 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -14,7 +14,7 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; -use namada::core::storage::Key; +use namada::core::storage::{BlockHeight, Key}; use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::governance::storage::keys as governance_storage; @@ -163,6 +163,13 @@ impl Shell { } } +/// Determine if the ledger is migrating state. +pub fn migrating_state() -> Option { + const ENV_INITIAL_HEIGHT: &str = "NAMADA_INITIAL_HEIGHT"; + let height = std::env::var(ENV_INITIAL_HEIGHT).ok()?; + height.parse::().ok().map(BlockHeight) +} + /// Run the ledger with an async runtime pub fn run(config: config::Ledger, wasm_dir: PathBuf) { let logical_cores = num_cpus::get(); @@ -224,6 +231,64 @@ pub fn dump_db( db.dump_block(out_file_path, historic, block_height); } +/// Change the funds of an account in-place. Use with +/// caution, as this modifies state in storage without +/// going through the consensus protocol. +pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { + use std::io::Read; + + use namada::ledger::storage::DB; + + let mut update_json = String::new(); + let mut file = std::fs::File::open(updates) + .expect("Could not fine updates file at the specified path."); + file.read_to_string(&mut update_json) + .expect("Unable to read the updates json file"); + let updates: namada_sdk::migrations::DbChanges = + serde_json::from_str(&update_json) + .expect("Could not parse the updates file as json"); + let cometbft_path = config.cometbft_dir(); + let chain_id = config.chain_id; + let db_path = config.shell.db_dir(&chain_id); + + let db = storage::PersistentDB::open(db_path, None); + let mut db_visitor = storage::RocksDBUpdateVisitor::new(&db); + + for change in &updates.changes { + match change.update(&mut db_visitor) { + Ok(Some(deserialized)) => { + tracing::debug!( + "Writing key <{}> with value: {}...", + change.key(), + deserialized + ); + } + Ok(None) => { + tracing::debug!("Deleting key <{}>", change.key()); + } + e => { + tracing::error!( + "Attempt to write to key <{}> failed.", + change.key() + ); + e.unwrap(); + } + } + } + if !dry_run { + tracing::debug!("Persisting DB changes..."); + let batch = db_visitor.take_batch(); + let mut db = db; + db.exec_batch(batch).expect("Failed to execute write batch"); + db.flush(true).expect("Failed to flush data to disk"); + + // reset CometBFT's state, such that we can resume with a different app + // hash + tendermint_node::reset(cometbft_path) + .expect("Failed to reset CometBFT state"); + } +} + /// Roll Namada state back to the previous height pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { shell::rollback(config) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index 8982367b78..870e9f80e8 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -27,6 +27,7 @@ use crate::config::genesis::transactions::{ BondTx, EstablishedAccountTx, Signed as SignedTx, ValidatorAccountTx, }; use crate::facade::tendermint_proto::google::protobuf; +use crate::node::ledger; use crate::wasm_loader; /// Errors that represent panics in normal flow but get demoted to errors @@ -94,6 +95,38 @@ where chain_id, init.chain_id ))); } + if ledger::migrating_state().is_some() { + let rsp = response::InitChain { + validators: self + .get_abci_validator_updates(true, |pk, power| { + let pub_key: crate::facade::tendermint::PublicKey = + pk.into(); + let power = + crate::facade::tendermint::vote::Power::try_from( + power, + ) + .unwrap(); + validator::Update { pub_key, power } + }) + .expect("Must be able to set genesis validator set"), + app_hash: self + .state + .in_mem() + .merkle_root() + .0 + .to_vec() + .try_into() + .expect("Infallible"), + ..Default::default() + }; + debug_assert!(!rsp.validators.is_empty()); + debug_assert!( + !Vec::::from(rsp.app_hash.clone()) + .iter() + .all(|&b| b == 0) + ); + return Ok(rsp); + } // Read the genesis files #[cfg(any( diff --git a/crates/apps/src/lib/node/ledger/storage/mod.rs b/crates/apps/src/lib/node/ledger/storage/mod.rs index 7da4d37fc9..2e1c383ca4 100644 --- a/crates/apps/src/lib/node/ledger/storage/mod.rs +++ b/crates/apps/src/lib/node/ledger/storage/mod.rs @@ -11,6 +11,7 @@ use arse_merkle_tree::H256; use blake2b_rs::{Blake2b, Blake2bBuilder}; use namada::state::StorageHasher; use namada_sdk::state::FullAccessState; +pub use rocksdb::RocksDBUpdateVisitor; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 8706dacf24..54d5878d88 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -65,6 +65,7 @@ use namada::state::{ StoreType, DB, }; use namada::token::ConversionState; +use namada_sdk::migrations::DBUpdateVisitor; use rayon::prelude::*; use regex::Regex; use rocksdb::{ @@ -1535,6 +1536,119 @@ impl DB for RocksDB { Ok(()) } + + #[inline] + fn overwrite_entry( + &self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()> { + let last_height: BlockHeight = { + let state_cf = self.get_column_family(STATE_CF)?; + + decode( + self.0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + })?, + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + })? + }; + let desired_height = height.unwrap_or(last_height); + + if desired_height != last_height { + todo!( + "Overwriting values at heights different than the last \ + committed height hast yet to be implemented" + ); + } + // NB: the following code only updates values + // written to at the last committed height + + let val = new_value.as_ref(); + + // update subspace value + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; + let subspace_key = key.to_string(); + + batch.0.put_cf(subspace_cf, subspace_key, val); + + // update value stored in diffs + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let diffs_key = Key::from(last_height.to_db_key()) + .with_segment("new".to_owned()) + .join(key) + .to_string(); + + batch.0.put_cf(diffs_cf, diffs_key, val); + + Ok(()) + } +} + +/// A struct that can visit a set of updates, +/// registering them all in the batch +pub struct RocksDBUpdateVisitor<'db> { + db: &'db RocksDB, + batch: RocksDBWriteBatch, +} + +impl<'db> RocksDBUpdateVisitor<'db> { + pub fn new(db: &'db RocksDB) -> Self { + Self { + db, + batch: Default::default(), + } + } + + pub fn take_batch(self) -> RocksDBWriteBatch { + self.batch + } +} + +impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { + fn read(&self, key: &Key) -> Option> { + self.db + .read_subspace_val(key) + .expect("Failed to read from storage") + } + + fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { + self.db + .overwrite_entry(&mut self.batch, None, key, value) + .expect("Failed to overwrite a key in storage") + } + + fn delete(&mut self, key: &Key) { + let last_height: BlockHeight = { + let state_cf = self.db.get_column_family(STATE_CF).unwrap(); + + decode( + self.db + .0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string())) + .unwrap() + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + }) + .unwrap(), + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + }) + .unwrap() + }; + self.db + .batch_delete_subspace_val(&mut self.batch, last_height, key, true) + .expect("Failed to delete key from storage"); + } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/crates/apps/src/lib/node/ledger/tendermint_node.rs b/crates/apps/src/lib/node/ledger/tendermint_node.rs index b08dce9ca6..31626e2226 100644 --- a/crates/apps/src/lib/node/ledger/tendermint_node.rs +++ b/crates/apps/src/lib/node/ledger/tendermint_node.rs @@ -452,6 +452,12 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); + if let Some(height) = super::migrating_state() { + genesis.initial_height = height + .0 + .try_into() + .expect("Failed to convert initial genesis height"); + } let size = block::Size { // maximum size of a serialized Tendermint block. // on Namada, we have a hard-cap of 16 MiB (6 MiB max diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 00d567f6d0..c6e1d2645f 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -93,6 +93,7 @@ data-encoding.workspace = true derivation-path.workspace = true ethbridge-bridge-contract.workspace = true ethers.workspace = true +eyre.workspace = true fd-lock = { workspace = true, optional = true } futures.workspace = true itertools.workspace = true diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 71dc57aa46..d4922d9203 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -24,6 +24,7 @@ pub mod error; pub mod events; pub(crate) mod internal_macros; pub mod io; +pub mod migrations; pub mod queries; pub mod wallet; diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs new file mode 100644 index 0000000000..5afe55c870 --- /dev/null +++ b/crates/sdk/src/migrations.rs @@ -0,0 +1,181 @@ +use core::fmt::Formatter; + +use borsh::{BorshDeserialize, BorshSerialize}; +use borsh_ext::BorshSerializeExt; +use data_encoding::HEXUPPER; +use namada_core::storage::Key; +use namada_migrations::{TypeHash, TYPE_DESERIALIZERS}; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub trait DBUpdateVisitor { + fn read(&self, key: &Key) -> Option>; + fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); + fn delete(&mut self, key: &Key); +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +/// A value to be added to the database that can be +/// validated. +pub struct UpdateValue { + type_hash: [u8; 32], + bytes: Vec, +} + +impl From for UpdateValue { + fn from(value: T) -> Self { + Self { + type_hash: T::HASH, + bytes: value.serialize_to_vec(), + } + } +} + +struct UpdateValueVisitor; + +impl<'de> Visitor<'de> for UpdateValueVisitor { + type Value = UpdateValue; + + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { + formatter.write_str( + "a hex encoded series of bytes that borsh decode to an \ + UpdateValue.", + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + UpdateValue::try_from_slice( + &HEXUPPER + .decode(v.as_bytes()) + .map_err(|e| E::custom(e.to_string()))?, + ) + .map_err(|e| E::custom(e.to_string())) + } +} + +impl Serialize for UpdateValue { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let hex_bytes = HEXUPPER.encode(&self.serialize_to_vec()); + Serialize::serialize(&hex_bytes, serializer) + } +} + +impl<'de> Deserialize<'de> for UpdateValue { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(UpdateValueVisitor) + } +} +#[derive(Debug, Clone, Serialize, Deserialize)] +/// An update to the database +pub enum DbUpdateType { + Add { + key: Key, + value: UpdateValue, + force: bool, + }, + Delete(Key), +} + +impl DbUpdateType { + /// Get the key being modified + pub fn key(&self) -> &Key { + match self { + DbUpdateType::Add { key, .. } => key, + DbUpdateType::Delete(key) => key, + } + } + + /// Validate that the contained value deserializes correctly given its data + /// hash. + pub fn validate(&self) -> eyre::Result<()> { + match self { + DbUpdateType::Add { value, .. } => { + let locked = TYPE_DESERIALIZERS.lock().unwrap(); + let deserializer = + locked.get(&value.type_hash).ok_or_else(|| { + eyre::eyre!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + ) + })?; + _ = deserializer(value.bytes.clone()).ok_or_else(|| { + eyre::eyre!( + "The value {:?} could not be successfully deserialized", + value + ) + })?; + Ok(()) + } + DbUpdateType::Delete(_) => Ok(()), + } + } + + /// Validate a DB change and persist it if so. The debug representation of + /// the new value is returned for logging purposes. + #[allow(dead_code)] + pub fn update( + &self, + db: &mut DB, + ) -> eyre::Result> { + match self { + Self::Add { key, value, force } => { + let deserialized = if !force { + let locked = TYPE_DESERIALIZERS.lock().unwrap(); + + let deserializer = + locked.get(&value.type_hash).ok_or_else(|| { + eyre::eyre!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + ) + })?; + let deserialized = deserializer(value.bytes.clone()) + .ok_or_else(|| { + eyre::eyre!( + "The value {:?} for key {} could not be \ + successfully deserialized", + value, + key + ) + })?; + if let Some(prev) = db.read(key) { + deserializer(prev).ok_or_else(|| { + eyre::eyre!( + "The previous value under the key {} did not \ + have the same type as that provided: Input \ + was {}", + key, + deserialized + ) + })?; + } + Some(deserialized) + } else { + None + }; + db.write(key, &value.bytes); + Ok(deserialized) + } + Self::Delete(key) => { + db.delete(key); + Ok(None) + } + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct DbChanges { + pub changes: Vec, +} diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 072baa1f3d..4ae5d35b0c 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -261,6 +261,16 @@ pub trait DB: Debug { batch: &mut Self::WriteBatch, key: &Key, ) -> Result<()>; + + /// Overwrite a new value in storage, taking into + /// account values stored at a previous height + fn overwrite_entry( + &self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()>; } /// A database prefix iterator. diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 78dbb1b736..b3740be8a9 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -675,6 +675,16 @@ impl DB for MockDB { Ok(()) } + + fn overwrite_entry( + &self, + _batch: &mut Self::WriteBatch, + _height: Option, + _key: &Key, + _new_value: impl AsRef<[u8]>, + ) -> Result<()> { + unimplemented!() + } } impl<'iter> DBIter<'iter> for MockDB { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 439a84b197..6a08635639 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3609,6 +3609,7 @@ dependencies = [ "derivation-path", "ethbridge-bridge-contract", "ethers", + "eyre", "fd-lock", "futures", "itertools 0.10.5", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index c869c732b3..8fe1f66fd3 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3609,6 +3609,7 @@ dependencies = [ "derivation-path", "ethbridge-bridge-contract", "ethers", + "eyre", "fd-lock", "futures", "itertools 0.10.5", From 93ce4e4e9060b45b84a2fb3ba0229a091b392888 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 12:11:45 +0100 Subject: [PATCH 58/96] [feat]: Fixed and tested migration tooling. --- Cargo.lock | 90 +++++++------ Cargo.toml | 2 +- crates/apps/src/bin/namada-node/cli.rs | 20 +++ crates/apps/src/lib/cli.rs | 124 +++++++++++++++++- crates/apps/src/lib/cli/client.rs | 31 ++++- crates/apps/src/lib/node/ledger/mod.rs | 43 ++++-- crates/apps/src/lib/node/ledger/shell/mod.rs | 10 ++ .../src/lib/node/ledger/tendermint_node.rs | 17 +++ crates/macros/src/lib.rs | 3 +- crates/migrations/src/lib.rs | 35 ++++- crates/sdk/src/lib.rs | 1 + crates/sdk/src/migrations.rs | 68 ++++++++-- crates/trans_token/Cargo.toml | 5 + examples/Cargo.toml | 9 +- examples/make-db-migration.rs | 33 +++++ 15 files changed, 409 insertions(+), 82 deletions(-) create mode 100644 examples/make-db-migration.rs diff --git a/Cargo.lock b/Cargo.lock index 496f8f364e..2473e5a055 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -296,7 +296,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -492,7 +492,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -665,7 +665,7 @@ dependencies = [ "proc-macro-crate 2.0.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "syn_derive", ] @@ -1076,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1449,7 +1449,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1486,7 +1486,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1497,7 +1497,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1623,7 +1623,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1828,7 +1828,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2033,7 +2033,7 @@ dependencies = [ "regex", "serde 1.0.193", "serde_json", - "syn 2.0.39", + "syn 2.0.52", "toml 0.8.2", "walkdir", ] @@ -2051,7 +2051,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2077,7 +2077,7 @@ dependencies = [ "serde 1.0.193", "serde_json", "strum 0.25.0", - "syn 2.0.39", + "syn 2.0.52", "tempfile", "thiserror", "tiny-keccak", @@ -2421,7 +2421,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3285,7 +3285,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3811,22 +3811,22 @@ dependencies = [ [[package]] name = "linkme" -version = "0.3.20" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ae8aae8e1d516e0a3ceee1219eded7f73741607e4227bf11ef2c3e31580427" +checksum = "7ca16377a6dae364fb00769699ba440899f1a720d4f5abf2667d0a8a95f933dd" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.20" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad083d767be37e709a232ae2a244445ed032bb9c6bf7d9442dd416ba5a7b7264" +checksum = "1e5ac81de04bf8215501c50a436632c3789b22ef1625fe0bf8927dd4ba3696c5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4434,7 +4434,10 @@ version = "0.31.9" dependencies = [ "data-encoding", "masp_proofs", + "namada_apps", + "namada_migrations", "namada_sdk", + "namada_trans_token", "proptest", "serde_json", "tokio", @@ -4816,6 +4819,7 @@ dependencies = [ name = "namada_trans_token" version = "0.31.9" dependencies = [ + "linkme", "namada_core", "namada_storage", ] @@ -5202,7 +5206,7 @@ dependencies = [ "proc-macro-crate 2.0.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5298,7 +5302,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5576,7 +5580,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5718,7 +5722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5781,9 +5785,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -5882,7 +5886,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5940,9 +5944,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -6704,7 +6708,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -6726,7 +6730,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7056,7 +7060,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7099,9 +7103,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -7117,7 +7121,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7370,7 +7374,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7390,7 +7394,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7544,7 +7548,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7813,7 +7817,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -7940,7 +7944,7 @@ checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -8192,7 +8196,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -8226,7 +8230,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8961,7 +8965,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3681402671..90880a0b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,7 @@ ledger-namada-rs = { git = "https://github.com/Zondax/ledger-namada", tag = "v0. ledger-transport-hid = "0.10.0" libc = "0.2.97" libloading = "0.7.2" -linkme = "0.3" +linkme = "0.3.24" # branch = "murisi/namada-integration" masp_primitives = { git = "https://github.com/anoma/masp", rev = "30492323d98b0531fd18b6285cd94afcaa4066d2" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "30492323d98b0531fd18b6285cd94afcaa4066d2", default-features = false, features = ["local-prover"] } diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 0a79d8515f..64284756c6 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -39,13 +39,33 @@ pub fn main() -> Result<()> { .wrap_err("Failed to rollback the Namada node")?; } cmds::Ledger::UpdateDB(cmds::LedgerUpdateDB(args)) => { + #[cfg(not(feature = "migrations"))] + { + panic!( + "This command is only available if build with the \ + \"migrations\" feature." + ) + } let chain_ctx = ctx.take_chain_or_exit(); + #[cfg(feature = "migrations")] ledger::update_db_keys( chain_ctx.config.ledger, args.updates, args.dry_run, ); } + cmds::Ledger::QueryDB(cmds::LedgerQueryDB(args)) => { + #[cfg(not(feature = "migrations"))] + { + panic!( + "This command is only available if build with the \ + \"migrations\" feature." + ) + } + let chain_ctx = ctx.take_chain_or_exit(); + #[cfg(feature = "migrations")] + ledger::query_db(chain_ctx.config.ledger, &args.key_hash_pairs); + } }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 8363f77a75..555098f5d8 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -840,6 +840,7 @@ pub mod cmds { Reset(LedgerReset), DumpDb(LedgerDumpDb), UpdateDB(LedgerUpdateDB), + QueryDB(LedgerQueryDB), RollBack(LedgerRollBack), } @@ -852,11 +853,13 @@ pub mod cmds { let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); let update_db = SubCmd::parse(matches).map(Self::UpdateDB); + let query_db = SubCmd::parse(matches).map(Self::QueryDB); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) .or(update_db) + .or(query_db) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -877,6 +880,7 @@ pub mod cmds { .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) .subcommand(LedgerUpdateDB::def()) + .subcommand(LedgerQueryDB::def()) .subcommand(LedgerRollBack::def()) } } @@ -963,7 +967,7 @@ pub mod cmds { pub struct LedgerUpdateDB(pub args::LedgerUpdateDb); impl SubCmd for LedgerUpdateDB { - const CMD: &'static str = "update_db"; + const CMD: &'static str = "update-db"; fn parse(matches: &ArgMatches) -> Option { matches @@ -972,11 +976,35 @@ pub mod cmds { } fn def() -> App { - App::new(Self::CMD).about( - "Applies a set of updates to the DB for hard forking. The \ - input should be a path to a file dictating a set of keys and \ - their new values. Can be dry-run for testing.", - ) + App::new(Self::CMD) + .about( + "Applies a set of updates to the DB for hard forking. The \ + input should be a path to a file dictating a set of keys \ + and their new values. Can be dry-run for testing.", + ) + .add_args::() + } + } + + #[derive(Clone, Debug)] + pub struct LedgerQueryDB(pub args::LedgerQueryDb); + + impl SubCmd for LedgerQueryDB { + const CMD: &'static str = "query-db"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerQueryDb::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query the value of keys from the DB while the ledger is \ + not running.", + ) + .add_args::() } } @@ -2200,6 +2228,7 @@ pub mod cmds { ValidateGenesisTemplates(ValidateGenesisTemplates), TestGenesis(TestGenesis), SignGenesisTxs(SignGenesisTxs), + ParseMigrationJson(MigrationJson), } impl SubCmd for Utils { @@ -2233,6 +2262,8 @@ pub mod cmds { SubCmd::parse(matches).map(Self::SignGenesisTxs); let test_genesis = SubCmd::parse(matches).map(Self::TestGenesis); + let parse_migrations_json = + SubCmd::parse(matches).map(Self::ParseMigrationJson); join_network .or(fetch_wasms) .or(validate_wasm) @@ -2247,6 +2278,7 @@ pub mod cmds { .or(validate_genesis_templates) .or(test_genesis) .or(genesis_tx) + .or(parse_migrations_json) }) } @@ -2267,6 +2299,7 @@ pub mod cmds { .subcommand(ValidateGenesisTemplates::def()) .subcommand(TestGenesis::def()) .subcommand(SignGenesisTxs::def()) + .subcommand(MigrationJson::def()) .subcommand_required(true) .arg_required_else_help(true) } @@ -2496,6 +2529,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct MigrationJson(pub args::MigrationJson); + + impl SubCmd for MigrationJson { + const CMD: &'static str = "parse-migration-json"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::MigrationJson::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Parse and print a migration JSON file.") + .add_args::() + } + } + /// Used as sub-commands (`SubCmd` instance) in `namadar` binary. #[derive(Clone, Debug)] pub enum EthBridgePool { @@ -2956,6 +3008,7 @@ pub mod args { use std::path::PathBuf; use std::str::FromStr; + use data_encoding::HEXUPPER; use namada::core::address::{Address, EstablishedAddress}; use namada::core::chain::{ChainId, ChainIdPrefix}; use namada::core::dec::Dec; @@ -3121,6 +3174,8 @@ pub mod args { scheme is not supplied, it is assumed to be TCP."; pub const CONFIG_RPC_LEDGER_ADDRESS: ArgDefaultFromCtx = arg_default_from_ctx("node", DefaultFn(|| "".to_string())); + pub const KEY_HASH_LIST: ArgMulti = + arg_multi("key-hash-list"); pub const LEDGER_ADDRESS: ArgDefault = arg("node").default(DefaultFn(|| { let raw = "http://127.0.0.1:26657"; @@ -3424,6 +3479,43 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerQueryDb { + pub key_hash_pairs: Vec<(storage::Key, [u8; 32])>, + } + + impl Args for LedgerQueryDb { + fn parse(matches: &ArgMatches) -> Self { + let pairs = KEY_HASH_LIST + .parse(matches) + .into_iter() + .map(|pair| { + let (hash, key) = pair + .split_once(':') + .expect("Key hash pairs must be colon separated."); + let hash: [u8; 32] = HEXUPPER + .decode(hash.to_uppercase().as_bytes()) + .unwrap() + .try_into() + .unwrap(); + let key = storage::Key::parse(key).unwrap(); + (key, hash) + }) + .collect(); + + Self { + key_hash_pairs: pairs, + } + } + + fn def(app: App) -> App { + app.arg(KEY_HASH_LIST.def().help( + "A comma separated list of entries of the form \ + :.", + )) + } + } + #[derive(Clone, Debug)] pub struct UpdateLocalConfig { pub config_path: PathBuf, @@ -7312,6 +7404,26 @@ pub mod args { )) } } + + #[derive(Clone, Debug)] + pub struct MigrationJson { + pub path: PathBuf, + } + + impl Args for MigrationJson { + fn parse(matches: &ArgMatches) -> Self { + let path = PATH.parse(matches); + + Self { path } + } + + fn def(app: App) -> App { + app.arg(PATH.def().help( + "Path to the migrations JSON file. Requires the binary to be \ + built with the \"migrations\" feature.", + )) + } + } } pub fn namada_cli() -> (cmds::Namada, String) { diff --git a/crates/apps/src/lib/cli/client.rs b/crates/apps/src/lib/cli/client.rs index 566cfab888..cd1559e474 100644 --- a/crates/apps/src/lib/cli/client.rs +++ b/crates/apps/src/lib/cli/client.rs @@ -1,7 +1,9 @@ +use std::io::Read; + use color_eyre::eyre::Result; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::io::Io; -use namada_sdk::{Namada, NamadaImpl}; +use namada_sdk::{display_line, Namada, NamadaImpl}; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -807,6 +809,33 @@ impl CliApi { Utils::SignGenesisTxs(SignGenesisTxs(args)) => { utils::sign_genesis_tx(global_args, args).await } + Utils::ParseMigrationJson(MigrationJson(args)) => { + #[cfg(feature = "migrations")] + { + let mut update_json = String::new(); + let mut file = std::fs::File::open(args.path).expect( + "Could not fine updates file at the specified \ + path.", + ); + file.read_to_string(&mut update_json) + .expect("Unable to read the updates json file"); + let updates: namada_sdk::migrations::DbChanges = + serde_json::from_str(&update_json).expect( + "Could not parse the updates file as json", + ); + for change in updates.changes { + display_line!(io, "{}", change); + } + } + #[cfg(not(feature = "migrations"))] + { + display_line!( + io, + "Can only use this function if compiled with \ + feature \"migrations\" enabled." + ) + } + } }, } Ok(()) diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index c6a6ff3baa..ffe3686b81 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -18,6 +18,7 @@ use namada::core::storage::{BlockHeight, Key}; use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::governance::storage::keys as governance_storage; +use namada::state::DB; use namada::tendermint::abci::request::CheckTxKind; use namada_sdk::state::StateRead; use once_cell::unsync::Lazy; @@ -222,8 +223,6 @@ pub fn dump_db( historic, }: args::LedgerDumpDb, ) { - use namada::state::DB; - let chain_id = config.chain_id; let db_path = config.shell.db_dir(&chain_id); @@ -231,14 +230,40 @@ pub fn dump_db( db.dump_block(out_file_path, historic, block_height); } +#[cfg(feature = "migrations")] +pub fn query_db(config: config::Ledger, keys: &[(Key, [u8; 32])]) { + let chain_id = config.chain_id; + let db_path = config.shell.db_dir(&chain_id); + + let db = storage::PersistentDB::open(db_path, None); + for (key, type_hash) in keys { + let bytes = db.read_subspace_val(key).unwrap(); + if let Some(bytes) = bytes { + let deserializer = namada_migrations::get_deserializer(type_hash) + .unwrap_or_else(|| { + panic!( + "Could not find a deserializer for the type provided \ + with key <{}>", + key + ) + }); + let value = deserializer(bytes).unwrap_or_else(|| { + panic!("Unable to deserialize the value under key <{}>", key) + }); + tracing::info!("Key <{}>: {}", key, value); + } else { + tracing::info!("Key <{}> is not present in storage.", key); + } + } +} + /// Change the funds of an account in-place. Use with /// caution, as this modifies state in storage without /// going through the consensus protocol. +#[cfg(feature = "migrations")] pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { use std::io::Read; - use namada::ledger::storage::DB; - let mut update_json = String::new(); let mut file = std::fs::File::open(updates) .expect("Could not fine updates file at the specified path."); @@ -257,14 +282,14 @@ pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { for change in &updates.changes { match change.update(&mut db_visitor) { Ok(Some(deserialized)) => { - tracing::debug!( + tracing::info!( "Writing key <{}> with value: {}...", change.key(), deserialized ); } Ok(None) => { - tracing::debug!("Deleting key <{}>", change.key()); + tracing::info!("Deleting key <{}>", change.key()); } e => { tracing::error!( @@ -276,15 +301,15 @@ pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { } } if !dry_run { - tracing::debug!("Persisting DB changes..."); + tracing::info!("Persisting DB changes..."); let batch = db_visitor.take_batch(); let mut db = db; db.exec_batch(batch).expect("Failed to execute write batch"); db.flush(true).expect("Failed to flush data to disk"); - // reset CometBFT's state, such that we can resume with a different app + // reset CometBFT's state, such that we can resume with a different appq // hash - tendermint_node::reset(cometbft_path) + tendermint_node::reset_state(cometbft_path) .expect("Failed to reset CometBFT state"); } } diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index e144794e2c..393ba26a63 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -77,6 +77,7 @@ use crate::config::{self, genesis, TendermintMode, ValidatorLocalConfig}; use crate::facade::tendermint::v0_37::abci::{request, response}; use crate::facade::tendermint::{self, validator}; use crate::facade::tendermint_proto::v0_37::crypto::public_key; +use crate::node::ledger; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; @@ -551,6 +552,15 @@ where /// Load the Merkle root hash and the height of the last committed block, if /// any. This is returned when ABCI sends an `info` request. pub fn last_state(&mut self) -> response::Info { + if ledger::migrating_state().is_some() { + // When migrating state, return a height of 0, such + // that CometBFT calls InitChain and subsequently + // updates the apphash in its state. + return response::Info { + last_block_height: 0u32.into(), + ..response::Info::default() + }; + } let mut response = response::Info { last_block_height: tendermint::block::Height::from(0_u32), ..Default::default() diff --git a/crates/apps/src/lib/node/ledger/tendermint_node.rs b/crates/apps/src/lib/node/ledger/tendermint_node.rs index 31626e2226..7c70de5958 100644 --- a/crates/apps/src/lib/node/ledger/tendermint_node.rs +++ b/crates/apps/src/lib/node/ledger/tendermint_node.rs @@ -217,6 +217,23 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { Ok(()) } +pub fn reset_state(tendermint_dir: impl AsRef) -> Result<()> { + let tendermint_path = from_env_or_default()?; + let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); + // reset all the Tendermint state, if any + std::process::Command::new(tendermint_path) + .args([ + "unsafe-reset-all", + // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels + // "--log-level=\"*debug\"", + "--home", + &tendermint_dir, + ]) + .output() + .expect("Failed to reset tendermint node's data"); + Ok(()) +} + pub fn rollback(tendermint_dir: impl AsRef) -> Result { let tendermint_path = from_env_or_default()?; let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index d0e68b8ca9..e66aaf180c 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -414,8 +414,7 @@ fn impl_borsh_deserializer( #[cfg(feature = "migrations")] #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] static #deserializer_ident: fn() = || { - let mut locked = ::namada_migrations::TYPE_DESERIALIZERS.lock().unwrap(); - locked.insert(#hash, |bytes| { + ::namada_migrations::register_deserializer(#hash, |bytes| { #type_def::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() }); }; diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 72f2b7780d..988bf8ba4d 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::sync::Mutex; +use std::sync::{Mutex, OnceLock}; use lazy_static::lazy_static; pub use linkme::distributed_slice; @@ -7,10 +7,10 @@ pub use linkme::distributed_slice; /// Predicate that checks if an arbitrary byte array deserializes as some type /// `T` erased inside of the callback. If the serialization is correct, the full /// path of `T` is returned as a string (via [`std::any::type_name`]). -type CbFromByteArrayToTypeName = fn(Vec) -> Option; +pub type CbFromByteArrayToTypeName = fn(Vec) -> Option; lazy_static! { - pub static ref TYPE_DESERIALIZERS: Mutex> = + static ref TYPE_DESERIALIZERS: Mutex> = Mutex::new(HashMap::new()); } @@ -23,8 +23,29 @@ pub trait TypeHash { /// Calls all of the registered callbacks which place type /// deserializers into [`TYPE_DESERIALIZERS`]. -pub fn initialize_deserializers() { - for func in REGISTER_DESERIALIZERS { - func(); - } +fn initialize_deserializers() { + static INIT_GUARD: OnceLock<()> = OnceLock::new(); + INIT_GUARD.get_or_init(|| { + for func in REGISTER_DESERIALIZERS { + func(); + } + }); +} + +/// Register a serializer to the global list. +pub fn register_deserializer( + type_hash: [u8; 32], + func: CbFromByteArrayToTypeName, +) { + let mut locked = TYPE_DESERIALIZERS.lock().unwrap(); + locked.insert(type_hash, func); +} + +/// Retrieve a deserializer for the provided type. +pub fn get_deserializer( + type_hash: &[u8; 32], +) -> Option { + initialize_deserializers(); + let locked = TYPE_DESERIALIZERS.lock().unwrap(); + locked.get(type_hash).cloned() } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index d4922d9203..081865d26e 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,4 +1,5 @@ extern crate alloc; +extern crate core; pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 5afe55c870..973d154ac0 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -1,10 +1,16 @@ +#[cfg(not(feature = "migrations"))] use core::fmt::Formatter; +#[cfg(feature = "migrations")] +use core::fmt::{Display, Formatter}; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_core::storage::Key; -use namada_migrations::{TypeHash, TYPE_DESERIALIZERS}; +#[cfg(feature = "migrations")] +use namada_migrations::get_deserializer; +#[cfg(feature = "migrations")] +use namada_migrations::TypeHash; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -22,6 +28,7 @@ pub struct UpdateValue { bytes: Vec, } +#[cfg(feature = "migrations")] impl From for UpdateValue { fn from(value: T) -> Self { Self { @@ -85,6 +92,7 @@ pub enum DbUpdateType { Delete(Key), } +#[cfg(feature = "migrations")] impl DbUpdateType { /// Get the key being modified pub fn key(&self) -> &Key { @@ -99,15 +107,15 @@ impl DbUpdateType { pub fn validate(&self) -> eyre::Result<()> { match self { DbUpdateType::Add { value, .. } => { - let locked = TYPE_DESERIALIZERS.lock().unwrap(); let deserializer = - locked.get(&value.type_hash).ok_or_else(|| { - eyre::eyre!( - "Type hash {:?} did not correspond to a \ - deserializer in TYPE_DESERIALIZERS.", - value.type_hash - ) - })?; + namada_migrations::get_deserializer(&value.type_hash) + .ok_or_else(|| { + eyre::eyre!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + ) + })?; _ = deserializer(value.bytes.clone()).ok_or_else(|| { eyre::eyre!( "The value {:?} could not be successfully deserialized", @@ -130,10 +138,9 @@ impl DbUpdateType { match self { Self::Add { key, value, force } => { let deserialized = if !force { - let locked = TYPE_DESERIALIZERS.lock().unwrap(); - let deserializer = - locked.get(&value.type_hash).ok_or_else(|| { + namada_migrations::get_deserializer(&value.type_hash) + .ok_or_else(|| { eyre::eyre!( "Type hash {:?} did not correspond to a \ deserializer in TYPE_DESERIALIZERS.", @@ -179,3 +186,40 @@ impl DbUpdateType { pub struct DbChanges { pub changes: Vec, } + +#[cfg(feature = "migrations")] +impl Display for DbUpdateType { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + DbUpdateType::Add { + key, + value: UpdateValue { type_hash, bytes }, + .. + } => { + let Some(deserializer) = get_deserializer(type_hash) else { + return f.write_str(&format!( + "Type hash {:?} did not correspond to a deserializer \ + in TYPE_DESERIALIZERS.", + type_hash + )); + }; + + let Some(value) = deserializer(bytes.clone()) else { + return f.write_str(&format!( + "The value {:?} for key<{}> could not be successfully \ + deserialized", + bytes, key + )); + }; + + f.write_str(&format!( + "Write to key: <{}> with value: {}", + key, value + )) + } + DbUpdateType::Delete(key) => { + f.write_str(&format!("Delete key: <{}>", key)) + } + } + } +} diff --git a/crates/trans_token/Cargo.toml b/crates/trans_token/Cargo.toml index deed56fac0..a3302c4de8 100644 --- a/crates/trans_token/Cargo.toml +++ b/crates/trans_token/Cargo.toml @@ -14,10 +14,15 @@ version.workspace = true [features] default = [] +migrations = [ + "linkme" +] [dependencies] namada_core = { path = "../core" } namada_storage = { path = "../storage" } +linkme = {workspace = true, optional = true} + [dev-dependencies] namada_storage = { path = "../storage", features = ["testing"] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 278e2b444d..89ca3716f7 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -16,9 +16,16 @@ version.workspace = true name = "generate-txs" path = "generate_txs.rs" +[[example]] +name = "make-db-migration" +path = "make-db-migration.rs" + [dev-dependencies] masp_proofs = { workspace = true, default-features = false, features = ["local-prover", "download-params"] } -namada_sdk = { path = "../crates/sdk", default-features = false, features = ["namada-sdk", "std", "testing"] } +namada_apps = {path = "../crates/apps", features = ["migrations"]} +namada_migrations = {path = "../crates/migrations"} +namada_trans_token = {path = "../crates/trans_token", features = ["migrations"]} +namada_sdk = { path = "../crates/sdk", default-features = false, features = ["namada-sdk", "std", "testing", "migrations"] } data-encoding.workspace = true proptest.workspace = true serde_json.workspace = true diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs new file mode 100644 index 0000000000..983783655f --- /dev/null +++ b/examples/make-db-migration.rs @@ -0,0 +1,33 @@ +use namada_sdk::address::Address; +use namada_sdk::migrations; +use namada_trans_token::storage_key::{balance_key, minted_balance_key}; +use namada_trans_token::Amount; + +fn main() { + let person = + Address::decode("tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn") + .unwrap(); + let nam = Address::decode("tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e") + .unwrap(); + let amount = Amount::native_whole(3200000036910u64); + let minted_key = minted_balance_key(&nam); + let minted_value = Amount::from(117600000504441u64); + + let updates = [ + migrations::DbUpdateType::Add { + key: balance_key(&nam, &person), + value: amount.into(), + force: false, + }, + migrations::DbUpdateType::Add { + key: minted_key, + value: minted_value.into(), + force: false, + }, + ]; + let changes = migrations::DbChanges { + changes: updates.into_iter().collect(), + }; + std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) + .unwrap(); +} From 4eb0b7285ddb1366eb8b668d62f6f51cf42b414e Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 18:02:47 +0100 Subject: [PATCH 59/96] [feat]: Rebased on the serializers branch and added cli command to apply db changes from a json file --- crates/apps/src/bin/namada-node/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 64284756c6..b6b4c095ec 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -42,7 +42,7 @@ pub fn main() -> Result<()> { #[cfg(not(feature = "migrations"))] { panic!( - "This command is only available if build with the \ + "This command is only available if built with the \ \"migrations\" feature." ) } @@ -58,7 +58,7 @@ pub fn main() -> Result<()> { #[cfg(not(feature = "migrations"))] { panic!( - "This command is only available if build with the \ + "This command is only available if built with the \ \"migrations\" feature." ) } From 2adfe40912db7fab4ca3dcf42c4a377b3b818acd Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 12:24:30 +0100 Subject: [PATCH 60/96] tinies --- crates/apps/src/bin/namada-node/cli.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index b6b4c095ec..531b769f59 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -5,6 +5,7 @@ use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; +use namada_sdk::display_line; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::namada_node_cli()?; @@ -41,7 +42,8 @@ pub fn main() -> Result<()> { cmds::Ledger::UpdateDB(cmds::LedgerUpdateDB(args)) => { #[cfg(not(feature = "migrations"))] { - panic!( + display_line!( + io, "This command is only available if built with the \ \"migrations\" feature." ) @@ -57,7 +59,8 @@ pub fn main() -> Result<()> { cmds::Ledger::QueryDB(cmds::LedgerQueryDB(args)) => { #[cfg(not(feature = "migrations"))] { - panic!( + display_line!( + io, "This command is only available if built with the \ \"migrations\" feature." ) From 466c01814589f52d7954c46fcc9a462333556e26 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 12:27:39 +0100 Subject: [PATCH 61/96] [chore]: Added changelog --- .changelog/unreleased/improvements/2835-db-migrations.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/2835-db-migrations.md diff --git a/.changelog/unreleased/improvements/2835-db-migrations.md b/.changelog/unreleased/improvements/2835-db-migrations.md new file mode 100644 index 0000000000..25bf210a65 --- /dev/null +++ b/.changelog/unreleased/improvements/2835-db-migrations.md @@ -0,0 +1 @@ + - Adds tools to create json files to change db keys and various debugging and dry running logic. ([\#2835](https://github.com/anoma/namada/pull/2835)) \ No newline at end of file From 5fc7c7fa1ab1f5d993cfc9dcfa89371e68650d02 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 15:01:29 +0100 Subject: [PATCH 62/96] [feat]: Added a pattern matching iterator to the db --- wasm/Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 6a08635639..6d60f502ed 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3364,6 +3364,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "regex", "ripemd", "serde", "serde_json", @@ -3707,6 +3708,7 @@ dependencies = [ "namada_merkle_tree", "namada_replay_protection", "namada_tx", + "regex", "thiserror", "tracing", ] From c5e6e5dd72f9559fc04e8ecbffa243779f8d4082 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 17:38:06 +0100 Subject: [PATCH 63/96] [feat]: Added pattern based db migration. Added e2e test and better examples. --- Cargo.lock | 1 + Makefile | 1 + crates/apps/src/bin/namada-node/cli.rs | 7 +- crates/apps/src/lib/node/ledger/mod.rs | 13 +- .../src/lib/node/ledger/storage/rocksdb.rs | 7 + crates/sdk/Cargo.toml | 1 + crates/sdk/src/migrations.rs | 152 ++++++++++++++++-- crates/tests/src/e2e/ledger_tests.rs | 92 ++++++++++- examples/make-db-migration.rs | 2 + examples/migration_example.json | 1 + wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 3 + 12 files changed, 256 insertions(+), 25 deletions(-) create mode 100644 examples/migration_example.json diff --git a/Cargo.lock b/Cargo.lock index 2473e5a055..9a512fa70e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4660,6 +4660,7 @@ dependencies = [ "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", + "regex", "ripemd", "serde 1.0.193", "serde_json", diff --git a/Makefile b/Makefile index 8c3b8a8309..b9a2892c72 100644 --- a/Makefile +++ b/Makefile @@ -167,6 +167,7 @@ test-e2e: NAMADA_E2E_DEBUG=$(NAMADA_E2E_DEBUG) \ RUST_BACKTRACE=$(RUST_BACKTRACE) \ $(cargo) +$(nightly) test $(jobs) e2e::$(TEST_FILTER) \ + --features migrations \ -Z unstable-options \ -- \ --test-threads=1 \ diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 531b769f59..cef4766b4e 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -5,6 +5,7 @@ use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; +#[cfg(not(feature = "migrations"))] use namada_sdk::display_line; pub fn main() -> Result<()> { @@ -42,8 +43,7 @@ pub fn main() -> Result<()> { cmds::Ledger::UpdateDB(cmds::LedgerUpdateDB(args)) => { #[cfg(not(feature = "migrations"))] { - display_line!( - io, + panic!( "This command is only available if built with the \ \"migrations\" feature." ) @@ -59,8 +59,7 @@ pub fn main() -> Result<()> { cmds::Ledger::QueryDB(cmds::LedgerQueryDB(args)) => { #[cfg(not(feature = "migrations"))] { - display_line!( - io, + panic!( "This command is only available if built with the \ \"migrations\" feature." ) diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index ffe3686b81..28a7c3f237 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -281,19 +281,12 @@ pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { for change in &updates.changes { match change.update(&mut db_visitor) { - Ok(Some(deserialized)) => { - tracing::info!( - "Writing key <{}> with value: {}...", - change.key(), - deserialized - ); - } - Ok(None) => { - tracing::info!("Deleting key <{}>", change.key()); + Ok(status) => { + tracing::info!("{}", status); } e => { tracing::error!( - "Attempt to write to key <{}> failed.", + "Attempt to write to key/pattern <{}> failed.", change.key() ); e.unwrap(); diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 54d5878d88..e057e65ad9 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1649,6 +1649,13 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { .batch_delete_subspace_val(&mut self.batch, last_height, key, true) .expect("Failed to delete key from storage"); } + + fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)> { + self.db + .iter_pattern(None, pattern) + .map(|(k, v, _)| (k, v)) + .collect() + } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index c6e1d2645f..5ab5b625c4 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -125,6 +125,7 @@ tiny-hderive.workspace = true toml.workspace = true tracing.workspace = true zeroize.workspace = true +regex = "1.10.2" [target.'cfg(not(target_family = "wasm"))'.dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 973d154ac0..8a5fa22ef0 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -2,6 +2,8 @@ use core::fmt::Formatter; #[cfg(feature = "migrations")] use core::fmt::{Display, Formatter}; +#[cfg(feature = "migrations")] +use core::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; @@ -11,6 +13,7 @@ use namada_core::storage::Key; use namada_migrations::get_deserializer; #[cfg(feature = "migrations")] use namada_migrations::TypeHash; +use regex::Regex; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -18,6 +21,7 @@ pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); fn delete(&mut self, key: &Key); + fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)>; } #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] @@ -90,15 +94,23 @@ pub enum DbUpdateType { force: bool, }, Delete(Key), + RepeatAdd { + pattern: String, + value: UpdateValue, + force: bool, + }, + RepeatDelete(String), } #[cfg(feature = "migrations")] impl DbUpdateType { /// Get the key being modified - pub fn key(&self) -> &Key { + pub fn key(&self) -> String { match self { - DbUpdateType::Add { key, .. } => key, - DbUpdateType::Delete(key) => key, + DbUpdateType::Add { key, .. } => key.to_string(), + DbUpdateType::Delete(key) => key.to_string(), + DbUpdateType::RepeatAdd { pattern, .. } => pattern.to_string(), + DbUpdateType::RepeatDelete(pattern) => pattern.to_string(), } } @@ -106,7 +118,8 @@ impl DbUpdateType { /// hash. pub fn validate(&self) -> eyre::Result<()> { match self { - DbUpdateType::Add { value, .. } => { + DbUpdateType::RepeatAdd { value, .. } + | DbUpdateType::Add { value, .. } => { let deserializer = namada_migrations::get_deserializer(&value.type_hash) .ok_or_else(|| { @@ -124,7 +137,7 @@ impl DbUpdateType { })?; Ok(()) } - DbUpdateType::Delete(_) => Ok(()), + DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => Ok(()), } } @@ -134,7 +147,7 @@ impl DbUpdateType { pub fn update( &self, db: &mut DB, - ) -> eyre::Result> { + ) -> eyre::Result { match self { Self::Add { key, value, force } => { let deserialized = if !force { @@ -172,11 +185,74 @@ impl DbUpdateType { None }; db.write(key, &value.bytes); - Ok(deserialized) + Ok(deserialized + .map(|d| UpdateStatus::Add(vec![(key.to_string(), d)])) + .unwrap_or_else(|| UpdateStatus::Add(vec![]))) } Self::Delete(key) => { db.delete(key); - Ok(None) + Ok(UpdateStatus::Deleted(vec![key.to_string()])) + } + DbUpdateType::RepeatAdd { + pattern, + value, + force, + } => { + let pattern = Regex::new(pattern).unwrap(); + let mut pairs = vec![]; + let (deserialized, deserializer) = if !force { + let deserializer = + namada_migrations::get_deserializer(&value.type_hash) + .ok_or_else(|| { + eyre::eyre!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + ) + })?; + let deserialized = deserializer(value.bytes.clone()) + .ok_or_else(|| { + eyre::eyre!( + "The value {:?} for pattern {} could not be \ + successfully deserialized", + value, + pattern, + ) + })?; + (Some(deserialized), Some(deserializer)) + } else { + (None, None) + }; + for (key, prev) in db.get_pattern(pattern.clone()) { + if let (Some(func), Some(d)) = + (deserializer, deserialized.as_ref()) + { + func(prev).ok_or_else(|| { + eyre::eyre!( + "The previous value under the key {} did not \ + have the same type as that provided: Input \ + was {}", + key, + d, + ) + })?; + pairs.push((key.to_string(), d.clone())); + } + db.write(&Key::from_str(&key).unwrap(), &value.bytes); + } + Ok(UpdateStatus::Add(pairs)) + } + DbUpdateType::RepeatDelete(pattern) => { + let pattern = Regex::new(pattern).unwrap(); + Ok(UpdateStatus::Deleted( + db.get_pattern(pattern.clone()) + .into_iter() + .map(|(key, _)| { + db.delete(&Key::from_str(&key).unwrap()); + key + }) + .collect(), + )) } } } @@ -206,8 +282,8 @@ impl Display for DbUpdateType { let Some(value) = deserializer(bytes.clone()) else { return f.write_str(&format!( - "The value {:?} for key<{}> could not be successfully \ - deserialized", + "The value {:?} for key <{}> could not be \ + successfully deserialized", bytes, key )); }; @@ -220,6 +296,62 @@ impl Display for DbUpdateType { DbUpdateType::Delete(key) => { f.write_str(&format!("Delete key: <{}>", key)) } + DbUpdateType::RepeatAdd { + pattern, + value: UpdateValue { type_hash, bytes }, + .. + } => { + let Some(deserializer) = get_deserializer(type_hash) else { + return f.write_str(&format!( + "Type hash {:?} did not correspond to a deserializer \ + in TYPE_DESERIALIZERS.", + type_hash + )); + }; + + let Some(value) = deserializer(bytes.clone()) else { + return f.write_str(&format!( + "The value {:?} for pattern <{}> could not be \ + successfully deserialized", + bytes, pattern + )); + }; + + f.write_str(&format!( + "Write to pattern: <{}> with value: {}", + pattern, value + )) + } + DbUpdateType::RepeatDelete(pattern) => { + f.write_str(&format!("Delete pattern: <{}>", pattern)) + } + } + } +} + +pub enum UpdateStatus { + Deleted(Vec), + Add(Vec<(String, String)>), +} + +#[cfg(feature = "migrations")] +impl Display for UpdateStatus { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::Deleted(keys) => { + for key in keys { + f.write_str(&format!("Deleting key <{}>", key))?; + } + } + Self::Add(pairs) => { + for (k, v) in pairs { + f.write_str(&format!( + "Writing key <{}> with value: {}", + k, v + ))?; + } + } } + Ok(()) } } diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 17c85d5fd7..21e1e1da3a 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -48,7 +48,9 @@ use super::helpers::{ get_height, get_pregenesis_wallet, wait_for_block_height, wait_for_wasm_pre_compile, }; -use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode, NamadaCmd}; +use super::setup::{ + get_all_wasms_hashes, set_ethereum_bridge_mode, working_dir, NamadaCmd, +}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, is_debug_mode, parse_reached_epoch, @@ -343,6 +345,94 @@ fn run_ledger_load_state_and_reset() -> Result<()> { Ok(()) } +/// This test makes sure the tool for migrating the DB +/// during a hard-fork works correctly. +/// +/// 1. Run the ledger node, halting at height 2 +/// 2. Update the db +/// 3. Run the ledger node, halting at height 4 +/// 4. restart ledge with migrated db +/// 5. Check that a key was changed successfully +#[test] +fn test_db_migration() -> Result<()> { + let test = setup::single_node_net()?; + + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + None, + ); + + // 1. Run the ledger node, halting at height 2 + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "2", "--halt",], + Some(40) + )?; + // There should be no previous state + ledger.exp_string("No state could be found")?; + // Wait to commit a block + ledger.exp_string("Reached block height 2, halting the chain.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; + ledger.exp_eof()?; + drop(ledger); + let migrations_json_path = working_dir() + .join("examples") + .join("migration_example.json"); + // 2. Update the db + let mut session = run_as!( + test, + Who::Validator(0), + Bin::Node, + &[ + "ledger", + "update-db", + "--path", + migrations_json_path.to_string_lossy().as_ref(), + ], + Some(10), + )?; + session.exp_eof()?; + std::env::set_var("NAMADA_INITIAL_HEIGHT", "3"); + // 3. Run the ledger node, halting at height 4 + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "4", "--halt",], + Some(40) + )?; + ledger.exp_string("Reached block height 4, halting the chain.")?; + ledger.exp_string(LEDGER_SHUTDOWN)?; + ledger.exp_eof()?; + drop(ledger); + + // 4. restart ledge with migrated db + std::env::remove_var("NAMADA_INITIAL_HEIGHT"); + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + // 5. Check that a key was changed successfully + let mut query = run_as!( + test, + Who::Validator(0), + Bin::Client, + &[ + "balance", + "--owner", + "tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn" + ], + Some(20), + )?; + query.exp_regex("nam: 3200000036910")?; + ledger.interrupt()?; + Ok(()) +} + /// In this test we /// 1. Run the ledger node until a pre-configured height, at which point it /// should suspend. diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 983783655f..df36d67637 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -9,6 +9,7 @@ fn main() { .unwrap(); let nam = Address::decode("tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e") .unwrap(); + let apfel = "tnam1qyvfwdkz8zgs9n3qn9xhp8scyf8crrxwuq26r6gy".to_string(); let amount = Amount::native_whole(3200000036910u64); let minted_key = minted_balance_key(&nam); let minted_value = Amount::from(117600000504441u64); @@ -24,6 +25,7 @@ fn main() { value: minted_value.into(), force: false, }, + migrations::DbUpdateType::RepeatDelete(apfel), ]; let changes = migrations::DbChanges { changes: updates.into_iter().collect(), diff --git a/examples/migration_example.json b/examples/migration_example.json new file mode 100644 index 0000000000..c5a4f6e7e1 --- /dev/null +++ b/examples/migration_example.json @@ -0,0 +1 @@ +{"changes":[{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"AddressSeg":"tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A2000000080E7414914AF682C000000000000000000000000000000000000000000000000","force":false}},{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"StringSeg":"minted"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A20000000797241E2F46A0000000000000000000000000000000000000000000000000000","force":false}},{"RepeatDelete":"tnam1qyvfwdkz8zgs9n3qn9xhp8scyf8crrxwuq26r6gy"}]} \ No newline at end of file diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 6d60f502ed..9df06b9700 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3641,6 +3641,7 @@ dependencies = [ "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", + "regex", "ripemd", "serde", "serde_json", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8fe1f66fd3..68a6e61412 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3364,6 +3364,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "regex", "ripemd", "serde", "serde_json", @@ -3640,6 +3641,7 @@ dependencies = [ "prost 0.12.3", "rand 0.8.5", "rand_core 0.6.4", + "regex", "ripemd", "serde", "serde_json", @@ -3707,6 +3709,7 @@ dependencies = [ "namada_merkle_tree", "namada_replay_protection", "namada_tx", + "regex", "thiserror", "tracing", ] From b46b6f198a8b53e5207d0501565f742032f813a5 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 5 Mar 2024 18:02:47 +0100 Subject: [PATCH 64/96] [feat]: Rebased on the serializers branch and added cli command to apply db changes from a json file --- crates/apps/src/bin/namada-node/cli.rs | 2 - .../src/lib/node/ledger/storage/rocksdb.rs | 113 ++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index cef4766b4e..b6b4c095ec 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -5,8 +5,6 @@ use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; -#[cfg(not(feature = "migrations"))] -use namada_sdk::display_line; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::namada_node_cli()?; diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index e057e65ad9..8e32cd3b1e 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1590,6 +1590,119 @@ impl DB for RocksDB { Ok(()) } + + #[inline] + fn overwrite_entry( + &self, + batch: &mut Self::WriteBatch, + height: Option, + key: &Key, + new_value: impl AsRef<[u8]>, + ) -> Result<()> { + let last_height: BlockHeight = { + let state_cf = self.get_column_family(STATE_CF)?; + + decode( + self.0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + })?, + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + })? + }; + let desired_height = height.unwrap_or(last_height); + + if desired_height != last_height { + todo!( + "Overwriting values at heights different than the last \ + committed height hast yet to be implemented" + ); + } + // NB: the following code only updates values + // written to at the last committed height + + let val = new_value.as_ref(); + + // update subspace value + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; + let subspace_key = key.to_string(); + + batch.0.put_cf(subspace_cf, subspace_key, val); + + // update value stored in diffs + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let diffs_key = Key::from(last_height.to_db_key()) + .with_segment("new".to_owned()) + .join(key) + .to_string(); + + batch.0.put_cf(diffs_cf, diffs_key, val); + + Ok(()) + } +} + +/// A struct that can visit a set of updates, +/// registering them all in the batch +pub struct RocksDBUpdateVisitor<'db> { + db: &'db RocksDB, + batch: RocksDBWriteBatch, +} + +impl<'db> RocksDBUpdateVisitor<'db> { + pub fn new(db: &'db RocksDB) -> Self { + Self { + db, + batch: Default::default(), + } + } + + pub fn take_batch(self) -> RocksDBWriteBatch { + self.batch + } +} + +impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { + fn read(&self, key: &Key) -> Option> { + self.db + .read_subspace_val(key) + .expect("Failed to read from storage") + } + + fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { + self.db + .overwrite_entry(&mut self.batch, None, key, value) + .expect("Failed to overwrite a key in storage") + } + + fn delete(&mut self, key: &Key) { + let last_height: BlockHeight = { + let state_cf = self.db.get_column_family(STATE_CF).unwrap(); + + decode( + self.db + .0 + .get_cf(state_cf, "height") + .map_err(|e| Error::DBError(e.to_string())) + .unwrap() + .ok_or_else(|| { + Error::DBError("No block height found".to_string()) + }) + .unwrap(), + ) + .map_err(|e| { + Error::DBError(format!("Unable to decode block height: {e}")) + }) + .unwrap() + }; + self.db + .batch_delete_subspace_val(&mut self.batch, last_height, key, true) + .expect("Failed to delete key from storage"); + } } /// A struct that can visit a set of updates, From b0974ce765ec87ae71d03da4abae1fc4f8d02933 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 12:24:30 +0100 Subject: [PATCH 65/96] tinies --- crates/apps/src/bin/namada-node/cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index b6b4c095ec..665278c0f4 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -5,6 +5,7 @@ use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; +use namada_sdk::display_line; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::namada_node_cli()?; From dff1128b0362bf68c6d55b1ab43bc760235243b9 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 7 Mar 2024 17:38:06 +0100 Subject: [PATCH 66/96] [feat]: Added pattern based db migration. Added e2e test and better examples. --- crates/apps/src/bin/namada-node/cli.rs | 1 + crates/apps/src/lib/node/ledger/storage/rocksdb.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 665278c0f4..cef4766b4e 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -5,6 +5,7 @@ use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::config::ValidatorLocalConfig; use namada_apps::node::ledger; +#[cfg(not(feature = "migrations"))] use namada_sdk::display_line; pub fn main() -> Result<()> { diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 8e32cd3b1e..aa13aaf18a 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1703,6 +1703,13 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { .batch_delete_subspace_val(&mut self.batch, last_height, key, true) .expect("Failed to delete key from storage"); } + + fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)> { + self.db + .iter_pattern(None, pattern) + .map(|(k, v, _)| (k, v)) + .collect() + } } /// A struct that can visit a set of updates, From 0f701a04700f3a0f92e174d42d1292f7fc5222a4 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 8 Mar 2024 10:51:03 +0100 Subject: [PATCH 67/96] [fix]: Make migrations enabled by default --- Cargo.lock | 4 +- Makefile | 1 - crates/apps/Cargo.toml | 2 +- crates/sdk/Cargo.toml | 2 +- crates/tests/Cargo.toml | 9 +- wasm/Cargo.lock | 124 ++++++++++++++++++-------- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 8 files changed, 101 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a512fa70e..5321433610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", diff --git a/Makefile b/Makefile index b9a2892c72..8c3b8a8309 100644 --- a/Makefile +++ b/Makefile @@ -167,7 +167,6 @@ test-e2e: NAMADA_E2E_DEBUG=$(NAMADA_E2E_DEBUG) \ RUST_BACKTRACE=$(RUST_BACKTRACE) \ $(cargo) +$(nightly) test $(jobs) e2e::$(TEST_FILTER) \ - --features migrations \ -Z unstable-options \ -- \ --test-threads=1 \ diff --git a/crates/apps/Cargo.toml b/crates/apps/Cargo.toml index 6b2dff74a3..b470bbbcaf 100644 --- a/crates/apps/Cargo.toml +++ b/crates/apps/Cargo.toml @@ -50,7 +50,7 @@ name = "namadar" path = "src/bin/namada-relayer/main.rs" [features] -default = [] +default = ["migrations"] mainnet = [ "namada/mainnet", ] diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 5ab5b625c4..f4d05e74aa 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -15,7 +15,7 @@ version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["tendermint-rpc", "download-params", "std", "rand"] +default = ["tendermint-rpc", "download-params", "std", "rand", "migrations"] multicore = ["masp_proofs/multicore"] diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 93fc05e064..2aec37b1ed 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -13,17 +13,22 @@ repository.workspace = true version.workspace = true [features] -default = ["wasm-runtime"] +default = ["wasm-runtime", "migrations"] mainnet = [ "namada/mainnet", ] wasm-runtime = ["namada/wasm-runtime"] integration = ["namada_apps/integration"] +migrations = [ + "namada/migrations", + "namada_sdk/migrations", + "namada_core/migrations", +] [dependencies] namada = {path = "../namada", features = ["testing"]} namada_core = {path = "../core", features = ["testing"]} -namada_sdk = {path = "../sdk"} +namada_sdk = {path = "../sdk", default-features = false, features = ["tendermint-rpc", "download-params", "std", "rand"]} namada_test_utils = {path = "../test_utils"} namada_vp_prelude = {path = "../vp_prelude"} namada_tx_prelude = {path = "../tx_prelude"} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9df06b9700..549f41c6cd 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -236,13 +236,13 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -516,7 +516,7 @@ dependencies = [ "proc-macro-crate 2.0.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "syn_derive", ] @@ -814,7 +814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1119,7 +1119,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1130,7 +1130,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1244,7 +1244,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1423,7 +1423,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1607,7 +1607,7 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.39", + "syn 2.0.52", "toml 0.8.2", "walkdir", ] @@ -1625,7 +1625,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -1651,7 +1651,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.39", + "syn 2.0.52", "tempfile", "thiserror", "tiny-keccak", @@ -1934,7 +1934,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -2708,7 +2708,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -3079,6 +3079,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "linkme" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cfee0de9bd869589fb9a015e155946d1be5ff415cb844c2caccc6cc4b5db9" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf157a4dc5a29b7b464aa8fe7edeff30076e07e13646a1c3874f58477dc99f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -3332,6 +3352,7 @@ dependencies = [ "eyre", "futures", "itertools 0.10.5", + "linkme", "loupe", "masp_primitives", "masp_proofs", @@ -3342,6 +3363,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_replay_protection", @@ -3395,8 +3417,10 @@ name = "namada_account" version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_storage", "proptest", "serde", @@ -3420,8 +3444,10 @@ dependencies = [ "impl-num-traits", "index-set", "k256", + "linkme", "masp_primitives", "namada_macros", + "namada_migrations", "num-integer", "num-rational 0.4.1", "num-traits", @@ -3455,9 +3481,11 @@ dependencies = [ "ethers", "eyre", "itertools 0.10.5", + "linkme", "namada_account", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3492,8 +3520,10 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3556,6 +3586,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "namada_migrations" +version = "0.31.8" +dependencies = [ + "data-encoding", + "lazy_static", + "linkme", + "serde", +] + [[package]] name = "namada_parameters" version = "0.31.8" @@ -3574,10 +3614,12 @@ dependencies = [ "borsh", "data-encoding", "derivative", + "linkme", "namada_account", "namada_core", "namada_governance", "namada_macros", + "namada_migrations", "namada_parameters", "namada_storage", "namada_trans_token", @@ -3616,6 +3658,7 @@ dependencies = [ "itertools 0.10.5", "jubjub", "lazy_static", + "linkme", "masp_primitives", "masp_proofs", "namada_account", @@ -3624,6 +3667,7 @@ dependencies = [ "namada_governance", "namada_ibc", "namada_macros", + "namada_migrations", "namada_parameters", "namada_proof_of_stake", "namada_state", @@ -3680,10 +3724,12 @@ dependencies = [ "borsh", "ics23", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_parameters", "namada_replay_protection", "namada_storage", @@ -3703,10 +3749,12 @@ version = "0.31.8" dependencies = [ "borsh", "itertools 0.10.5", + "linkme", "namada_core", "namada_gas", "namada_macros", "namada_merkle_tree", + "namada_migrations", "namada_replay_protection", "namada_tx", "regex", @@ -3782,10 +3830,12 @@ dependencies = [ "ark-bls12-381", "borsh", "data-encoding", + "linkme", "masp_primitives", "namada_core", "namada_gas", "namada_macros", + "namada_migrations", "num-derive", "num-traits", "proptest", @@ -3842,8 +3892,10 @@ name = "namada_vote_ext" version = "0.31.8" dependencies = [ "borsh", + "linkme", "namada_core", "namada_macros", + "namada_migrations", "namada_tx", "serde", ] @@ -4082,7 +4134,7 @@ dependencies = [ "proc-macro-crate 2.0.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4406,7 +4458,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4471,7 +4523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4534,9 +4586,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -4626,7 +4678,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -4675,9 +4727,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -5378,7 +5430,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5400,7 +5452,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5668,7 +5720,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5711,9 +5763,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -5729,7 +5781,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5945,7 +5997,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -5965,7 +6017,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -6089,7 +6141,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -6209,7 +6261,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -6279,7 +6331,7 @@ checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] [[package]] @@ -6477,7 +6529,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -6511,7 +6563,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7168,5 +7220,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.52", ] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index b7747f442a..6d92565a98 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -48,7 +48,7 @@ getrandom = { version = "0.2", features = ["custom"] } [dev-dependencies] namada = {path = "../../crates/namada"} -namada_tests = {path = "../../crates/tests"} +namada_tests = {path = "../../crates/tests", default-features = false, features = ["wasm-runtime"]} namada_test_utils = {path = "../../crates/test_utils"} namada_tx_prelude = {path = "../../crates/tx_prelude"} namada_vp_prelude = {path = "../../crates/vp_prelude"} diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 257d8db8f6..d2302542a2 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -33,7 +33,7 @@ wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } [dev-dependencies] -namada_tests = {path = "../../crates/tests"} +namada_tests = {path = "../../crates/tests", default-features = false, features = ["wasm-runtime"]} [profile.release] # smaller and faster wasm https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto From c215db7d8d5c4c677b0e8b76bc29215d1f7e1e4e Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 8 Mar 2024 11:45:38 +0100 Subject: [PATCH 68/96] Finished rebasing --- .../src/lib/node/ledger/storage/rocksdb.rs | 120 ------------------ crates/migrations/Cargo.toml | 2 +- wasm/Cargo.lock | 62 ++++----- wasm_for_tests/wasm_source/Cargo.lock | 56 ++++---- 4 files changed, 60 insertions(+), 180 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index aa13aaf18a..e057e65ad9 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1590,126 +1590,6 @@ impl DB for RocksDB { Ok(()) } - - #[inline] - fn overwrite_entry( - &self, - batch: &mut Self::WriteBatch, - height: Option, - key: &Key, - new_value: impl AsRef<[u8]>, - ) -> Result<()> { - let last_height: BlockHeight = { - let state_cf = self.get_column_family(STATE_CF)?; - - decode( - self.0 - .get_cf(state_cf, "height") - .map_err(|e| Error::DBError(e.to_string()))? - .ok_or_else(|| { - Error::DBError("No block height found".to_string()) - })?, - ) - .map_err(|e| { - Error::DBError(format!("Unable to decode block height: {e}")) - })? - }; - let desired_height = height.unwrap_or(last_height); - - if desired_height != last_height { - todo!( - "Overwriting values at heights different than the last \ - committed height hast yet to be implemented" - ); - } - // NB: the following code only updates values - // written to at the last committed height - - let val = new_value.as_ref(); - - // update subspace value - let subspace_cf = self.get_column_family(SUBSPACE_CF)?; - let subspace_key = key.to_string(); - - batch.0.put_cf(subspace_cf, subspace_key, val); - - // update value stored in diffs - let diffs_cf = self.get_column_family(DIFFS_CF)?; - let diffs_key = Key::from(last_height.to_db_key()) - .with_segment("new".to_owned()) - .join(key) - .to_string(); - - batch.0.put_cf(diffs_cf, diffs_key, val); - - Ok(()) - } -} - -/// A struct that can visit a set of updates, -/// registering them all in the batch -pub struct RocksDBUpdateVisitor<'db> { - db: &'db RocksDB, - batch: RocksDBWriteBatch, -} - -impl<'db> RocksDBUpdateVisitor<'db> { - pub fn new(db: &'db RocksDB) -> Self { - Self { - db, - batch: Default::default(), - } - } - - pub fn take_batch(self) -> RocksDBWriteBatch { - self.batch - } -} - -impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { - fn read(&self, key: &Key) -> Option> { - self.db - .read_subspace_val(key) - .expect("Failed to read from storage") - } - - fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { - self.db - .overwrite_entry(&mut self.batch, None, key, value) - .expect("Failed to overwrite a key in storage") - } - - fn delete(&mut self, key: &Key) { - let last_height: BlockHeight = { - let state_cf = self.db.get_column_family(STATE_CF).unwrap(); - - decode( - self.db - .0 - .get_cf(state_cf, "height") - .map_err(|e| Error::DBError(e.to_string())) - .unwrap() - .ok_or_else(|| { - Error::DBError("No block height found".to_string()) - }) - .unwrap(), - ) - .map_err(|e| { - Error::DBError(format!("Unable to decode block height: {e}")) - }) - .unwrap() - }; - self.db - .batch_delete_subspace_val(&mut self.batch, last_height, key, true) - .expect("Failed to delete key from storage"); - } - - fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)> { - self.db - .iter_pattern(None, pattern) - .map(|(k, v, _)| (k, v)) - .collect() - } } /// A struct that can visit a set of updates, diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index 1f0ae72bbc..1ad4c63df6 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -17,5 +17,5 @@ version.workspace = true [dependencies] data-encoding.workspace = true lazy_static.workspace = true -linkme = {workspace = true, optional = true} +linkme.workspace = true serde.workspace = true diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 549f41c6cd..d8af2cb72d 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3336,7 +3336,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "bimap", @@ -3414,7 +3414,7 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "linkme", @@ -3428,7 +3428,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.31.8" +version = "0.31.9" dependencies = [ "bech32 0.8.1", "borsh", @@ -3474,7 +3474,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ethabi", @@ -3505,7 +3505,7 @@ dependencies = [ [[package]] name = "namada_gas" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3516,7 +3516,7 @@ dependencies = [ [[package]] name = "namada_governance" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", @@ -3536,7 +3536,7 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ibc", @@ -3561,7 +3561,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.31.8" +version = "0.31.9" dependencies = [ "data-encoding", "lazy_static", @@ -3574,7 +3574,7 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "eyre", @@ -3588,7 +3588,7 @@ dependencies = [ [[package]] name = "namada_migrations" -version = "0.31.8" +version = "0.31.9" dependencies = [ "data-encoding", "lazy_static", @@ -3598,7 +3598,7 @@ dependencies = [ [[package]] name = "namada_parameters" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3609,7 +3609,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "data-encoding", @@ -3633,14 +3633,14 @@ dependencies = [ [[package]] name = "namada_replay_protection" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "bimap", @@ -3705,7 +3705,7 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3719,7 +3719,7 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ics23", @@ -3745,7 +3745,7 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", @@ -3764,7 +3764,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3773,7 +3773,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "chrono", @@ -3807,7 +3807,7 @@ dependencies = [ [[package]] name = "namada_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_shielded_token", @@ -3817,7 +3817,7 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_storage", @@ -3825,7 +3825,7 @@ dependencies = [ [[package]] name = "namada_tx" -version = "0.31.8" +version = "0.31.9" dependencies = [ "ark-bls12-381", "borsh", @@ -3850,7 +3850,7 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_storage", @@ -3858,7 +3858,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3880,7 +3880,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3889,7 +3889,7 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "linkme", @@ -3902,7 +3902,7 @@ dependencies = [ [[package]] name = "namada_vp_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "derivative", "masp_primitives", @@ -3914,7 +3914,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_account", @@ -3935,7 +3935,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.31.8" +version = "0.31.9" dependencies = [ "getrandom 0.2.11", "namada", @@ -6306,7 +6306,7 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tx_template" -version = "0.31.8" +version = "0.31.9" dependencies = [ "getrandom 0.2.11", "namada_tests", @@ -6460,7 +6460,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.31.8" +version = "0.31.9" dependencies = [ "getrandom 0.2.11", "namada_tests", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 68a6e61412..9ebb9988a6 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3316,7 +3316,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "bimap", @@ -3392,7 +3392,7 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3404,7 +3404,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.31.8" +version = "0.31.9" dependencies = [ "bech32 0.8.1", "borsh", @@ -3448,7 +3448,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ethabi", @@ -3477,7 +3477,7 @@ dependencies = [ [[package]] name = "namada_gas" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3488,7 +3488,7 @@ dependencies = [ [[package]] name = "namada_governance" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", @@ -3506,7 +3506,7 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ibc", @@ -3531,7 +3531,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.31.8" +version = "0.31.9" dependencies = [ "data-encoding", "lazy_static", @@ -3544,7 +3544,7 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "eyre", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "namada_parameters" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3569,7 +3569,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "data-encoding", @@ -3591,14 +3591,14 @@ dependencies = [ [[package]] name = "namada_replay_protection" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "bimap", @@ -3661,7 +3661,7 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3675,7 +3675,7 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "ics23", @@ -3699,7 +3699,7 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "itertools 0.10.5", @@ -3716,7 +3716,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3725,7 +3725,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.31.8" +version = "0.31.9" dependencies = [ "async-trait", "chrono", @@ -3759,7 +3759,7 @@ dependencies = [ [[package]] name = "namada_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_shielded_token", @@ -3769,7 +3769,7 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_storage", @@ -3777,7 +3777,7 @@ dependencies = [ [[package]] name = "namada_tx" -version = "0.31.8" +version = "0.31.9" dependencies = [ "ark-bls12-381", "borsh", @@ -3800,7 +3800,7 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "namada_core", "namada_storage", @@ -3808,7 +3808,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3830,7 +3830,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "masp_primitives", @@ -3839,7 +3839,7 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_core", @@ -3850,7 +3850,7 @@ dependencies = [ [[package]] name = "namada_vp_env" -version = "0.31.8" +version = "0.31.9" dependencies = [ "derivative", "masp_primitives", @@ -3862,7 +3862,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.31.8" +version = "0.31.9" dependencies = [ "borsh", "namada_account", @@ -3883,7 +3883,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.31.8" +version = "0.31.9" dependencies = [ "getrandom 0.2.11", "namada_test_utils", From 6492acd708aacc4c44f167d0c83eccfca555afb4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 9 Mar 2024 15:10:33 +0100 Subject: [PATCH 69/96] test state migration first commit --- Cargo.lock | 3 + crates/macros/src/lib.rs | 22 ++++-- crates/migrations/Cargo.toml | 3 + crates/migrations/src/foreign_types.rs | 33 +++++++++ crates/migrations/src/lib.rs | 2 + examples/Cargo.toml | 1 + examples/make-db-migration.rs | 97 +++++++++++++++++++++++++- wasm/Cargo.lock | 2 + 8 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 crates/migrations/src/foreign_types.rs diff --git a/Cargo.lock b/Cargo.lock index 5321433610..db995b556b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4436,6 +4436,7 @@ dependencies = [ "masp_proofs", "namada_apps", "namada_migrations", + "namada_parameters", "namada_sdk", "namada_trans_token", "proptest", @@ -4554,9 +4555,11 @@ dependencies = [ name = "namada_migrations" version = "0.31.9" dependencies = [ + "borsh", "data-encoding", "lazy_static", "linkme", + "namada_macros", "serde 1.0.193", ] diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index e66aaf180c..04938aed0f 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -369,11 +369,11 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { } #[proc_macro] -pub fn derive_borshdeserializer(item: TokenStream) -> TokenStream { - derive_borshdeserializer_inner(item.into()).into() +pub fn derive_typehash(item: TokenStream) -> TokenStream { + derive_typehash_inner(item.into()).into() } -fn derive_borshdeserializer_inner(item: TokenStream2) -> TokenStream2 { +fn derive_typehash_inner(item: TokenStream2) -> TokenStream2 { let type_def = syn::parse2::(item).expect( "Could not parse input to `derive_borshdesrializer` as a type.", ); @@ -387,7 +387,20 @@ fn derive_borshdeserializer_inner(item: TokenStream2) -> TokenStream2 { let mut hasher = sha2::Sha256::new(); hasher.update(type_def.to_token_stream().to_string().as_bytes()); let type_hash: [u8; 32] = hasher.finalize().into(); - impl_borsh_deserializer(type_hash, type_def) + let hash = syn::ExprArray { + attrs: vec![], + bracket_token: Default::default(), + elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { + syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Byte(LitByte::new( + b, + proc_macro2::Span::call_site(), + )), + }) + })), + }; + quote!(#hash) } fn impl_borsh_deserializer( @@ -410,6 +423,7 @@ fn impl_borsh_deserializer( let hex = data_encoding::HEXUPPER.encode(&type_hash); let deserializer_ident = syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); + quote!( #[cfg(feature = "migrations")] #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] diff --git a/crates/migrations/Cargo.toml b/crates/migrations/Cargo.toml index 1ad4c63df6..2b1a615b5d 100644 --- a/crates/migrations/Cargo.toml +++ b/crates/migrations/Cargo.toml @@ -15,7 +15,10 @@ version.workspace = true [lib] [dependencies] +namada_macros = { path = "../macros" } + data-encoding.workspace = true lazy_static.workspace = true linkme.workspace = true serde.workspace = true +borsh.workspace = true diff --git a/crates/migrations/src/foreign_types.rs b/crates/migrations/src/foreign_types.rs new file mode 100644 index 0000000000..27e8537962 --- /dev/null +++ b/crates/migrations/src/foreign_types.rs @@ -0,0 +1,33 @@ +use borsh::BorshDeserialize; +use namada_macros::derive_typehash; + +use crate::REGISTER_DESERIALIZERS; + +const HASHU8: [u8; 32] = derive_typehash!(Vec::); +pub const HASHVECSTR: [u8; 32] = derive_typehash!(Vec::); + +impl crate::TypeHash for Vec { + const HASH: [u8; 32] = HASHU8; +} + +impl crate::TypeHash for Vec { + const HASH: [u8; 32] = HASHVECSTR; +} + +#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] +static BYTES: fn() = || { + crate::register_deserializer(HASHU8, |bytes| { + Vec::::try_from_slice(&bytes) + .map(|val| format!("{:?}", val)) + .ok() + }); +}; + +#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] +static STRINGS: fn() = || { + crate::register_deserializer(HASHVECSTR, |bytes| { + Vec::::try_from_slice(&bytes) + .map(|val| format!("{:?}", val)) + .ok() + }); +}; diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 988bf8ba4d..5c3e0c7dd2 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -4,6 +4,8 @@ use std::sync::{Mutex, OnceLock}; use lazy_static::lazy_static; pub use linkme::distributed_slice; +pub mod foreign_types; + /// Predicate that checks if an arbitrary byte array deserializes as some type /// `T` erased inside of the callback. If the serialization is correct, the full /// path of `T` is returned as a string (via [`std::any::type_name`]). diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 89ca3716f7..070790bd34 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -24,6 +24,7 @@ path = "make-db-migration.rs" masp_proofs = { workspace = true, default-features = false, features = ["local-prover", "download-params"] } namada_apps = {path = "../crates/apps", features = ["migrations"]} namada_migrations = {path = "../crates/migrations"} +namada_parameters = { path = "../crates/parameters"} namada_trans_token = {path = "../crates/trans_token", features = ["migrations"]} namada_sdk = { path = "../crates/sdk", default-features = false, features = ["namada-sdk", "std", "testing", "migrations"] } data-encoding.workspace = true diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index df36d67637..3682173205 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -1,9 +1,15 @@ +use data_encoding::HEXLOWER; +use namada_apps::wasm_loader::read_wasm; +use namada_parameters::storage; use namada_sdk::address::Address; +use namada_sdk::hash::Hash as CodeHash; use namada_sdk::migrations; +use namada_sdk::storage::Key; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; -fn main() { +#[allow(dead_code)] +fn example() { let person = Address::decode("tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn") .unwrap(); @@ -33,3 +39,92 @@ fn main() { std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) .unwrap(); } + +fn main() { + se_migration() +} + +fn se_migration() { + // Get VP + let wasm_path = "wasm"; + let bytes = read_wasm(wasm_path, "vp_user.wasm").expect("bingbong"); + let vp_hash = CodeHash::sha256(&bytes); + + // account VPs + let account_vp_str = "#tnam[a-z,0-9]*\\/\\?".to_string(); + let accounts_update = migrations::DbUpdateType::RepeatAdd { + pattern: account_vp_str, + value: vp_hash.into(), + force: false, + }; + + // wasm/hash and wasm/name + let wasm_name_key = Key::wasm_code_name("vp_user.wasm".to_string()); + let wasm_hash_key = Key::wasm_hash("vp_user.wasm"); + let wasm_name_update = migrations::DbUpdateType::Add { + key: wasm_name_key, + value: vp_hash.into(), + force: false, + }; + let wasm_hash_update = migrations::DbUpdateType::Add { + key: wasm_hash_key, + value: vp_hash.into(), + force: false, + }; + + // wasm/code/ + let code_key = Key::wasm_code(&vp_hash); + let code_update = migrations::DbUpdateType::Add { + key: code_key, + value: bytes.into(), + force: true, + }; + + // VP allowlist + let vp_allowlist_key = storage::get_vp_allowlist_storage_key(); + let new_hash_str = HEXLOWER.encode(vp_hash.as_ref()); + let new_vp_allowlist = vec![ + "8781c170ad1e3d2bbddc308b77b7a2edda3fff3bc5d746232feec968ee4fe3cd" + .to_string(), + new_hash_str, + ]; + let allowlist_update = migrations::DbUpdateType::Add { + key: vp_allowlist_key, + value: new_vp_allowlist.into(), + force: true, + }; + + let updates = [ + accounts_update, + wasm_name_update, + wasm_hash_update, + code_update, + allowlist_update, + ]; + + let changes = migrations::DbChanges { + changes: updates.into_iter().collect(), + }; + std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) + .unwrap(); +} + +#[test] +fn bingbong() { + let key = storage::get_vp_allowlist_storage_key(); + let type_hash = namada_migrations::foreign_types::HASHVECSTR; + let hex = HEXUPPER.encode(&type_hash); + println!("{}", hex); + println!("{}", key); + + let token_amount_hash = HEXUPPER.encode(&Amount::HASH); + println!("{}", token_amount_hash); + + let serialized = "0200000040000000383738316331373061643165336432626264646333303862373762376132656464613366666633626335643734363233326665656339363865653466653363644000000031323965653762656536386230326266616536333864613261363334623865636266666132636233663436636661386531373262616630303936323765633738"; + // let serialized = serialized.chars().map(|bing| + // u8::try_from(bing).unwrap()).collect::>(); + let serialized = HEXUPPER.decode(serialized.as_bytes()).unwrap(); + let allowlist = + Vec::::try_from_slice(serialized.as_slice()).unwrap(); + println!("{:?}", allowlist); +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index d8af2cb72d..1d7cee04a2 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3590,9 +3590,11 @@ dependencies = [ name = "namada_migrations" version = "0.31.9" dependencies = [ + "borsh", "data-encoding", "lazy_static", "linkme", + "namada_macros", "serde", ] From becbc5bd3474e419336ce04994fd07632e91cb97 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 9 Mar 2024 15:17:21 +0100 Subject: [PATCH 70/96] fix iteration --- crates/apps/src/lib/node/ledger/storage/rocksdb.rs | 7 ------- crates/sdk/src/migrations.rs | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index e057e65ad9..74ea817f58 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1808,7 +1808,6 @@ fn iter_pattern<'a>( iter: iter_prefix(db, cf, stripped_prefix, prefix), pattern, }, - finished: false, } } @@ -1849,7 +1848,6 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { #[derive(Debug)] pub struct PersistentPatternIterator<'a> { inner: PatternIterator>, - finished: bool, } impl<'a> Iterator for PersistentPatternIterator<'a> { @@ -1857,15 +1855,10 @@ impl<'a> Iterator for PersistentPatternIterator<'a> { /// Returns the next pair and the gas cost fn next(&mut self) -> Option<(String, Vec, u64)> { - if self.finished { - return None; - } loop { let next_result = self.inner.iter.next()?; if self.inner.pattern.is_match(&next_result.0) { return Some(next_result); - } else { - self.finished = true; } } } diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 8a5fa22ef0..ca5f44570a 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -340,13 +340,13 @@ impl Display for UpdateStatus { match self { Self::Deleted(keys) => { for key in keys { - f.write_str(&format!("Deleting key <{}>", key))?; + f.write_str(&format!("Deleting key <{}>\n", key))?; } } Self::Add(pairs) => { for (k, v) in pairs { f.write_str(&format!( - "Writing key <{}> with value: {}", + "Writing key <{}> with value: {}\n", k, v ))?; } From 3d0b02fda790ba4932e038df7145ca5661b9c2c6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 9 Mar 2024 18:14:03 +0100 Subject: [PATCH 71/96] working --- crates/migrations/src/foreign_types.rs | 18 +++++++- crates/sdk/src/migrations.rs | 64 ++++++++++++++++++++------ examples/make-db-migration.rs | 25 +++++++--- 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/crates/migrations/src/foreign_types.rs b/crates/migrations/src/foreign_types.rs index 27e8537962..c54afb1fa1 100644 --- a/crates/migrations/src/foreign_types.rs +++ b/crates/migrations/src/foreign_types.rs @@ -3,8 +3,11 @@ use namada_macros::derive_typehash; use crate::REGISTER_DESERIALIZERS; +// TODO: change naming bc HASHu8 and HASHBYTE conflicts + const HASHU8: [u8; 32] = derive_typehash!(Vec::); pub const HASHVECSTR: [u8; 32] = derive_typehash!(Vec::); +pub const HASHBYTE: [u8; 32] = derive_typehash!(u64); impl crate::TypeHash for Vec { const HASH: [u8; 32] = HASHU8; @@ -14,6 +17,10 @@ impl crate::TypeHash for Vec { const HASH: [u8; 32] = HASHVECSTR; } +impl crate::TypeHash for u64 { + const HASH: [u8; 32] = HASHBYTE; +} + #[linkme::distributed_slice(REGISTER_DESERIALIZERS)] static BYTES: fn() = || { crate::register_deserializer(HASHU8, |bytes| { @@ -26,8 +33,17 @@ static BYTES: fn() = || { #[linkme::distributed_slice(REGISTER_DESERIALIZERS)] static STRINGS: fn() = || { crate::register_deserializer(HASHVECSTR, |bytes| { - Vec::::try_from_slice(&bytes) + Vec::::try_from_slice(&bytes) .map(|val| format!("{:?}", val)) .ok() }); }; + +#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] +static BYTE: fn() = || { + crate::register_deserializer(HASHBYTE, |bytes| { + u64::try_from_slice(&bytes) + .map(|val| format!("{:?}", val)) + .ok() + }); +}; \ No newline at end of file diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index ca5f44570a..6c882963b8 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -32,6 +32,19 @@ pub struct UpdateValue { bytes: Vec, } +impl UpdateValue { + pub fn raw(bytes: Vec) -> Self { + Self { + type_hash: Default::default(), + bytes, + } + } + + pub fn is_raw(&self) -> bool { + self.type_hash == [0u8; 32] + } +} + #[cfg(feature = "migrations")] impl From for UpdateValue { fn from(value: T) -> Self { @@ -150,7 +163,7 @@ impl DbUpdateType { ) -> eyre::Result { match self { Self::Add { key, value, force } => { - let deserialized = if !force { + let deserialized = if !force && !value.is_raw() { let deserializer = namada_migrations::get_deserializer(&value.type_hash) .ok_or_else(|| { @@ -187,7 +200,12 @@ impl DbUpdateType { db.write(key, &value.bytes); Ok(deserialized .map(|d| UpdateStatus::Add(vec![(key.to_string(), d)])) - .unwrap_or_else(|| UpdateStatus::Add(vec![]))) + .unwrap_or_else(|| { + UpdateStatus::Add(vec![( + key.to_string(), + format!("{:?}", value.bytes), + )]) + })) } Self::Delete(key) => { db.delete(key); @@ -200,7 +218,8 @@ impl DbUpdateType { } => { let pattern = Regex::new(pattern).unwrap(); let mut pairs = vec![]; - let (deserialized, deserializer) = if !force { + let (deserialized, deserializer) = if !force && !value.is_raw() + { let deserializer = namada_migrations::get_deserializer(&value.type_hash) .ok_or_else(|| { @@ -237,6 +256,11 @@ impl DbUpdateType { ) })?; pairs.push((key.to_string(), d.clone())); + } else { + pairs.push(( + key.to_string(), + format!("{:?}", value.bytes), + )); } db.write(&Key::from_str(&key).unwrap(), &value.bytes); } @@ -269,24 +293,29 @@ impl Display for DbUpdateType { match self { DbUpdateType::Add { key, - value: UpdateValue { type_hash, bytes }, - .. + value, + force, } => { - let Some(deserializer) = get_deserializer(type_hash) else { + let value = if !force && !value.is_raw() { + let Some(deserializer) = get_deserializer(&value.type_hash) else { return f.write_str(&format!( "Type hash {:?} did not correspond to a deserializer \ in TYPE_DESERIALIZERS.", - type_hash + value.type_hash )); }; - let Some(value) = deserializer(bytes.clone()) else { + let Some(value) = deserializer(value.bytes.clone()) else { return f.write_str(&format!( "The value {:?} for key <{}> could not be \ successfully deserialized", - bytes, key + value.bytes, key )); }; + value + } else { + format!("{:?}", value.bytes) + }; f.write_str(&format!( "Write to key: <{}> with value: {}", @@ -298,24 +327,29 @@ impl Display for DbUpdateType { } DbUpdateType::RepeatAdd { pattern, - value: UpdateValue { type_hash, bytes }, - .. + value, + force, } => { - let Some(deserializer) = get_deserializer(type_hash) else { + let value = if !force && !value.is_raw() { + let Some(deserializer) = get_deserializer(&value.type_hash) else { return f.write_str(&format!( "Type hash {:?} did not correspond to a deserializer \ in TYPE_DESERIALIZERS.", - type_hash + value.type_hash )); }; - let Some(value) = deserializer(bytes.clone()) else { + let Some(value) = deserializer(value.bytes.clone()) else { return f.write_str(&format!( "The value {:?} for pattern <{}> could not be \ successfully deserialized", - bytes, pattern + value.bytes, pattern )); }; + value + } else { + format!("{:?}", value.bytes) + }; f.write_str(&format!( "Write to pattern: <{}> with value: {}", diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 3682173205..415224214b 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -45,6 +45,8 @@ fn main() { } fn se_migration() { + // TODO: may want to remove some keys corresponding to the old VP and it hash + // Get VP let wasm_path = "wasm"; let bytes = read_wasm(wasm_path, "vp_user.wasm").expect("bingbong"); @@ -54,8 +56,8 @@ fn se_migration() { let account_vp_str = "#tnam[a-z,0-9]*\\/\\?".to_string(); let accounts_update = migrations::DbUpdateType::RepeatAdd { pattern: account_vp_str, - value: vp_hash.into(), - force: false, + value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), + force: true, }; // wasm/hash and wasm/name @@ -63,20 +65,28 @@ fn se_migration() { let wasm_hash_key = Key::wasm_hash("vp_user.wasm"); let wasm_name_update = migrations::DbUpdateType::Add { key: wasm_name_key, - value: vp_hash.into(), - force: false, + value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), + force: true, }; let wasm_hash_update = migrations::DbUpdateType::Add { key: wasm_hash_key, - value: vp_hash.into(), - force: false, + value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), + force: true, }; // wasm/code/ let code_key = Key::wasm_code(&vp_hash); let code_update = migrations::DbUpdateType::Add { key: code_key, - value: bytes.into(), + value: migrations::UpdateValue::raw(bytes.clone()), + force: true, + }; + + // wasm/len/ + let len_key = Key::wasm_code_len(&vp_hash); + let code_len_update = migrations::DbUpdateType::Add { + key: len_key, + value: (bytes.len() as u64).into(), force: true, }; @@ -100,6 +110,7 @@ fn se_migration() { wasm_hash_update, code_update, allowlist_update, + code_len_update, ]; let changes = migrations::DbChanges { From 4fce5298b2a1d325fd177f0523e2d8c2c63dc3e3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 9 Mar 2024 21:43:08 +0100 Subject: [PATCH 72/96] fmt --- crates/migrations/src/foreign_types.rs | 2 +- crates/sdk/src/migrations.rs | 72 +++++++++++++------------- examples/make-db-migration.rs | 3 +- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/crates/migrations/src/foreign_types.rs b/crates/migrations/src/foreign_types.rs index c54afb1fa1..fd88247598 100644 --- a/crates/migrations/src/foreign_types.rs +++ b/crates/migrations/src/foreign_types.rs @@ -46,4 +46,4 @@ static BYTE: fn() = || { .map(|val| format!("{:?}", val)) .ok() }); -}; \ No newline at end of file +}; diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 6c882963b8..89b4b09717 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -291,28 +291,25 @@ pub struct DbChanges { impl Display for DbUpdateType { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { - DbUpdateType::Add { - key, - value, - force, - } => { + DbUpdateType::Add { key, value, force } => { let value = if !force && !value.is_raw() { - let Some(deserializer) = get_deserializer(&value.type_hash) else { - return f.write_str(&format!( - "Type hash {:?} did not correspond to a deserializer \ - in TYPE_DESERIALIZERS.", - value.type_hash - )); - }; + let Some(deserializer) = get_deserializer(&value.type_hash) + else { + return f.write_str(&format!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + )); + }; - let Some(value) = deserializer(value.bytes.clone()) else { - return f.write_str(&format!( - "The value {:?} for key <{}> could not be \ - successfully deserialized", - value.bytes, key - )); - }; - value + let Some(value) = deserializer(value.bytes.clone()) else { + return f.write_str(&format!( + "The value {:?} for key <{}> could not be \ + successfully deserialized", + value.bytes, key + )); + }; + value } else { format!("{:?}", value.bytes) }; @@ -331,25 +328,26 @@ impl Display for DbUpdateType { force, } => { let value = if !force && !value.is_raw() { - let Some(deserializer) = get_deserializer(&value.type_hash) else { - return f.write_str(&format!( - "Type hash {:?} did not correspond to a deserializer \ - in TYPE_DESERIALIZERS.", - value.type_hash - )); - }; + let Some(deserializer) = get_deserializer(&value.type_hash) + else { + return f.write_str(&format!( + "Type hash {:?} did not correspond to a \ + deserializer in TYPE_DESERIALIZERS.", + value.type_hash + )); + }; - let Some(value) = deserializer(value.bytes.clone()) else { - return f.write_str(&format!( - "The value {:?} for pattern <{}> could not be \ - successfully deserialized", - value.bytes, pattern - )); + let Some(value) = deserializer(value.bytes.clone()) else { + return f.write_str(&format!( + "The value {:?} for pattern <{}> could not be \ + successfully deserialized", + value.bytes, pattern + )); + }; + value + } else { + format!("{:?}", value.bytes) }; - value - } else { - format!("{:?}", value.bytes) - }; f.write_str(&format!( "Write to pattern: <{}> with value: {}", diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 415224214b..eef921f5a3 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -45,7 +45,8 @@ fn main() { } fn se_migration() { - // TODO: may want to remove some keys corresponding to the old VP and it hash + // TODO: may want to remove some keys corresponding to the old VP and it + // hash // Get VP let wasm_path = "wasm"; From 33c6ba65cfd9e7e920202e08b5617c637c7221c3 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 11 Mar 2024 02:26:36 -0700 Subject: [PATCH 73/96] Cleanupt up the foreign type macros. Tidied up some logging. Added a migration step to remove old keys --- crates/macros/src/lib.rs | 130 ++++++++++++++++--------- crates/migrations/src/foreign_types.rs | 57 ++--------- crates/sdk/src/migrations.rs | 18 +++- examples/make-db-migration.rs | 32 ++---- 4 files changed, 120 insertions(+), 117 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 04938aed0f..01e44f9fe3 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -337,14 +337,39 @@ where } #[proc_macro_derive(BorshDeserializer)] -pub fn derive_borsh_deserializer(struct_def: TokenStream) -> TokenStream { - derive_borsh_deserializer_inner(struct_def.into()).into() +pub fn derive_borsh_deserializer(type_def: TokenStream) -> TokenStream { + derive_borsh_deserializer_inner(type_def.into()).into() +} + +#[proc_macro] +pub fn derive_borshdeserializer(type_def: TokenStream) -> TokenStream { + derive_borsh_deserialize_inner(type_def.into()).into() +} + +#[proc_macro] +pub fn derive_typehash(type_def: TokenStream) -> TokenStream { + let type_def = syn::parse2::(type_def.into()).expect( + "Could not parse input to `derive_borshdesrializer` as a type.", + ); + match type_def { + syn::Type::Array(_) | syn::Type::Tuple(_) | syn::Type::Path(_) => {} + _ => panic!( + "The `borsh_derserializer!` macro may only be called on arrays, \ + tuples, structs, and enums." + ), + } + let (_, hash) = derive_typehash_inner(&type_def); + quote!( + impl TypeHash for #type_def { + const HASH: [u8; 32] = #hash; + } + ).into() } #[inline] fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { let mut hasher = sha2::Sha256::new(); - let (ident, generics) = syn::parse2::(item_def.clone()) + let (type_def, generics) = syn::parse2::(item_def.clone()) .map(|def| { hasher.update(def.to_token_stream().to_string().as_bytes()); (def.ident, def.generics) @@ -365,28 +390,6 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { derive_borshdeserializer! macro." ); } - impl_borsh_deserializer(type_hash, ident) -} - -#[proc_macro] -pub fn derive_typehash(item: TokenStream) -> TokenStream { - derive_typehash_inner(item.into()).into() -} - -fn derive_typehash_inner(item: TokenStream2) -> TokenStream2 { - let type_def = syn::parse2::(item).expect( - "Could not parse input to `derive_borshdesrializer` as a type.", - ); - match type_def { - syn::Type::Array(_) | syn::Type::Tuple(_) | syn::Type::Path(_) => {} - _ => panic!( - "The `borsh_derserializer!` macro may only be called on arrays, \ - tuples, structs, and enums." - ), - } - let mut hasher = sha2::Sha256::new(); - hasher.update(type_def.to_token_stream().to_string().as_bytes()); - let type_hash: [u8; 32] = hasher.finalize().into(); let hash = syn::ExprArray { attrs: vec![], bracket_token: Default::default(), @@ -395,36 +398,53 @@ fn derive_typehash_inner(item: TokenStream2) -> TokenStream2 { attrs: vec![], lit: syn::Lit::Byte(LitByte::new( b, - proc_macro2::Span::call_site(), + Span::call_site(), )), }) })), }; - quote!(#hash) + let hex = data_encoding::HEXUPPER.encode(&type_hash); + let deserializer_ident = + syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); + + quote!( + #[cfg(feature = "migrations")] + #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] + static #deserializer_ident: fn() = || { + ::namada_migrations::register_deserializer(#hash, |bytes| { + #type_def::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() + }); + }; + #[cfg(feature = "migrations")] + impl ::namada_migrations::TypeHash for #type_def { + const HASH: [u8; 32] = #hash; + } + ) } -fn impl_borsh_deserializer( - type_hash: [u8; 32], - type_def: T, -) -> TokenStream2 { - let hash = syn::ExprArray { - attrs: vec![], - bracket_token: Default::default(), - elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { - syn::Expr::Lit(syn::ExprLit { - attrs: vec![], - lit: syn::Lit::Byte(LitByte::new( - b, - proc_macro2::Span::call_site(), - )), - }) - })), - }; +#[inline] +fn derive_borsh_deserialize_inner(item: TokenStream2) -> TokenStream2 { + let type_def = syn::parse2::(item).expect( + "Could not parse input to `derive_borshdesrializer` as a type.", + ); + match type_def { + syn::Type::Array(_) | syn::Type::Tuple(_) | syn::Type::Path(_) => {} + _ => panic!( + "The `borsh_derserializer!` macro may only be called on arrays, \ + tuples, structs, and enums." + ), + } + let (type_hash, hash) = derive_typehash_inner(&type_def); let hex = data_encoding::HEXUPPER.encode(&type_hash); + let wrapper_ident = syn::Ident::new(&format!("Wrapper_{}", hex), Span::call_site()); let deserializer_ident = syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); quote!( + #[derive(BorshSerialize)] + #[repr(transparent)] + pub struct #wrapper_ident(#type_def); + #[cfg(feature = "migrations")] #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] static #deserializer_ident: fn() = || { @@ -433,12 +453,32 @@ fn impl_borsh_deserializer( }); }; #[cfg(feature = "migrations")] - impl ::namada_migrations::TypeHash for #type_def { + impl ::namada_migrations::TypeHash for #wrapper_ident { const HASH: [u8; 32] = #hash; } ) } +#[inline] +fn derive_typehash_inner(type_def: &syn::Type) -> ([u8;32], syn::ExprArray) { + let mut hasher = sha2::Sha256::new(); + hasher.update(type_def.to_token_stream().to_string().as_bytes()); + let type_hash: [u8; 32] = hasher.finalize().into(); + (type_hash, syn::ExprArray { + attrs: vec![], + bracket_token: Default::default(), + elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { + syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Byte(LitByte::new( + b, + Span::call_site(), + )), + }) + })), + }) +} + #[cfg(test)] mod test_proc_macros { use syn::File; diff --git a/crates/migrations/src/foreign_types.rs b/crates/migrations/src/foreign_types.rs index fd88247598..1225465067 100644 --- a/crates/migrations/src/foreign_types.rs +++ b/crates/migrations/src/foreign_types.rs @@ -1,49 +1,12 @@ -use borsh::BorshDeserialize; -use namada_macros::derive_typehash; - -use crate::REGISTER_DESERIALIZERS; - -// TODO: change naming bc HASHu8 and HASHBYTE conflicts - -const HASHU8: [u8; 32] = derive_typehash!(Vec::); -pub const HASHVECSTR: [u8; 32] = derive_typehash!(Vec::); -pub const HASHBYTE: [u8; 32] = derive_typehash!(u64); - -impl crate::TypeHash for Vec { - const HASH: [u8; 32] = HASHU8; -} +//! The procedure for deriving deserializers for types not defined in Namada +//! is as follows: +//! +//! 1. We derive the [`TypeHash`] on the type here. +//! 2. We derive the deserialization function in `crates::sdk::migrations.rs` -impl crate::TypeHash for Vec { - const HASH: [u8; 32] = HASHVECSTR; -} - -impl crate::TypeHash for u64 { - const HASH: [u8; 32] = HASHBYTE; -} - -#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] -static BYTES: fn() = || { - crate::register_deserializer(HASHU8, |bytes| { - Vec::::try_from_slice(&bytes) - .map(|val| format!("{:?}", val)) - .ok() - }); -}; - -#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] -static STRINGS: fn() = || { - crate::register_deserializer(HASHVECSTR, |bytes| { - Vec::::try_from_slice(&bytes) - .map(|val| format!("{:?}", val)) - .ok() - }); -}; +use namada_macros::derive_typehash; +use crate::TypeHash; -#[linkme::distributed_slice(REGISTER_DESERIALIZERS)] -static BYTE: fn() = || { - crate::register_deserializer(HASHBYTE, |bytes| { - u64::try_from_slice(&bytes) - .map(|val| format!("{:?}", val)) - .ok() - }); -}; +derive_typehash!(Vec::); +derive_typehash!(Vec::); +derive_typehash!(u64); diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 89b4b09717..e33b2afc84 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -10,12 +10,14 @@ use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_core::storage::Key; #[cfg(feature = "migrations")] -use namada_migrations::get_deserializer; +use namada_migrations::*; #[cfg(feature = "migrations")] use namada_migrations::TypeHash; use regex::Regex; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use namada_macros::derive_borshdeserializer; + pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; @@ -311,7 +313,11 @@ impl Display for DbUpdateType { }; value } else { - format!("{:?}", value.bytes) + if value.bytes.len() > 100 { + format!("{:?}...", &value.bytes[..100]) + } else { + format!("{:?}", value.bytes) + } }; f.write_str(&format!( @@ -387,3 +393,11 @@ impl Display for UpdateStatus { Ok(()) } } + + +#[cfg(feature = "migrations")] +derive_borshdeserializer!(Vec::); +#[cfg(feature = "migrations")] +derive_borshdeserializer!(Vec::); +#[cfg(feature = "migrations")] +derive_borshdeserializer!(u64); diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index eef921f5a3..7431491d48 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -44,9 +44,10 @@ fn main() { se_migration() } +// TODO: put in the correct hash +const REMOVED_HASH: &str = "000000000000000000000000000000000000000"; fn se_migration() { - // TODO: may want to remove some keys corresponding to the old VP and it - // hash + // Get VP let wasm_path = "wasm"; @@ -88,7 +89,7 @@ fn se_migration() { let code_len_update = migrations::DbUpdateType::Add { key: len_key, value: (bytes.len() as u64).into(), - force: true, + force: false, }; // VP allowlist @@ -102,9 +103,12 @@ fn se_migration() { let allowlist_update = migrations::DbUpdateType::Add { key: vp_allowlist_key, value: new_vp_allowlist.into(), - force: true, + force: false, }; + // remove keys associated with old wasm + let remove_old_wasm = migrations::DbUpdateType::RepeatDelete(format!("/wasm/[a-z]+/{}", REMOVED_HASH)); + let updates = [ accounts_update, wasm_name_update, @@ -112,6 +116,7 @@ fn se_migration() { code_update, allowlist_update, code_len_update, + remove_old_wasm, ]; let changes = migrations::DbChanges { @@ -121,22 +126,3 @@ fn se_migration() { .unwrap(); } -#[test] -fn bingbong() { - let key = storage::get_vp_allowlist_storage_key(); - let type_hash = namada_migrations::foreign_types::HASHVECSTR; - let hex = HEXUPPER.encode(&type_hash); - println!("{}", hex); - println!("{}", key); - - let token_amount_hash = HEXUPPER.encode(&Amount::HASH); - println!("{}", token_amount_hash); - - let serialized = "0200000040000000383738316331373061643165336432626264646333303862373762376132656464613366666633626335643734363233326665656339363865653466653363644000000031323965653762656536386230326266616536333864613261363334623865636266666132636233663436636661386531373262616630303936323765633738"; - // let serialized = serialized.chars().map(|bing| - // u8::try_from(bing).unwrap()).collect::>(); - let serialized = HEXUPPER.decode(serialized.as_bytes()).unwrap(); - let allowlist = - Vec::::try_from_slice(serialized.as_slice()).unwrap(); - println!("{:?}", allowlist); -} From 0e9b7c3e33e0f3c7729fbea0f9b0fc23d459c0c4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 11 Mar 2024 11:13:15 -0400 Subject: [PATCH 74/96] clippy and fmt --- crates/macros/src/lib.rs | 41 +++++++++++++------------- crates/migrations/src/foreign_types.rs | 1 + crates/sdk/src/migrations.rs | 16 ++++------ examples/make-db-migration.rs | 8 ++--- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 01e44f9fe3..178fc679aa 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -363,7 +363,8 @@ pub fn derive_typehash(type_def: TokenStream) -> TokenStream { impl TypeHash for #type_def { const HASH: [u8; 32] = #hash; } - ).into() + ) + .into() } #[inline] @@ -396,10 +397,7 @@ fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { syn::Expr::Lit(syn::ExprLit { attrs: vec![], - lit: syn::Lit::Byte(LitByte::new( - b, - Span::call_site(), - )), + lit: syn::Lit::Byte(LitByte::new(b, Span::call_site())), }) })), }; @@ -436,7 +434,8 @@ fn derive_borsh_deserialize_inner(item: TokenStream2) -> TokenStream2 { } let (type_hash, hash) = derive_typehash_inner(&type_def); let hex = data_encoding::HEXUPPER.encode(&type_hash); - let wrapper_ident = syn::Ident::new(&format!("Wrapper_{}", hex), Span::call_site()); + let wrapper_ident = + syn::Ident::new(&format!("Wrapper_{}", hex), Span::call_site()); let deserializer_ident = syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); @@ -460,23 +459,25 @@ fn derive_borsh_deserialize_inner(item: TokenStream2) -> TokenStream2 { } #[inline] -fn derive_typehash_inner(type_def: &syn::Type) -> ([u8;32], syn::ExprArray) { +fn derive_typehash_inner(type_def: &syn::Type) -> ([u8; 32], syn::ExprArray) { let mut hasher = sha2::Sha256::new(); hasher.update(type_def.to_token_stream().to_string().as_bytes()); let type_hash: [u8; 32] = hasher.finalize().into(); - (type_hash, syn::ExprArray { - attrs: vec![], - bracket_token: Default::default(), - elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map(|b| { - syn::Expr::Lit(syn::ExprLit { - attrs: vec![], - lit: syn::Lit::Byte(LitByte::new( - b, - Span::call_site(), - )), - }) - })), - }) + ( + type_hash, + syn::ExprArray { + attrs: vec![], + bracket_token: Default::default(), + elems: Punctuated::<_, _>::from_iter(type_hash.into_iter().map( + |b| { + syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Byte(LitByte::new(b, Span::call_site())), + }) + }, + )), + }, + ) } #[cfg(test)] diff --git a/crates/migrations/src/foreign_types.rs b/crates/migrations/src/foreign_types.rs index 1225465067..19a6805f71 100644 --- a/crates/migrations/src/foreign_types.rs +++ b/crates/migrations/src/foreign_types.rs @@ -5,6 +5,7 @@ //! 2. We derive the deserialization function in `crates::sdk::migrations.rs` use namada_macros::derive_typehash; + use crate::TypeHash; derive_typehash!(Vec::); diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index e33b2afc84..125d12de87 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -9,15 +9,14 @@ use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_core::storage::Key; -#[cfg(feature = "migrations")] -use namada_migrations::*; +use namada_macros::derive_borshdeserializer; #[cfg(feature = "migrations")] use namada_migrations::TypeHash; +#[cfg(feature = "migrations")] +use namada_migrations::*; use regex::Regex; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use namada_macros::derive_borshdeserializer; - pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; @@ -312,12 +311,10 @@ impl Display for DbUpdateType { )); }; value + } else if value.bytes.len() > 100 { + format!("{:?}...", &value.bytes[..100]) } else { - if value.bytes.len() > 100 { - format!("{:?}...", &value.bytes[..100]) - } else { - format!("{:?}", value.bytes) - } + format!("{:?}", value.bytes) }; f.write_str(&format!( @@ -394,7 +391,6 @@ impl Display for UpdateStatus { } } - #[cfg(feature = "migrations")] derive_borshdeserializer!(Vec::); #[cfg(feature = "migrations")] diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 7431491d48..bacf9947d6 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -47,8 +47,6 @@ fn main() { // TODO: put in the correct hash const REMOVED_HASH: &str = "000000000000000000000000000000000000000"; fn se_migration() { - - // Get VP let wasm_path = "wasm"; let bytes = read_wasm(wasm_path, "vp_user.wasm").expect("bingbong"); @@ -107,7 +105,10 @@ fn se_migration() { }; // remove keys associated with old wasm - let remove_old_wasm = migrations::DbUpdateType::RepeatDelete(format!("/wasm/[a-z]+/{}", REMOVED_HASH)); + let remove_old_wasm = migrations::DbUpdateType::RepeatDelete(format!( + "/wasm/[a-z]+/{}", + REMOVED_HASH + )); let updates = [ accounts_update, @@ -125,4 +126,3 @@ fn se_migration() { std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) .unwrap(); } - From 2e9359501882d795717c417cc17835bedd8ad904 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 12 Mar 2024 04:30:57 -0700 Subject: [PATCH 75/96] [fix]: Fixed log lines from migrations and refactored the dsl implementation to be DRYer --- crates/apps/src/lib/node/ledger/mod.rs | 2 +- crates/sdk/src/lib.rs | 1 + crates/sdk/src/migrations.rs | 281 ++++++++++++------------- examples/make-db-migration.rs | 14 +- 4 files changed, 143 insertions(+), 155 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 28a7c3f237..26a4ec961f 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -287,7 +287,7 @@ pub fn update_db_keys(config: config::Ledger, updates: PathBuf, dry_run: bool) { e => { tracing::error!( "Attempt to write to key/pattern <{}> failed.", - change.key() + change.pattern() ); e.unwrap(); } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 081865d26e..54fc2a45c0 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,6 +1,7 @@ extern crate alloc; extern crate core; + pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] pub use tendermint_rpc; diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 125d12de87..85d0718789 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -9,6 +9,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use namada_core::storage::Key; +#[cfg(feature = "migrations")] use namada_macros::derive_borshdeserializer; #[cfg(feature = "migrations")] use namada_migrations::TypeHash; @@ -18,44 +19,83 @@ use regex::Regex; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "migrations")] +/// The maximum number of character printed per value. +const PRINTLN_CUTOFF: usize = 300; + pub trait DBUpdateVisitor { fn read(&self, key: &Key) -> Option>; fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); fn delete(&mut self, key: &Key); fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)>; } +#[derive(Clone, BorshSerialize, BorshDeserialize)] + enum UpdateBytes { + Raw { + to_write: Vec, + serialized: Vec, + }, + Serialized { + bytes: Vec + } +} + -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Clone, BorshSerialize, BorshDeserialize)] /// A value to be added to the database that can be /// validated. -pub struct UpdateValue { +pub struct UpdateValue{ type_hash: [u8; 32], - bytes: Vec, + bytes: UpdateBytes } +#[cfg(feature = "migrations")] impl UpdateValue { - pub fn raw(bytes: Vec) -> Self { + + pub fn raw(value: T) -> Self + where + T: TypeHash + AsRef<[u8]> + BorshSerialize + BorshDeserialize + { Self { - type_hash: Default::default(), - bytes, + type_hash: T::HASH, + bytes: UpdateBytes::Raw { + to_write: value.as_ref().to_vec(), + serialized: value.serialize_to_vec(), + }, } } pub fn is_raw(&self) -> bool { - self.type_hash == [0u8; 32] + matches!(self.bytes, UpdateBytes::Raw{..}) + } + + fn bytes(&self) -> &[u8] { + match &self.bytes { + UpdateBytes::Raw { serialized, .. } => serialized, + UpdateBytes::Serialized {bytes } => bytes, + } + } + + /// The value to write to storage + fn to_write(&self) -> Vec { + match &self.bytes { + UpdateBytes::Raw {to_write, .. } => to_write.clone(), + UpdateBytes::Serialized { bytes } => bytes.clone(), + } } } #[cfg(feature = "migrations")] -impl From for UpdateValue { +impl From for UpdateValue { fn from(value: T) -> Self { Self { type_hash: T::HASH, - bytes: value.serialize_to_vec(), + bytes: UpdateBytes::Serialized {bytes: value.serialize_to_vec()}, } } } +#[derive(Default)] struct UpdateValueVisitor; impl<'de> Visitor<'de> for UpdateValueVisitor { @@ -99,7 +139,7 @@ impl<'de> Deserialize<'de> for UpdateValue { deserializer.deserialize_any(UpdateValueVisitor) } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] /// An update to the database pub enum DbUpdateType { Add { @@ -117,9 +157,10 @@ pub enum DbUpdateType { } #[cfg(feature = "migrations")] -impl DbUpdateType { - /// Get the key being modified - pub fn key(&self) -> String { +impl DbUpdateType +{ + /// Get the key or pattern being modified as string + pub fn pattern(&self) -> String { match self { DbUpdateType::Add { key, .. } => key.to_string(), DbUpdateType::Delete(key) => key.to_string(), @@ -128,9 +169,35 @@ impl DbUpdateType { } } + fn is_force(&self) -> bool { + match self{ + DbUpdateType::Add{force, ..} => *force, + DbUpdateType::RepeatAdd {force, ..} => *force, + _ => false, + } + } + fn formatted_bytes(&self) -> String { + match self { + DbUpdateType::Add {value, ..} | DbUpdateType::RepeatAdd {value, ..} => { + if value.to_write().len() > PRINTLN_CUTOFF { + format!("{:?} ...", &value.bytes()[..PRINTLN_CUTOFF]) + } else { + format!("{:?}", value.bytes()) + } + }, + _ => String::default(), + } + } + /// Validate that the contained value deserializes correctly given its data - /// hash. - pub fn validate(&self) -> eyre::Result<()> { + /// hash and the value is not "raw". Return the string formatted value and, + /// if the value is not "raw", the deserializer function. + pub fn validate(&self) -> eyre::Result<(String, Option)> { + // skip all checks if force == true + if self.is_force() { + return Ok((self.formatted_bytes(), None)); + } + let key_or_pattern = self.pattern(); match self { DbUpdateType::RepeatAdd { value, .. } | DbUpdateType::Add { value, .. } => { @@ -143,15 +210,21 @@ impl DbUpdateType { value.type_hash ) })?; - _ = deserializer(value.bytes.clone()).ok_or_else(|| { - eyre::eyre!( - "The value {:?} could not be successfully deserialized", - value + let deserialized = deserializer(value.bytes().to_vec()).ok_or_else(|| { + eyre::eyre!( + "The value {:?} for key/pattern {} could not be successfully deserialized", + value.bytes(), + key_or_pattern, ) - })?; - Ok(()) + })?; + let deserializer = value.is_raw().then_some(deserializer); + if deserialized.len() > PRINTLN_CUTOFF { + Ok((format!("{} ...", deserialized.chars().take(PRINTLN_CUTOFF).collect::()), deserializer)) + } else { + Ok((deserialized, deserializer)) + } } - DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => Ok(()), + DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => Ok((String::default(), None)), } } @@ -163,50 +236,21 @@ impl DbUpdateType { db: &mut DB, ) -> eyre::Result { match self { - Self::Add { key, value, force } => { - let deserialized = if !force && !value.is_raw() { - let deserializer = - namada_migrations::get_deserializer(&value.type_hash) - .ok_or_else(|| { - eyre::eyre!( - "Type hash {:?} did not correspond to a \ - deserializer in TYPE_DESERIALIZERS.", - value.type_hash - ) - })?; - let deserialized = deserializer(value.bytes.clone()) - .ok_or_else(|| { - eyre::eyre!( - "The value {:?} for key {} could not be \ - successfully deserialized", - value, - key - ) - })?; - if let Some(prev) = db.read(key) { - deserializer(prev).ok_or_else(|| { - eyre::eyre!( - "The previous value under the key {} did not \ - have the same type as that provided: Input \ - was {}", - key, - deserialized - ) - })?; - } - Some(deserialized) - } else { - None - }; - db.write(key, &value.bytes); - Ok(deserialized - .map(|d| UpdateStatus::Add(vec![(key.to_string(), d)])) - .unwrap_or_else(|| { - UpdateStatus::Add(vec![( - key.to_string(), - format!("{:?}", value.bytes), - )]) - })) + Self::Add { key, value, .. } => { + let (deserialized, deserializer) = self.validate()?; + if let (Some(prev), Some(des)) = (db.read(key), deserializer) { + des(prev).ok_or_else(|| { + eyre::eyre!( + "The previous value under the key {} did not \ + have the same type as that provided: Input \ + was {}", + key, + deserialized + ) + })?; + } + db.write(key, &value.to_write()); + Ok(UpdateStatus::Add(vec![(key.to_string(), deserialized)])) } Self::Delete(key) => { db.delete(key); @@ -215,55 +259,31 @@ impl DbUpdateType { DbUpdateType::RepeatAdd { pattern, value, - force, + .. } => { let pattern = Regex::new(pattern).unwrap(); let mut pairs = vec![]; - let (deserialized, deserializer) = if !force && !value.is_raw() - { - let deserializer = - namada_migrations::get_deserializer(&value.type_hash) - .ok_or_else(|| { - eyre::eyre!( - "Type hash {:?} did not correspond to a \ - deserializer in TYPE_DESERIALIZERS.", - value.type_hash - ) - })?; - let deserialized = deserializer(value.bytes.clone()) - .ok_or_else(|| { - eyre::eyre!( - "The value {:?} for pattern {} could not be \ - successfully deserialized", - value, - pattern, - ) - })?; - (Some(deserialized), Some(deserializer)) - } else { - (None, None) - }; + let (deserialized, deserializer) = self.validate()?; for (key, prev) in db.get_pattern(pattern.clone()) { - if let (Some(func), Some(d)) = - (deserializer, deserialized.as_ref()) + if let Some(des) = deserializer { - func(prev).ok_or_else(|| { + des(prev).ok_or_else(|| { eyre::eyre!( "The previous value under the key {} did not \ have the same type as that provided: Input \ was {}", key, - d, + deserialized, ) })?; - pairs.push((key.to_string(), d.clone())); + pairs.push((key.to_string(), deserialized.clone())); } else { pairs.push(( key.to_string(), - format!("{:?}", value.bytes), + deserialized.clone(), )); } - db.write(&Key::from_str(&key).unwrap(), &value.bytes); + db.write(&Key::from_str(&key).unwrap(), value.to_write()); } Ok(UpdateStatus::Add(pairs)) } @@ -292,34 +312,17 @@ pub struct DbChanges { impl Display for DbUpdateType { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { - DbUpdateType::Add { key, value, force } => { - let value = if !force && !value.is_raw() { - let Some(deserializer) = get_deserializer(&value.type_hash) - else { - return f.write_str(&format!( - "Type hash {:?} did not correspond to a \ - deserializer in TYPE_DESERIALIZERS.", - value.type_hash - )); - }; - - let Some(value) = deserializer(value.bytes.clone()) else { - return f.write_str(&format!( - "The value {:?} for key <{}> could not be \ - successfully deserialized", - value.bytes, key - )); - }; - value - } else if value.bytes.len() > 100 { - format!("{:?}...", &value.bytes[..100]) - } else { - format!("{:?}", value.bytes) + DbUpdateType::Add { key, value, .. } => { + let (formatted, _) = match self.validate() { + Ok(f) => f, + Err(e) => return f.write_str(&e.to_string()) }; f.write_str(&format!( - "Write to key: <{}> with value: {}", - key, value + "Write to key: <{}> with {}value: {}", + key, + value.is_raw().then_some("raw " ).unwrap_or_default(), + formatted )) } DbUpdateType::Delete(key) => { @@ -328,33 +331,17 @@ impl Display for DbUpdateType { DbUpdateType::RepeatAdd { pattern, value, - force, + .. } => { - let value = if !force && !value.is_raw() { - let Some(deserializer) = get_deserializer(&value.type_hash) - else { - return f.write_str(&format!( - "Type hash {:?} did not correspond to a \ - deserializer in TYPE_DESERIALIZERS.", - value.type_hash - )); - }; - - let Some(value) = deserializer(value.bytes.clone()) else { - return f.write_str(&format!( - "The value {:?} for pattern <{}> could not be \ - successfully deserialized", - value.bytes, pattern - )); - }; - value - } else { - format!("{:?}", value.bytes) + let (formatted, _) = match self.validate() { + Ok(f) => f, + Err(e) => return f.write_str(&e.to_string()) }; - f.write_str(&format!( - "Write to pattern: <{}> with value: {}", - pattern, value + "Write to pattern: <{}> with {}value: {}", + pattern, + value.is_raw().then_some("raw ").unwrap_or_default(), + formatted, )) } DbUpdateType::RepeatDelete(pattern) => { diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index bacf9947d6..7acd7a8050 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -56,8 +56,8 @@ fn se_migration() { let account_vp_str = "#tnam[a-z,0-9]*\\/\\?".to_string(); let accounts_update = migrations::DbUpdateType::RepeatAdd { pattern: account_vp_str, - value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), - force: true, + value: migrations::UpdateValue::raw(vp_hash), + force: false, }; // wasm/hash and wasm/name @@ -65,13 +65,13 @@ fn se_migration() { let wasm_hash_key = Key::wasm_hash("vp_user.wasm"); let wasm_name_update = migrations::DbUpdateType::Add { key: wasm_name_key, - value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), - force: true, + value: migrations::UpdateValue::raw(vp_hash), + force: false, }; let wasm_hash_update = migrations::DbUpdateType::Add { key: wasm_hash_key, - value: migrations::UpdateValue::raw(vp_hash.0.to_vec()), - force: true, + value: migrations::UpdateValue::raw(vp_hash), + force: false, }; // wasm/code/ @@ -79,7 +79,7 @@ fn se_migration() { let code_update = migrations::DbUpdateType::Add { key: code_key, value: migrations::UpdateValue::raw(bytes.clone()), - force: true, + force: false, }; // wasm/len/ From 9d228705df780346e36cac5db87f3a71b90f3965 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 12 Mar 2024 04:52:13 -0700 Subject: [PATCH 76/96] [fix]: Made migrating a single command --- crates/apps/src/bin/namada-node/cli.rs | 19 ++++++++++++++++--- crates/apps/src/lib/cli.rs | 7 ++++++- crates/tests/src/e2e/ledger_tests.rs | 19 +------------------ examples/migration_example.json | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index cef4766b4e..62662d7c8a 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -3,7 +3,7 @@ use eyre::{Context, Result}; use namada::core::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; -use namada_apps::config::ValidatorLocalConfig; +use namada_apps::config::{Action, ActionAtHeight, ValidatorLocalConfig}; use namada_apps::node::ledger; #[cfg(not(feature = "migrations"))] use namada_sdk::display_line; @@ -48,13 +48,26 @@ pub fn main() -> Result<()> { \"migrations\" feature." ) } - let chain_ctx = ctx.take_chain_or_exit(); + let mut chain_ctx = ctx.take_chain_or_exit(); #[cfg(feature = "migrations")] ledger::update_db_keys( - chain_ctx.config.ledger, + chain_ctx.config.ledger.clone(), args.updates, args.dry_run, ); + if !args.dry_run { + let wasm_dir = chain_ctx.wasm_dir(); + chain_ctx.config.ledger.shell.action_at_height = + Some(ActionAtHeight { + height: args.last_height + 2, + action: Action::Halt, + }); + std::env::set_var("NAMADA_INITIAL_HEIGHT", args.last_height.to_string()); + // don't stop on panics + let handle = std::thread::spawn(|| ledger::run(chain_ctx.config.ledger, wasm_dir)); + _ = handle.join(); + std::env::remove_var("NAMADA_INITIAL_HEIGHT"); + } } cmds::Ledger::QueryDB(cmds::LedgerQueryDB(args)) => { #[cfg(not(feature = "migrations"))] diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 555098f5d8..2ba55313f5 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -3459,13 +3459,15 @@ pub mod args { pub struct LedgerUpdateDb { pub updates: PathBuf, pub dry_run: bool, + pub last_height: BlockHeight, } impl Args for LedgerUpdateDb { fn parse(matches: &ArgMatches) -> Self { let updates = PATH.parse(matches); let dry_run = DRY_RUN_TX.parse(matches); - Self { updates, dry_run } + let last_height = BLOCK_HEIGHT.parse(matches); + Self { updates, dry_run , last_height} } fn def(app: App) -> App { @@ -3476,6 +3478,9 @@ pub mod args { "If set, applies the updates but does not persist them. Using \ for testing and debugging.", )) + .arg(BLOCK_HEIGHT.def().help( + "The height at which the hard fork is happening." + )) } } diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 21e1e1da3a..0189494136 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -373,8 +373,6 @@ fn test_db_migration() -> Result<()> { &["ledger", "run-until", "--block-height", "2", "--halt",], Some(40) )?; - // There should be no previous state - ledger.exp_string("No state could be found")?; // Wait to commit a block ledger.exp_string("Reached block height 2, halting the chain.")?; ledger.exp_string(LEDGER_SHUTDOWN)?; @@ -394,25 +392,10 @@ fn test_db_migration() -> Result<()> { "--path", migrations_json_path.to_string_lossy().as_ref(), ], - Some(10), + Some(30), )?; session.exp_eof()?; - std::env::set_var("NAMADA_INITIAL_HEIGHT", "3"); - // 3. Run the ledger node, halting at height 4 - let mut ledger = run_as!( - test, - Who::Validator(0), - Bin::Node, - &["ledger", "run-until", "--block-height", "4", "--halt",], - Some(40) - )?; - ledger.exp_string("Reached block height 4, halting the chain.")?; - ledger.exp_string(LEDGER_SHUTDOWN)?; - ledger.exp_eof()?; - drop(ledger); - // 4. restart ledge with migrated db - std::env::remove_var("NAMADA_INITIAL_HEIGHT"); let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; diff --git a/examples/migration_example.json b/examples/migration_example.json index c5a4f6e7e1..93af620b0f 100644 --- a/examples/migration_example.json +++ b/examples/migration_example.json @@ -1 +1 @@ -{"changes":[{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"AddressSeg":"tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A2000000080E7414914AF682C000000000000000000000000000000000000000000000000","force":false}},{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"StringSeg":"minted"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A20000000797241E2F46A0000000000000000000000000000000000000000000000000000","force":false}},{"RepeatDelete":"tnam1qyvfwdkz8zgs9n3qn9xhp8scyf8crrxwuq26r6gy"}]} \ No newline at end of file +{"changes":[{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"AddressSeg":"tnam1q9rhgyv3ydq0zu3whnftvllqnvhvhm270qxay5tn"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A012000000080E7414914AF682C000000000000000000000000000000000000000000000000","force":false}},{"Add":{"key":{"segments":[{"AddressSeg":"tnam1pyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqej6juv"},{"AddressSeg":"tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e"},{"StringSeg":"balance"},{"StringSeg":"minted"}]},"value":"E47D97A2C7D7834F0907AEEEBD30CAA28CA88EBDB854398482DB2E1A49D7811A0120000000797241E2F46A0000000000000000000000000000000000000000000000000000","force":false}},{"RepeatDelete":"tnam1qyvfwdkz8zgs9n3qn9xhp8scyf8crrxwuq26r6gy"}]} \ No newline at end of file From de5233e3168e998ce38c4789516c565bd931b8e9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 12 Mar 2024 18:45:36 -0400 Subject: [PATCH 77/96] add conversion state token map to migration --- Cargo.lock | 1 + crates/apps/src/bin/namada-node/cli.rs | 9 +- crates/apps/src/lib/cli.rs | 14 ++- crates/sdk/src/lib.rs | 1 - crates/sdk/src/migrations.rs | 117 +++++++++++++---------- crates/shielded_token/src/storage_key.rs | 9 ++ examples/Cargo.toml | 1 + examples/make-db-migration.rs | 13 +++ 8 files changed, 109 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db995b556b..8b5bedb0a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,6 +4438,7 @@ dependencies = [ "namada_migrations", "namada_parameters", "namada_sdk", + "namada_shielded_token", "namada_trans_token", "proptest", "serde_json", diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 62662d7c8a..2c40f135d8 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -62,9 +62,14 @@ pub fn main() -> Result<()> { height: args.last_height + 2, action: Action::Halt, }); - std::env::set_var("NAMADA_INITIAL_HEIGHT", args.last_height.to_string()); + std::env::set_var( + "NAMADA_INITIAL_HEIGHT", + args.last_height.to_string(), + ); // don't stop on panics - let handle = std::thread::spawn(|| ledger::run(chain_ctx.config.ledger, wasm_dir)); + let handle = std::thread::spawn(|| { + ledger::run(chain_ctx.config.ledger, wasm_dir) + }); _ = handle.join(); std::env::remove_var("NAMADA_INITIAL_HEIGHT"); } diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 2ba55313f5..c3e6e28b42 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -3467,7 +3467,11 @@ pub mod args { let updates = PATH.parse(matches); let dry_run = DRY_RUN_TX.parse(matches); let last_height = BLOCK_HEIGHT.parse(matches); - Self { updates, dry_run , last_height} + Self { + updates, + dry_run, + last_height, + } } fn def(app: App) -> App { @@ -3478,9 +3482,11 @@ pub mod args { "If set, applies the updates but does not persist them. Using \ for testing and debugging.", )) - .arg(BLOCK_HEIGHT.def().help( - "The height at which the hard fork is happening." - )) + .arg( + BLOCK_HEIGHT + .def() + .help("The height at which the hard fork is happening."), + ) } } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 54fc2a45c0..081865d26e 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,7 +1,6 @@ extern crate alloc; extern crate core; - pub use namada_core::*; #[cfg(feature = "tendermint-rpc")] pub use tendermint_rpc; diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 85d0718789..01ac427668 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -30,31 +30,31 @@ pub trait DBUpdateVisitor { fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)>; } #[derive(Clone, BorshSerialize, BorshDeserialize)] - enum UpdateBytes { +enum UpdateBytes { Raw { to_write: Vec, serialized: Vec, }, Serialized { - bytes: Vec - } + bytes: Vec, + }, } - #[derive(Clone, BorshSerialize, BorshDeserialize)] /// A value to be added to the database that can be /// validated. -pub struct UpdateValue{ +pub struct UpdateValue { type_hash: [u8; 32], - bytes: UpdateBytes + bytes: UpdateBytes, } #[cfg(feature = "migrations")] impl UpdateValue { - + /// Using a type that is a thin wrapper around bytes but with a custom + /// serialization when we don't want to use Borsh necessarily pub fn raw(value: T) -> Self where - T: TypeHash + AsRef<[u8]> + BorshSerialize + BorshDeserialize + T: TypeHash + AsRef<[u8]> + BorshSerialize + BorshDeserialize, { Self { type_hash: T::HASH, @@ -65,21 +65,35 @@ impl UpdateValue { } } + /// Using a type that is Borsh-serializable but we don't have an + /// implementation for conversion yet. Must provide `force: true` + pub fn force_borsh(value: T) -> Self + where + T: BorshSerialize + BorshDeserialize, + { + Self { + type_hash: Default::default(), + bytes: UpdateBytes::Serialized { + bytes: value.serialize_to_vec(), + }, + } + } + pub fn is_raw(&self) -> bool { - matches!(self.bytes, UpdateBytes::Raw{..}) + matches!(self.bytes, UpdateBytes::Raw { .. }) } fn bytes(&self) -> &[u8] { match &self.bytes { UpdateBytes::Raw { serialized, .. } => serialized, - UpdateBytes::Serialized {bytes } => bytes, + UpdateBytes::Serialized { bytes } => bytes, } } /// The value to write to storage fn to_write(&self) -> Vec { match &self.bytes { - UpdateBytes::Raw {to_write, .. } => to_write.clone(), + UpdateBytes::Raw { to_write, .. } => to_write.clone(), UpdateBytes::Serialized { bytes } => bytes.clone(), } } @@ -90,7 +104,9 @@ impl From for UpdateValue { fn from(value: T) -> Self { Self { type_hash: T::HASH, - bytes: UpdateBytes::Serialized {bytes: value.serialize_to_vec()}, + bytes: UpdateBytes::Serialized { + bytes: value.serialize_to_vec(), + }, } } } @@ -157,8 +173,7 @@ pub enum DbUpdateType { } #[cfg(feature = "migrations")] -impl DbUpdateType -{ +impl DbUpdateType { /// Get the key or pattern being modified as string pub fn pattern(&self) -> String { match self { @@ -170,21 +185,23 @@ impl DbUpdateType } fn is_force(&self) -> bool { - match self{ - DbUpdateType::Add{force, ..} => *force, - DbUpdateType::RepeatAdd {force, ..} => *force, + match self { + DbUpdateType::Add { force, .. } => *force, + DbUpdateType::RepeatAdd { force, .. } => *force, _ => false, } } + fn formatted_bytes(&self) -> String { match self { - DbUpdateType::Add {value, ..} | DbUpdateType::RepeatAdd {value, ..} => { + DbUpdateType::Add { value, .. } + | DbUpdateType::RepeatAdd { value, .. } => { if value.to_write().len() > PRINTLN_CUTOFF { format!("{:?} ...", &value.bytes()[..PRINTLN_CUTOFF]) } else { format!("{:?}", value.bytes()) } - }, + } _ => String::default(), } } @@ -192,7 +209,9 @@ impl DbUpdateType /// Validate that the contained value deserializes correctly given its data /// hash and the value is not "raw". Return the string formatted value and, /// if the value is not "raw", the deserializer function. - pub fn validate(&self) -> eyre::Result<(String, Option)> { + pub fn validate( + &self, + ) -> eyre::Result<(String, Option)> { // skip all checks if force == true if self.is_force() { return Ok((self.formatted_bytes(), None)); @@ -210,21 +229,34 @@ impl DbUpdateType value.type_hash ) })?; - let deserialized = deserializer(value.bytes().to_vec()).ok_or_else(|| { + let deserialized = deserializer(value.bytes().to_vec()) + .ok_or_else(|| { eyre::eyre!( - "The value {:?} for key/pattern {} could not be successfully deserialized", - value.bytes(), - key_or_pattern, - ) + "The value {:?} for key/pattern {} could not be \ + successfully deserialized", + value.bytes(), + key_or_pattern, + ) })?; let deserializer = value.is_raw().then_some(deserializer); if deserialized.len() > PRINTLN_CUTOFF { - Ok((format!("{} ...", deserialized.chars().take(PRINTLN_CUTOFF).collect::()), deserializer)) + Ok(( + format!( + "{} ...", + deserialized + .chars() + .take(PRINTLN_CUTOFF) + .collect::() + ), + deserializer, + )) } else { Ok((deserialized, deserializer)) } } - DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => Ok((String::default(), None)), + DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => { + Ok((String::default(), None)) + } } } @@ -241,9 +273,8 @@ impl DbUpdateType if let (Some(prev), Some(des)) = (db.read(key), deserializer) { des(prev).ok_or_else(|| { eyre::eyre!( - "The previous value under the key {} did not \ - have the same type as that provided: Input \ - was {}", + "The previous value under the key {} did not have \ + the same type as that provided: Input was {}", key, deserialized ) @@ -256,17 +287,12 @@ impl DbUpdateType db.delete(key); Ok(UpdateStatus::Deleted(vec![key.to_string()])) } - DbUpdateType::RepeatAdd { - pattern, - value, - .. - } => { + DbUpdateType::RepeatAdd { pattern, value, .. } => { let pattern = Regex::new(pattern).unwrap(); let mut pairs = vec![]; let (deserialized, deserializer) = self.validate()?; for (key, prev) in db.get_pattern(pattern.clone()) { - if let Some(des) = deserializer - { + if let Some(des) = deserializer { des(prev).ok_or_else(|| { eyre::eyre!( "The previous value under the key {} did not \ @@ -278,10 +304,7 @@ impl DbUpdateType })?; pairs.push((key.to_string(), deserialized.clone())); } else { - pairs.push(( - key.to_string(), - deserialized.clone(), - )); + pairs.push((key.to_string(), deserialized.clone())); } db.write(&Key::from_str(&key).unwrap(), value.to_write()); } @@ -315,27 +338,23 @@ impl Display for DbUpdateType { DbUpdateType::Add { key, value, .. } => { let (formatted, _) = match self.validate() { Ok(f) => f, - Err(e) => return f.write_str(&e.to_string()) + Err(e) => return f.write_str(&e.to_string()), }; f.write_str(&format!( "Write to key: <{}> with {}value: {}", key, - value.is_raw().then_some("raw " ).unwrap_or_default(), + value.is_raw().then_some("raw ").unwrap_or_default(), formatted )) } DbUpdateType::Delete(key) => { f.write_str(&format!("Delete key: <{}>", key)) } - DbUpdateType::RepeatAdd { - pattern, - value, - .. - } => { + DbUpdateType::RepeatAdd { pattern, value, .. } => { let (formatted, _) = match self.validate() { Ok(f) => f, - Err(e) => return f.write_str(&e.to_string()) + Err(e) => return f.write_str(&e.to_string()), }; f.write_str(&format!( "Write to pattern: <{}> with {}value: {}", diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index 832a8ac9dc..29d31477b2 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -17,6 +17,8 @@ pub const MASP_NOTE_COMMITMENT_TREE_KEY: &str = "commitment_tree"; pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; /// Key segment prefix for the convert anchor pub const MASP_CONVERT_ANCHOR_KEY: &str = "convert_anchor"; +/// The key for the token map +pub const MASP_TOKEN_MAP_KEY: &str = "tokens"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; /// The last locked ratio @@ -146,3 +148,10 @@ pub fn masp_convert_anchor_key() -> storage::Key { .push(&MASP_CONVERT_ANCHOR_KEY.to_owned()) .expect("Cannot obtain a storage key") } + +/// Get the key for the masp token map +pub fn masp_token_map_key() -> storage::Key { + storage::Key::from(address::MASP.to_db_key()) + .push(&MASP_TOKEN_MAP_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 070790bd34..52d24a72c3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -27,6 +27,7 @@ namada_migrations = {path = "../crates/migrations"} namada_parameters = { path = "../crates/parameters"} namada_trans_token = {path = "../crates/trans_token", features = ["migrations"]} namada_sdk = { path = "../crates/sdk", default-features = false, features = ["namada-sdk", "std", "testing", "migrations"] } +namada_shielded_token = { path = "../crates/shielded_token" } data-encoding.workspace = true proptest.workspace = true serde_json.workspace = true diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 7acd7a8050..22a56fe2ca 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use data_encoding::HEXLOWER; use namada_apps::wasm_loader::read_wasm; use namada_parameters::storage; @@ -7,6 +9,7 @@ use namada_sdk::migrations; use namada_sdk::storage::Key; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; +use namada_shielded_token::storage_key::masp_token_map_key; #[allow(dead_code)] fn example() { @@ -110,6 +113,15 @@ fn se_migration() { REMOVED_HASH )); + // Conversion state token map + let conversion_token_map: BTreeMap = BTreeMap::new(); + let conversion_token_map_key = masp_token_map_key(); + let conversion_state_token_map_update = migrations::DbUpdateType::Add { + key: conversion_token_map_key, + value: migrations::UpdateValue::force_borsh(conversion_token_map), + force: true, + }; + let updates = [ accounts_update, wasm_name_update, @@ -118,6 +130,7 @@ fn se_migration() { allowlist_update, code_len_update, remove_old_wasm, + conversion_state_token_map_update, ]; let changes = migrations::DbChanges { From 066185e780d74493c21c18b7c090bed9d9d56bb6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Mar 2024 09:09:21 -0400 Subject: [PATCH 78/96] rename from ratio to amount for MASP keys --- crates/shielded_token/src/storage_key.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index 832a8ac9dc..dfcaddd5e1 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -19,15 +19,15 @@ pub const MASP_NOTE_COMMITMENT_ANCHOR_PREFIX: &str = "note_commitment_anchor"; pub const MASP_CONVERT_ANCHOR_KEY: &str = "convert_anchor"; /// Last calculated inflation value handed out pub const MASP_LAST_INFLATION_KEY: &str = "last_inflation"; -/// The last locked ratio -pub const MASP_LAST_LOCKED_AMOUNT_KEY: &str = "last_locked_ratio"; +/// The last locked amount +pub const MASP_LAST_LOCKED_AMOUNT_KEY: &str = "last_locked_amount"; /// The key for the nominal proportional gain of a shielded pool for a given /// asset pub const MASP_KP_GAIN_KEY: &str = "proportional_gain"; /// The key for the nominal derivative gain of a shielded pool for a given asset pub const MASP_KD_GAIN_KEY: &str = "derivative_gain"; -/// The key for the locked ratio target for a given asset -pub const MASP_LOCKED_AMOUNT_TARGET_KEY: &str = "locked_ratio_target"; +/// The key for the locked amount target for a given asset +pub const MASP_LOCKED_AMOUNT_TARGET_KEY: &str = "locked_amount_target"; /// The key for the max reward rate for a given asset pub const MASP_MAX_REWARD_RATE_KEY: &str = "max_reward_rate"; @@ -47,13 +47,13 @@ pub fn masp_max_reward_rate_key(token_addr: &Address) -> storage::Key { .with_segment(MASP_MAX_REWARD_RATE_KEY.to_owned()) } -/// Obtain the locked target ratio key for the given token +/// Obtain the locked target amount key for the given token pub fn masp_locked_amount_target_key(token_addr: &Address) -> storage::Key { parameter_prefix(token_addr) .with_segment(MASP_LOCKED_AMOUNT_TARGET_KEY.to_owned()) } -/// Obtain the storage key for the last locked ratio of a token +/// Obtain the storage key for the last locked amount of a token pub fn masp_last_locked_amount_key(token_address: &Address) -> storage::Key { parameter_prefix(token_address) .with_segment(MASP_LAST_LOCKED_AMOUNT_KEY.to_owned()) From 41a138ed611f392ac0eef2288fcf4558d9d30dd6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Mar 2024 09:09:49 -0400 Subject: [PATCH 79/96] more PoS inflation logging --- crates/proof_of_stake/src/rewards.rs | 9 ++++++--- crates/proof_of_stake/src/tests/test_pos.rs | 7 +++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/proof_of_stake/src/rewards.rs b/crates/proof_of_stake/src/rewards.rs index e9c8ab72e7..05bc89613a 100644 --- a/crates/proof_of_stake/src/rewards.rs +++ b/crates/proof_of_stake/src/rewards.rs @@ -360,6 +360,7 @@ where num_blocks_in_last_epoch, inflation, &staking_token, + total_tokens, )?; // Write new rewards parameters that will be used for the inflation of @@ -388,6 +389,7 @@ pub fn update_rewards_products_and_mint_inflation( num_blocks_in_last_epoch: u64, inflation: token::Amount, staking_token: &Address, + total_native_tokens: token::Amount, ) -> namada_storage::Result<()> where S: StorageRead + StorageWrite, @@ -455,11 +457,12 @@ where let pos_reward_tokens = inflation - reward_tokens_remaining; tracing::info!( "Minting tokens for PoS rewards distribution into the PoS account. \ - Amount: {}. Total inflation: {}, number of blocks in the last epoch: \ - {num_blocks_in_last_epoch}, reward accumulators sum: \ - {accumulators_sum}.", + Amount: {}. Total inflation: {}. Total native supply: {}. Number of \ + blocks in the last epoch: {num_blocks_in_last_epoch}. Reward \ + accumulators sum: {accumulators_sum}.", pos_reward_tokens.to_string_native(), inflation.to_string_native(), + total_native_tokens.to_string_native(), ); credit_tokens(storage, staking_token, &address::POS, pos_reward_tokens)?; diff --git a/crates/proof_of_stake/src/tests/test_pos.rs b/crates/proof_of_stake/src/tests/test_pos.rs index afd40941ed..02e716bb54 100644 --- a/crates/proof_of_stake/src/tests/test_pos.rs +++ b/crates/proof_of_stake/src/tests/test_pos.rs @@ -17,6 +17,7 @@ use proptest::test_runner::Config; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; +use token::storage_key::minted_balance_key; use crate::parameters::testing::arb_pos_params; use crate::parameters::OwnedPosParams; @@ -1414,6 +1415,11 @@ fn test_update_rewards_products_aux(validators: Vec) { .unwrap(); } + let total_native_tokens: token::Amount = s + .read(&minted_balance_key(&staking_token)) + .unwrap() + .expect("Total NAM balance should exist in storage"); + // Distribute inflation into rewards let last_epoch = current_epoch.prev(); let inflation = token::Amount::native_whole(10_000_000); @@ -1424,6 +1430,7 @@ fn test_update_rewards_products_aux(validators: Vec) { num_blocks_in_last_epoch, inflation, &staking_token, + total_native_tokens, ) .unwrap(); From 20cc0411ecfa917a3af0f1130b596216412a6867 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Mar 2024 09:54:30 -0400 Subject: [PATCH 80/96] changelog: add #2894 --- .changelog/unreleased/improvements/2894-aesthetic-edits.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/2894-aesthetic-edits.md diff --git a/.changelog/unreleased/improvements/2894-aesthetic-edits.md b/.changelog/unreleased/improvements/2894-aesthetic-edits.md new file mode 100644 index 0000000000..da356613b7 --- /dev/null +++ b/.changelog/unreleased/improvements/2894-aesthetic-edits.md @@ -0,0 +1,2 @@ +- Some edits to logging and strings + ([\#2894](https://github.com/anoma/namada/pull/2894)) \ No newline at end of file From 5e59456442b573867b69bcbe75bc2d1b7356c164 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 13 Mar 2024 10:54:29 -0700 Subject: [PATCH 81/96] Added more type guardrails --- crates/macros/src/lib.rs | 26 +++++++++++++--------- crates/sdk/src/migrations.rs | 42 +++++++++++++++++++++++++++++++++++ examples/make-db-migration.rs | 6 ++--- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 178fc679aa..0fd7147598 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -367,6 +367,22 @@ pub fn derive_typehash(type_def: TokenStream) -> TokenStream { .into() } +#[proc_macro] +pub fn typehash(type_def: TokenStream) -> TokenStream { + let type_def = syn::parse2::(type_def.into()).expect( + "Could not parse input to `derive_borshdesrializer` as a type.", + ); + match type_def { + syn::Type::Array(_) | syn::Type::Tuple(_) | syn::Type::Path(_) => {} + _ => panic!( + "The `borsh_derserializer!` macro may only be called on arrays, \ + tuples, structs, and enums." + ), + } + let (_, hash) = derive_typehash_inner(&type_def); + quote!(#hash).into() +} + #[inline] fn derive_borsh_deserializer_inner(item_def: TokenStream2) -> TokenStream2 { let mut hasher = sha2::Sha256::new(); @@ -434,16 +450,10 @@ fn derive_borsh_deserialize_inner(item: TokenStream2) -> TokenStream2 { } let (type_hash, hash) = derive_typehash_inner(&type_def); let hex = data_encoding::HEXUPPER.encode(&type_hash); - let wrapper_ident = - syn::Ident::new(&format!("Wrapper_{}", hex), Span::call_site()); let deserializer_ident = syn::Ident::new(&format!("DESERIALIZER_{}", hex), Span::call_site()); quote!( - #[derive(BorshSerialize)] - #[repr(transparent)] - pub struct #wrapper_ident(#type_def); - #[cfg(feature = "migrations")] #[::namada_migrations::distributed_slice(REGISTER_DESERIALIZERS)] static #deserializer_ident: fn() = || { @@ -451,10 +461,6 @@ fn derive_borsh_deserialize_inner(item: TokenStream2) -> TokenStream2 { #type_def::try_from_slice(&bytes).map(|val| format!("{:?}", val)).ok() }); }; - #[cfg(feature = "migrations")] - impl ::namada_migrations::TypeHash for #wrapper_ident { - const HASH: [u8; 32] = #hash; - } ) } diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 01ac427668..539ab0c708 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -12,6 +12,8 @@ use namada_core::storage::Key; #[cfg(feature = "migrations")] use namada_macros::derive_borshdeserializer; #[cfg(feature = "migrations")] +use namada_macros::typehash; +#[cfg(feature = "migrations")] use namada_migrations::TypeHash; #[cfg(feature = "migrations")] use namada_migrations::*; @@ -65,6 +67,16 @@ impl UpdateValue { } } + /// Using a type that is Borsh-serializable but we don't have an + /// implementation for conversion yet. Must provide `force: true` + pub fn wrapped(value: T) -> Self + where + T: BorshSerialize + BorshDeserialize, + SerializeWrapper: TypeHash, + { + SerializeWrapper(value).into() + } + /// Using a type that is Borsh-serializable but we don't have an /// implementation for conversion yet. Must provide `force: true` pub fn force_borsh(value: T) -> Self @@ -403,3 +415,33 @@ derive_borshdeserializer!(Vec::); derive_borshdeserializer!(Vec::); #[cfg(feature = "migrations")] derive_borshdeserializer!(u64); + +#[derive(BorshSerialize, BorshDeserialize)] +#[repr(transparent)] +pub struct SerializeWrapper(T); + +#[cfg(feature = "migrations")] +impl TypeHash + for SerializeWrapper< + std::collections::BTreeMap, + > +{ + const HASH: [u8; 32] = + typehash!(SerializeWrapper>); +} + +#[cfg(feature = "migrations")] +#[distributed_slice(REGISTER_DESERIALIZERS)] +static BTREEMAP_STRING_ADDRESS: fn() = || { + use std::collections::BTreeMap; + + use namada_core::address::Address; + register_deserializer( + SerializeWrapper::>::HASH, + |bytes| { + BTreeMap::::try_from_slice(&bytes) + .map(|val| format!("{:?}", val)) + .ok() + }, + ); +}; diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 22a56fe2ca..1d8a9195b2 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -7,9 +7,9 @@ use namada_sdk::address::Address; use namada_sdk::hash::Hash as CodeHash; use namada_sdk::migrations; use namada_sdk::storage::Key; +use namada_shielded_token::storage_key::masp_token_map_key; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; -use namada_shielded_token::storage_key::masp_token_map_key; #[allow(dead_code)] fn example() { @@ -118,8 +118,8 @@ fn se_migration() { let conversion_token_map_key = masp_token_map_key(); let conversion_state_token_map_update = migrations::DbUpdateType::Add { key: conversion_token_map_key, - value: migrations::UpdateValue::force_borsh(conversion_token_map), - force: true, + value: migrations::UpdateValue::wrapped(conversion_token_map), + force: false, }; let updates = [ From 6e5de5be8e09f7ddd630e5811972141b0af39006 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 13 Mar 2024 19:56:53 +0100 Subject: [PATCH 82/96] Rollback `udpate_epoch_blocks_delay` --- crates/apps/src/lib/node/ledger/storage/rocksdb.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 707800aee2..967a4dab4d 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -12,11 +12,13 @@ //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next //! epoch can start -//! - `replay_protection`: hashes of the processed transactions +//! - `update_epoch_blocks_delay`: number of missing blocks before updating +//! PoS with CometBFT //! - `pred`: predecessor values of the top-level keys of the same name //! - `tx_queue` //! - `next_epoch_min_start_height` //! - `next_epoch_min_start_time` +//! - `update_epoch_blocks_delay` //! - `conversion_state`: MASP conversion state //! - `subspace`: accounts sub-spaces //! - `{address}/{dyn}`: any byte data associated with accounts @@ -515,6 +517,7 @@ impl RocksDB { for metadata_key in [ "next_epoch_min_start_height", "next_epoch_min_start_time", + "update_epoch_blocks_delay", "tx_queue", ] { let previous_key = format!("pred/{}", metadata_key); @@ -528,7 +531,7 @@ impl RocksDB { // NOTE: we cannot restore the "pred/" keys themselves since we // don't have their predecessors in storage, but there's no need to // since we cannot do more than one rollback anyway because of - // Tendermint. + // CometBFT. } // Revert conversion state if the epoch had been changed From 6de6525fd372c1685eb567b45fd477163e7abcac Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Wed, 13 Mar 2024 20:24:47 +0100 Subject: [PATCH 83/96] added e2e test --- .github/workflows/scripts/e2e.json | 1 + crates/tests/src/e2e/ledger_tests.rs | 69 ++++++++++++++++++++++++++++ wasm/checksums.json | 48 +++++++++---------- 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index d9b43599d9..a139c9f6c0 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -13,6 +13,7 @@ "e2e::ledger_tests::implicit_account_reveal_pk": 30, "e2e::ledger_tests::pos_init_validator": 40, "e2e::ledger_tests::proposal_offline": 21, + "e2e::ledger_tests::rollback": 21, "e2e::ledger_tests::pgf_governance_proposal": 320, "e2e::ledger_tests::proposal_submission": 200, "e2e::ledger_tests::proposal_change_shielded_reward": 200, diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 17c85d5fd7..86e8cad676 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -4141,3 +4141,72 @@ where Ok(result) } + +#[test] +fn rollback() -> Result<()> { + let test = setup::single_node_net()?; + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + None, + ); + + // 1. Run the ledger node + let mut ledger = + start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))?; + + let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); + + ledger.exp_regex("Committed block hash: .*,")?; + + let ledger = ledger.background(); + + let txs_args = vec![ + // 2. Submit a token transfer tx (from an established account) + vec![ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + ]; + + for tx_args in &txs_args { + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string(TX_APPLIED_SUCCESS)?; + client.assert_success(); + } + + let mut ledger = ledger.foreground(); + ledger.exp_regex("Committed block hash: ")?; + ledger.interrupt()?; + drop(ledger); + + let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; + let (_, matched_one) = ledger.exp_regex("Last state root hash: .*, height: .*")?; + + ledger.exp_regex("Committed block hash: .*,")?; + ledger.interrupt()?; + drop(ledger); + + let mut rollback = run_as!(test, Who::Validator(0), Bin::Node, &["ledger", "rollback"], Some(40))?; + rollback.assert_success(); + + let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; + let (_, matched_two) = ledger.exp_regex("Last state root hash: .*, height: .*")?; + + assert_eq!(matched_one, matched_two); + + Ok(()) +} \ No newline at end of file diff --git a/wasm/checksums.json b/wasm/checksums.json index 976b6b08dd..d1460cd2be 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,26 +1,26 @@ { - "tx_become_validator.wasm": "tx_become_validator.9e1581a717be270c9b336e8e4a05902b545656bd4bb2ef7e4b076ba7799df881.wasm", - "tx_bond.wasm": "tx_bond.e4aef7917fdc861b395bdf794940936c6b3475bd3fb7b19ebc6739d21496dbc8.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.aee3981b50e51d53ca77dcb7e1804f561172a4a6be988d2c4b582cae68f880fa.wasm", - "tx_change_consensus_key.wasm": "tx_change_consensus_key.4c91bd00e96ba51f0eb544a22bc633af42a50cf6b80f9250a2a53c59c570ffb6.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.657b039436f54c0c909eb8b9ad89597aaf97d8a3f7db0815adb4ec7783cb276f.wasm", - "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.35e648a95ad6d08d505c359d1f2448ebfe0853564e7f4504df1df7ac31037d0f.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.8435a4c9c5d4f4ac934f1bbc002adaf97dd6ec484caa04d8324497bf11787812.wasm", - "tx_deactivate_validator.wasm": "tx_deactivate_validator.41370c15473827b391ecd2c12bef3fedd461dc12e1e5e81914113d032ea3fc3a.wasm", - "tx_ibc.wasm": "tx_ibc.331fc15363137fb5f5c2236be928c50d7240ea5b31311b4accafffd3adb5fb96.wasm", - "tx_init_account.wasm": "tx_init_account.bff39a666b66bdabb89e2a26717e0e044254e27603df4909256e42ace1cd2531.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bd852c9a30b71570ffb3a58e3d4e37b749c793bc1def46501b8b71a2ae6740bd.wasm", - "tx_reactivate_validator.wasm": "tx_reactivate_validator.3aad7fdfcdaea68e7f67e3e02b0a211caa3694f8782342d13afb00e86a8a7663.wasm", - "tx_redelegate.wasm": "tx_redelegate.13c9f7a4290dec2821fc7e23cd74926c6cc4a3adfef36e084aed41f262f2cb12.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.3b3172994453feb494506af62567368bdb3619192ceaed0c73cf39418dc68e1e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b540eaeff830b227d126e5ff246d978e87f50fcdb0e2fc22155563875a2fec91.wasm", - "tx_transfer.wasm": "tx_transfer.0ec5f718c7f695937402b25452b59b174021f42567b6e4cb22bec4427b913d08.wasm", - "tx_unbond.wasm": "tx_unbond.1afd1145d1c43a093882d9cc2518986e7f46a0192b006c17fd997c19ab405a43.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.7efdc35af561c5846d73b83e8f8dac4cadf803e134361083658f8b96caaa2ffa.wasm", - "tx_update_account.wasm": "tx_update_account.99da4d1cc91b1819a93c97e647bfecbefc34bcc153bc6ee31e09311c4239ce37.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.c197b2c43016adff69c41ca5e33c938971c648777e42afdf2bef688a2c827685.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1ffc6aed4848972a1cdec9f3188ae1e6737ca824183f217396ded631efe7baaa.wasm", - "tx_withdraw.wasm": "tx_withdraw.cd0cfdbc9d7454864eb1ff12ae971b995b9deb04df4b450f1364c5081853cefb.wasm", - "vp_implicit.wasm": "vp_implicit.64511b9db10869a77342071a58129695ed55ccab12383602b5002d333972b8a5.wasm", - "vp_user.wasm": "vp_user.3497e62040090bf6e39fb8b9a2dc7478abb95d0201ab5dd58bc66119c84565ce.wasm" + "tx_become_validator.wasm": "tx_become_validator.48fe3e03530d5491b157c1887b2b277bd925ba8e5f95ae316451300fde12319e.wasm", + "tx_bond.wasm": "tx_bond.94d9920fba3789158900425d0ea778dfd9bcfc9a17a2848b251f2f35f07db136.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.5d29a4c5095d3769391df24e04f1b8820faa1e85ce0f4ba4fd401609f3a968d3.wasm", + "tx_change_consensus_key.wasm": "tx_change_consensus_key.edb63363ca6bd5ca86d0dd7d7fd71f1975d624e3a219b0c403e49fa855900b16.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.43d8ea26e377ae8af98dafaa5758077b204a3d81ebbcc55113f092001050acee.wasm", + "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.b548320f4500ae2653d45ed3789c7ddc97c3d96d30d7359642a45caa8acb9f6c.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.70cd2e086d3a5dfaa287a14d7679e8e174ef3b4f5951752e3f9751c826c85934.wasm", + "tx_deactivate_validator.wasm": "tx_deactivate_validator.06093d25fa11ae8e21183d7d6e746f8ffeb71e952978d247e206f1ea74987008.wasm", + "tx_ibc.wasm": "tx_ibc.537f4d0604a1fd3845bcb184f92054c89420094944707f60ddd3accc7e086807.wasm", + "tx_init_account.wasm": "tx_init_account.712dae9b52f2c88704fa7b3d9b0f382375c7c96d5a664c091cf2c2a155ae449b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.21d9ef56bfec09928c27997bbab727bcded9b5f23d7b3b1d92b207dcfb339b80.wasm", + "tx_reactivate_validator.wasm": "tx_reactivate_validator.1e1bb1f90e6d2b37571d2632f6b04e558edeadba43bb116e17fcca609d97adf7.wasm", + "tx_redelegate.wasm": "tx_redelegate.9c41750435db5dbcf327099e8163ff03afdbdb4d6a31a1724314f52c69c6387a.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.89aef0322689b36ad615b81d2f17662f8cf8f42200a7694920188f6c7697c11a.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.e67e1a1e8139783675aabc8959703472b8e50f8705c7539298cbddbda274d11a.wasm", + "tx_transfer.wasm": "tx_transfer.c4d6a2724918f3d92c55a9025a605863daad04ebc92c1b1aadcdf87756e5b2a7.wasm", + "tx_unbond.wasm": "tx_unbond.d7255b1ee52208bfd37f052e13766fad743b1ab2bcab704bd945df486dc4af01.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.aa946d1eecdaa8c0789ba0b86392688af43d510202c0beed34093d9846e66587.wasm", + "tx_update_account.wasm": "tx_update_account.39226e2856466ee79d12be4c36320af0a6625ec53fed9fd4be597f40dca4ec89.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.ec1b7213f686e4c038552b6a8ffd646316c34f92b0afbf6fd8c0373bdc53ae44.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.58d27b1e4728165c8719431adc1f82b4920a34ebe24ac5721a76242cb3277ea5.wasm", + "tx_withdraw.wasm": "tx_withdraw.8809b5f3428a7b18656bed66a02dbc8a6a3a58111676921fd759efd6f69b5b22.wasm", + "vp_implicit.wasm": "vp_implicit.53c564192f3b53c51107519843c78f76b5a27111509d78201a7b2037e4e5dd90.wasm", + "vp_user.wasm": "vp_user.33a6d043bc34186cde8b79f815892bd71f26ff4b6afe38a546e1cf9c5dec42ce.wasm" } \ No newline at end of file From be5ef4c3e77a32100386f7914d8568d9c0ac079b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Wed, 13 Mar 2024 20:31:29 +0100 Subject: [PATCH 84/96] fmt --- crates/tests/src/e2e/ledger_tests.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 86e8cad676..b074fcb170 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -4194,19 +4194,27 @@ fn rollback() -> Result<()> { drop(ledger); let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; - let (_, matched_one) = ledger.exp_regex("Last state root hash: .*, height: .*")?; + let (_, matched_one) = + ledger.exp_regex("Last state root hash: .*, height: .*")?; ledger.exp_regex("Committed block hash: .*,")?; ledger.interrupt()?; drop(ledger); - let mut rollback = run_as!(test, Who::Validator(0), Bin::Node, &["ledger", "rollback"], Some(40))?; + let mut rollback = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "rollback"], + Some(40) + )?; rollback.assert_success(); let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; - let (_, matched_two) = ledger.exp_regex("Last state root hash: .*, height: .*")?; + let (_, matched_two) = + ledger.exp_regex("Last state root hash: .*, height: .*")?; assert_eq!(matched_one, matched_two); - + Ok(()) -} \ No newline at end of file +} From bf03525ef7c4de04ccbf4d4fb131e137b12cc53a Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 13 Mar 2024 17:50:57 -0400 Subject: [PATCH 85/96] specify the column family --- Cargo.lock | 1 + .../src/lib/node/ledger/storage/rocksdb.rs | 77 ++++++++++++------- crates/core/src/storage.rs | 39 ++++++++++ crates/sdk/src/migrations.rs | 74 +++++++++++------- crates/storage/Cargo.toml | 1 + crates/storage/src/db.rs | 5 +- crates/storage/src/mockdb.rs | 5 +- examples/make-db-migration.rs | 21 +++-- 8 files changed, 157 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b5bedb0a2..8ebd4d92d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4745,6 +4745,7 @@ dependencies = [ "namada_replay_protection", "namada_tx", "regex", + "serde 1.0.193", "thiserror", "tracing", ] diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 74ea817f58..3880cb736e 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -64,6 +64,9 @@ use namada::state::{ DbResult as Result, MerkleTreeStoresRead, PatternIterator, PrefixIterator, StoreType, DB, }; +use namada::storage::{ + DbColFam, BLOCK_CF, DIFFS_CF, REPLAY_PROTECTION_CF, STATE_CF, SUBSPACE_CF, +}; use namada::token::ConversionState; use namada_sdk::migrations::DBUpdateVisitor; use rayon::prelude::*; @@ -82,13 +85,6 @@ use crate::config::utils::num_of_threads; const ENV_VAR_ROCKSDB_COMPACTION_THREADS: &str = "NAMADA_ROCKSDB_COMPACTION_THREADS"; -/// Column family names -const SUBSPACE_CF: &str = "subspace"; -const DIFFS_CF: &str = "diffs"; -const STATE_CF: &str = "state"; -const BLOCK_CF: &str = "block"; -const REPLAY_PROTECTION_CF: &str = "replay_protection"; - const OLD_DIFF_PREFIX: &str = "old"; const NEW_DIFF_PREFIX: &str = "new"; @@ -1542,6 +1538,7 @@ impl DB for RocksDB { &self, batch: &mut Self::WriteBatch, height: Option, + cf: &DbColFam, key: &Key, new_value: impl AsRef<[u8]>, ) -> Result<()> { @@ -1573,20 +1570,20 @@ impl DB for RocksDB { let val = new_value.as_ref(); - // update subspace value - let subspace_cf = self.get_column_family(SUBSPACE_CF)?; - let subspace_key = key.to_string(); + // Write the new key-val in the Db column family + let cf_name = self.get_column_family(cf.to_str())?; + batch.0.put_cf(cf_name, key.to_string(), val); - batch.0.put_cf(subspace_cf, subspace_key, val); + // If the CF is subspace, additionally update the diffs + if cf == &DbColFam::SUBSPACE { + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let diffs_key = Key::from(last_height.to_db_key()) + .with_segment("new".to_owned()) + .join(key) + .to_string(); - // update value stored in diffs - let diffs_cf = self.get_column_family(DIFFS_CF)?; - let diffs_key = Key::from(last_height.to_db_key()) - .with_segment("new".to_owned()) - .join(key) - .to_string(); - - batch.0.put_cf(diffs_cf, diffs_key, val); + batch.0.put_cf(diffs_cf, diffs_key, val); + } Ok(()) } @@ -1613,19 +1610,27 @@ impl<'db> RocksDBUpdateVisitor<'db> { } impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { - fn read(&self, key: &Key) -> Option> { - self.db - .read_subspace_val(key) - .expect("Failed to read from storage") + fn read(&self, key: &Key, cf: &DbColFam) -> Option> { + match cf { + DbColFam::SUBSPACE => self + .db + .read_subspace_val(key) + .expect("Failed to read from storage"), + _ => { + let cf_str = cf.to_str(); + let cf = self.db.get_column_family(cf_str).expect("Failed to delete key from storage"); + self.db.0.get_cf(cf, key.to_string()).expect("Failed to get key from storage") + } + } } - fn write(&mut self, key: &Key, value: impl AsRef<[u8]>) { + fn write(&mut self, key: &Key, cf: &DbColFam, value: impl AsRef<[u8]>) { self.db - .overwrite_entry(&mut self.batch, None, key, value) + .overwrite_entry(&mut self.batch, None, cf, key, value) .expect("Failed to overwrite a key in storage") } - fn delete(&mut self, key: &Key) { + fn delete(&mut self, key: &Key, cf: &DbColFam) { let last_height: BlockHeight = { let state_cf = self.db.get_column_family(STATE_CF).unwrap(); @@ -1645,9 +1650,23 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { }) .unwrap() }; - self.db - .batch_delete_subspace_val(&mut self.batch, last_height, key, true) - .expect("Failed to delete key from storage"); + match cf { + DbColFam::SUBSPACE => { + self.db + .batch_delete_subspace_val( + &mut self.batch, + last_height, + key, + true, + ) + .expect("Failed to delete key from storage"); + } + _ => { + let cf_str = cf.to_str(); + let cf = self.db.get_column_family(cf_str).expect("Failed to get valid column family from name"); + self.batch.0.delete_cf(cf, key.to_string()); + } + }; } fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)> { diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index 15e0eeec5d..d6d6dc92c4 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -79,6 +79,45 @@ pub const WASM_CODE_LEN_PREFIX: &str = "len"; /// The reserved storage key prefix for wasm code hashes pub const WASM_HASH_PREFIX: &str = "hash"; +#[derive(Clone, Serialize, Deserialize, PartialEq)] +/// Storage column families +pub enum DbColFam { + /// Subspace + SUBSPACE, + /// Block + BLOCK, + /// State + STATE, + /// Diffs + DIFFS, + /// Replay protection + REPLAYPROT, +} + +/// Subspace column family name +pub const SUBSPACE_CF: &str = "subspace"; +/// Diffs column family name +pub const DIFFS_CF: &str = "diffs"; +/// State column family name +pub const STATE_CF: &str = "state"; +/// Block column family name +pub const BLOCK_CF: &str = "block"; +/// Replay protection column family name +pub const REPLAY_PROTECTION_CF: &str = "replay_protection"; + +impl DbColFam { + /// Get the name of the column family + pub fn to_str(&self) -> &str { + match self { + DbColFam::SUBSPACE => SUBSPACE_CF, + DbColFam::BLOCK => BLOCK_CF, + DbColFam::STATE => STATE_CF, + DbColFam::DIFFS => DIFFS_CF, + DbColFam::REPLAYPROT => REPLAY_PROTECTION_CF, + } + } +} + /// Transaction index within block. #[derive( Default, diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index 539ab0c708..ecb2fbb0bc 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -17,6 +17,7 @@ use namada_macros::typehash; use namada_migrations::TypeHash; #[cfg(feature = "migrations")] use namada_migrations::*; +use namada_storage::DbColFam; use regex::Regex; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -26,11 +27,12 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; const PRINTLN_CUTOFF: usize = 300; pub trait DBUpdateVisitor { - fn read(&self, key: &Key) -> Option>; - fn write(&mut self, key: &Key, value: impl AsRef<[u8]>); - fn delete(&mut self, key: &Key); + fn read(&self, key: &Key, cf: &DbColFam) -> Option>; + fn write(&mut self, key: &Key, cf: &DbColFam, value: impl AsRef<[u8]>); + fn delete(&mut self, key: &Key, cf: &DbColFam); fn get_pattern(&self, pattern: Regex) -> Vec<(String, Vec)>; } + #[derive(Clone, BorshSerialize, BorshDeserialize)] enum UpdateBytes { Raw { @@ -172,16 +174,18 @@ impl<'de> Deserialize<'de> for UpdateValue { pub enum DbUpdateType { Add { key: Key, + cf: DbColFam, value: UpdateValue, force: bool, }, - Delete(Key), + Delete(Key, DbColFam), RepeatAdd { pattern: String, + cf: DbColFam, value: UpdateValue, force: bool, }, - RepeatDelete(String), + RepeatDelete(String, DbColFam), } #[cfg(feature = "migrations")] @@ -190,9 +194,9 @@ impl DbUpdateType { pub fn pattern(&self) -> String { match self { DbUpdateType::Add { key, .. } => key.to_string(), - DbUpdateType::Delete(key) => key.to_string(), + DbUpdateType::Delete(key, ..) => key.to_string(), DbUpdateType::RepeatAdd { pattern, .. } => pattern.to_string(), - DbUpdateType::RepeatDelete(pattern) => pattern.to_string(), + DbUpdateType::RepeatDelete(pattern, ..) => pattern.to_string(), } } @@ -266,7 +270,7 @@ impl DbUpdateType { Ok((deserialized, deserializer)) } } - DbUpdateType::Delete(_) | DbUpdateType::RepeatDelete(_) => { + DbUpdateType::Delete(_, _) | DbUpdateType::RepeatDelete(_, _) => { Ok((String::default(), None)) } } @@ -280,9 +284,11 @@ impl DbUpdateType { db: &mut DB, ) -> eyre::Result { match self { - Self::Add { key, value, .. } => { + Self::Add { key, cf, value, .. } => { let (deserialized, deserializer) = self.validate()?; - if let (Some(prev), Some(des)) = (db.read(key), deserializer) { + if let (Some(prev), Some(des)) = + (db.read(key, cf), deserializer) + { des(prev).ok_or_else(|| { eyre::eyre!( "The previous value under the key {} did not have \ @@ -292,14 +298,16 @@ impl DbUpdateType { ) })?; } - db.write(key, &value.to_write()); + db.write(key, cf, &value.to_write()); Ok(UpdateStatus::Add(vec![(key.to_string(), deserialized)])) } - Self::Delete(key) => { - db.delete(key); + Self::Delete(key, cf) => { + db.delete(key, cf); Ok(UpdateStatus::Deleted(vec![key.to_string()])) } - DbUpdateType::RepeatAdd { pattern, value, .. } => { + DbUpdateType::RepeatAdd { + pattern, cf, value, .. + } => { let pattern = Regex::new(pattern).unwrap(); let mut pairs = vec![]; let (deserialized, deserializer) = self.validate()?; @@ -318,17 +326,21 @@ impl DbUpdateType { } else { pairs.push((key.to_string(), deserialized.clone())); } - db.write(&Key::from_str(&key).unwrap(), value.to_write()); + db.write( + &Key::from_str(&key).unwrap(), + cf, + value.to_write(), + ); } Ok(UpdateStatus::Add(pairs)) } - DbUpdateType::RepeatDelete(pattern) => { + DbUpdateType::RepeatDelete(pattern, cf) => { let pattern = Regex::new(pattern).unwrap(); Ok(UpdateStatus::Deleted( db.get_pattern(pattern.clone()) .into_iter() .map(|(key, _)| { - db.delete(&Key::from_str(&key).unwrap()); + db.delete(&Key::from_str(&key).unwrap(), cf); key }) .collect(), @@ -347,37 +359,45 @@ pub struct DbChanges { impl Display for DbUpdateType { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { - DbUpdateType::Add { key, value, .. } => { + DbUpdateType::Add { key, cf, value, .. } => { let (formatted, _) = match self.validate() { Ok(f) => f, Err(e) => return f.write_str(&e.to_string()), }; f.write_str(&format!( - "Write to key: <{}> with {}value: {}", + "Write to key in {} CF: <{}> with {}value: {}", + cf.to_str(), key, value.is_raw().then_some("raw ").unwrap_or_default(), formatted )) } - DbUpdateType::Delete(key) => { - f.write_str(&format!("Delete key: <{}>", key)) - } - DbUpdateType::RepeatAdd { pattern, value, .. } => { + DbUpdateType::Delete(key, cf) => f.write_str(&format!( + "Delete key in {} CF: <{}>", + cf.to_str(), + key + )), + DbUpdateType::RepeatAdd { + pattern, cf, value, .. + } => { let (formatted, _) = match self.validate() { Ok(f) => f, Err(e) => return f.write_str(&e.to_string()), }; f.write_str(&format!( - "Write to pattern: <{}> with {}value: {}", + "Write to pattern in {} CF: <{}> with {}value: {}", + cf.to_str(), pattern, value.is_raw().then_some("raw ").unwrap_or_default(), formatted, )) } - DbUpdateType::RepeatDelete(pattern) => { - f.write_str(&format!("Delete pattern: <{}>", pattern)) - } + DbUpdateType::RepeatDelete(pattern, cf) => f.write_str(&format!( + "Delete pattern in {} CF: <{}>", + cf.to_str(), + pattern + )), } } } diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 7889d95251..cfb60ac1ce 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -34,6 +34,7 @@ namada_tx = { path = "../tx" } borsh.workspace = true itertools.workspace = true linkme = {workspace = true, optional = true} +serde.workspace = true thiserror.workspace = true tracing.workspace = true regex = "1.10.2" diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 4ae5d35b0c..184f1ff87b 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -3,8 +3,8 @@ use std::fmt::Debug; use namada_core::address::EstablishedAddressGen; use namada_core::hash::{Error as HashError, Hash}; use namada_core::storage::{ - BlockHash, BlockHeight, BlockResults, Epoch, Epochs, EthEventsQueue, - Header, Key, + BlockHash, BlockHeight, BlockResults, DbColFam, Epoch, Epochs, + EthEventsQueue, Header, Key, }; use namada_core::time::DateTimeUtc; use namada_core::{ethereum_events, ethereum_structs}; @@ -268,6 +268,7 @@ pub trait DB: Debug { &self, batch: &mut Self::WriteBatch, height: Option, + cf: &DbColFam, key: &Key, new_value: impl AsRef<[u8]>, ) -> Result<()>; diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index b3740be8a9..f854a7820b 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -10,8 +10,8 @@ use itertools::Either; use namada_core::borsh::{BorshDeserialize, BorshSerializeExt}; use namada_core::hash::Hash; use namada_core::storage::{ - BlockHeight, BlockResults, Epoch, EthEventsQueue, Header, Key, KeySeg, - KEY_SEGMENT_SEPARATOR, + BlockHeight, BlockResults, DbColFam, Epoch, EthEventsQueue, Header, Key, + KeySeg, KEY_SEGMENT_SEPARATOR, }; use namada_core::time::DateTimeUtc; use namada_core::{decode, encode, ethereum_events, ethereum_structs}; @@ -680,6 +680,7 @@ impl DB for MockDB { &self, _batch: &mut Self::WriteBatch, _height: Option, + _cf: &DbColFam, _key: &Key, _new_value: impl AsRef<[u8]>, ) -> Result<()> { diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 1d8a9195b2..787a141652 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -6,7 +6,7 @@ use namada_parameters::storage; use namada_sdk::address::Address; use namada_sdk::hash::Hash as CodeHash; use namada_sdk::migrations; -use namada_sdk::storage::Key; +use namada_sdk::storage::{DbColFam, Key}; use namada_shielded_token::storage_key::masp_token_map_key; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; @@ -26,15 +26,17 @@ fn example() { let updates = [ migrations::DbUpdateType::Add { key: balance_key(&nam, &person), + cf: DbColFam::SUBSPACE, value: amount.into(), force: false, }, migrations::DbUpdateType::Add { key: minted_key, + cf: DbColFam::SUBSPACE, value: minted_value.into(), force: false, }, - migrations::DbUpdateType::RepeatDelete(apfel), + migrations::DbUpdateType::RepeatDelete(apfel, DbColFam::SUBSPACE), ]; let changes = migrations::DbChanges { changes: updates.into_iter().collect(), @@ -59,6 +61,7 @@ fn se_migration() { let account_vp_str = "#tnam[a-z,0-9]*\\/\\?".to_string(); let accounts_update = migrations::DbUpdateType::RepeatAdd { pattern: account_vp_str, + cf: DbColFam::SUBSPACE, value: migrations::UpdateValue::raw(vp_hash), force: false, }; @@ -68,11 +71,13 @@ fn se_migration() { let wasm_hash_key = Key::wasm_hash("vp_user.wasm"); let wasm_name_update = migrations::DbUpdateType::Add { key: wasm_name_key, + cf: DbColFam::SUBSPACE, value: migrations::UpdateValue::raw(vp_hash), force: false, }; let wasm_hash_update = migrations::DbUpdateType::Add { key: wasm_hash_key, + cf: DbColFam::SUBSPACE, value: migrations::UpdateValue::raw(vp_hash), force: false, }; @@ -81,6 +86,7 @@ fn se_migration() { let code_key = Key::wasm_code(&vp_hash); let code_update = migrations::DbUpdateType::Add { key: code_key, + cf: DbColFam::SUBSPACE, value: migrations::UpdateValue::raw(bytes.clone()), force: false, }; @@ -89,6 +95,7 @@ fn se_migration() { let len_key = Key::wasm_code_len(&vp_hash); let code_len_update = migrations::DbUpdateType::Add { key: len_key, + cf: DbColFam::SUBSPACE, value: (bytes.len() as u64).into(), force: false, }; @@ -103,21 +110,23 @@ fn se_migration() { ]; let allowlist_update = migrations::DbUpdateType::Add { key: vp_allowlist_key, + cf: DbColFam::SUBSPACE, value: new_vp_allowlist.into(), force: false, }; // remove keys associated with old wasm - let remove_old_wasm = migrations::DbUpdateType::RepeatDelete(format!( - "/wasm/[a-z]+/{}", - REMOVED_HASH - )); + let remove_old_wasm = migrations::DbUpdateType::RepeatDelete( + format!("/wasm/[a-z]+/{}", REMOVED_HASH), + DbColFam::SUBSPACE, + ); // Conversion state token map let conversion_token_map: BTreeMap = BTreeMap::new(); let conversion_token_map_key = masp_token_map_key(); let conversion_state_token_map_update = migrations::DbUpdateType::Add { key: conversion_token_map_key, + cf: DbColFam::SUBSPACE, value: migrations::UpdateValue::wrapped(conversion_token_map), force: false, }; From a52a6b59774ea9276c36fdf655cf20463cca3b0e Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 14 Mar 2024 02:17:24 -0700 Subject: [PATCH 86/96] [fix]: Added the column family to the query-db cli command --- crates/apps/src/bin/namada-node/cli.rs | 7 ++- crates/apps/src/lib/cli.rs | 57 ++++++++++--------- crates/apps/src/lib/node/ledger/mod.rs | 43 +++++++------- .../src/lib/node/ledger/storage/rocksdb.rs | 15 ++++- crates/core/src/storage.rs | 23 +++++++- 5 files changed, 92 insertions(+), 53 deletions(-) diff --git a/crates/apps/src/bin/namada-node/cli.rs b/crates/apps/src/bin/namada-node/cli.rs index 2c40f135d8..5e63a6bae2 100644 --- a/crates/apps/src/bin/namada-node/cli.rs +++ b/crates/apps/src/bin/namada-node/cli.rs @@ -84,7 +84,12 @@ pub fn main() -> Result<()> { } let chain_ctx = ctx.take_chain_or_exit(); #[cfg(feature = "migrations")] - ledger::query_db(chain_ctx.config.ledger, &args.key_hash_pairs); + ledger::query_db( + chain_ctx.config.ledger, + &args.key, + &args.hash, + &args.cf, + ); } }, cmds::NamadaNode::Config(sub) => match sub { diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index c3e6e28b42..38676c3f47 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -3104,6 +3104,11 @@ pub mod args { arg_opt("success-sleep"); pub const DATA_PATH_OPT: ArgOpt = arg_opt("data-path"); pub const DATA_PATH: Arg = arg("data-path"); + pub const DB_KEY: Arg = arg("db-key"); + pub const DB_COLUMN_FAMILY: ArgDefault = arg_default( + "db_column_family", + DefaultFn(|| storage::SUBSPACE_CF.to_string()), + ); pub const DECRYPT: ArgFlag = flag("decrypt"); pub const DESCRIPTION_OPT: ArgOpt = arg_opt("description"); pub const DISPOSABLE_SIGNING_KEY: ArgFlag = flag("disposable-gas-payer"); @@ -3160,6 +3165,7 @@ pub mod args { pub const GENESIS_VALIDATOR_ADDRESS: Arg = arg("validator"); pub const HALT_ACTION: ArgFlag = flag("halt"); + pub const HASH: Arg = arg("hash"); pub const HASH_LIST: Arg = arg("hash-list"); pub const HD_DERIVATION_PATH: ArgDefault = arg_default("hd-path", DefaultFn(|| "default".to_string())); @@ -3174,8 +3180,7 @@ pub mod args { scheme is not supplied, it is assumed to be TCP."; pub const CONFIG_RPC_LEDGER_ADDRESS: ArgDefaultFromCtx = arg_default_from_ctx("node", DefaultFn(|| "".to_string())); - pub const KEY_HASH_LIST: ArgMulti = - arg_multi("key-hash-list"); + pub const LEDGER_ADDRESS: ArgDefault = arg("node").default(DefaultFn(|| { let raw = "http://127.0.0.1:26657"; @@ -3492,38 +3497,36 @@ pub mod args { #[derive(Clone, Debug)] pub struct LedgerQueryDb { - pub key_hash_pairs: Vec<(storage::Key, [u8; 32])>, + pub key: storage::Key, + pub hash: [u8; 32], + pub cf: storage::DbColFam, } impl Args for LedgerQueryDb { fn parse(matches: &ArgMatches) -> Self { - let pairs = KEY_HASH_LIST - .parse(matches) - .into_iter() - .map(|pair| { - let (hash, key) = pair - .split_once(':') - .expect("Key hash pairs must be colon separated."); - let hash: [u8; 32] = HEXUPPER - .decode(hash.to_uppercase().as_bytes()) - .unwrap() - .try_into() - .unwrap(); - let key = storage::Key::parse(key).unwrap(); - (key, hash) - }) - .collect(); - - Self { - key_hash_pairs: pairs, - } + let key = storage::Key::parse(DB_KEY.parse(matches)).unwrap(); + let hex_hash = HASH.parse(matches); + let hash: [u8; 32] = HEXUPPER + .decode(hex_hash.to_uppercase().as_bytes()) + .unwrap() + .try_into() + .unwrap(); + let cf = + storage::DbColFam::from_str(&DB_COLUMN_FAMILY.parse(matches)) + .unwrap(); + Self { key, hash, cf } } fn def(app: App) -> App { - app.arg(KEY_HASH_LIST.def().help( - "A comma separated list of entries of the form \ - :.", - )) + app.arg(DB_KEY.def().help("A database key to query")) + .arg(HASH.def().help( + "The hex encoded type hash of the value contained under \ + the provided key.", + )) + .arg(DB_COLUMN_FAMILY.def().help( + "The column family under which the key is kept. Defaults \ + to the subspace column family if none is provided.", + )) } } diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 26a4ec961f..7675578d7d 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -19,6 +19,7 @@ use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::governance::storage::keys as governance_storage; use namada::state::DB; +use namada::storage::DbColFam; use namada::tendermint::abci::request::CheckTxKind; use namada_sdk::state::StateRead; use once_cell::unsync::Lazy; @@ -231,30 +232,32 @@ pub fn dump_db( } #[cfg(feature = "migrations")] -pub fn query_db(config: config::Ledger, keys: &[(Key, [u8; 32])]) { +pub fn query_db( + config: config::Ledger, + key: &Key, + type_hash: &[u8; 32], + cf: &DbColFam, +) { + use namada_sdk::migrations::DBUpdateVisitor; let chain_id = config.chain_id; let db_path = config.shell.db_dir(&chain_id); let db = storage::PersistentDB::open(db_path, None); - for (key, type_hash) in keys { - let bytes = db.read_subspace_val(key).unwrap(); - if let Some(bytes) = bytes { - let deserializer = namada_migrations::get_deserializer(type_hash) - .unwrap_or_else(|| { - panic!( - "Could not find a deserializer for the type provided \ - with key <{}>", - key - ) - }); - let value = deserializer(bytes).unwrap_or_else(|| { - panic!("Unable to deserialize the value under key <{}>", key) - }); - tracing::info!("Key <{}>: {}", key, value); - } else { - tracing::info!("Key <{}> is not present in storage.", key); - } - } + let db_visitor = storage::RocksDBUpdateVisitor::new(&db); + let bytes = db_visitor.read(key, cf).unwrap(); + + let deserializer = namada_migrations::get_deserializer(type_hash) + .unwrap_or_else(|| { + panic!( + "Could not find a deserializer for the type provided with key \ + <{}>", + key + ) + }); + let value = deserializer(bytes).unwrap_or_else(|| { + panic!("Unable to deserialize the value under key <{}>", key) + }); + tracing::info!("Key <{}>: {}", key, value); } /// Change the funds of an account in-place. Use with diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 3880cb736e..beeff5fbc6 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1618,8 +1618,14 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { .expect("Failed to read from storage"), _ => { let cf_str = cf.to_str(); - let cf = self.db.get_column_family(cf_str).expect("Failed to delete key from storage"); - self.db.0.get_cf(cf, key.to_string()).expect("Failed to get key from storage") + let cf = self + .db + .get_column_family(cf_str) + .expect("Failed to read column family from storage"); + self.db + .0 + .get_cf(cf, key.to_string()) + .expect("Failed to get key from storage") } } } @@ -1663,7 +1669,10 @@ impl<'db> DBUpdateVisitor for RocksDBUpdateVisitor<'db> { } _ => { let cf_str = cf.to_str(); - let cf = self.db.get_column_family(cf_str).expect("Failed to get valid column family from name"); + let cf = self + .db + .get_column_family(cf_str) + .expect("Failed to get read column family from storage"); self.batch.0.delete_cf(cf, key.to_string()); } }; diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index d6d6dc92c4..8b0d149bb6 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -44,8 +44,13 @@ pub enum Error { ParseBlockHash(String), #[error("The key is empty")] EmptyKey, - #[error("They key is missing sub-key segments: {0}")] + #[error("The key is missing sub-key segments: {0}")] MissingSegments(String), + #[error( + "The following input could not be interpreted as a DB column family: \ + {0}" + )] + DbColFamily(String), } /// Result for functions that may fail @@ -79,7 +84,7 @@ pub const WASM_CODE_LEN_PREFIX: &str = "len"; /// The reserved storage key prefix for wasm code hashes pub const WASM_HASH_PREFIX: &str = "hash"; -#[derive(Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] /// Storage column families pub enum DbColFam { /// Subspace @@ -118,6 +123,20 @@ impl DbColFam { } } +impl FromStr for DbColFam { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s.to_lowercase().as_str() { + SUBSPACE_CF => Ok(Self::SUBSPACE), + DIFFS_CF => Ok(Self::DIFFS), + STATE_CF => Ok(Self::STATE), + REPLAY_PROTECTION_CF => Ok(Self::REPLAYPROT), + BLOCK_CF => Ok(Self::BLOCK), + _ => Err(Error::DbColFamily(s.to_string())), + } + } +} /// Transaction index within block. #[derive( Default, From 4cb86077985a09e2781462e8dfd0f27a44ab9e60 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 14 Mar 2024 03:46:35 -0700 Subject: [PATCH 87/96] Update the migration example to perform migration on the conversion state --- Cargo.lock | 3 ++ crates/apps/src/lib/cli.rs | 2 +- crates/apps/src/lib/node/ledger/mod.rs | 4 +- crates/sdk/src/migrations.rs | 2 +- examples/Cargo.toml | 6 +++ examples/make-db-migration.rs | 60 +++++++++++++++++++++++++- 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ebd4d92d7..c380d7b6ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4432,15 +4432,18 @@ dependencies = [ name = "namada_examples" version = "0.31.9" dependencies = [ + "borsh", "data-encoding", "masp_proofs", "namada_apps", + "namada_macros", "namada_migrations", "namada_parameters", "namada_sdk", "namada_shielded_token", "namada_trans_token", "proptest", + "regex", "serde_json", "tokio", ] diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 38676c3f47..24058e3856 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -3106,7 +3106,7 @@ pub mod args { pub const DATA_PATH: Arg = arg("data-path"); pub const DB_KEY: Arg = arg("db-key"); pub const DB_COLUMN_FAMILY: ArgDefault = arg_default( - "db_column_family", + "db-column-family", DefaultFn(|| storage::SUBSPACE_CF.to_string()), ); pub const DECRYPT: ArgFlag = flag("decrypt"); diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 7675578d7d..755250857d 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -13,6 +13,7 @@ use std::str::FromStr; use std::thread; use byte_unit::Byte; +use data_encoding::HEXUPPER; use futures::future::TryFutureExt; use namada::core::storage::{BlockHeight, Key}; use namada::core::time::DateTimeUtc; @@ -254,10 +255,11 @@ pub fn query_db( key ) }); + let hex_bytes = HEXUPPER.encode(&bytes); let value = deserializer(bytes).unwrap_or_else(|| { panic!("Unable to deserialize the value under key <{}>", key) }); - tracing::info!("Key <{}>: {}", key, value); + tracing::info!("Key <{}>: {}\nThe value in bytes is {}", key, value, hex_bytes); } /// Change the funds of an account in-place. Use with diff --git a/crates/sdk/src/migrations.rs b/crates/sdk/src/migrations.rs index ecb2fbb0bc..da51d1222c 100644 --- a/crates/sdk/src/migrations.rs +++ b/crates/sdk/src/migrations.rs @@ -254,7 +254,7 @@ impl DbUpdateType { key_or_pattern, ) })?; - let deserializer = value.is_raw().then_some(deserializer); + let deserializer = (!value.is_raw()).then_some(deserializer); if deserialized.len() > PRINTLN_CUTOFF { Ok(( format!( diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 52d24a72c3..bdcb985055 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -23,12 +23,18 @@ path = "make-db-migration.rs" [dev-dependencies] masp_proofs = { workspace = true, default-features = false, features = ["local-prover", "download-params"] } namada_apps = {path = "../crates/apps", features = ["migrations"]} +namada_macros = {path = "../crates/macros"} namada_migrations = {path = "../crates/migrations"} namada_parameters = { path = "../crates/parameters"} namada_trans_token = {path = "../crates/trans_token", features = ["migrations"]} namada_sdk = { path = "../crates/sdk", default-features = false, features = ["namada-sdk", "std", "testing", "migrations"] } namada_shielded_token = { path = "../crates/shielded_token" } + +borsh.workspace = true data-encoding.workspace = true proptest.workspace = true serde_json.workspace = true tokio = {workspace = true, default-features = false} + +[dependencies] +regex = "1.10.2" \ No newline at end of file diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 787a141652..821c3ce26a 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -1,16 +1,58 @@ use std::collections::BTreeMap; -use data_encoding::HEXLOWER; +use data_encoding::{HEXLOWER, HEXUPPER}; use namada_apps::wasm_loader::read_wasm; +use namada_macros::BorshDeserializer; use namada_parameters::storage; use namada_sdk::address::Address; +use borsh::{BorshDeserialize, BorshSerialize}; use namada_sdk::hash::Hash as CodeHash; +use namada_sdk::masp_primitives::asset_type::AssetType; +use namada_sdk::masp_primitives::convert::AllowedConversion; +use namada_sdk::masp_primitives::merkle_tree::FrozenCommitmentTree; +use namada_sdk::masp_primitives::sapling; use namada_sdk::migrations; +use namada_sdk::proof_of_stake::Epoch; use namada_sdk::storage::{DbColFam, Key}; +use namada_sdk::token::{Denomination, MaspDigitPos}; +use namada_shielded_token::ConversionState; use namada_shielded_token::storage_key::masp_token_map_key; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; +pub const OLD_CONVERSION_STATE_TYPE_HASH: &str = "05E2FD0BEBD54A05AAE349BBDE61F90893F09A72850EFD4F69060821EC5DE65F"; + +#[derive( + Debug, Default, BorshSerialize, BorshDeserialize, BorshDeserializer, +)] +pub struct NewConversionState { + /// The last amount of the native token distributed + pub normed_inflation: Option, + /// The tree currently containing all the conversions + pub tree: FrozenCommitmentTree, + /// Map assets to their latest conversion and position in Merkle tree + #[allow(clippy::type_complexity)] + pub assets: BTreeMap< + AssetType, + ( + (Address, Denomination, MaspDigitPos), + Epoch, + AllowedConversion, + usize, + ), + >, +} + +impl From for NewConversionState { + fn from(value: ConversionState) -> Self { + Self { + normed_inflation: value.normed_inflation, + tree: value.tree, + assets: value.assets, + } + } +} + #[allow(dead_code)] fn example() { let person = @@ -131,6 +173,19 @@ fn se_migration() { force: false, }; + // Conversion state + let query_result = std::fs::read_to_string("conversion_state.txt").unwrap(); + let hex_bytes = query_result.split("\n").nth(2).unwrap(); + let bytes = HEXUPPER.decode(hex_bytes.strip_prefix("The value in bytes is ").unwrap().trim().as_bytes()).unwrap(); + let old_conversion_state = ConversionState::try_from_slice(&bytes).unwrap(); + let new_conversion_state: NewConversionState = old_conversion_state.into(); + let conversion_state_update = migrations::DbUpdateType::Add { + key: Key::parse("conversion_state").unwrap(), + cf: DbColFam::STATE, + value: migrations::UpdateValue::force_borsh(new_conversion_state), + force: true, + }; + let updates = [ accounts_update, wasm_name_update, @@ -140,6 +195,7 @@ fn se_migration() { code_len_update, remove_old_wasm, conversion_state_token_map_update, + conversion_state_update, ]; let changes = migrations::DbChanges { @@ -148,3 +204,5 @@ fn se_migration() { std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) .unwrap(); } + + From 4d6fa11e065851183b78611bb773060272d58998 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 14 Mar 2024 12:22:43 +0100 Subject: [PATCH 88/96] add logs to e2e test --- crates/tests/src/e2e/ledger_tests.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index b074fcb170..535db320c5 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -4153,18 +4153,19 @@ fn rollback() -> Result<()> { None, ); - // 1. Run the ledger node + // 1. Run the ledger node once let mut ledger = start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))?; let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); + // wait for a commited block ledger.exp_regex("Committed block hash: .*,")?; let ledger = ledger.background(); + // send a transaction let txs_args = vec![ - // 2. Submit a token transfer tx (from an established account) vec![ "transfer", "--source", @@ -4188,19 +4189,23 @@ fn rollback() -> Result<()> { client.assert_success(); } + // wait for the ledger to commit a block after the transaction, and shut it down let mut ledger = ledger.foreground(); ledger.exp_regex("Committed block hash: ")?; ledger.interrupt()?; drop(ledger); - + + // restart and take the app hash + height let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; let (_, matched_one) = ledger.exp_regex("Last state root hash: .*, height: .*")?; + // wait for a block and stop the ledger ledger.exp_regex("Committed block hash: .*,")?; ledger.interrupt()?; drop(ledger); + // run rollback let mut rollback = run_as!( test, Who::Validator(0), @@ -4210,6 +4215,7 @@ fn rollback() -> Result<()> { )?; rollback.assert_success(); + // restart ledger and check that the app hash is the same as before the rollback let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; let (_, matched_two) = ledger.exp_regex("Last state root hash: .*, height: .*")?; From 90703d02d4b33062e027e3ac4efc68b6429a6c4f Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 14 Mar 2024 09:57:30 -0400 Subject: [PATCH 89/96] fmt --- crates/tests/src/e2e/ledger_tests.rs | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 535db320c5..707e38a325 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -4165,23 +4165,21 @@ fn rollback() -> Result<()> { let ledger = ledger.background(); // send a transaction - let txs_args = vec![ - vec![ - "transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - ]; + let txs_args = vec![vec![ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ]]; for tx_args in &txs_args { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -4189,12 +4187,13 @@ fn rollback() -> Result<()> { client.assert_success(); } - // wait for the ledger to commit a block after the transaction, and shut it down + // wait for the ledger to commit a block after the transaction, and shut it + // down let mut ledger = ledger.foreground(); ledger.exp_regex("Committed block hash: ")?; ledger.interrupt()?; drop(ledger); - + // restart and take the app hash + height let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; let (_, matched_one) = @@ -4215,7 +4214,8 @@ fn rollback() -> Result<()> { )?; rollback.assert_success(); - // restart ledger and check that the app hash is the same as before the rollback + // restart ledger and check that the app hash is the same as before the + // rollback let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; let (_, matched_two) = ledger.exp_regex("Last state root hash: .*, height: .*")?; From fd85e3773d86efd9e87dcc4354bd86e1ce255f51 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 14 Mar 2024 16:06:16 +0100 Subject: [PATCH 90/96] Updates rollback e2e test --- crates/tests/src/e2e/ledger_tests.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 707e38a325..393cd6aaf5 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -4144,7 +4144,13 @@ where #[test] fn rollback() -> Result<()> { - let test = setup::single_node_net()?; + let test = setup::network( + |genesis, base_dir| { + setup::set_validators(1, genesis, base_dir, default_port_offset) + }, + // slow block production rate + Some("5s"), + )?; set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -4164,7 +4170,7 @@ fn rollback() -> Result<()> { let ledger = ledger.background(); - // send a transaction + // send a few transactions let txs_args = vec![vec![ "transfer", "--source", @@ -4187,14 +4193,14 @@ fn rollback() -> Result<()> { client.assert_success(); } - // wait for the ledger to commit a block after the transaction, and shut it - // down + // shut the ledger down let mut ledger = ledger.foreground(); - ledger.exp_regex("Committed block hash: ")?; ledger.interrupt()?; drop(ledger); // restart and take the app hash + height + // TODO: check that the height matches the one at which the last transaction + // was applied let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; let (_, matched_one) = ledger.exp_regex("Last state root hash: .*, height: .*")?; @@ -4212,7 +4218,7 @@ fn rollback() -> Result<()> { &["ledger", "rollback"], Some(40) )?; - rollback.assert_success(); + rollback.exp_eof().unwrap(); // restart ledger and check that the app hash is the same as before the // rollback From 401c619e8c4cb2783e53a1e708b60608609a65f5 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 15 Mar 2024 07:00:41 -0700 Subject: [PATCH 91/96] [fix]: Added an optional starting block argument for shielded sync --- crates/apps/src/lib/bench_utils.rs | 1 + crates/apps/src/lib/cli.rs | 15 +++++++++++++-- crates/apps/src/lib/cli/client.rs | 1 + crates/apps/src/lib/client/masp.rs | 12 +++++++++++- crates/sdk/src/args.rs | 2 ++ crates/sdk/src/masp.rs | 5 ++++- 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/apps/src/lib/bench_utils.rs b/crates/apps/src/lib/bench_utils.rs index 7b886eacdb..766f73e20b 100644 --- a/crates/apps/src/lib/bench_utils.rs +++ b/crates/apps/src/lib/bench_utils.rs @@ -1000,6 +1000,7 @@ impl BenchShieldedCtx { &StdIo, 1, None, + None, &[spending_key.into()], &[], )) diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index 109c0d78af..b3b1bc533a 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -2987,6 +2987,9 @@ pub mod args { arg_default("batch-size", DefaultFn(|| 1)); pub const BLOCK_HEIGHT: Arg = arg("block-height"); pub const BLOCK_HEIGHT_OPT: ArgOpt = arg_opt("height"); + pub const BLOCK_HEIGHT_FROM_OPT: ArgOpt = + arg_opt("from-height"); + pub const BLOCK_HEIGHT_TO_OPT: ArgOpt = arg_opt("to-height"); pub const BRIDGE_POOL_GAS_AMOUNT: ArgDefault = arg_default( "pool-gas-amount", @@ -5722,12 +5725,14 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let ledger_address = LEDGER_ADDRESS.parse(matches); let batch_size = BATCH_SIZE_OPT.parse(matches); - let last_query_height = BLOCK_HEIGHT_OPT.parse(matches); + let start_query_height = BLOCK_HEIGHT_FROM_OPT.parse(matches); + let last_query_height = BLOCK_HEIGHT_TO_OPT.parse(matches); let spending_keys = SPENDING_KEYS.parse(matches); let viewing_keys = VIEWING_KEYS.parse(matches); Self { ledger_address, batch_size, + start_query_height, last_query_height, spending_keys, viewing_keys, @@ -5740,9 +5745,14 @@ pub mod args { "Optional batch size which determines how many txs to \ fetch before caching locally. Default is 1.", )) - .arg(BLOCK_HEIGHT_OPT.def().help( + .arg(BLOCK_HEIGHT_TO_OPT.def().help( "Option block height to sync up to. Default is latest.", )) + .arg( + BLOCK_HEIGHT_FROM_OPT + .def() + .help("Option block height to sync from."), + ) .arg(SPENDING_KEYS.def().help( "List of new spending keys with which to check note \ ownership. These will be added to the shielded context.", @@ -5760,6 +5770,7 @@ pub mod args { ShieldedSync { ledger_address: self.ledger_address, batch_size: self.batch_size, + start_query_height: self.start_query_height, last_query_height: self.last_query_height, spending_keys: self .spending_keys diff --git a/crates/apps/src/lib/cli/client.rs b/crates/apps/src/lib/cli/client.rs index 566cfab888..8fe864203c 100644 --- a/crates/apps/src/lib/cli/client.rs +++ b/crates/apps/src/lib/cli/client.rs @@ -343,6 +343,7 @@ impl CliApi { &client, &io, args.batch_size, + args.start_query_height, args.last_query_height, &sks, &vks, diff --git a/crates/apps/src/lib/client/masp.rs b/crates/apps/src/lib/client/masp.rs index 929f575e6b..983be6d718 100644 --- a/crates/apps/src/lib/client/masp.rs +++ b/crates/apps/src/lib/client/masp.rs @@ -13,6 +13,7 @@ use namada_sdk::queries::Client; use namada_sdk::storage::BlockHeight; use namada_sdk::{display, display_line, MaybeSend, MaybeSync}; +#[allow(clippy::too_many_arguments)] pub async fn syncing< U: ShieldedUtils + MaybeSend + MaybeSync, C: Client + Sync, @@ -22,6 +23,7 @@ pub async fn syncing< client: &C, io: &IO, batch_size: u64, + start_query_height: Option, last_query_height: Option, sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], @@ -37,7 +39,15 @@ pub async fn syncing< let logger = CliLogger::new(io); let sync = async move { shielded - .fetch(client, &logger, last_query_height, batch_size, sks, fvks) + .fetch( + client, + &logger, + start_query_height, + last_query_height, + batch_size, + sks, + fvks, + ) .await .map(|_| shielded) }; diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 1b3617be49..b14d0240bf 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -1818,6 +1818,8 @@ pub struct ShieldedSync { pub ledger_address: C::TendermintAddress, /// The number of txs to fetch before caching pub batch_size: u64, + /// Height to start syncing from. Defaults to the correct one. + pub start_query_height: Option, /// Height to sync up to. Defaults to most recent pub last_query_height: Option, /// Spending keys used to determine note ownership diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 35315ea39a..ea605533b4 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -697,10 +697,12 @@ impl ShieldedContext { /// Fetch the current state of the multi-asset shielded pool into a /// ShieldedContext + #[allow(clippy::too_many_arguments)] pub async fn fetch( &mut self, client: &C, logger: &impl ProgressLogger, + start_query_height: Option, last_query_height: Option, _batch_size: u64, sks: &[ExtendedSpendingKey], @@ -739,6 +741,7 @@ impl ShieldedContext { // get the bounds on the block heights to fetch let start_idx = std::cmp::min(last_witnessed_tx, least_idx).map(|ix| ix.height); + let start_idx = start_query_height.or(start_idx); // Load all transactions accepted until this point // N.B. the cache is a hash map self.unscanned.extend( @@ -2396,7 +2399,7 @@ impl ShieldedContext { .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - self.fetch(client, &DefaultLogger::new(io), None, 1, &[], &fvks) + self.fetch(client, &DefaultLogger::new(io), None, None, 1, &[], &fvks) .await?; // Save the update state so that future fetches can be short-circuited let _ = self.save().await; From 80be8d6195dafcede47ce03d9e4adc44b7dcc439 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 15 Mar 2024 07:31:33 -0700 Subject: [PATCH 92/96] added changelog --- .../unreleased/improvements/2902-shielded-sync-start-height.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/2902-shielded-sync-start-height.md diff --git a/.changelog/unreleased/improvements/2902-shielded-sync-start-height.md b/.changelog/unreleased/improvements/2902-shielded-sync-start-height.md new file mode 100644 index 0000000000..ad86ba6769 --- /dev/null +++ b/.changelog/unreleased/improvements/2902-shielded-sync-start-height.md @@ -0,0 +1 @@ + - Added an optional starting block argument for shielded sync ([\#2902](https://github.com/anoma/namada/pull/2902)) \ No newline at end of file From 33f326c0d7dcd0f129907c195461240cc41b034c Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Mar 2024 17:19:12 -0400 Subject: [PATCH 93/96] Revert "Merge branch 'grarco/reprot-rollback' (#2599)" This reverts commit bc0e8ebfbcd55eb59d829dca6ef69eceb0320663, reversing changes made to c3e99b7c332517aa3b8e5094ba3c026bb0f9c3b5. --- .../bug-fixes/2599-reprot-rollback.md | 2 - .../lib/node/ledger/shell/finalize_block.rs | 3 +- .../src/lib/node/ledger/storage/rocksdb.rs | 119 +----------------- crates/replay_protection/src/lib.rs | 10 -- crates/state/src/wl_state.rs | 31 +---- crates/storage/src/db.rs | 9 -- crates/storage/src/mockdb.rs | 25 ---- 7 files changed, 9 insertions(+), 190 deletions(-) delete mode 100644 .changelog/unreleased/bug-fixes/2599-reprot-rollback.md diff --git a/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md b/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md deleted file mode 100644 index 2f6c401aa3..0000000000 --- a/.changelog/unreleased/bug-fixes/2599-reprot-rollback.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fixes the rollback command to correctly restore replay protection keys. - ([\#2599](https://github.com/anoma/namada/pull/2599)) \ No newline at end of file diff --git a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs index f4376ba0f5..ec0e535c4d 100644 --- a/crates/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/crates/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -82,8 +82,7 @@ where self.state.in_mem().update_epoch_blocks_delay ); - // Finalize the transactions' hashes from the previous block. Also cache - // "last" hashes from the previous block in case of a rollback + // Finalize the transactions' hashes from the previous block let (write_log, _in_mem, db) = self.state.split_borrow(); for (raw_key, _, _) in db.iter_replay_protection() { let hash = raw_key.parse().expect("Failed hash conversion"); diff --git a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs index 25e55e639b..549a844d30 100644 --- a/crates/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/crates/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -552,30 +552,11 @@ impl RocksDB { tracing::info!("Removing last block results"); batch.delete_cf(block_cf, format!("results/{}", last_block.height)); - // Restore the state of replay protection to the last block + // Delete the tx hashes included in the last block let reprot_cf = self.get_column_family(REPLAY_PROTECTION_CF)?; - tracing::info!("Restoring replay protection state"); - // Remove the "last" tx hashes - for (ref hash_str, _, _) in self.iter_replay_protection() { - let hash = namada::core::hash::Hash::from_str(hash_str) - .expect("Failed hash conversion"); - let key = replay_protection::last_key(&hash); - batch.delete_cf(reprot_cf, key.to_string()); - } - - for (ref hash_str, _, _) in self.iter_replay_protection_buffer() { - let hash = namada::core::hash::Hash::from_str(hash_str) - .expect("Failed hash conversion"); - let last_key = replay_protection::last_key(&hash); - // Restore "buffer" bucket to "last" - batch.put_cf(reprot_cf, last_key.to_string(), vec![]); - - // Remove anything in the buffer from the "all" prefix. Note that - // some hashes might be missing from "all" if they have been - // deleted, this is fine, in this case just continue - let all_key = replay_protection::all_key(&hash); - batch.delete_cf(reprot_cf, all_key.to_string()); - } + tracing::info!("Removing replay protection hashes"); + batch + .delete_cf(reprot_cf, replay_protection::last_prefix().to_string()); // Execute next step in parallel let batch = Mutex::new(batch); @@ -1556,23 +1537,6 @@ impl DB for RocksDB { Ok(()) } - fn prune_replay_protection_buffer( - &mut self, - batch: &mut Self::WriteBatch, - ) -> Result<()> { - let replay_protection_cf = - self.get_column_family(REPLAY_PROTECTION_CF)?; - - for (ref hash_str, _, _) in self.iter_replay_protection_buffer() { - let hash = namada::core::hash::Hash::from_str(hash_str) - .expect("Failed hash conversion"); - let key = replay_protection::buffer_key(&hash); - batch.0.delete_cf(replay_protection_cf, key.to_string()); - } - - Ok(()) - } - #[inline] fn overwrite_entry( &self, @@ -1753,15 +1717,6 @@ impl<'iter> DBIter<'iter> for RocksDB { let stripped_prefix = Some(replay_protection::last_prefix()); iter_prefix(self, replay_protection_cf, stripped_prefix.as_ref(), None) } - - fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter { - let replay_protection_cf = self - .get_column_family(REPLAY_PROTECTION_CF) - .expect("{REPLAY_PROTECTION_CF} column family should exist"); - - let stripped_prefix = Some(replay_protection::buffer_prefix()); - iter_prefix(self, replay_protection_cf, stripped_prefix.as_ref(), None) - } } fn iter_subspace_prefix<'iter>( @@ -2017,7 +1972,6 @@ mod imp { #[cfg(test)] mod test { use namada::core::address::EstablishedAddressGen; - use namada::core::hash::Hash; use namada::core::storage::{BlockHash, Epochs}; use namada::state::{MerkleTree, Sha256Hasher}; use tempfile::tempdir; @@ -2247,26 +2201,6 @@ mod test { true, ) .unwrap(); - for tx in [b"tx1", b"tx2"] { - db.write_replay_protection_entry( - &mut batch, - &replay_protection::all_key(&Hash::sha256(tx)), - ) - .unwrap(); - db.write_replay_protection_entry( - &mut batch, - &replay_protection::buffer_key(&Hash::sha256(tx)), - ) - .unwrap(); - } - - for tx in [b"tx3", b"tx4"] { - db.write_replay_protection_entry( - &mut batch, - &replay_protection::last_key(&Hash::sha256(tx)), - ) - .unwrap(); - } add_block_to_batch( &db, @@ -2301,34 +2235,6 @@ mod test { db.batch_delete_subspace_val(&mut batch, height_1, &delete_key, true) .unwrap(); - db.prune_replay_protection_buffer(&mut batch).unwrap(); - db.write_replay_protection_entry( - &mut batch, - &replay_protection::all_key(&Hash::sha256(b"tx3")), - ) - .unwrap(); - - for tx in [b"tx3", b"tx4"] { - db.delete_replay_protection_entry( - &mut batch, - &replay_protection::last_key(&Hash::sha256(tx)), - ) - .unwrap(); - db.write_replay_protection_entry( - &mut batch, - &replay_protection::buffer_key(&Hash::sha256(tx)), - ) - .unwrap(); - } - - for tx in [b"tx5", b"tx6"] { - db.write_replay_protection_entry( - &mut batch, - &replay_protection::last_key(&Hash::sha256(tx)), - ) - .unwrap(); - } - add_block_to_batch( &db, &mut batch, @@ -2348,14 +2254,6 @@ mod test { let deleted = db.read_subspace_val(&delete_key).unwrap(); assert_eq!(deleted, None); - for tx in [b"tx1", b"tx2", b"tx3", b"tx5", b"tx6"] { - assert!(db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap()); - } - assert!( - !db.has_replay_protection_entry(&Hash::sha256(b"tx4")) - .unwrap() - ); - // Rollback to the first block height db.rollback(height_0).unwrap(); @@ -2373,15 +2271,6 @@ mod test { .unwrap() .unwrap(); assert_eq!(conversion_state, encode(&conversion_state_0)); - for tx in [b"tx1", b"tx2", b"tx3", b"tx4"] { - assert!(db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap()); - } - - for tx in [b"tx5", b"tx6"] { - assert!( - !db.has_replay_protection_entry(&Hash::sha256(tx)).unwrap() - ); - } } #[test] diff --git a/crates/replay_protection/src/lib.rs b/crates/replay_protection/src/lib.rs index 610aeb54c3..b014f39f02 100644 --- a/crates/replay_protection/src/lib.rs +++ b/crates/replay_protection/src/lib.rs @@ -24,13 +24,3 @@ pub fn last_prefix() -> Key { pub fn last_key(hash: &Hash) -> Key { last_prefix().push(&hash.to_string()).expect(ERROR_MSG) } - -/// Get the full transaction hash prefix under the `buffer` subkey -pub fn buffer_prefix() -> Key { - Key::parse("buffer").expect(ERROR_MSG) -} - -/// Get the full transaction hash key under the `buffer` subkey -pub fn buffer_key(hash: &Hash) -> Key { - buffer_prefix().push(&hash.to_string()).expect(ERROR_MSG) -} diff --git a/crates/state/src/wl_state.rs b/crates/state/src/wl_state.rs index ec20360bfb..c90403ea3c 100644 --- a/crates/state/src/wl_state.rs +++ b/crates/state/src/wl_state.rs @@ -215,10 +215,7 @@ where } debug_assert!(self.0.write_log.block_write_log.is_empty()); - // Replay protections specifically. Starts with pruning the buffer from - // the previous block - self.prune_replay_protection_buffer(batch)?; - + // Replay protections specifically for (hash, entry) in std::mem::take(&mut self.0.write_log.replay_protection).into_iter() { @@ -230,30 +227,18 @@ where // further &replay_protection::last_key(&hash), )?, - ReProtStorageModification::Delete => { - // Cache in case of a rollback - self.write_replay_protection_entry( - batch, - &replay_protection::buffer_key(&hash), - )?; - - self.delete_replay_protection_entry( + ReProtStorageModification::Delete => self + .delete_replay_protection_entry( batch, // Can only delete tx hashes from the previous block, // no further &replay_protection::last_key(&hash), - )? - } + )?, ReProtStorageModification::Finalize => { self.write_replay_protection_entry( batch, &replay_protection::all_key(&hash), )?; - // Cache in case of a rollback - self.write_replay_protection_entry( - batch, - &replay_protection::buffer_key(&hash), - )?; self.delete_replay_protection_entry( batch, &replay_protection::last_key(&hash), @@ -406,14 +391,6 @@ where Ok(()) } - /// Delete the replay protection buffer - pub fn prune_replay_protection_buffer( - &mut self, - batch: &mut D::WriteBatch, - ) -> Result<()> { - Ok(self.db.prune_replay_protection_buffer(batch)?) - } - /// Iterate the replay protection storage from the last block pub fn iter_replay_protection( &self, diff --git a/crates/storage/src/db.rs b/crates/storage/src/db.rs index 3b87334b94..4ae5d35b0c 100644 --- a/crates/storage/src/db.rs +++ b/crates/storage/src/db.rs @@ -262,12 +262,6 @@ pub trait DB: Debug { key: &Key, ) -> Result<()>; - /// Delete the entire replay protection buffer - fn prune_replay_protection_buffer( - &mut self, - batch: &mut Self::WriteBatch, - ) -> Result<()>; - /// Overwrite a new value in storage, taking into /// account values stored at a previous height fn overwrite_entry( @@ -324,9 +318,6 @@ pub trait DBIter<'iter> { /// Read replay protection storage from the last block fn iter_replay_protection(&'iter self) -> Self::PrefixIter; - - /// Read replay protection storage from the the buffer - fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter; } /// Atomic batch write. diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 8c2d2066f2..b3740be8a9 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -676,21 +676,6 @@ impl DB for MockDB { Ok(()) } - fn prune_replay_protection_buffer( - &mut self, - _batch: &mut Self::WriteBatch, - ) -> Result<()> { - let buffer_key = Key::parse("replay_protection") - .map_err(Error::KeyError)? - .push(&"buffer".to_string()) - .map_err(Error::KeyError)?; - self.0 - .borrow_mut() - .retain(|key, _| !key.starts_with(&buffer_key.to_string())); - - Ok(()) - } - fn overwrite_entry( &self, _batch: &mut Self::WriteBatch, @@ -796,16 +781,6 @@ impl<'iter> DBIter<'iter> for MockDB { let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) } - - fn iter_replay_protection_buffer(&'iter self) -> Self::PrefixIter { - let stripped_prefix = format!( - "replay_protection/{}/", - replay_protection::buffer_prefix() - ); - let prefix = stripped_prefix.clone(); - let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new(MockIterator { prefix, iter }, stripped_prefix) - } } /// A prefix iterator base for the [`MockPrefixIterator`]. From b91310ffc18154b56546558c8eb5d78debfffbf7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Mar 2024 17:24:22 -0400 Subject: [PATCH 94/96] fmt --- crates/apps/src/lib/node/ledger/mod.rs | 7 ++++++- examples/make-db-migration.rs | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index 755250857d..28abdc0567 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -259,7 +259,12 @@ pub fn query_db( let value = deserializer(bytes).unwrap_or_else(|| { panic!("Unable to deserialize the value under key <{}>", key) }); - tracing::info!("Key <{}>: {}\nThe value in bytes is {}", key, value, hex_bytes); + tracing::info!( + "Key <{}>: {}\nThe value in bytes is {}", + key, + value, + hex_bytes + ); } /// Change the funds of an account in-place. Use with diff --git a/examples/make-db-migration.rs b/examples/make-db-migration.rs index 821c3ce26a..7afae91af6 100644 --- a/examples/make-db-migration.rs +++ b/examples/make-db-migration.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; +use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::{HEXLOWER, HEXUPPER}; use namada_apps::wasm_loader::read_wasm; use namada_macros::BorshDeserializer; use namada_parameters::storage; use namada_sdk::address::Address; -use borsh::{BorshDeserialize, BorshSerialize}; use namada_sdk::hash::Hash as CodeHash; use namada_sdk::masp_primitives::asset_type::AssetType; use namada_sdk::masp_primitives::convert::AllowedConversion; @@ -15,12 +15,13 @@ use namada_sdk::migrations; use namada_sdk::proof_of_stake::Epoch; use namada_sdk::storage::{DbColFam, Key}; use namada_sdk::token::{Denomination, MaspDigitPos}; -use namada_shielded_token::ConversionState; use namada_shielded_token::storage_key::masp_token_map_key; +use namada_shielded_token::ConversionState; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::Amount; -pub const OLD_CONVERSION_STATE_TYPE_HASH: &str = "05E2FD0BEBD54A05AAE349BBDE61F90893F09A72850EFD4F69060821EC5DE65F"; +pub const OLD_CONVERSION_STATE_TYPE_HASH: &str = + "05E2FD0BEBD54A05AAE349BBDE61F90893F09A72850EFD4F69060821EC5DE65F"; #[derive( Debug, Default, BorshSerialize, BorshDeserialize, BorshDeserializer, @@ -175,8 +176,16 @@ fn se_migration() { // Conversion state let query_result = std::fs::read_to_string("conversion_state.txt").unwrap(); - let hex_bytes = query_result.split("\n").nth(2).unwrap(); - let bytes = HEXUPPER.decode(hex_bytes.strip_prefix("The value in bytes is ").unwrap().trim().as_bytes()).unwrap(); + let hex_bytes = query_result.split('\n').nth(2).unwrap(); + let bytes = HEXUPPER + .decode( + hex_bytes + .strip_prefix("The value in bytes is ") + .unwrap() + .trim() + .as_bytes(), + ) + .unwrap(); let old_conversion_state = ConversionState::try_from_slice(&bytes).unwrap(); let new_conversion_state: NewConversionState = old_conversion_state.into(); let conversion_state_update = migrations::DbUpdateType::Add { @@ -204,5 +213,3 @@ fn se_migration() { std::fs::write("migrations.json", serde_json::to_string(&changes).unwrap()) .unwrap(); } - - From a0670da4d8bdc14eb3ad68d6dd2ef96a6f24a6fb Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Mar 2024 17:28:55 -0400 Subject: [PATCH 95/96] changelog: add #2870 --- .changelog/unreleased/features/2870-native-updatetype.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/2870-native-updatetype.md diff --git a/.changelog/unreleased/features/2870-native-updatetype.md b/.changelog/unreleased/features/2870-native-updatetype.md new file mode 100644 index 0000000000..2019bd0726 --- /dev/null +++ b/.changelog/unreleased/features/2870-native-updatetype.md @@ -0,0 +1,2 @@ +- Implements state migration functionality. + ([\#2870](https://github.com/anoma/namada/pull/2870)) \ No newline at end of file From a462b682f4ea059a669b6cac9825342a82474874 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 18 Mar 2024 20:43:29 +0100 Subject: [PATCH 96/96] evil: update tx_proposal_ibc_token_inflation.wasm --- .../tx_proposal_ibc_token_inflation.wasm | Bin 461186 -> 462580 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index 1113926e2cebe6bb43e65b634f24708d6d72dc52..1a43f7aa942aff231bf6e197a67015a3047c95ef 100755 GIT binary patch delta 53015 zcmd442Yi&p_CGvl=GoosZZ_HUo@^>15J)JYNXZjAC>^8;L_i@Rh#+Hl*Bh&|x=55OxHPDb}VDZ5z zNmzu27cJROLo9`{RF=lvj9Ho0>V1NDDSv`Hh7B88I=OV%sPQ8OIfo4!J*xE9VK~ zsL@ySH7(}D?pVYh;mi5dhk5@>{up1tm+;4V;?ul}KgAs%U5MDqmkKMDQ@KnT$y2L8 zEty2t6(_ecx5eADeLj(QYWp~RE^1#_Y<5I?pBfM)Q-06#wuwpBN=eI)q9A>OU+=Xwv; z3HL_T847-DJ0y_9`(T|3szhPOWR>*jc(b`+O~;GmeIV~Nb?{EFH;OuXkJRfVT<$P$ zqxz$>7GK!P>I&9OFb~&vvogKPjMxjr$_v$->qCnwFL<}+N98<5AgmB&85R$@4WNq3 zGRR{AP{PuMhXKFTdoI6zR(s|IEi2+xn!gM_tF$urD`aHUSjw`P_s8t{-YxBaHZ^5n zcn0NiEko$;9xe?X^UvDVt-&z<=vnWo2Gflu6*lZcF7N#fb1B@rreUn}!LwUYe>^JY z)w>!xsA*-ed7VAXXs@XQn6BDu>HwziH#)!@R@h5*Y-wR%Q^$I@6ef7@h}%su-p=v6 zP;God3+hzeJE4Fmrg}!=T%s;sS8}iX7*7~_v9_N>%~jZSlK?_TC(lbxp-$fClY4;g z+2lTWzb@rAyw{}o-+OvTrlwy3=cQ#Ba5L}Bw1KV|kI;`(nTI@fcZ~OkwCR>kI=Nlm ziJs9dT%Pcf>6H&vR9#xVZ7=KPc1?7EXAbTFj-D7l&(Vr;XK+;Mt?HvE0937B?^GWH zF2fb15X1clZ^JCTptDEhv(XUPld9XMOme%@$z$z_(iVIuE?OY9`BQIIR{MCDM>Vjv zp>{==h7a%gtmD;t3rjggds{ZShA%qny`#wjjJCZgY`4qnE=mhsX{g)|yP)HQ7JAo4cMd3Yj5j={6?k78 zbL_JAvcICe6Jqc4gk#DvowsBf5-Q?~K}Z>7QuBj_l6P&x81roL?rK=*y&*2KdP2)~ zA>vn?TMqx3$H>a}xU;&hGn~xxpd@q}ovYp+x=?k?pzwNXs>}}0rgPra;U|q&tZ?5E ztt8<3US6h0>l@HsJ=yK>HjEf<;#9pP;zjNXgKV&Rl@&G2?W?Zr+MTL5cOF3zH@TR{ zqKB60@6FnVrnqnNHtO2dih1RJnR~zNb}w=7pljz7um1kpW`YKJ(|e8F4vj~Z}g4Zyct7!lg)ekkONfg9dyHFeD1oTgS$9` zAT>wBL&`jw`wef_(8g3;Jz!`YR%-99rNdF?!_qXW=RHxH9pW%}x|?{vy74V4l1p~c z5UN*ABTo{ws6I8S4G-mNIy1}wtay#bKch5#g*Mr>M1Kl80SD+7S##{%ou(BKR zIkBvH_#h`*$qE@%3)X*7^@g&gX<^{t!6E@J=(qOXkq>Od+LJ!-T6~V5H>H)4t=BO9 z(YY!g#N4C#0hOg)l_m5Q0lFXS7GhOR@~#^Y`X_(R>gf;O869P?K;<(7g?5ES$`h?A zv%27^F`?z1Fd&|?j3`t|?ic{YV>4VH?pAo1{ESeK%XqKA(iY|kMGlCNx(s5q;Dvj_ z0#FSghbPou7VmE4gc@`euyzqB7v?Vo$mt1JbXWnjvJByg@;G6lv)1$~S2FAeL9`zS zNG-83o@kH5Ul>pvAU7bl2MdaUtpPR`5V)YyI3oogVB$N#Q)U|j!;R>uAZrB$ub|6w`I zw*OGtKepbCQ8LB-m%RVecA4e)=kKZp%;5j32NVyOm%q$Im4nsz%jN!gi~g9q_3s6+ z6@SG4bBvM>mhLZHn+N1#A1Oz4G6d`@b2xzapg?si=QB&Os{A zcf<1Fyw?>W0k0mf?#r+7{@0*m?|uHeo2i>O;y{sdLYdzy_x3u_1>W1^2b$sY;{!M0 zv*p17`1BskYFIUW8*9keHB5P^?lOHb;JS=8Ghq1Bm1cO(bnl^q^Q)&GYDv`H``Y1j zYT^AteO6yMd|IQP)xRBWPIY>9aM|EiS=~Kd)=*==zmt{aa+Yq>2{8hP9j>Wx1yB?q$M5h+=`BTq~KcF&2`h&okIKlvclQ@*g!7YKdtayV?Y(1rk{ zm#e%T22|bRRFt6Z@yX83 zyD*=Uy^AiKr{rqy#fC(KRZr+R|O7 z#ZYS+DQ}3OE=Zn>p?)-0UWlQyV0k*09-?@)K|w3@D(p{m4iCNp``fYZ!SacCN^{%> zH_?NQzWX-$7T`FwYfUHUF}zTwd!QF6?RNQokEh9Dkp0#=bPtlFM&cWti{7%uGSFAN zBqQ=DMlR2z_GI%N$)jk^X>1a?o`r3Jv^Au9DA{9x*1%WV5V1aA-9mbrlTGeyOl$N# z(E1`CYHZs=%Zhjicw#ba2O4kKu^Dd!P1bkJ*v8zcwR;PFgMz!uH=5Az^0TJYIye!d z-8Shd!X~Kx3994kUqp`+J>vVN8GR!x*t|r0&6Djc^QdrlKB9nGEmv0daD2 zD_ViUO=wLil-vm_tNI8Z@VtfL-A4l~9_#Mp>)i&SOX}o1+?Ezt(qkYiNY#c=eJU?M z1Ph3BgHn$(K4) zheTH*_L0=lMY=T|A+vnObn6r&m?9&(P<&$}U#NU7n6LTsRY6TgFv zeuPw~;S=40?y#!vz{W`-0jg{u)DFrW*YQNZcr!U*fWHh`k3i~L{!o8mNUTUPbPg zl!*@m%C~q3oe!a>r^ETP4pX%5Pc6b3`a}W7k#{2!_kd=V*w7(N~C;* zZbcH}rA5tD*iG4eegACb=>QV&S`0vC!EMry2XmA+6QJ{b;-vx}fiPnr=BTgy0mh@d zFLn`q3rEs-b_sRoVan$u-J(~$jZBN|_XN)OEI!whG*uXR_F&!+-;yev43b5rJWb!w z26^de$hbk?x*V?7VEOuTxPKO(bp`dcPz&GS=c!6iGkJa$k+`StJ|23_q+$PJS8AYLVRf zGTjA|YhIzB@~}U!>AVxX^iZuB3nzxy4IB;<<1;ugcu5A5!+YxeD&>P^#jCUh)fM{; z8K(FQ8NT%4thPwb*+6CR_kP`gGhEQje%+wi^twTl^#&aQ(zTIN=?z(bBi4jsIc_5@ zEKth;$1ty28yI7Y2cA3-`(~(KK&}ld4W!Wb-EVYD;U=`8SiZQ4rX)eyD@1ofY6pm& z5V_T6nimEFGwzek=!;^RxP_XbHNCgc6eRC%!BKFr>{|n!E|%Zc;QV=$oVyj?e6jp( zD=3R){x+i(k8XpJD3(Vtcqo_tCJm%9@}4)*?#=Shn{bzGGIu);j%{+?cB+fy^X-(5 zUVFSB;jV3e=eP19vaA*DabS=%2=RT(o!1>)TXtaj3 z(SDk$(JbHk1Ihs|mhBFqn~LRqhtw$fzCVO>dL`Hqj6tz)+Yxj=cpUwnb^*Kb2l62K z@&|ev$ci6T;>$crYc=_5Q_7Sd9XGV$mOP3JZW!W~%AcT?vT0sz9=AE=yc2XY?UKKq zpb_AB{V%k{-&-RyUg6FtX6#u`eu%+6fDiZU-01`kdkHuo<9!+D=mjA+pP^K_^8#gu zp@=&7@n`RqBYvfs$;s);ugY!7lHhV4n}ZV*b=sX&rq^{ROK}ngp;$IMY0Ru!P8vG7 z=Oip}f^7dAtq)7a2?dJSP&N?ICS?D`D_OHdEiz%LbTm| zK&FTDvdHf!R46=7^@8{(!+Bpw`AayTZdi;Js>XDE2NEn~_q%y_4cU95d6QT}pl^|# zr_cP(FW^!~f^ov?YaGMpXhsu`?>9tV{LP;>VP66tVgaMd5O1#|9vp;ezS2a#U7(Jf zRGx+K!q8u+gRD$N(*oupC5=B4c<0Y(-l_qK^46!h)h9D?4ao{#7f`t8TL=Wq&)^5p zKR>1MSw_ZB;SS>>%S+jO7-%AMctNr))L|?=+nJ{zc@9b98C<2% zn?VPQ$Li}UFr*Kv(P+q=8)QX)t%<$%bo{ShOpq~M`3vZrny&m9v}bKMj)VVh^2=-a zR3t;L;~P~kBz8CYsHi(edX8Mto%gcO!Oa8QesO7XU3cC>HtWGl)i^xXgFmR!+@7$1 z-F&-x^4mE5>MQ93iwu8oQD1&N0Y27`H?yYAz|!l7~dQFR5&U~RxiA;E!rSWiEDL_$KE zi7RRdanJRAHw23f8mbTF`;ep#gYkCAdxr64==-GM+zX?&b~rzTyhWw_Xd$lN;Koo- z70ZTgftWG+vxhM_k(gs#-MoWbwKwks?!fq}Z^U#+@vDx8r5N{Qa3@9`3E(XmiPE_d zyc25ZIFiqW-s~O;W7W;~)F{jYq)l$-gDEn2n+9_e`erF}cau+z=2cWBOUCdmka^8` zZa6H6p5Y!`H&IfepO#hQ_}D;UU*;|R0V0PFoDinlEIqgK z<47VV@(FOzrcFdOq)B}eKY}E0GOtG8d^H)~piMq`JMUk9{2W#jwJ)>6UK~9i07Bus z2XpjwGoic*`p)}RWR}6UT#do44qGibT8i_E64neZ6DEHVZ;xaT7#Bfpxc`3Fd*mGhnG)b17hIdtl& z3jQ$0f7UdfsD|UI>HK6!(4fmFXYhE?ygGwF3C-y{6H2y4&X~!U#2Xso!6rIf*={T5 zl;3vCCinCCpndax{wk{W-7||1q^2(SOB8BRN`-hWW))m!gzpp*&IvTl4y1dXa?dv4 zmFwTY)UESj_CHOCldPJ{hlU{-fLsThH;^v(#XrDLs~~{;L7rfBdE&uNHhhq0E344| zK{UzbJMDDg^yIZi`HINfz}yZTw4w^rUg^(-g)llUIbacg2hP4na{vqie>y_-jW~qQTAO7f5YW_dNKc$Xp-;l$M`G}U8_-0a((yfhDQ0Km+}$;((9k% zFR3-au^i4E9FOJvpwiXo6}%I_Nb-gi@HdakIV<>E$nE|NAEdA^JOi(Dl>GP^UV@Eb z?6bTW*5LYQd0PUle2$OEFy>(;852T3$tz)LyZPp?R7Pa8T=qQw z{j$r3g{EgZq8+*X3|;B6ZG3^xhA8b{r8M06?U}jZo8GU@H@t_64h8 z$41FctN0kWW$Rb*JCM{}jg{Oc*S<0SZc15|4TN%F2-O}(O|kvn08SZm=(pMr_3*v7fj?%Ue)6Wxyb1M@k8kGL z`3TgRkAuP2qE}+0q?oF_lk;kro~=LeAmA~+pEkq8q#nM=8k|g6`suhuE-SY31le;d zk4oyJde=Df<~j~FAR9xl$v$?KyPur8l^4Ys1R*ZmqpwnO{inlQCaaO?~IOq_f4MCvQ`ODm%i?VUkUtn4TlnHe|)(g zED#1xkzQqVmh^1L4r8+{-p;p0W2^%g_c7=t)3MmqoRhI{@xQ4B<=eNg>^tPFw|Orl zd*6n;?U1?ez?XE$j_+Xlbn}Jn;4x}1m-QZRW7@9!#=XbKxVxzZJVbZum1;YyfN&jm z3IjlJ|Fr*eWU8&^`7h9l0)5rK0DK%7x<}t=z|yl18rt30YajpYPj+7|zrvW<xll27ll9+r9@1QT|gZWa_|H$TsWPAk?i*! zuh-1ZfP}xmf{~eu`S3+nJPc3d4S!??y;|sgwf2l@hr0gznq2)IUri6m!3Uu056LGF z@Zo6D#RGg4r*GuQ!~AFZ#@F`HY8U#fuP+Oz5YD+XH85bI$u7KE}TyIwnh}ie&k~am?{sPe&n6J)B-jtL99Qqp{3S+kEH$F>Qn^SxZc2B>af(hs*Z$FLgVmJBBY2IIvUO24` znVfKjzlRdnoP`c{^L>4mmk`{U?AJvj+4pz;F$O#LJgnI-GW`Odob)R}U__NGlMEMK zpM$^_P7ghC?q7WCF7TtAYzvd)hG%8sB{ zf0N&k$e`b(#zj4dP{2hj;ig2DMR?`sLb&LlJR-!;kYukWu7SwW7BSCHP=!3*D!xLV z$0iWGmh)_)0iBkw+r&P&crrwMj5TPiU1Xzz1$I#v6}({=-IbH>a0+}As++G)D5{5l z*wrQClz(_*m^jFdI~s`|M2-$%pxVoR5n?obFE>XBwP;+55LvnQ{CHMF8#5X)jeh?#Gf&4RL z#S^sOmmVknPOwvF;>CExLvKzHkDzPixg?`?b(6(uv@ShG97XHGQUwOvw=h-oAgb~m zOcU4fsHGG~#9n3*1?qCOq2N*dZ{%! zCr7lQGjddp_yACoI^ub3V!o;)2GChwvs`gT^?gB}C{qo5ygr7=Cf}-$W@8(bFG4ts zVC#n9;E?w=MCUu?riLOF$-ai-7IMn21>y{XBw9W@f};?G?mSv9ufj=JN5iGB3N6EF z-yu1oP<#T$EgFeKG*5aOi;jo^-P~BH{rdfl1?H4YZz5KyvwFF^iRjrV@C}`eSapk| zeegRv8R-g)!j?DqMV*WSKL&R@dN&pAl^K*p5ElpMe{UvA$SxC_ixW6iceW6DNQzpB zqDb5W&~zo5y5qjzRpx%&H?@U$k8ttZkJt1a)-q3BTqwYG?7a^4Srys~$NqTPZ-6Tm zh*4FTBL-%j33J@QYzSar%rvzD^<=5*7IS8`#BcHW2GJZI;yuy174@#0RP1X zW`hY+WngN87_|`yDpID3xU~`;(7%0KVWxGH%UfZBot1C566ru5X(hgZh1uR(ybEZ4 z8*zOuEU90>3?(S~#cgyDq{P?CG<`3eT5RIoPe@l=k%mxaep^v5;i!MRv)U7~y2LYU zrus?^PshRJB{{JzOuEa@ilO^I?rK@|}iBfWQ zKxJp_WxCVdLALK8#!yGOrh_oP9VgFp5JMZ)eqD@ViM^}?N&~`GT;B9$v2b<0Y2^jq zoQ@)aJosiD+7YFkbFRK7hv7h@INWOh+`Uw8?IaSh_21VCR&%LL?F@tUifrFmOu@`~ zqqA5KS#Rlr78J`fiK%VF!R46vFr^xaLH?jA?2zKr%#;WrIU4=~=!+Pnu z6^6|BR!`xj=ExX|=`{%kZm7xNd1t-T#Ui#_+5*!_S~#@Cq_Yk;FSCs{4yT7 zOwSd+$INhCnf}x%ncq)*j=O0g{n5j}%P#%Jb@gpdfAj)3s1W)N@uZ=*xY1i^l|sSN z2T}5vPsGZ-{Y9v9{0{Ypv7ap;93=Y4zh5sRDNcTWy_k#S-oc`Qd}@H0h?$Z+1Z#1J zfv{0F`OQG_g1^Kl3YEVM0&%D!UgoF64L(IONVdO0^rhLpB{zuq&;UP+LBk9dr-q5Q zROOq7i`!IMSStG19N2=W0RRvupDq<`fx(0V=13{5xI?zQ(J;CLZxq8RM81Bb_!Y^T z5m-SF$@fNxzoFtqBe5#Ylk-QRYmUlYqcG`l{xM1n4cYx0M%0559dMHS8;ur^QmpTt(E<_rSh;fyqT*Y9qsl}p z6-a-2tQZ2SMdL&ZB))6Li8s}kW?vmI3M^_n*Ai;k5H3nfy+)lXK%s#s2Ewn;viBrW zV6FUKxo^HXlf*Qxls*$Q>fAijUq~iR5e=*rexe>z#9@P|4Tus!)FwdG`7Y5V8U7@+ z>Udc2^!;R*G4d4_)Gb%vB@&at9u#xJgA@Q(-J2v-w30hn-n z(+D&w$|m%h)H2_!yT#{%URM5QFC^7dMLv>mroxQAES=>>(zINhq7q;I3aBHnpH36? zk!aHq$a&e9Jzb0<82UbauUr*@vZC=(wlMW#M!YFAvQYi(tF% zn`5w?JXbUc0rm3>MAEorpw_F5c)bc%B}0k)45|<+R@WZF%EqBg$(; zl|bzF^R=nqSS?>)!6V{+j+-m5J_`5pWm&RNd=IGpA_JQCtf(&!EW!y(iF`&Xl&^5H zSVL_LR)|&wScTS#7-%E#`w^lEMXgG{t0-m4V`48@o_fq+IsI|bMvGE(iw)uMooI{^ z8ZAFwhQ;$``P(wgs+VPam62RiC6b~I0sQ2uEq?N{Duet%gU+|MN>r$p6)X?7rJg*z zTxt%rGfS=M<8hvX%`qpI#e8N#H!_WE^xIA>QLE#U2Kq*pIlo%Jmz>3@oQj zU&DUG*ae+|jY)k&O#fE}H@_if2R2}Jw?}C@{qXjsDu7UkE zwzgNn{*J;nV9w8=D}XWo`W-PwzPV9E_y+D!4)0dq;&-uNVSD6wPkiRz9%UdT8`wde z_y9YoBXZC#(E=oo?h-Tnn=B5Jzz!<=BQcoT$!Q;n=ro(B4%~*e{ykHQS~7MrwP|u0 zLEa8#U!X5%$c6N3fD>B84yQ~VEx zsXxjy)cqU{>Sn{&CzTQZXur4>?Am<;ZMVr=zYzY#HmJ`n7e z5kH7L`TlXZGR1QLagh*VEz@^>Tt%_^$~})pFHOqHd$;E2IIWq)``Ajh_2cY5qlD zjJr~heiMhjmt6GsKq^pxlQ~iFA0p&{RJS!mmanOF&uSZiW^*JxN9{>pMP!PHdD-PKybc z2P;m)3Fsz2J}qX&1cIto{k$mC_k+xctdBoqZ2G@EBf=3`_x*HM5TyqX&pcLaQ8|K7 z)?AJ&CYW-$@O{+XlJMWL=5&*f{Ejwkk=uS3uc5Yk&x@?&+WQY`&^?5k4<0gZKxnuD zvGKgeEs$x_D8fwJU2ERWcQ-!=E`CNx7H4bGa(R}PmJ~$kq7yb>YN+)ZSVdeV zcuRhnr46BFvSqf`I`dEHwktZT`z?IM4O#_%G&D!+DEDV;jzm9oh>2;CSZX=i05H8F zN5euUWsa7PPHi*IM*oHD>qmW$|ZfZ zP@D{{tGU&cP_;q18Kaa5{U2MXWwZk>%`-r)BZ3IC9aKkf$MJ zx>*+1)7m1LSWmkLNlgPSMLOzhS$J<)UmK33vc9&;55>sd`C4X>%9|gg>MD5yEk1~E z-XMtYBiA+1!u@#N&!Vfmw&7*c=?#OVUF7kGS`t(zv_Na4wox!e?w`n$fG%-jIwk1w z#%2hEeV&x{&E*wb5#R;qbsETE`k>UcWXA(!Cn-R7^iRnS1jvpA$@Wu*e78VzwSW*{ zzBiD+FPOjQPuc7Ykm1_fPcpnvi|OPqUK7A?4;J6}$Kn=GREOJC99QjIqtpiRY8^=c z?$32ZK3k~e;^OYELM@Rmz9fGv)Ur#}NztlESqu3*4xZRlT+AU^9;OHVjGKK87J#&_^2tOTx1ZJrQ}W(^ znlk$<`hoh-z8Q7(FUiPvuZO+nz6}F3;gI{sXqkN71xeGj1escozD z$aHP4eB(w<;{DzmLk~y(6`*!JKbL26K|+L!&Og*R9Z;vfWME%pf7| z4?BIwZqiB#w}|)ugjT8RnHqMixJ5jCv^HMeK3?k&>Yd}YdGctPRtmbk__h|T2$p$2 z-O#b1`(U(|tt9_#w3crYdf*ssw%j>Z+YcVA$7$;+!8hy{Etcf<6SR7<=<&dXno#(< z*cU3UBo|N6cH;{Bm|L~SV&H6IIVjI$x_fMf$ifMej&ItH|?5D)Y>Vdr*2XU zXv7iD<+L!=_0&YI0iSt3*mu_3w6T2gd2jlqsCq`+5kDIituIx>h_A~b91GRT<)INw zt#xo|jO-WB%k{Tu^C5EaB<)GU1?-g@P{4t^69V1S_8yFpcDuF|)h)jr5(iKZO#v$K z-r){yQ{df~e5bZVF`1$6E2ukFwjh&zcU>;==-t|rAbLR&;i6`+rfO93>pj{lL1x?T z)#?#&$L`bK^(RU4NWS7Uv#0+F(ShGm(?3Ci^Vz3)3bU0Zo0 z)t8kAwZHM=3g3`Jkc@tnzaNGX|4CkRL~F|h$$O7z-En&H-Vv>tC7c*blpmhc;(V#! zYdd*<=`9m(A3u567>cS#!VP8cGSpW{tN>%X8cX#N+*w*Fy0q;RD0Y` zYkbweX%Q5&4a|Bm{Pf^tH!DT@2H=*8c|CcQEG2U9X)V$>;I#IxkRNah&Sier+J>=$ zyWIGxLdJTiRJIV7%(R1dy4lb8JBvRKv>ksD_>0G1`dw~W_&t1t&HJ@t`R4DaxJG{R zyY@IAdbhmoymrhob8d1-t0v&RpU2E(Isre=+g1yM~%xb z!28LCt1WP@%NHK9G-x!W+|3+7&c$Cg{(|uRSHbrI&NT96N3yg;3#Z|0P8f38l3U8? z3Hc1S)J^X)bZ4Co^=-s=YdWSzdA;dTYGwh?&xAKOph%Gff0p3IK1)GvR%5o1Qx!)^6a z19V9E@HqAPQ3yV^qAIbpKV$cXLxqo9NHlnvvBwp#6B6$Kh_QC~A=S|LHAwh2iV3!p zvGWks_7Q%vwc9AhzE?mofO|JG_7Iwb@9={wk0?h4;*N2Q&4H{Tk@#VWYr=q8vW>BG z3e#E;6a9t;p!2GRgQ%E4qWULU^~+9th)30V2xE>VHgF$_3~^vPa?b(PQs9EdvyAmU8vy3r!`Ls17@g1f z9jdWSJ^`^pJ!2zr4kJG6upa>W9W}C#LonQ%#i0B=k1-v!MC>8$P!Gn&C6)tt0-UNq zqit%%*f2<(JBYL?br@@sgxouj+d+-Mv|`5IMQ+3#qm7}e2kuf_2drRh2{JsNsx+y3E@Qhc z0Mkzu8mnqEbnfHV82jBxtZsXS@2VPTNYSG?W1pddEc`yk1V#M-47GOfI>y>6YyoK+ zy328fsdwZ&UB%e_r^^9iVt~A}lIZVH2c8R1%fzN8!m6E6hAWIjqnjD)A4e=^k)6cLpJ5`Qvtn*`LMAlIkV$NUp5M-a9`7X7 z5A-OdQjuV$b%vU+jVG3QkVHqc^c-56iI_bfUt9daEsPxx1-eX)gHisV6>{X4V%@-q zMdeyaY-!EdtI(jR-=Tbin=*DP0H7)U$3Gd%*|IxzEO^j$klM4MeS49wirM1<91j9R z(VHQtPvtnKLDb+wkvzv)5D^3!qbMG0?Po%`F2)+U{8NbO*N>r4)B7Q7Y`I$_Q40Y; zLryngEM7Ha3Z{Bm8Dp~{MN~BA=GV0h3Lo{OGw}00B5atSTgHoqg58Ow9GCFH>ADQY ze9*jPfvI5z7@r0rk=z9RKVK2Jp{MI7GiFH!unmI)7AsY+MnJ6&f+Fr-2VDU$ejL5P zs}#}NL5#hUMJ(ZcEBPDPuR{Pc5{&o2kJzEISAy{s#G>2{>(mwMn()09mGZVq_NnN2 zJ`W#a{(b(gNJ7wDiS)7RfCr$d+6FXjdK7+0I*YVz+Zek67A_@%w3c1*qe$xANm?p2 zWv!y=K^p4>Yoze;SJ0H33kT+=n!#H6-HfSel$uB8cgBxF4y~6rkh>An)tG)mYZzMy z9Z8*`nqjcmHJGu{;Fb0XiKS05b{phQV;Ca;T#ErCQGRa&d?F*g9TfEk2&wE!dMTQV zuMD83?QcLMe+Q}+X|ao8QO+Chm@eQf7UTp<0Tk06KG@vTu(SE}6 zGixDZaj1n_a%(xJM(rcYaxkYsuPrTQY{pTL9HW2qF5aKb*l_d);hVVH3Ru?tKLWT! zqG}Qr16BATWW4_ZECXsKIBkx0jQyZ8DoHy5VK@B<`$wlptcJ$hRmLndWB2QfZB!+4 zP~0p?pol7Q1>VG#i79I_PeV~UiIXuhJRDH%tVaikWtC$Y`%2YNpURz+p~b<63Mtu% z4z7Lh14AAMY(wo6`aY#GXBkE&_|S_K-3J(J2zyTXyqs!< z33^9&EQfQWROuu+J~y}K0>wIBKu-oA=$geDXEL<-&3c#ftXXsiod4D)3fCuT@b(Xb zYdpAMFJ5kOL}MZaAH~vPGb2!`ePXJxP=%f~8l0TBK##f>L#El8c@rC|d^X(93kiv!M zDI8Eu73vYN67Bjh)(Hb*{Sp22z)r?q#}Z&o)-LM}rZiu6;7xc+s_J5kbKzxGV^-Y~ z>6l?cB#h#AW`7!(o~Y4U-p*2B-vnqhpoABAo@1Sv_y=n^X6{)>P(M(j1Pl+D=9D(o zmhSinLI)phhRx-)LYCm8O}8aG)M6QYv;s>cbG)g_1m{0!>N4j?=8D=`BAuhmY}VTC z?zc=7@y+6#8%(fv+-A4Ghk7c|7VTjXY2OTBF#!DJM1e#n#g?)YOOCFSbb4aM9#(#u}hIt$U#4!I3Oi zLy6572>xIQ&SU7`RHMDv>HNyn5!NNnuq(SNMYI2hT~+Qe_f_+M+E=O->iJ)GRrH_r zm7JJii7NkcJnRMf+Pc)~_c4raR00@_*me5?bA^S3-Ox*+{g0JG>`_N^8S9L$%bVn~ zHBSv@8L*KzTIJn!Ro-1LMaZ956FpUF2LA?QV(dClApAZ*E7bx=zN4pEsZMIBubhR)nzHeF{F-B735<*o;up%)DBfA&^G4%d|xHH$1sNXy&W|j7C1cw|0UReS=0(P~srPflv~Oz|`vTL? zHJh|Hm2m3I6)=RfXYv{QPQ6c76A`_LpLYbqV3!%@wdZKYo`Fd2Qtm7X<{)+7$m5+e zQFrjcx5hd<2bmBtjl=BjznNCl$VRd5MhXs^zUcexvHO51050N4Qk>fAcE)>jKjk6g zv8y#@I9}W%c%uD9bjY0m@CBMU`%(aC3)8+KyZu=RxKaUCJUaF<05t$;f!)0ty%cQt9*yb4?YHs>kDuk;4dCy@TxNN+7RSrWbHO*9l0WlVRqI^5D&oMZj>r0 zH|KS}B5$}k>&`2(D$Q9dgIUb6*jSGIjYynAsrK8jXw@j}7s#(rz(Q&py92;t0O_s6oo|$zN~VvXaJvLzoyu)bp|OuaupI!> zzDje=#`}J}=M3cG%vxj`th67Jl#tHlCSItigY!8Q2|j5g>NtCv5IMgHyH%8%lqPp* z55;B-Bo97dqkf|$2%-f~;9t`^X=0Pr%I@B0BB)LM;sa zOPv+z9&h59(l^>#*CZB3!a2m0AF+T^-CfKCGqN15OmOo1Ty@BmOozt(DU-|dFXGR+ zShH(o#aIsyxsvOMq(9|a+h6~Zsj5F^ROpplN2mQ~Tno*jj=?t843}L6j|)@zm)oB* z9=6{7t9j^tig9L`;jt8Ly~m^-S%pDw%YGPDoq={HE~3Vmy_qYMTKi)kC`yj9* zQuY6@HfFocQXKjBq$v0AVgEN$IMhx$_^AH)*L&mg|9ErE;=_tj7xXZ38pVaDUK6Ys z$Lw8sSlFnJCd764m#Jgt>3km%Xp3@BVOYv#s}&^fw+X} z-QjWr$nMS?v+(|EwFo>p8P0eX_NEDwt*Xo~4iX?t@B>c>t7n$77mrCSnbh_&Nx~+( zt=t6H!4J&f2^-n-GEH_j9v^dU2UBW^51!Q?&w<-|`Koy6D%_@$JLDB1C4jNxArVDuew zmW^ZVxo`kQ$k|dpfwAR~mgeAhXM)lOcfS1^GZqf(N@q0JjmT_{I&mK%)qz5_4_(sY zorgdfe8^&nbuTd?QX1La51HU({3CgF5zA2J>;wsT{z`roonH8Y80M{Myu6;F9V5$b?9I2%F@|XvYzVo;Z*q z?NO?;L;Q_$?EFK&I^8B25(kmn{fkLsQ#L2KZ!{64S9i z3)DN7zq?KJv4(H$9B%3g)RFAyV?r2x!>*KLK77!BOAe3KsJpWq-PDp-;(X6kH{E23 za=J}tUl|+FH(Nk48j@xzcm4TXxcLAw;I7}42QNPaUwlR%!}#4|k`SDd?P{j411(Lm zWX7slz5@Vec&rca`|+NfVRyfpdwKklTh?(*H}$s`9iHagZ6eU(!?PW4ni1V?am?{$ zZs}zvT0(dhyV8hsim5dsitazMA#!JtrD1ujG970iI?P6_G8?(rgTiFQzJMkTzyAd31&ukH8;*BaHbcBS@g5X!vBC$$Q$g{dczf}FS-dj_RUod! zVz+^z8-_l)Yov2W;bnod-YmByIbSznY#VT>x6+L0XB_IK)-{#dj%gcVLTDH>$C3t@ zDYP}VVnA-qM3mg4zVozspffEE9X~WPW7N>I*lY-!tAy>D5ceZ2sS0DpU}GEirWz5I z(KpF0*EY9!atu_3LXAv{EkwC0^*$mo?hMLp2$XZn-2gWCw?8T{(c@Sb1Ek#-J)y0!j$R03pkjM*coa?ti#eC>tMqs65XSV;K4~ zsIAt}yAk}VHT1a(2pamF^6%bftRuV>tNZtPmJd_lYEv+4b(C*3#zx5tFkKp}-Vq^! zlFAc?U|5up73#W{aba%X491?$C8qu4Xi$^A* zB1rCkjA&%t`G{}lAV~RX+-?|?8u+}Czr|R~P4hDHw<05NuWZxG5BbS1enK<-iGHcEBK8q`>x8sryj)DVQYm0yeY z_)lH`(Cnwx#A$zTEt%>nbtgpY{4`@fs`loSwq4=|3~t^;;@TtsTIKd$!`N6{1&-`Q zT4D#r9#QYtDqtu&9AQIbFs|arX|LTMV7?R>7-g2G%ucnEY927^U@`5dFHAHT2$r@} zKGoWi${&APZf$MJEq0raUn1X(alEP=w!jmaN{KA?A}~EMDI>SU#O}fSX!X77#Y z&4BuzQKh%W#O_B8Zs=gJhFtl%QNvKwP^|E%;W!u#$GcI(6aWsI{>I^ zKfJnHnO1B`z9wzU&R`uF4_F_TRt5WD=nckhb&WCp`3w1bKE_Ikz zqjH985%y6a+pB<^wUpQ{pgIm9yJ3VivLMLLza%o}1Poa4pa#0JW@!nIff;|Gsb^Zb=^-u6DSyx{8GboKpkY+0S(v>!s5}a~L(6eZ&BG=U zfTm)-j1N~`9gPod0i+H;h=W*|yviqW=>sE?cY!=XsnBnhW!2T#i=vr%Gu@vU#@U~n zcYm1sr&@QUHS!l^ItN7si^8jiwReAGF7Zf=H4W}h@X5Ov$>P+avRCdXvGh-fU4YWZ z(Mx%YGh*w5Fa(A3675d8rjsR84r*uVE7Fcv%k}LnG3CRDF}Cx7sue1w#M7p}>$m86 z_XE=j{iY4?8Ni%UsyG)0FYRnMj1P1rF9t-$9XZb)m?J1mUN~u{cmK&ISG3x<%>#UJ zUB-X@>w%K{pP;#X7_&%S7063-m%t|tK6#!%aSu zSD*2=Fy7*ZE{r{(6s|YCir1h<#$>6atE|kN#evzTPnpj3 zFkQ8(q$Z5>7IT^F;U2Xx&zJ?M&84QyZj5K%jkr_O#O!b>#CEU@K6&`3;;;0t22zY& zO|v7a{0&g2`VPe(USRD+OLHj}@d`Ec>f*zIkjFd0MkfvV5Z9udAeyNhD|Pkfd;87+5MPK@@rBXq9S3^}n2U2F<>O5(IdzSV zeHAi-0fr6gUC(waqTmx-oaUBG2UyZ0u0%J-HVNnq`F0meu`4g-h)r(!SGlWFJKtX%;(%jxF8901a=7wtMh? z1nfH|sAe>*t3)R0vHqyn5!^hMF&LyhdIj{c@D#7~{#g56E%stt<1X=N(v8kv;xfUtY;e0p3eUfOH6ik^~Y!5dlRJMS79)g^&bc zrGzT*RUfPi`>A zyi5R>#hWD2aTf0YRQm_QiCyFZyK0M}A+c(EYn-Z$RfhyZZ_&!GQ-=nc1^0OZoIS>V zo@P=<1TsUHdjd4M{8;A=urEIbttA5xqGx+^pC1Lo-F(&PvR+You-?<2RCV{Fza|&` zf4R?@$!nXr;XxcMsH;g@^as;?t4+Jg(m;j)k$wuSUwsy(Rn5Ung z7aH#b*rCQG4i6W212~BZS^FjrDh#kGd|tAx_V)X^kj@qCKZJ2~GF( zd~J_f8cOj{q&=v{0?{{79KFErcv~d8i<(Tv->I7!{@`?~ir*NO5>}#DfVIPp2?ai3 zXxNXx_+r*`^|1O7fUjjBuvOU3XvkHsmVrrNWeV_~+CIFb6Rjt}V%3py-#EM8*5iZB zuX0_2@?tZawPoyY*O$|9Cxi*)dK%4&X0qsK{X; zw<3OYNJB+(1#m0UEz{aEh?lB~Wbb9Nk=VQ3XEa*EGa8LIHw|xUP6XWdHJZGy(H^DO z8LeTfbl*T&HAbGr=8pIe@)XN?G}QMgMR|~kcgJd(FYH#4lmpY8=Pn&Dkf0eMb{@{^ zttdyB(|2TNC;Kn^73D%gJ9cGu=AXn=jeg&eFYf5~RxrQ_&{}D24*{g0;lzHT=CM)`dXqaOpX3t2`2g)UUZzUzN1I4JbKTzM zPIF&;E>7*o(GT-`KpNdkKNv{+!J|_|>3rdVZKb247F>sdLFv3SRFuwI5Pu>QTj|{7 z>6?zy2@i9X4qv`@mChN%%={+N6ml#kP?XO7_RLr_Fm`O+?RLp7km*KZn%;zUlF~-&V81-8HYyQ1V>*h+8hs%%4z`3-)-Q2%N zHxx2Mkuv`oNGtZRNBEcRfo=X(cmr(ytr#x+yA%HHAQPK^_npAMmxsIj+b>9t(*BK< zU88--@sJb`|DKcFb3m|__ErdUoha>Zd%_H2ZH|r@Asp@R;h-q(Gd!@v(JgSXR1(3_ zbMg1XZ*%nflW|nhq|fEFqVo;MQ^xn~T?$;zT|bLn8YIq0%RSV=jRj81OMBTPZiQJX zAuD5~SrCp+1Z_VP6hX1h9ch+b7;T19J|scGlIU#wN=x)wlR#*Z_~XuZ9XoL;C~Vio zmxKwE^0J^aDQB+!o~QJ(Xv>iG(?~PFc~oAtWRiVZX0E*yD8*k<9I&VIxzrZ4_mJpzYyx z?76hsZHEB_E%sDBBZ@$R_w7P-2z>yXe2wrD(4L0bZ%P^=?zP)co8JKlJ(W*#h2gh2 zuzNgeOGxO$qaL|D(j^^0YFBwVd%n#8s$xLu0Y!N%URUjYVa3+|vOpky649IUxIMvM zjSUO|cXZSN6un7IQSO$MHZQJXC+ll~UR3g-I`Hzq6P6|LZ^{NmdD|sqRO}Yk>XLa? zBSI4M&Z}k~)dV!5{;3ZYWxLSuFViTR+;Vi@Wv)o$3NzHOQ(=66sq;|V_l{~zc!Uak z{Tbp)#0~H97W@nRL@eLUOKciK!f%@k!Q%fv4_ETnGY)tQrZ&% zb{>31X>W+Ch&x|#AS1wtwa7=JF7(*^@;MA|2GIG9vRL?E5EdY!cUq*xO%Hgslfynn zK;H%A;FtrbMdM`g4%YXfY@R&JgIfV&uLGStN@A8?ttev=X`cWxi5reQ**V<)D)Rjm zB^)58_zgvwEx;H5D!2G<-{@g%0{;p!4|PY!Jiy*;M%H*l3Jp+*APdq92=(Q0zbCP? z9ikzJqz0FXhy##+nx#0|=2z zRb%aAj3y)#oX5^#_9$6^<4LP%Ur|H-TclAaDty|ciniT^^hX`L2@$cI;OzRUS5oPi zWZ0ZU+6=_ixZw%rn0VS}t2qyx%aI)eetRZbPta*QCgX3-V`Aphb>c^UwRC1*&TH(7 z%^1yrr5(<53+U5=m9d1dt?co`a7}Cl*bnJrHZ6L}68H%zCXeU!3^bIy%RdpgbQ}*T zsF)pd?q67ne;x-C(yU#^YzGdSLs8=iF(&}AhJvQwg;5LCjGcF%)0qJOdwLSmcmWz= z#3&w{Lu)dY5Yk=k;12w2auMZ2M8=M(9*YFyyErv(CI$&Uoy2(fH5*BeL4rJnt;zK9 zwE1)jXFYM@b&e5&JTXGx0b;6t_B-rpAb-$-9_AF#$+A+c9>K zC&mt@tU~{bwSzpY9q5sd9HvVm7+62kX~pCgDLsw)tY)ce!T>hn}&6>^&TU`Dfm(C?k&m%`QNU(TdZtQ&@+(`wm&KI^vSPJpSX8NQ} zZ4*1s3HMy9IYF=0oS@fg4j+70_1RZ3wvbuQXK>23rz0`(#c~N_MMHc7@JSOz84@7n zr)2YS>~+U(PY%p$FydnabI74E4ZNW=bMsD&83OR^TtBxpN?Cw@q&YR(DPt6E5H&U0 zHN}{a^-8sw5zFbztX?+eLo<;LtpZ<8M;b9T%#P>Ps?q;lh93CTB8$&z6J~Tq66H9d z#ZpztDcVH;lUS5 zA5pA+JT=s@7!vhZ42fbMtifbR6q6xEOmuFgb4SrmPari0m57Gv*E7x#(40Q3@EvogQm!JdgT9OTnG0F=a(Onn~+rcXG=b`7GE4 zrko5J*M3X~V-{6_4Us3%*kVIO{I}nX5)k7ejUpNu7b#%iZZIxVu%!_I%@t6QOOSwEE+UM5tAjORlG!n~eT{R2tz@0l&JSPop>@1t zh5021#b8Pgs-cfTE89W6MM)RB`M3q1|Gc8yK<~;QUf@PC-8MH4MCf+Hy8KJhK7$a~ zlW?@O1eLfVA$p2#`%|6_4;SY0ya;?;H&LKxs=-VX)EQtoAo!v-azgiUV zw`a3gA_Hps=nnvg0a$DYJ22w8&eugt3Cl{{-<-%U9K|SZEo~ILl5bJ%T;d}at3??( zrPNq}_OWJx6~rtOfM;3JNyKas;L5`AMfmsQ&vTw**?js*;#@xEWe z8r_P20{&AT{mhq0`{*Z~&aqmO#c$kh$+FoJf$-?>aATL9Wo!FT@Z+1On5~_A#!5Ev z@q5OZ;y>9vb7QOD2fTGoaY<%&7gIr#qy8e8{1BjoD zFAMXNlT7ZjuVMCPZTJIsUDIELafjIp9uu_o{J|_L^QVE;TdH;eKgR6+Sb{{?rJ!48(OC_VK`IY>5|N(UhqO{!>hg>z+4f@I zpSn6#L47!moO~N+jZ_pdV3B$@x?hIyQGzx?O}BwM0S05+CYG--w2%4%@EJA#2>t72 zDRV3R_jq_~PtMB5)l1j{f#InS>1h*AccXwGcA9<%2~+o=PZdtc_>m2xHZGd0d*)qC z;P;F@>DPg>g);S(NURvnYAS}a)VtY_`N|qN)oc;Vc^MOYeym|Nn0h;UCQH2_A;YD8 zZls#d-o;m!U?|{T-ivPFH5w$W-P52JDjoF(<0-e~^ASbxOPwW8xbd zk-g4pGqW?p@u`7*{hZ}HJ2Qzqgpkm?wRA7Bzg<7YliUwxHY85Or4523nZuuOk) z01fyRuU}U~#{$p>e(nGsQ)U%Y$c3e48%&w$4iHX&tTofQ*>TB6Og+G~+;K2%SSj}{ z1XvC*??p`7<*H814Q*Mq3Lr820p`&Jx%Cu(?>5%QUjpPXd8})~0UGA`a!Z(e+zs$4 zD`y*6F_>8bB1Aghn%ahQ)p-R*;s}fHs%CK zn0)IS(|WVIMgDT1ND2+}bgqE1qaFljl8D26)OjoPVJDeaDFZVI;-BBRdC~DDs!~C``(&+OG5N@C za;=RF81ECM)$vnY_y|%Iq^sqt#`+5tWh^*y1QOGhf@s|OxT5?woXA2%w8tgs43M>X z{1{vBfa+8(=+Knnw1LD&%4=9k>p-8QT5QX2ktYW!%I6fu_h(cMqrpnsZ{rYXyU4+@ zk0xhv?d+WRv2tgcra9nht_@r5W|~=9d%?>t9_1X_8OLOm^9n)wh<(<7%`~I!f4znc z@*kO}`Pp82;1;yB0RLcBhj{GFQ=N#twd`)^S(|2>`6YW8(*}Qr^dVwG{@b)oiCey_ zP@h3}bjr8>MuJ!$A@xgis?o2AjUFn}gn6Z+&4Fk7U6*ksnYeVmHs`EvkaEW{aA+Hw zq2gqDlfFX_zd$N~F8;^#)BshY!2zDoTSb3olF<(!E2Tx?Yy1=NN0tWzfj1&dk>?Ba zVlWhaoT$|R&0p&oxC{Rl{Fz<#l)$Y3y9C&zrUaG&91!3!&a?j;;IIHQ0?`8e{yhA* zHxImt8qUR^AqFq@0dxasim{T;_+8f&OC*t)C%}&>62l8Wa$d$%*fWU4e%lO5me&kP z)0eVSyr$gzm~snkb93k4k14m%ZJq#;l@LwZ;Pja~Cef_L^UTcjwEYz2y3D&;1g)*} z%yyH*#~6VoLqR%UWNO~66?-RfAoPtVLHbjAIP{SxAn6!JHh(5y6X-S&gh4yvK=5Nv zJ)4S=O{@PL9@^QZ?Uw?c){Da*s2o5!z-e~TEY0kg1mi*JyY!C1PV`pSrSH_skf3XU zvztdlzgG;N-&HI`*PhVD8n6Q=az4g;`v(hO_LAoRn!N}YuEkNCzuAlnu1mj1FA6R3bT#uwf6w;s!mV7YmwrPqk<4XD*@-Pi z&q|=uyf!It5-RD|t~-SkSf?+*X!rAULc9NOYbx#0O=#GLdeQnY#nAtU@q7wnQ##fh zosHYXA*nZNN34gX?{kKvO^j;k&+Dn7%79xs0WavM1x1;-E`61r$^%dd84?c@oJLmy zKfIeI59&DmH7D;~lK1PROeEoV7G~SwZ~A)<+5xNELNo88!}Qj|p1Pj0f390B0cYyX zlb-Pe6tp(kZE_LZ5t|PsC~s5!!ogbbYO;4-;S#M`(0XX0*(UbcDrc*!@G33n_q1-| zGA%cFy*JHuS~R%98?cgq1dN*+W8AEU@ZV4{5fKI5~<>xa2lGqQm&_DQhUEyb1G}O^c=rXlau!Sce zI4YQ>gyi6WiG<^zA&^$&Y1)z>10mUHbX`e`niifxiyfx*3RkM_1Iq!@S_53qh1tsh ziUHcLXR`4jW%Ux^T{RH)F*h0o&|V(bJ1+F<@yd{1(IaWrxXXC=87lIWSeT)kq4PZ? z6|RNGo}K`qF~gHWXbe5jAXQ|2D;r;TcmjmH(AOST2#LX_kSNY(hKEkt?rWsaGtX_c z6Xo?Si_85s86L|I30C^~S`X(uS8}ji%n6#jKzu&anG%{?rLFBVn^6>Z$=57LfAq)i zqFw|IuIO&+%VhJI9UJW9nI(vlk$nTlx!B>(%>2Kwu{p$*4)4AKHSJfR)?1gGh3fOx zFPEF`Vpp|ule0PSOr-t89hj`4E?PbvX9(n`U@%()S5) zi=;ziJb4a}CVh#%ab3%i&DwVv$LWgXH6^1%=HBy|QUT=iSg!q<00#i_nxv*&fZug_ zJmW}>^tTqrSb?^G78-w&^t&VNpP}qDq|l|YSo|lUF{8xIi}>L!l8*cv8hb-yFUf04 zM&=t(G6BC$;zg~176A#3lN!^QZ`0V)nw@W^$C3xIAf!l zR�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 delta 52265 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 AVE_OC delta 161526 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 Date: Thu, 29 Feb 2024 23:59:41 +0100 Subject: [PATCH 22/96] revert LazyMap --- .../src/lib/node/ledger/shell/init_chain.rs | 14 ++-- crates/core/src/masp.rs | 4 + crates/sdk/src/queries/shell.rs | 10 ++- crates/shielded_token/src/conversion.rs | 33 +++----- crates/tests/src/integration/masp.rs | 71 ++++++++++++------ .../tx_proposal_ibc_token_inflation.wasm | Bin 449202 -> 461186 bytes wasm_for_tests/wasm_source/src/lib.rs | 11 ++- 7 files changed, 84 insertions(+), 59 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index a351adfe40..11f9acf50a 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -1,5 +1,5 @@ //! Implementation of chain initialization for the Shell -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::ops::ControlFlow; use masp_primitives::merkle_tree::CommitmentTree; @@ -12,7 +12,7 @@ use namada::ledger::parameters::Parameters; use namada::ledger::{ibc, pos}; use namada::proof_of_stake::BecomeValidator; use namada::state::StorageWrite; -use namada::token::conversion::token_map_handle; +use namada::token::storage_key::masp_token_map_key; use namada::token::{credit_tokens, write_denom}; use namada::vm::validate_untrusted_wasm; use namada_sdk::eth_bridge::EthBridgeStatus; @@ -419,7 +419,7 @@ where /// Init genesis token accounts fn init_token_accounts(&mut self, genesis: &genesis::chain::Finalized) { - let token_map = token_map_handle(); + let mut token_map = BTreeMap::new(); for (alias, token) in &genesis.tokens.token { tracing::debug!("Initializing token {alias}"); @@ -440,11 +440,12 @@ where // add token addresses to the masp reward conversions lookup // table. let alias = alias.to_string(); - token_map - .insert(&mut self.state, alias, address.clone()) - .expect("Couldn't init token accounts"); + token_map.insert(alias, address.clone()); } } + self.state + .write(&masp_token_map_key(), token_map) + .expect("Couldn't init token accounts"); } /// Init genesis token balances @@ -937,7 +938,6 @@ impl Policy { #[cfg(all(test, not(feature = "integration")))] mod test { - use std::collections::BTreeMap; use std::str::FromStr; use namada::core::string_encoding::StringEncoded; diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index ad37985461..d0bcdfc12e 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -1,5 +1,6 @@ //! MASP types +use std::collections::BTreeMap; use std::fmt::Display; use std::str::FromStr; @@ -89,6 +90,9 @@ pub fn encode_asset_type( .encode() } +/// MASP token map +pub type TokenMap = BTreeMap; + // enough capacity to store the payment address // plus the pinned/unpinned discriminant const PAYMENT_ADDRESS_SIZE: usize = 43 + 1; diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index 596023c9fa..5f4295d08f 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -12,6 +12,7 @@ use namada_core::address::Address; use namada_core::dec::Dec; use namada_core::hash::Hash; use namada_core::hints; +use namada_core::masp::TokenMap; use namada_core::storage::{ self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, }; @@ -19,7 +20,7 @@ use namada_core::token::{Denomination, MaspDigitPos}; use namada_core::uint::Uint; use namada_state::{DBIter, LastBlock, StateRead, StorageHasher, DB}; use namada_storage::{ResultExt, StorageRead}; -use namada_token::conversion::token_map_handle; +use namada_token::storage_key::masp_token_map_key; #[cfg(any(test, feature = "async-client"))] use namada_tx::data::TxResult; @@ -229,10 +230,11 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let token_map = token_map_handle(); + let token_map_key = masp_token_map_key(); + let token_map: TokenMap = + ctx.state.read(&token_map_key)?.unwrap_or_default(); let mut data = Vec::::new(); - for res in token_map.iter(ctx.state)? { - let (name, token) = res.unwrap(); + for (name, token) in token_map { let max_reward_rate = ctx .state .read::(&namada_token::storage_key::masp_max_reward_rate_key( diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index 4eb19bc724..b0fa016a53 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -8,7 +8,6 @@ use namada_core::dec::Dec; use namada_core::hash::Hash; use namada_core::uint::Uint; use namada_parameters as parameters; -use namada_storage::collections::{LazyCollection, LazyMap}; use namada_storage::{StorageRead, StorageWrite}; use namada_trans_token::inflation::{ ShieldedRewardsController, ShieldedValsToUpdate, @@ -17,20 +16,14 @@ use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_trans_token::{read_denom, Amount, DenominatedAmount, Denomination}; #[cfg(any(feature = "multicore", test))] -use crate::storage_key::masp_assets_hash_key; +use crate::storage_key::{masp_assets_hash_key, masp_token_map_key}; use crate::storage_key::{ masp_kd_gain_key, masp_kp_gain_key, masp_last_inflation_key, masp_last_locked_amount_key, masp_locked_amount_target_key, - masp_max_reward_rate_key, masp_token_map_key, + masp_max_reward_rate_key, }; use crate::WithConversionState; -type TokenMap = LazyMap; - -pub fn token_map_handle() -> TokenMap { - LazyMap::open(masp_token_map_key()) -} - /// Compute the precision of MASP rewards for the given token. This function /// must be a non-zero constant for a given token. pub fn calculate_masp_rewards_precision( @@ -233,15 +226,10 @@ where // The derived conversions will be placed in MASP address space let masp_addr = MASP; - let token_map = token_map_handle(); - let mut masp_reward_keys = token_map - .iter(storage)? - .map(|a| { - let (_, address) = a.expect("The address should be stored"); - address - }) - .collect::>(); - + let token_map_key = masp_token_map_key(); + let token_map: namada_core::masp::TokenMap = + storage.read(&token_map_key)?.unwrap_or_default(); + let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect(); let mut masp_reward_denoms = BTreeMap::new(); // Put the native rewards first because other inflation computations depend // on it @@ -635,10 +623,11 @@ mod tests { .unwrap(); // Insert tokens into MASP conversion state - let token_map = token_map_handle(); - token_map - .insert(&mut s, alias.to_string(), token_addr.clone()) - .unwrap(); + let token_map_key = masp_token_map_key(); + let mut token_map: namada_core::masp::TokenMap = + s.read(&token_map_key).unwrap().unwrap_or_default(); + token_map.insert(alias.to_string(), token_addr.clone()); + s.write(&token_map_key, token_map).unwrap(); } } diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 857a773d47..3e0bc5dd64 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -1,17 +1,17 @@ -use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::state::StorageWrite; -use namada::token::conversion::token_map_handle; +use namada::state::{StorageRead, StorageWrite}; +use namada::token::storage_key::masp_token_map_key; use namada::token::{self, DenominatedAmount}; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::node::NodeResults; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_apps::wallet::defaults::christel_keypair; use namada_core::dec::Dec; +use namada_core::masp::TokenMap; use namada_sdk::masp::fs::FsShieldedUtils; use test_log::test; @@ -2127,21 +2127,26 @@ fn dynamic_assets() -> Result<()> { let btc = BTC.to_lowercase(); let nam = NAM.to_lowercase(); - let token_map = token_map_handle(); + let token_map_key = masp_token_map_key(); let test_tokens = { // Only distribute rewards for NAM tokens - let wls = &mut node.shell.lock().unwrap().state; - let tokens = token_map - .iter(wls) + let mut tokens: TokenMap = node + .shell + .lock() .unwrap() - .map(|res| res.unwrap()) - .collect::>(); - for alias in tokens.keys() { - if alias != &nam { - token_map.remove(wls, alias).unwrap(); - } - } - tokens + .state + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + let test_tokens = tokens.clone(); + tokens.retain(|k, _v| *k == nam); + node.shell + .lock() + .unwrap() + .state + .write(&token_map_key, tokens.clone()) + .unwrap(); + test_tokens }; // add necessary viewing keys to shielded context run( @@ -2226,12 +2231,20 @@ fn dynamic_assets() -> Result<()> { { // Start decoding and distributing shielded rewards for BTC in next // epoch - token_map - .insert( - &mut node.shell.lock().unwrap().state, - btc.clone(), - test_tokens[&btc].clone(), - ) + let mut tokens: TokenMap = node + .shell + .lock() + .unwrap() + .state + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + tokens.insert(btc.clone(), test_tokens[&btc].clone()); + node.shell + .lock() + .unwrap() + .state + .write(&token_map_key, tokens) .unwrap(); } @@ -2460,8 +2473,20 @@ fn dynamic_assets() -> Result<()> { { // Stop decoding and distributing shielded rewards for BTC in next epoch - token_map - .remove(&mut node.shell.lock().unwrap().state, &btc) + let mut tokens: TokenMap = node + .shell + .lock() + .unwrap() + .state + .read(&token_map_key) + .unwrap() + .unwrap_or_default(); + tokens.remove(&btc); + node.shell + .lock() + .unwrap() + .state + .write(&token_map_key, tokens) .unwrap(); } diff --git a/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm b/wasm_for_tests/tx_proposal_ibc_token_inflation.wasm index 7949b1fd4d8f36c1abb8b87962240c873b70537f..1113926e2cebe6bb43e65b634f24708d6d72dc52 100755 GIT binary patch delta 149710 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