diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index 3e3d351511..b129c5e058 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -15,8 +15,10 @@ pub(crate) fn try_add_gateway( let sender_bytes = info.sender.as_bytes(); // if the client has an active bonded mixnode, don't allow gateway bonding - if mixnodes_storage::mixnodes_owners_read(deps.storage) - .may_load(sender_bytes)? + if mixnodes_storage::mixnodes() + .idx + .owner + .item(deps.storage, info.sender.to_string())? .is_some() { return Err(ContractError::AlreadyOwnsMixnode); diff --git a/contracts/mixnet/src/mixnodes/bonding_queries.rs b/contracts/mixnet/src/mixnodes/bonding_queries.rs index 2767ad25b3..f811efd78e 100644 --- a/contracts/mixnet/src/mixnodes/bonding_queries.rs +++ b/contracts/mixnet/src/mixnodes/bonding_queries.rs @@ -22,7 +22,7 @@ pub fn query_mixnodes_paged( let start = start_after.map(Bound::exclusive); - let nodes = storage::MIXNODES + let nodes = storage::mixnodes() .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| res.map(|item| item.1)) @@ -43,8 +43,10 @@ pub fn query_mixnodes_paged( } pub fn query_owns_mixnode(deps: Deps, address: Addr) -> StdResult { - let has_node = storage::mixnodes_owners_read(deps.storage) - .may_load(address.as_bytes())? + let has_node = storage::mixnodes() + .idx + .owner + .item(deps.storage, address.to_string())? .is_some(); Ok(MixOwnershipResponse { address, has_node }) } diff --git a/contracts/mixnet/src/mixnodes/bonding_transactions.rs b/contracts/mixnet/src/mixnodes/bonding_transactions.rs index 73765f67d2..89a779b88e 100644 --- a/contracts/mixnet/src/mixnodes/bonding_transactions.rs +++ b/contracts/mixnet/src/mixnodes/bonding_transactions.rs @@ -28,15 +28,19 @@ pub(crate) fn try_add_mixnode( } // if the client has an active bonded mixnode, regardless of its identity, don't allow bonding - if storage::mixnodes_owners_read(deps.storage) - .may_load(sender_bytes)? + if storage::mixnodes() + .idx + .owner + .item(deps.storage, info.sender.to_string())? .is_some() { return Err(ContractError::AlreadyOwnsMixnode); } // check if somebody else has already bonded a mixnode with this identity - if let Some(existing_bond) = storage::MIXNODES.may_load(deps.storage, &mix_node.identity_key)? { + if let Some(existing_bond) = + storage::mixnodes().may_load(deps.storage, mix_node.identity_key.as_bytes())? + { if existing_bond.owner != info.sender { return Err(ContractError::DuplicateMixnode { owner: existing_bond.owner, @@ -62,13 +66,11 @@ pub(crate) fn try_add_mixnode( None, ); - let identity = stored_bond.identity(); - // technically we don't have to set the total_delegation bucket, but it makes things easier // in different places that we can guarantee that if node exists, so does the data behind the total delegation - storage::MIXNODES.save(deps.storage, identity, &stored_bond)?; - storage::mixnodes_owners(deps.storage).save(sender_bytes, identity)?; - storage::total_delegation(deps.storage).save(identity.as_bytes(), &Uint128::zero())?; + let identity = stored_bond.identity().as_bytes(); + storage::mixnodes().save(deps.storage, identity, &stored_bond)?; + storage::total_delegation(deps.storage).save(identity, &Uint128::zero())?; mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?; Ok(Response::new()) @@ -78,17 +80,16 @@ pub(crate) fn try_remove_mixnode( deps: DepsMut, info: MessageInfo, ) -> Result { - let sender_bytes = info.sender.as_bytes(); - - // try to find the identity of the sender's node - let mix_identity = match storage::mixnodes_owners_read(deps.storage).may_load(sender_bytes)? { - Some(identity) => identity, + // try to find the node of the sender + let (entry_key, mixnode_bond) = match storage::mixnodes() + .idx + .owner + .item(deps.storage, info.sender.to_string())? + { + Some(node) => (node.0, node.1), None => return Err(ContractError::NoAssociatedMixNodeBond { owner: info.sender }), }; - // get the bond, since we found associated identity, the node MUST exist - let mixnode_bond = storage::MIXNODES.load(deps.storage, &mix_identity)?; - // send bonded funds back to the bond owner let messages = vec![BankMsg::Send { to_address: info.sender.as_str().to_owned(), @@ -96,10 +97,9 @@ pub(crate) fn try_remove_mixnode( } .into()]; - // remove the bond from the list of bonded mixnodes - storage::MIXNODES.remove(deps.storage, &mix_identity); - // remove the node ownership - storage::mixnodes_owners(deps.storage).remove(sender_bytes); + // remove the bond + storage::mixnodes().remove(deps.storage, &entry_key)?; + // decrement layer count mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?; @@ -303,8 +303,10 @@ pub mod tests { }; // before the execution the node had no associated owner - assert!(storage::mixnodes_owners_read(deps.as_ref().storage) - .may_load("myAwesomeMixnode".as_bytes()) + assert!(storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, "mix-owner".to_string()) .unwrap() .is_none()); @@ -314,9 +316,14 @@ pub mod tests { assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, "mix-owner".to_string()) .unwrap() + .unwrap() + .1 + .identity() ); } @@ -513,9 +520,14 @@ pub mod tests { execute(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, "mix-owner".to_string()) .unwrap() + .unwrap() + .1 + .identity() ); let info = mock_info("mix-owner", &[]); @@ -523,8 +535,10 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); - assert!(storage::mixnodes_owners_read(deps.as_ref().storage) - .may_load("mix-owner".as_bytes()) + assert!(storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, "mix-owner".to_string()) .unwrap() .is_none()); @@ -540,9 +554,14 @@ pub mod tests { assert!(execute(deps.as_mut(), mock_env(), info, msg).is_ok()); assert_eq!( "myAwesomeMixnode", - storage::mixnodes_owners_read(deps.as_ref().storage) - .load("mix-owner".as_bytes()) + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, "mix-owner".to_string()) .unwrap() + .unwrap() + .1 + .identity() ); } diff --git a/contracts/mixnet/src/mixnodes/delegation_transactions.rs b/contracts/mixnet/src/mixnodes/delegation_transactions.rs index 1507077bec..2901d7a6ae 100644 --- a/contracts/mixnet/src/mixnodes/delegation_transactions.rs +++ b/contracts/mixnet/src/mixnodes/delegation_transactions.rs @@ -41,7 +41,10 @@ pub(crate) fn try_delegate_to_mixnode( validate_delegation_stake(&info.funds)?; // check if the target node actually exists - if storage::MIXNODES.load(deps.storage, &mix_identity).is_err() { + if storage::mixnodes() + .load(deps.storage, mix_identity.as_bytes()) + .is_err() + { return Err(ContractError::MixNodeBondNotFound { identity: mix_identity, }); diff --git a/contracts/mixnet/src/mixnodes/storage.rs b/contracts/mixnet/src/mixnodes/storage.rs index da8a7f3db2..63382ea1a7 100644 --- a/contracts/mixnet/src/mixnodes/storage.rs +++ b/contracts/mixnet/src/mixnodes/storage.rs @@ -4,7 +4,7 @@ use config::defaults::DENOM; use cosmwasm_std::{StdResult, Storage, Uint128}; use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket}; -use cw_storage_plus::Map; +use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex}; use mixnet_contract::{ Addr, Coin, IdentityKey, IdentityKeyRef, Layer, MixNode, MixNodeBond, RawDelegationData, RewardingStatus, @@ -15,7 +15,7 @@ use std::fmt::{Display, Formatter}; // storage prefixes // const PREFIX_MIXNODES: &[u8] = b"mn"; -const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo"; +// const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo"; const PREFIX_MIX_DELEGATION: &[u8] = b"md"; const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm"; pub const PREFIX_REWARDED_MIXNODES: &[u8] = b"rm"; @@ -29,9 +29,32 @@ pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50; const PREFIX_TOTAL_DELEGATION: &[u8] = b"td"; -pub(crate) const MIXNODES: Map = Map::new("mn"); +pub(crate) struct MixnodeBondIndex<'a> { + pub(crate) identity: UniqueIndex<'a, IdentityKey, StoredMixnodeBond>, -#[derive(Serialize, Deserialize, Debug, PartialEq)] + // somehow PrimaryKey is not implemented for Addr but is for String? + // maybe it's an omission in this version and is fixed in the cosmwasm 1.0 compatible release? + pub(crate) owner: UniqueIndex<'a, String, StoredMixnodeBond>, +} + +// IndexList is just boilerplate code for fetching a struct's indexes +impl<'a> IndexList for MixnodeBondIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.identity, &self.owner]; + Box::new(v.into_iter()) + } +} + +// mixnodes() is the storage access function. +pub(crate) fn mixnodes<'a>() -> IndexedMap<'a, &'a [u8], StoredMixnodeBond, MixnodeBondIndex<'a>> { + let indexes = MixnodeBondIndex { + identity: UniqueIndex::new(|d| d.mix_node.identity_key.clone(), "mni"), + owner: UniqueIndex::new(|d| d.owner.to_string(), "mno"), + }; + IndexedMap::new("mn", indexes) +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub(crate) struct StoredMixnodeBond { pub bond_amount: Coin, pub owner: Addr, @@ -96,15 +119,6 @@ impl Display for StoredMixnodeBond { // Mixnode-related stuff -// owner address -> node identity -pub fn mixnodes_owners(storage: &mut dyn Storage) -> Bucket { - bucket(storage, PREFIX_MIXNODES_OWNERS) -} - -pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket { - bucket_read(storage, PREFIX_MIXNODES_OWNERS) -} - pub fn total_delegation(storage: &mut dyn Storage) -> Bucket { bucket(storage, PREFIX_TOTAL_DELEGATION) } @@ -146,7 +160,7 @@ pub(crate) fn read_mixnode_bond( storage: &dyn Storage, mix_identity: IdentityKeyRef, ) -> StdResult> { - let stored_bond = MIXNODES.may_load(storage, mix_identity)?; + let stored_bond = mixnodes().may_load(storage, mix_identity.as_bytes())?; match stored_bond { None => Ok(None), Some(stored_bond) => { @@ -218,13 +232,13 @@ mod tests { #[test] fn mixnode_single_read_retrieval() { let mut storage = MockStorage::new(); - let bond1 = test_helpers::stored_mixnode_bond_fixture(); - let bond2 = test_helpers::stored_mixnode_bond_fixture(); - MIXNODES.save(&mut storage, "bond1", &bond1).unwrap(); - MIXNODES.save(&mut storage, "bond2", &bond2).unwrap(); + let bond1 = test_helpers::stored_mixnode_bond_fixture("owner1"); + let bond2 = test_helpers::stored_mixnode_bond_fixture("owner2"); + mixnodes().save(&mut storage, b"bond1", &bond1).unwrap(); + mixnodes().save(&mut storage, b"bond2", &bond2).unwrap(); - let res1 = MIXNODES.load(&storage, "bond1").unwrap(); - let res2 = MIXNODES.load(&storage, "bond2").unwrap(); + let res1 = mixnodes().load(&storage, b"bond1").unwrap(); + let res2 = mixnodes().load(&storage, b"bond2").unwrap(); assert_eq!(bond1, res1); assert_eq!(bond2, res2); } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index e45c61d2c5..e9ab5215e7 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -341,9 +341,9 @@ pub(crate) fn try_reward_mixnode_v2( Ok(current_total.unwrap() + delegation_rewarding_result.total_rewarded) }, )?; - mixnodes_storage::MIXNODES.update::<_, ContractError>( + mixnodes_storage::mixnodes().update::<_, ContractError>( deps.storage, - &mix_identity, + mix_identity.as_bytes(), |current_bond| { // unwrap is fine because we just read the entry... let mut unwrapped = current_bond.unwrap(); @@ -1032,8 +1032,12 @@ pub mod tests { profit_margin_percent: Some(10), }; - mixnodes_storage::MIXNODES - .save(deps.as_mut().storage, &node_identity, &mixnode_bond) + mixnodes_storage::mixnodes() + .save( + deps.as_mut().storage, + node_identity.as_bytes(), + &mixnode_bond, + ) .unwrap(); mixnodes_storage::total_delegation(deps.as_mut().storage) .save(node_identity.as_bytes(), &Uint128::new(initial_delegation)) diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 8f5183ee7c..31f9845e23 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -139,13 +139,16 @@ pub mod test_helpers { ) } - pub(crate) fn stored_mixnode_bond_fixture() -> mixnodes_storage::StoredMixnodeBond { + pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond { StoredMixnodeBond::new( coin(50, DENOM), - Addr::unchecked("foo"), + Addr::unchecked(owner), Layer::One, 12_345, - mix_node_fixture(), + MixNode { + identity_key: format!("id-{}", owner), + ..mix_node_fixture() + }, None, ) } @@ -229,7 +232,7 @@ pub mod test_helpers { storage: &dyn Storage, identity: IdentityKeyRef, ) -> StdResult { - let node = mixnodes_storage::MIXNODES.load(storage, identity)?; + let node = mixnodes_storage::mixnodes().load(storage, identity.as_bytes())?; Ok(node.bond_amount.amount) } }