diff --git a/.changelog/unreleased/improvements/1943-refactor-past-epoch-offsets.md b/.changelog/unreleased/improvements/1943-refactor-past-epoch-offsets.md new file mode 100644 index 0000000000..958b40760d --- /dev/null +++ b/.changelog/unreleased/improvements/1943-refactor-past-epoch-offsets.md @@ -0,0 +1,3 @@ +- Improve the Epoched data structure's bookkeeping of past + epochs, now parameterizable by PoS and governance params. + ([\#1943](https://github.com/anoma/namada/pull/1943)) \ No newline at end of file diff --git a/ethereum_bridge/src/protocol/transactions/votes/update.rs b/ethereum_bridge/src/protocol/transactions/votes/update.rs index c1173bdf12..928bb4d7f8 100644 --- a/ethereum_bridge/src/protocol/transactions/votes/update.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/update.rs @@ -376,6 +376,7 @@ mod tests { #[test] fn test_apply_duplicate_votes() -> Result<()> { let mut wl_storage = TestWlStorage::default(); + test_utils::init_default_storage(&mut wl_storage); let validator = address::testing::established_address_1(); let already_voted_height = BlockHeight(100); @@ -411,6 +412,7 @@ mod tests { #[test] fn test_calculate_already_seen() -> Result<()> { let mut wl_storage = TestWlStorage::default(); + test_utils::init_default_storage(&mut wl_storage); let event = default_event(); let keys = vote_tallies::Keys::from(&event); let tally_pre = TallyParams { diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index c06a6efd8e..0d29affbdd 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -16,6 +16,7 @@ use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::storage::{self, Epoch}; use crate::parameters::PosParams; +use crate::read_pos_params; /// Sub-key holding a lazy map in storage pub const LAZY_MAP_SUB_KEY: &str = "lazy_map"; @@ -28,57 +29,48 @@ pub const OLDEST_EPOCH_SUB_KEY: &str = "oldest_epoch"; const DEFAULT_NUM_PAST_EPOCHS: u64 = 2; /// Discrete epoched data handle -pub struct Epoched< - Data, - FutureEpochs, - const NUM_PAST_EPOCHS: u64 = DEFAULT_NUM_PAST_EPOCHS, - SON = collections::Simple, -> { +pub struct Epoched { storage_prefix: storage::Key, future_epochs: PhantomData, + past_epochs: PhantomData, data: PhantomData, phantom_son: PhantomData, } /// Discrete epoched data handle with nested lazy structure -pub type NestedEpoched< - Data, - FutureEpochs, - const NUM_PAST_EPOCHS: u64 = DEFAULT_NUM_PAST_EPOCHS, -> = Epoched; +pub type NestedEpoched = + Epoched; /// Delta epoched data handle -pub struct EpochedDelta { +pub struct EpochedDelta { storage_prefix: storage::Key, future_epochs: PhantomData, + past_epochs: PhantomData, data: PhantomData, } -impl - Epoched +impl + Epoched where FutureEpochs: EpochOffset, + PastEpochs: EpochOffset, { /// Open the handle pub fn open(key: storage::Key) -> Self { Self { storage_prefix: key, future_epochs: PhantomData, + past_epochs: PhantomData, data: PhantomData, phantom_son: PhantomData, } } - - /// Return the number of past epochs to keep data for - pub fn get_num_past_epochs() -> u64 { - NUM_PAST_EPOCHS - } } -impl - Epoched +impl Epoched where FutureEpochs: EpochOffset, + PastEpochs: EpochOffset, Data: BorshSerialize + BorshDeserialize + 'static + Debug, { /// Initialize new epoched data. Sets the head to the given value. @@ -125,7 +117,8 @@ where Some(_) => return Ok(res), None => { if epoch.0 > 0 - && epoch > Self::sub_past_epochs(last_update) + && epoch + > Self::sub_past_epochs(params, last_update) { epoch = Epoch(epoch.0 - 1); } else { @@ -149,7 +142,8 @@ where where S: StorageWrite + StorageRead, { - self.update_data(storage, current_epoch)?; + let params = read_pos_params(storage)?; + self.update_data(storage, ¶ms, current_epoch)?; self.set_at_epoch(storage, value, current_epoch, offset) } @@ -177,6 +171,7 @@ where fn update_data( &self, storage: &mut S, + params: &PosParams, current_epoch: Epoch, ) -> storage_api::Result<()> where @@ -189,7 +184,7 @@ where { let oldest_to_keep = current_epoch .0 - .checked_sub(NUM_PAST_EPOCHS) + .checked_sub(PastEpochs::value(params)) .unwrap_or_default(); if oldest_epoch.0 < oldest_to_keep { let diff = oldest_to_keep - oldest_epoch.0; @@ -211,7 +206,8 @@ where } } if let Some(latest_value) = latest_value { - let new_oldest_epoch = Self::sub_past_epochs(current_epoch); + let new_oldest_epoch = + Self::sub_past_epochs(params, current_epoch); // TODO we can add `contains_key` to LazyMap if data_handler.get(storage, &new_oldest_epoch)?.is_none() { tracing::debug!( @@ -269,8 +265,13 @@ where LazyMap::open(key) } - fn sub_past_epochs(epoch: Epoch) -> Epoch { - Epoch(epoch.0.checked_sub(NUM_PAST_EPOCHS).unwrap_or_default()) + fn sub_past_epochs(params: &PosParams, epoch: Epoch) -> Epoch { + Epoch( + epoch + .0 + .checked_sub(PastEpochs::value(params)) + .unwrap_or_default(), + ) } fn get_oldest_epoch_storage_key(&self) -> storage::Key { @@ -303,10 +304,11 @@ where } } -impl - Epoched +impl + Epoched where FutureEpochs: EpochOffset, + PastEpochs: EpochOffset, Data: LazyCollection + Debug, { /// Get the inner LazyCollection value by the outer key @@ -342,7 +344,7 @@ where .unwrap() } - /// TODO + /// Get the epoch of the most recent update pub fn get_last_update( &self, storage: &S, @@ -354,7 +356,7 @@ where storage.read(&key) } - /// TODO + /// Set the epoch of the most recent update pub fn set_last_update( &self, storage: &mut S, @@ -366,37 +368,13 @@ where let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch) } - - /// TODO - pub fn sub_past_epochs(epoch: Epoch) -> Epoch { - Epoch(epoch.0.checked_sub(NUM_PAST_EPOCHS).unwrap_or_default()) - } - - // pub fn get_inner_by_epoch(&self) -> storage_api::Result {} - - // TODO: we may need an update_data() method, figure out when it should be - // called (in at()?) } -// impl -// Epoched< -// LazyMap, -// FutureEpochs, -// NUM_PAST_EPOCHS, -// collections::Nested, -// > -// where -// FutureEpochs: EpochOffset, -// { -// pub fn get_inner_by_epoch(&self, epoch: &Epoch) -> LazyMap { -// self.at() -// } -// } - -impl - EpochedDelta +impl + EpochedDelta where FutureEpochs: EpochOffset, + PastEpochs: EpochOffset, Data: BorshSerialize + BorshDeserialize + ops::Add @@ -409,6 +387,7 @@ where Self { storage_prefix: key, future_epochs: PhantomData, + past_epochs: PhantomData, data: PhantomData, } } @@ -456,7 +435,7 @@ where None => Ok(None), Some(last_update) => { let data_handler = self.get_data_handler(); - let start_epoch = Self::sub_past_epochs(last_update); + let start_epoch = Self::sub_past_epochs(params, last_update); let future_most_epoch = last_update + FutureEpochs::value(params); @@ -494,7 +473,8 @@ where S: StorageWrite + StorageRead, Data: Default, { - self.update_data(storage, current_epoch)?; + let params = read_pos_params(storage)?; + self.update_data(storage, ¶ms, current_epoch)?; let cur_value = self .get_delta_val(storage, current_epoch + offset)? .unwrap_or_default(); @@ -512,7 +492,8 @@ where where S: StorageWrite + StorageRead, { - self.update_data(storage, current_epoch)?; + let params = read_pos_params(storage)?; + self.update_data(storage, ¶ms, current_epoch)?; self.set_at_epoch(storage, value, current_epoch, offset) } @@ -538,6 +519,7 @@ where fn update_data( &self, storage: &mut S, + params: &PosParams, current_epoch: Epoch, ) -> storage_api::Result<()> where @@ -550,7 +532,7 @@ where { let oldest_to_keep = current_epoch .0 - .checked_sub(NUM_PAST_EPOCHS) + .checked_sub(PastEpochs::value(params)) .unwrap_or_default(); if oldest_epoch.0 < oldest_to_keep { let diff = oldest_to_keep - oldest_epoch.0; @@ -576,7 +558,8 @@ where } } if let Some(sum) = sum { - let new_oldest_epoch = Self::sub_past_epochs(current_epoch); + let new_oldest_epoch = + Self::sub_past_epochs(params, current_epoch); let new_oldest_epoch_data = match data_handler.get(storage, &new_oldest_epoch)? { Some(oldest_epoch_data) => oldest_epoch_data + sum, @@ -650,8 +633,13 @@ where handle.iter(storage)?.collect() } - fn sub_past_epochs(epoch: Epoch) -> Epoch { - Epoch(epoch.0.checked_sub(NUM_PAST_EPOCHS).unwrap_or_default()) + fn sub_past_epochs(params: &PosParams, epoch: Epoch) -> Epoch { + Epoch( + epoch + .0 + .checked_sub(PastEpochs::value(params)) + .unwrap_or_default(), + ) } fn get_oldest_epoch_storage_key(&self) -> storage::Key { @@ -698,7 +686,7 @@ where )] pub struct OffsetZero; impl EpochOffset for OffsetZero { - fn value(_paras: &PosParams) -> u64 { + fn value(_params: &PosParams) -> u64 { 0 } @@ -707,6 +695,29 @@ impl EpochOffset for OffsetZero { } } +/// Default offset +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub struct OffsetDefaultNumPastEpochs; +impl EpochOffset for OffsetDefaultNumPastEpochs { + fn value(_params: &PosParams) -> u64 { + DEFAULT_NUM_PAST_EPOCHS + } + + fn dyn_offset() -> DynEpochOffset { + DynEpochOffset::DefaultNumPastEpoch + } +} + /// Offset at pipeline length. #[derive( Debug, @@ -776,11 +787,59 @@ impl EpochOffset for OffsetPipelinePlusUnbondingLen { } } +/// Offset at the slash processing delay. +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub struct OffsetSlashProcessingLen; +impl EpochOffset for OffsetSlashProcessingLen { + fn value(params: &PosParams) -> u64 { + params.slash_processing_epoch_offset() + } + + fn dyn_offset() -> DynEpochOffset { + DynEpochOffset::SlashProcessingLen + } +} + +/// Maximum offset. +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub struct OffsetMaxU64; +impl EpochOffset for OffsetMaxU64 { + fn value(_params: &PosParams) -> u64 { + u64::MAX + } + + fn dyn_offset() -> DynEpochOffset { + DynEpochOffset::MaxU64 + } +} + /// Offset length dynamic choice. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum DynEpochOffset { /// Zero offset Zero, + /// Offset at the const default num past epochs (above) + DefaultNumPastEpoch, /// Offset at pipeline length - 1 PipelineLenMinusOne, /// Offset at pipeline length. @@ -789,6 +848,11 @@ pub enum DynEpochOffset { UnbondingLen, /// Offset at pipeline + unbonding length. PipelinePlusUnbondingLen, + /// Offset at slash processing delay (unbonding + + /// cubic_slashing_window + 1). + SlashProcessingLen, + /// Offset of the max u64 value + MaxU64, } /// Which offset should be used to set data. The value is read from @@ -805,19 +869,23 @@ pub trait EpochOffset: #[cfg(test)] mod test { use namada_core::ledger::storage::testing::TestWlStorage; + use namada_core::types::address::testing::established_address_1; + use namada_core::types::dec::Dec; + use namada_core::types::{key, token}; use test_log::test; use super::*; + use crate::types::GenesisValidator; #[test] fn test_epoched_data_trimming() -> storage_api::Result<()> { - let mut s = TestWlStorage::default(); + let mut s = init_storage()?; - const NUM_PAST_EPOCHS: u64 = 2; let key_prefix = storage::Key::parse("test").unwrap(); - let epoched = Epoched::::open( - key_prefix, - ); + let epoched = + Epoched::::open( + key_prefix, + ); let data_handler = epoched.get_data_handler(); assert!(epoched.get_last_update(&s)?.is_none()); assert!(epoched.get_oldest_epoch(&s)?.is_none()); @@ -882,13 +950,11 @@ mod test { #[test] fn test_epoched_without_data_trimming() -> storage_api::Result<()> { - let mut s = TestWlStorage::default(); + let mut s = init_storage()?; - const NUM_PAST_EPOCHS: u64 = u64::MAX; let key_prefix = storage::Key::parse("test").unwrap(); - let epoched = Epoched::::open( - key_prefix, - ); + let epoched = + Epoched::::open(key_prefix); let data_handler = epoched.get_data_handler(); assert!(epoched.get_last_update(&s)?.is_none()); assert!(epoched.get_oldest_epoch(&s)?.is_none()); @@ -952,12 +1018,11 @@ mod test { #[test] fn test_epoched_delta_data_trimming() -> storage_api::Result<()> { - let mut s = TestWlStorage::default(); + let mut s = init_storage()?; - const NUM_PAST_EPOCHS: u64 = 2; let key_prefix = storage::Key::parse("test").unwrap(); let epoched = - EpochedDelta::::open( + EpochedDelta::::open( key_prefix, ); let data_handler = epoched.get_data_handler(); @@ -1026,13 +1091,12 @@ mod test { #[test] fn test_epoched_delta_without_data_trimming() -> storage_api::Result<()> { - let mut s = TestWlStorage::default(); + let mut s = init_storage()?; // Nothing should ever get trimmed - const NUM_PAST_EPOCHS: u64 = u64::MAX; let key_prefix = storage::Key::parse("test").unwrap(); let epoched = - EpochedDelta::::open( + EpochedDelta::::open( key_prefix, ); let data_handler = epoched.get_data_handler(); @@ -1110,88 +1174,24 @@ mod test { Ok(()) } - // use namada_core::ledger::storage::testing::TestStorage; - // use namada_core::types::address::{self, Address}; - // use namada_core::types::storage::Key; - // - // use super::{ - // storage, storage_api, Epoch, LazyMap, NestedEpoched, NestedMap, - // OffsetPipelineLen, - // }; - // - // #[test] - // fn testing_epoched_new() -> storage_api::Result<()> { - // let mut storage = TestStorage::default(); - // - // let key1 = storage::Key::parse("test_nested1").unwrap(); - // let nested1 = - // NestedEpoched::, OffsetPipelineLen>::open( - // key1, - // ); - // nested1.init(&mut storage, Epoch(0))?; - // - // let key2 = storage::Key::parse("test_nested2").unwrap(); - // let nested2 = NestedEpoched::< - // NestedMap>, - // OffsetPipelineLen, - // >::open(key2); - // nested2.init(&mut storage, Epoch(0))?; - // - // dbg!(&nested1.get_last_update_storage_key()); - // dbg!(&nested1.get_last_update(&storage)); - // - // nested1.at(&Epoch(0)).insert( - // &mut storage, - // address::testing::established_address_1(), - // 1432, - // )?; - // dbg!(&nested1.at(&Epoch(0)).iter(&mut storage)?.next()); - // dbg!(&nested1.at(&Epoch(1)).iter(&mut storage)?.next()); - // - // nested2.at(&Epoch(0)).at(&100).insert( - // &mut storage, - // 1, - // address::testing::established_address_2(), - // )?; - // dbg!(&nested2.at(&Epoch(0)).iter(&mut storage)?.next()); - // dbg!(&nested2.at(&Epoch(1)).iter(&mut storage)?.next()); - // - // dbg!(&nested_epoched.get_epoch_key(&Epoch::from(0))); - // - // let epoch = Epoch::from(0); - // let addr = address::testing::established_address_1(); - // let amount: u64 = 234235; - // - // nested_epoched - // .at(&epoch) - // .insert(&mut storage, addr.clone(), amount)?; - // - // let epoch = epoch + 3_u64; - // nested_epoched.at(&epoch).insert( - // &mut storage, - // addr.clone(), - // 999_u64, - // )?; - // - // dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(0))?); - // dbg!( - // nested_epoched - // .get_data_handler() - // .get_data_key(&Epoch::from(3)) - // ); - // dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(3))?); - // dbg!( - // nested_epoched - // .at(&Epoch::from(0)) - // .get(&storage, &addr.clone())? - // ); - // dbg!( - // nested_epoched - // .at(&Epoch::from(3)) - // .get(&storage, &addr.clone())? - // ); - // dbg!(nested_epoched.at(&Epoch::from(3)).get_data_key(&addr)); - // - // Ok(()) - // } + fn init_storage() -> storage_api::Result { + let mut s = TestWlStorage::default(); + crate::init_genesis( + &mut s, + &PosParams::default(), + [GenesisValidator { + address: established_address_1(), + tokens: token::Amount::native_whole(1_000), + consensus_key: key::testing::keypair_1().to_public(), + eth_hot_key: key::testing::keypair_3().to_public(), + eth_cold_key: key::testing::keypair_3().to_public(), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), + }] + .into_iter(), + Epoch::default(), + )?; + Ok(s) + } } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 8477b21cf2..842073144a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -14,7 +14,6 @@ use namada_core::ledger::storage_api::collections::lazy_map::NestedMap; use namada_core::ledger::storage_api::collections::{ LazyMap, LazySet, LazyVec, }; -use namada_core::ledger::storage_api::{self, StorageRead}; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::key::common; @@ -25,82 +24,43 @@ pub use rev_order::ReverseOrdTokenAmount; use crate::parameters::PosParams; -// TODO: replace `POS_MAX_DECIMAL_PLACES` with -// core::types::token::NATIVE_MAX_DECIMAL_PLACES?? -const U64_MAX: u64 = u64::MAX; - -/// Number of epochs below the current epoch for which validator deltas and -/// slashes are stored -const VALIDATOR_DELTAS_SLASHES_LEN: u64 = 23; - // TODO: add this to the spec /// Stored positions of validators in validator sets pub type ValidatorSetPositions = crate::epoched::NestedEpoched< LazyMap, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; -impl ValidatorSetPositions { - /// TODO - pub fn get_position( - &self, - storage: &S, - epoch: &Epoch, - address: &Address, - params: &PosParams, - ) -> storage_api::Result> - where - S: StorageRead, - { - let last_update = self.get_last_update(storage)?; - // dbg!(&last_update); - if last_update.is_none() { - return Ok(None); - } - let last_update = last_update.unwrap(); - let future_most_epoch: Epoch = last_update + params.pipeline_len; - // dbg!(future_most_epoch); - let mut epoch = std::cmp::min(*epoch, future_most_epoch); - loop { - // dbg!(epoch); - match self.at(&epoch).get(storage, address)? { - Some(val) => return Ok(Some(val)), - None => { - if epoch.0 > 0 && epoch > Self::sub_past_epochs(last_update) - { - epoch = Epoch(epoch.0 - 1); - } else { - return Ok(None); - } - } - } - } - } -} - // TODO: check the offsets for each epoched type!! /// Epoched validator's consensus key. pub type ValidatorConsensusKeys = crate::epoched::Epoched< common::PublicKey, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Epoched validator's eth hot key. pub type ValidatorEthHotKeys = crate::epoched::Epoched< common::PublicKey, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Epoched validator's eth cold key. pub type ValidatorEthColdKeys = crate::epoched::Epoched< common::PublicKey, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Epoched validator's state. -pub type ValidatorStates = - crate::epoched::Epoched; +pub type ValidatorStates = crate::epoched::Epoched< + ValidatorState, + crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, +>; /// A map from a position to an address in a Validator Set pub type ValidatorPositionAddresses = LazyMap; @@ -117,41 +77,49 @@ pub type BelowCapacityValidatorSet = pub type ConsensusValidatorSets = crate::epoched::NestedEpoched< ConsensusValidatorSet, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Epoched below-capacity validator sets. pub type BelowCapacityValidatorSets = crate::epoched::NestedEpoched< BelowCapacityValidatorSet, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Epoched total consensus validator stake -pub type TotalConsensusStakes = - crate::epoched::Epoched; +pub type TotalConsensusStakes = crate::epoched::Epoched< + Amount, + crate::epoched::OffsetZero, + crate::epoched::OffsetMaxU64, +>; /// Epoched validator's deltas. pub type ValidatorDeltas = crate::epoched::EpochedDelta< token::Change, crate::epoched::OffsetUnbondingLen, - VALIDATOR_DELTAS_SLASHES_LEN, + crate::epoched::OffsetSlashProcessingLen, >; /// Epoched total deltas. pub type TotalDeltas = crate::epoched::EpochedDelta< token::Change, crate::epoched::OffsetUnbondingLen, - VALIDATOR_DELTAS_SLASHES_LEN, + crate::epoched::OffsetSlashProcessingLen, >; /// Epoched validator commission rate -pub type CommissionRates = - crate::epoched::Epoched; +pub type CommissionRates = crate::epoched::Epoched< + Dec, + crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, +>; /// Epoched validator's bonds pub type Bonds = crate::epoched::EpochedDelta< token::Amount, crate::epoched::OffsetPipelineLen, - U64_MAX, + crate::epoched::OffsetMaxU64, >; /// An epoched lazy set of all known active validator addresses (consensus, @@ -159,6 +127,7 @@ pub type Bonds = crate::epoched::EpochedDelta< pub type ValidatorAddresses = crate::epoched::NestedEpoched< LazySet
, crate::epoched::OffsetPipelineLen, + crate::epoched::OffsetDefaultNumPastEpochs, >; /// Slashes indexed by validator address and then block height (for easier @@ -172,7 +141,7 @@ pub type ValidatorSlashes = NestedMap; pub type EpochedSlashes = crate::epoched::NestedEpoched< ValidatorSlashes, crate::epoched::OffsetUnbondingLen, - VALIDATOR_DELTAS_SLASHES_LEN, + crate::epoched::OffsetSlashProcessingLen, >; /// Epoched validator's unbonds diff --git a/wasm/checksums.json b/wasm/checksums.json index 629d590c18..ab2d7ecf71 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -20,4 +20,4 @@ "vp_masp.wasm": "vp_masp.ac713f88be7d977c37b68c7b76a8f10ee689cda22e79d24a458f30c317163457.wasm", "vp_user.wasm": "vp_user.1944e4ef7512c2d07c124316952e642eb257daa351dc0af9ea9bfed3d1534ddd.wasm", "vp_validator.wasm": "vp_validator.345491f65e2ca3553ba2033a4afdacf99c0d6cd8729108362f2e66b642399cbf.wasm" -} \ No newline at end of file +}