Usingn IndexedMap for mixnodes

This commit is contained in:
Jędrzej Stuczyński
2021-11-25 16:56:53 +00:00
parent 18add0a0f1
commit bde44164e7
7 changed files with 111 additions and 64 deletions
@@ -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);
@@ -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<MixOwnershipResponse> {
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 })
}
@@ -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<Response, ContractError> {
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()
);
}
@@ -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,
});
+34 -20
View File
@@ -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<IdentityKeyRef, StoredMixnodeBond> = 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<StoredMixnodeBond> for MixnodeBondIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<StoredMixnodeBond>> + '_> {
let v: Vec<&dyn Index<StoredMixnodeBond>> = 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<IdentityKey> {
bucket(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey> {
bucket_read(storage, PREFIX_MIXNODES_OWNERS)
}
pub fn total_delegation(storage: &mut dyn Storage) -> Bucket<Uint128> {
bucket(storage, PREFIX_TOTAL_DELEGATION)
}
@@ -146,7 +160,7 @@ pub(crate) fn read_mixnode_bond(
storage: &dyn Storage,
mix_identity: IdentityKeyRef,
) -> StdResult<Option<MixNodeBond>> {
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);
}
+8 -4
View File
@@ -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))
+7 -4
View File
@@ -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<cosmwasm_std::Uint128> {
let node = mixnodes_storage::MIXNODES.load(storage, identity)?;
let node = mixnodes_storage::mixnodes().load(storage, identity.as_bytes())?;
Ok(node.bond_amount.amount)
}
}