Feature/migrate hidden delegations (#786)
* Remove migration code * Added function to iterate over delegation of variable type * Add unit tests * Refactored some naming and reused mix/gateway functionality * Borrow bucket instead of move * Linked with existing delegations function * Migration of left-over delegations * Remove unused imports * Put a gateway test as well, next to the mix one * Expose queries for all delegations * Change break point * Added client side calls to the new queries * Fix clippy * Added pagination and read check tests * Fix gateway test from the last commit * Test functions for (de)serialization of identity and owner (in)to storage keys * Add delegation function unit test * Feature guard import * Changed UnpackedDelegation from type to struct * Remove mutable parameter and put start_after in returned value * Made all delegations into iterator for OOM safety * Fix clippy * Add test for delegations iterator size in memory * Change map with if let for ease of read * Use DENOM instead of hardcoded value
This commit is contained in:
committed by
GitHub
parent
12637d93ff
commit
2a98f7482d
@@ -10,6 +10,8 @@ use mixnet_contract::StateParams;
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::RawDelegationData;
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
use url::Url;
|
||||
|
||||
@@ -227,7 +229,7 @@ impl<C> Client<C> {
|
||||
Ok(gateways)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
pub async fn get_all_nymd_single_mixnode_delegations(
|
||||
&self,
|
||||
identity: mixnet_contract::IdentityKey,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
@@ -257,6 +259,34 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_mix_delegations_paged(
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_mixnode_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
@@ -309,7 +339,7 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations(
|
||||
pub async fn get_all_nymd_single_gateway_delegations(
|
||||
&self,
|
||||
identity: mixnet_contract::IdentityKey,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
@@ -339,6 +369,34 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_gateway_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_gateway_delegations(
|
||||
start_after.take(),
|
||||
self.gateway_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_reverse_gateway_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
|
||||
@@ -15,10 +15,10 @@ use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
|
||||
use cosmwasm_std::Coin;
|
||||
use mixnet_contract::{
|
||||
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg,
|
||||
StateParams,
|
||||
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
|
||||
PagedGatewayDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -283,6 +283,25 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all mixnode delegations on particular page.
|
||||
pub async fn get_all_mix_delegations_paged(
|
||||
&self,
|
||||
// I really hate mixing cosmwasm and cosmos-sdk types here...
|
||||
start_after: Option<Vec<u8>>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetAllMixDelegations {
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all the mixnodes on which a particular address delegated.
|
||||
pub async fn get_reverse_mix_delegations_paged(
|
||||
&self,
|
||||
@@ -321,7 +340,7 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all delegations towards particular mixnode on particular page.
|
||||
/// Gets list of all delegations towards particular gateway on particular page.
|
||||
pub async fn get_gateway_delegations(
|
||||
&self,
|
||||
gateway_identity: IdentityKey,
|
||||
@@ -341,6 +360,24 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all gateway delegations on particular page.
|
||||
pub async fn get_all_gateway_delegations(
|
||||
&self,
|
||||
start_after: Option<Vec<u8>>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = QueryMsg::GetAllGatewayDelegations {
|
||||
start_after,
|
||||
limit: page_limit,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.contract_address()?, &request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets list of all the gateways on which a particular address delegated.
|
||||
pub async fn get_reverse_gateway_delegations_paged(
|
||||
&self,
|
||||
|
||||
@@ -7,7 +7,24 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct UnpackedDelegation<T> {
|
||||
pub owner: Addr,
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegation_data: T,
|
||||
}
|
||||
|
||||
impl<T> UnpackedDelegation<T> {
|
||||
pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self {
|
||||
UnpackedDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
delegation_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct RawDelegationData {
|
||||
pub amount: Uint128,
|
||||
pub block_height: u64,
|
||||
@@ -144,3 +161,18 @@ impl PagedReverseGatewayDelegationsResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse<T> {
|
||||
pub delegations: Vec<UnpackedDelegation<T>>,
|
||||
pub start_next_after: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<T> PagedAllDelegationsResponse<T> {
|
||||
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
|
||||
PagedAllDelegationsResponse {
|
||||
delegations,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ mod types;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin};
|
||||
pub use delegation::{
|
||||
Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
|
||||
Delegation, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
|
||||
PagedMixDelegationsResponse, PagedReverseGatewayDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
|
||||
};
|
||||
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
|
||||
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
|
||||
|
||||
@@ -75,6 +75,10 @@ pub enum QueryMsg {
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetAllMixDelegations {
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseMixDelegations {
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
@@ -89,6 +93,10 @@ pub enum QueryMsg {
|
||||
start_after: Option<Addr>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetAllGatewayDelegations {
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
GetReverseGatewayDelegations {
|
||||
delegation_owner: Addr,
|
||||
start_after: Option<IdentityKey>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::calculate_epoch_reward_rate;
|
||||
use crate::helpers::{calculate_epoch_reward_rate, Delegations};
|
||||
use crate::state::State;
|
||||
use crate::storage::{config, layer_distribution};
|
||||
use crate::{error::ContractError, queries, transactions};
|
||||
@@ -155,6 +155,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetAllMixDelegations { start_after, limit } => to_binary(
|
||||
&queries::query_all_mixnode_delegations_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetReverseMixDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
@@ -183,6 +186,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
start_after,
|
||||
limit,
|
||||
)?),
|
||||
QueryMsg::GetAllGatewayDelegations { start_after, limit } => to_binary(
|
||||
&queries::query_all_gateway_delegations_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetReverseGatewayDelegations {
|
||||
delegation_owner,
|
||||
start_after,
|
||||
@@ -207,7 +213,37 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
use crate::storage::{
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations,
|
||||
mix_delegations, reverse_gateway_delegations, reverse_mix_delegations,
|
||||
};
|
||||
use mixnet_contract::{RawDelegationData, UnpackedDelegation};
|
||||
|
||||
let mix_bucket = all_mix_delegations_read::<Uint128>(deps.storage);
|
||||
let delegations = Delegations::new(mix_bucket).collect::<Vec<UnpackedDelegation<Uint128>>>();
|
||||
for delegation in delegations {
|
||||
let owner = delegation.owner;
|
||||
let node_identity = delegation.node_identity;
|
||||
let amount = delegation.delegation_data;
|
||||
let raw_delegation = RawDelegationData::new(amount, env.block.height);
|
||||
mix_delegations(deps.storage, &node_identity).save(owner.as_bytes(), &raw_delegation)?;
|
||||
reverse_mix_delegations(deps.storage, &owner).save(node_identity.as_bytes(), &())?;
|
||||
}
|
||||
|
||||
let gateway_bucket = all_gateway_delegations_read::<Uint128>(deps.storage);
|
||||
let delegations =
|
||||
Delegations::new(gateway_bucket).collect::<Vec<UnpackedDelegation<Uint128>>>();
|
||||
for delegation in delegations {
|
||||
let owner = delegation.owner;
|
||||
let node_identity = delegation.node_identity;
|
||||
let amount = delegation.delegation_data;
|
||||
let raw_delegation = RawDelegationData::new(amount, env.block.height);
|
||||
gateway_delegations(deps.storage, &node_identity)
|
||||
.save(owner.as_bytes(), &raw_delegation)?;
|
||||
reverse_gateway_delegations(deps.storage, &owner).save(node_identity.as_bytes(), &())?;
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use crate::transactions::OLD_DELEGATIONS_CHUNK_SIZE;
|
||||
use cosmwasm_std::{Decimal, Order, StdError, StdResult, Uint128};
|
||||
use cosmwasm_storage::ReadonlyBucket;
|
||||
use mixnet_contract::{Addr, IdentityKey, PagedAllDelegationsResponse, UnpackedDelegation};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::ops::Sub;
|
||||
|
||||
// for time being completely ignore concept of a leap year and assume each year is exactly 365 days
|
||||
@@ -11,6 +16,9 @@ const HOURS_IN_YEAR: u128 = 8760;
|
||||
|
||||
const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000_000_000_000u128);
|
||||
|
||||
// cosmwasm bucket internal value
|
||||
const NAMESPACE_LENGTH: usize = 2;
|
||||
|
||||
fn decimal_to_uint128(value: Decimal) -> Uint128 {
|
||||
value * DECIMAL_FRACTIONAL
|
||||
}
|
||||
@@ -65,11 +73,185 @@ pub(crate) fn scale_reward_by_uptime(
|
||||
Ok(uint128_to_decimal(scaled))
|
||||
}
|
||||
|
||||
// Extracts the node identity and owner of a delegation from the bytes used as
|
||||
// key in the delegation buckets.
|
||||
fn extract_identity_and_owner(bytes: Vec<u8>) -> StdResult<(Addr, IdentityKey)> {
|
||||
if bytes.len() < NAMESPACE_LENGTH {
|
||||
return Err(StdError::parse_err(
|
||||
"mixnet_contract::types::IdentityKey",
|
||||
"Invalid type",
|
||||
));
|
||||
}
|
||||
let identity_size = u16::from_be_bytes([bytes[0], bytes[1]]) as usize;
|
||||
let identity_bytes: Vec<u8> = bytes
|
||||
.iter()
|
||||
.skip(NAMESPACE_LENGTH)
|
||||
.take(identity_size)
|
||||
.copied()
|
||||
.collect();
|
||||
let identity = IdentityKey::from_utf8(identity_bytes)
|
||||
.map_err(|_| StdError::parse_err("mixnet_contract::types::IdentityKey", "Invalid type"))?;
|
||||
let owner_bytes: Vec<u8> = bytes
|
||||
.iter()
|
||||
.skip(NAMESPACE_LENGTH + identity_size)
|
||||
.copied()
|
||||
.collect();
|
||||
let owner = Addr::unchecked(
|
||||
String::from_utf8(owner_bytes)
|
||||
.map_err(|_| StdError::parse_err("cosmwasm_std::addresses::Addr", "Invalid type"))?,
|
||||
);
|
||||
|
||||
Ok((owner, identity))
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
// Converts the node identity and owner of a delegation into the bytes used as
|
||||
// key in the delegation buckets.
|
||||
pub(crate) fn identity_and_owner_to_bytes(identity: &IdentityKey, owner: &Addr) -> Vec<u8> {
|
||||
let mut bytes = u16::to_be_bytes(identity.len() as u16).to_vec();
|
||||
bytes.append(&mut identity.as_bytes().to_vec());
|
||||
bytes.append(&mut owner.as_bytes().to_vec());
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub(crate) fn get_all_delegations_paged<T>(
|
||||
bucket: &ReadonlyBucket<T>,
|
||||
start_after: &Option<Vec<u8>>,
|
||||
limit: usize,
|
||||
) -> StdResult<PagedAllDelegationsResponse<T>>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
let delegations = bucket
|
||||
.range(start_after.as_deref(), None, Order::Ascending)
|
||||
.filter(|res| res.is_ok())
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|entry| {
|
||||
let (owner, identity) = extract_identity_and_owner(entry.0).expect("Invalid node identity or address used as key in bucket. The storage is corrupted!");
|
||||
UnpackedDelegation::new(owner, identity, entry.1)
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<UnpackedDelegation<T>>>>()?;
|
||||
|
||||
let start_next_after = if let Some(Ok(last)) = bucket
|
||||
.range(start_after.as_deref(), None, Order::Ascending)
|
||||
.filter(|res| res.is_ok())
|
||||
.take(limit)
|
||||
.last()
|
||||
{
|
||||
Some(last.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(PagedAllDelegationsResponse::new(
|
||||
delegations,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
pub struct Delegations<'a, T: Clone + Serialize + DeserializeOwned> {
|
||||
delegations_bucket: ReadonlyBucket<'a, T>,
|
||||
curr_delegations: Vec<UnpackedDelegation<T>>,
|
||||
curr_index: usize,
|
||||
start_after: Option<Vec<u8>>,
|
||||
last_page: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Serialize + DeserializeOwned> Delegations<'a, T> {
|
||||
pub fn new(delegations_bucket: ReadonlyBucket<'a, T>) -> Self {
|
||||
Delegations {
|
||||
delegations_bucket,
|
||||
curr_delegations: vec![],
|
||||
curr_index: OLD_DELEGATIONS_CHUNK_SIZE,
|
||||
start_after: None,
|
||||
last_page: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Clone + Serialize + DeserializeOwned> Iterator for Delegations<'a, T> {
|
||||
type Item = UnpackedDelegation<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr_index == OLD_DELEGATIONS_CHUNK_SIZE && !self.last_page {
|
||||
self.start_after = self.start_after.clone().map(|mut v: Vec<u8>| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
let delegations_paged = get_all_delegations_paged(
|
||||
&self.delegations_bucket,
|
||||
&self.start_after,
|
||||
OLD_DELEGATIONS_CHUNK_SIZE,
|
||||
)
|
||||
.ok()?;
|
||||
self.curr_delegations = delegations_paged.delegations;
|
||||
self.curr_index = 0;
|
||||
self.start_after = delegations_paged.start_next_after;
|
||||
if self.start_after.is_none() {
|
||||
self.last_page = true;
|
||||
}
|
||||
}
|
||||
if self.curr_index < self.curr_delegations.len() {
|
||||
let ret = self.curr_delegations[self.curr_index].clone();
|
||||
self.curr_index += 1;
|
||||
Some(ret)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::queries::tests::store_n_mix_delegations;
|
||||
use crate::storage::{
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations,
|
||||
mix_delegations,
|
||||
};
|
||||
use crate::support::tests::helpers;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use mixnet_contract::RawDelegationData;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn delegations_iterator() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
store_n_mix_delegations(
|
||||
2 * OLD_DELEGATIONS_CHUNK_SIZE as u32,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
let mix_bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let mut delegations = Delegations::new(mix_bucket);
|
||||
assert!(delegations.curr_delegations.is_empty());
|
||||
assert_eq!(delegations.curr_index, OLD_DELEGATIONS_CHUNK_SIZE);
|
||||
delegations.next().unwrap();
|
||||
assert_eq!(
|
||||
delegations.curr_delegations.len(),
|
||||
OLD_DELEGATIONS_CHUNK_SIZE
|
||||
);
|
||||
assert_eq!(delegations.curr_index, 1);
|
||||
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE {
|
||||
delegations.next().unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
delegations.curr_delegations.len(),
|
||||
OLD_DELEGATIONS_CHUNK_SIZE
|
||||
);
|
||||
assert_eq!(delegations.curr_index, 1);
|
||||
for _ in 0..OLD_DELEGATIONS_CHUNK_SIZE - 1 {
|
||||
delegations.next().unwrap();
|
||||
}
|
||||
assert!(delegations.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculating_epoch_reward_rate() {
|
||||
// 1.10
|
||||
@@ -124,4 +306,149 @@ mod tests {
|
||||
// anything larger than 100 returns an error
|
||||
assert!(scale_reward_by_uptime(epoch_reward, 101).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_and_owner_deserialization() {
|
||||
assert!(extract_identity_and_owner(vec![]).is_err());
|
||||
assert!(extract_identity_and_owner(vec![0]).is_err());
|
||||
let (owner, identity) = extract_identity_and_owner(vec![
|
||||
0, 7, 109, 105, 120, 110, 111, 100, 101, 97, 108, 105, 99, 101,
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(owner, "alice");
|
||||
assert_eq!(identity, "mixnode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_and_owner_serialization() {
|
||||
let identity: IdentityKey = "gateway".into();
|
||||
let owner = Addr::unchecked("bob");
|
||||
assert_eq!(
|
||||
vec![0, 7, 103, 97, 116, 101, 119, 97, 121, 98, 111, 98],
|
||||
identity_and_owner_to_bytes(&identity, &owner)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_mix_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
|
||||
let mut start_after = None;
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity1.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 2);
|
||||
assert_eq!(
|
||||
delegations[1],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner2.clone(),
|
||||
node_identity2.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1).remove(delegation_owner1.as_bytes());
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_gateway_delegations() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
|
||||
let mut start_after = None;
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity1.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation)
|
||||
.unwrap();
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
start_after = response.start_next_after;
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 2);
|
||||
assert_eq!(
|
||||
delegations[1],
|
||||
UnpackedDelegation::new(
|
||||
delegation_owner2.clone(),
|
||||
node_identity2.clone(),
|
||||
raw_delegation.clone()
|
||||
)
|
||||
);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.remove(delegation_owner1.as_bytes());
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let response =
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start_after, 10).unwrap();
|
||||
let delegations = response.delegations;
|
||||
assert_eq!(delegations.len(), 1);
|
||||
assert_eq!(
|
||||
delegations[0],
|
||||
UnpackedDelegation::new(delegation_owner2, node_identity2, raw_delegation.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+321
-36
@@ -2,21 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::get_all_delegations_paged;
|
||||
use crate::storage::{
|
||||
gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
|
||||
mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params,
|
||||
reverse_gateway_delegations_read, reverse_mix_delegations_read,
|
||||
all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations_read,
|
||||
gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read,
|
||||
read_layer_distribution, read_state_params, reverse_gateway_delegations_read,
|
||||
reverse_mix_delegations_read,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::Deps;
|
||||
use cosmwasm_std::Order;
|
||||
use cosmwasm_std::StdResult;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult};
|
||||
use mixnet_contract::{
|
||||
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
|
||||
PagedReverseMixDelegationsResponse, StateParams,
|
||||
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
|
||||
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
|
||||
StateParams,
|
||||
};
|
||||
|
||||
const BOND_PAGE_MAX_LIMIT: u32 = 100;
|
||||
@@ -24,7 +24,7 @@ const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
// currently the maximum limit before running into memory issue is somewhere between 1150 and 1200
|
||||
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 750;
|
||||
const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
|
||||
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
|
||||
|
||||
pub fn query_mixnodes_paged(
|
||||
deps: Deps,
|
||||
@@ -144,6 +144,23 @@ pub(crate) fn query_mixnode_delegations_paged(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_all_mixnode_delegations_paged(
|
||||
deps: Deps,
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedAllDelegationsResponse<RawDelegationData>> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let bucket = all_mix_delegations_read::<RawDelegationData>(deps.storage);
|
||||
let start = start_after.map(|mut v| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start, limit)
|
||||
}
|
||||
|
||||
pub(crate) fn query_reverse_mixnode_delegations_paged(
|
||||
deps: Deps,
|
||||
delegation_owner: Addr,
|
||||
@@ -230,6 +247,23 @@ pub(crate) fn query_gateway_delegations_paged(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_all_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
start_after: Option<Vec<u8>>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedAllDelegationsResponse<RawDelegationData>> {
|
||||
let limit = limit
|
||||
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
|
||||
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let bucket = all_gateway_delegations_read::<RawDelegationData>(deps.storage);
|
||||
let start = start_after.map(|mut v| {
|
||||
v.push(0);
|
||||
v
|
||||
});
|
||||
get_all_delegations_paged::<RawDelegationData>(&bucket, &start, limit)
|
||||
}
|
||||
|
||||
pub(crate) fn query_reverse_gateway_delegations_paged(
|
||||
deps: Deps,
|
||||
delegation_owner: Addr,
|
||||
@@ -281,7 +315,7 @@ pub(crate) fn query_gateway_delegation(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::state::State;
|
||||
use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes};
|
||||
@@ -656,25 +690,25 @@ mod tests {
|
||||
assert_eq!(dummy_state.params, query_state_params(deps.as_ref()))
|
||||
}
|
||||
|
||||
pub fn store_n_mix_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
mix_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_mixnode_delegations_paged {
|
||||
use super::*;
|
||||
|
||||
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
mix_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(100, &mut deps.storage, &node_identity);
|
||||
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 = query_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
@@ -690,7 +724,7 @@ mod tests {
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -709,7 +743,7 @@ mod tests {
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -811,6 +845,129 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_all_mixnode_delegations_paged {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(limit))
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_all_mixnode_delegations_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_all_mixnode_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = identity_and_owner_to_bytes(&node_identity, &Addr::unchecked("2"));
|
||||
let page2 = query_all_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let page2 = query_all_mixnode_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mix_deletion_query_returns_current_delegation_value() {
|
||||
let mut deps = helpers::init_contract();
|
||||
@@ -1047,26 +1204,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_n_gateway_delegations(
|
||||
n: u32,
|
||||
storage: &mut dyn Storage,
|
||||
node_identity: &IdentityKey,
|
||||
) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
gateway_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::storage::gateway_delegations;
|
||||
|
||||
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
|
||||
for i in 0..n {
|
||||
let address = format!("address{}", i);
|
||||
gateway_delegations(storage, node_identity)
|
||||
.save(address.as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(100, &mut deps.storage, &node_identity);
|
||||
store_n_gateway_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 = query_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
@@ -1082,7 +1243,7 @@ mod tests {
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -1101,7 +1262,7 @@ mod tests {
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_delegations(
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
@@ -1278,6 +1439,130 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_all_gateway_delegations_paged {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
use crate::storage::gateway_delegations;
|
||||
|
||||
#[test]
|
||||
fn retrieval_obeys_limits() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let limit = 2;
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(100, &mut deps.storage, &node_identity);
|
||||
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(limit))
|
||||
.unwrap();
|
||||
assert_eq!(limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_default_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_all_gateway_delegations_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT,
|
||||
page1.delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrieval_has_max_limit() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(crazy_limit))
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.delegations.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pagination_works() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("1".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.delegations.len());
|
||||
|
||||
// save another
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("2".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("3".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_all_gateway_delegations_paged(deps.as_ref(), None, Option::from(per_page))
|
||||
.unwrap();
|
||||
assert_eq!(2, page1.delegations.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = identity_and_owner_to_bytes(&node_identity, &Addr::unchecked("2"));
|
||||
let page2 = query_all_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.delegations.len());
|
||||
|
||||
// save another one
|
||||
gateway_delegations(&mut deps.storage, &node_identity)
|
||||
.save("4".as_bytes(), &raw_delegation_fixture(42))
|
||||
.unwrap();
|
||||
|
||||
let page2 = query_all_gateway_delegations_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.delegations.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod querying_for_reverse_gateway_delegations_paged {
|
||||
use super::*;
|
||||
|
||||
@@ -13,6 +13,8 @@ use mixnet_contract::{
|
||||
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
|
||||
RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
// storage prefixes
|
||||
// all of them must be unique and presumably not be a prefix of a different one
|
||||
@@ -304,6 +306,13 @@ pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey
|
||||
}
|
||||
|
||||
// delegation related
|
||||
pub fn all_mix_delegations_read<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
bucket_read(storage, PREFIX_MIX_DELEGATION)
|
||||
}
|
||||
|
||||
pub fn mix_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
mix_identity: IdentityKeyRef,
|
||||
@@ -329,6 +338,13 @@ pub fn reverse_mix_delegations_read<'a>(
|
||||
ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
|
||||
}
|
||||
|
||||
pub fn all_gateway_delegations_read<T>(storage: &dyn Storage) -> ReadonlyBucket<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
{
|
||||
bucket_read(storage, PREFIX_GATEWAY_DELEGATION)
|
||||
}
|
||||
|
||||
pub fn gateway_delegations<'a>(
|
||||
storage: &'a mut dyn Storage,
|
||||
gateway_identity: IdentityKeyRef,
|
||||
@@ -394,12 +410,13 @@ pub(crate) fn read_gateway_delegation(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::helpers::identity_and_owner_to_bytes;
|
||||
use crate::support::tests::helpers::{
|
||||
gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture,
|
||||
raw_delegation_fixture,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
|
||||
use cosmwasm_std::{coin, Addr, Uint128};
|
||||
use mixnet_contract::{Gateway, MixNode};
|
||||
|
||||
@@ -466,6 +483,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
fn reading_gateway_bond() {
|
||||
let mut storage = MockStorage::new();
|
||||
@@ -500,6 +518,72 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_mixnode_delegations_read_retrieval() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation1)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity1,
|
||||
&delegation_owner1,
|
||||
))
|
||||
.unwrap();
|
||||
let res2 = all_mix_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity2,
|
||||
&delegation_owner2,
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(raw_delegation1, res1);
|
||||
assert_eq!(raw_delegation2, res2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_gateway_delegations_read_retrieval() {
|
||||
let mut deps = mock_dependencies(&[]);
|
||||
let node_identity1: IdentityKey = "foo1".into();
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
|
||||
|
||||
gateway_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation1)
|
||||
.unwrap();
|
||||
gateway_delegations(&mut deps.storage, &node_identity2)
|
||||
.save(delegation_owner2.as_bytes(), &raw_delegation2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = all_gateway_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity1,
|
||||
&delegation_owner1,
|
||||
))
|
||||
.unwrap();
|
||||
let res2 = all_gateway_delegations_read::<RawDelegationData>(&deps.storage)
|
||||
.load(&*identity_and_owner_to_bytes(
|
||||
&node_identity2,
|
||||
&delegation_owner2,
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(raw_delegation1, res1);
|
||||
assert_eq!(raw_delegation2, res2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod increasing_mix_delegated_stakes {
|
||||
use super::*;
|
||||
|
||||
@@ -2,84 +2,27 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::{calculate_epoch_reward_rate, scale_reward_by_uptime};
|
||||
use crate::helpers::{calculate_epoch_reward_rate, scale_reward_by_uptime, Delegations};
|
||||
use crate::queries;
|
||||
use crate::storage::*;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Order, Response, StdResult,
|
||||
Uint128,
|
||||
attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
|
||||
};
|
||||
use cosmwasm_storage::ReadonlyBucket;
|
||||
use mixnet_contract::{
|
||||
Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500;
|
||||
pub(crate) const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500;
|
||||
pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280;
|
||||
|
||||
// Looks for the total amount of delegations towards a particular node.
|
||||
// This function is used only in very specific circumstances:
|
||||
// 1. The mixnode/gateway bonds
|
||||
// 2. Some addresses start to delegate to the node
|
||||
// 3. The node unbonds
|
||||
// 4. Some of the addresses that delegated in the past have not removed the delegation yet
|
||||
// 5. The node rebonds with the same identity
|
||||
pub fn delegations<T: DeserializeOwned + Serialize>(
|
||||
delegations_bucket: ReadonlyBucket<T>,
|
||||
) -> StdResult<Vec<(Vec<u8>, T)>> {
|
||||
// I think it's incredibly unlikely to ever read more than that
|
||||
// but in case we do, we should guard ourselves against possible
|
||||
// out of memory errors (wasm contracts can only allocate at most 2MB
|
||||
// of RAM, so we don't want to box the entire iterator)
|
||||
let mut delegations = Vec::new();
|
||||
// let mut total_delegation = Coin::new(0, DENOM);
|
||||
let mut start = None;
|
||||
loop {
|
||||
let iterator = delegations_bucket
|
||||
.range(start.as_deref(), None, Order::Ascending)
|
||||
.take(OLD_DELEGATIONS_CHUNK_SIZE + 1);
|
||||
|
||||
let mut iterated = 0;
|
||||
|
||||
for result_tuple in iterator {
|
||||
iterated += 1;
|
||||
match result_tuple {
|
||||
Ok((position, delegation)) => {
|
||||
// We might skip some values due to deserializatio errors
|
||||
if iterated > OLD_DELEGATIONS_CHUNK_SIZE {
|
||||
// we reached start of next chunk, don't process it, mark it for the next iteration of the loop
|
||||
start = Some(position);
|
||||
continue;
|
||||
}
|
||||
delegations.push((position, delegation))
|
||||
}
|
||||
Err(_e) => {
|
||||
// Skip errors
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if iterated <= OLD_DELEGATIONS_CHUNK_SIZE {
|
||||
// that was the final chunk
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
fn total_delegations(delegations_bucket: ReadonlyBucket<RawDelegationData>) -> StdResult<Coin> {
|
||||
match delegations(delegations_bucket) {
|
||||
Ok(delegations) => Ok(Coin::new(
|
||||
delegations.iter().fold(0, |acc, x| acc + x.1.amount.u128()),
|
||||
"upunk",
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
Ok(Coin::new(
|
||||
Delegations::new(delegations_bucket)
|
||||
.fold(0, |acc, x| acc + x.delegation_data.amount.u128()),
|
||||
DENOM,
|
||||
))
|
||||
}
|
||||
|
||||
fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> {
|
||||
@@ -790,6 +733,7 @@ pub mod tests {
|
||||
INITIAL_MIXNODE_DELEGATION_REWARD_RATE,
|
||||
};
|
||||
use crate::helpers::calculate_epoch_reward_rate;
|
||||
use crate::queries::DELEGATION_PAGE_DEFAULT_LIMIT;
|
||||
use crate::storage::{
|
||||
gateway_delegations, gateway_delegations_read, layer_distribution_read,
|
||||
mix_delegations_read, read_gateway_bond, read_gateway_epoch_bond_reward_rate,
|
||||
@@ -804,7 +748,9 @@ pub mod tests {
|
||||
use cosmwasm_std::{coin, coins, from_binary, Addr, Uint128};
|
||||
use mixnet_contract::{
|
||||
ExecuteMsg, LayerDistribution, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg,
|
||||
UnpackedDelegation,
|
||||
};
|
||||
use queries::tests::{store_n_gateway_delegations, store_n_mix_delegations};
|
||||
|
||||
#[test]
|
||||
fn validating_mixnode_bond() {
|
||||
@@ -3948,6 +3894,38 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_page_delegations() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let node_identity: IdentityKey = "foo".into();
|
||||
|
||||
store_n_mix_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
let mix_bucket = all_mix_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let mix_delegations =
|
||||
Delegations::new(mix_bucket).collect::<Vec<UnpackedDelegation<RawDelegationData>>>();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
mix_delegations.len() as u32
|
||||
);
|
||||
|
||||
store_n_gateway_delegations(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
&mut deps.storage,
|
||||
&node_identity,
|
||||
);
|
||||
let gateway_bucket = all_gateway_delegations_read::<RawDelegationData>(&deps.storage);
|
||||
let gateway_delegations = Delegations::new(gateway_bucket)
|
||||
.collect::<Vec<UnpackedDelegation<RawDelegationData>>>();
|
||||
assert_eq!(
|
||||
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
|
||||
gateway_delegations.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod finding_old_delegations {
|
||||
use super::*;
|
||||
|
||||
@@ -3,14 +3,20 @@ use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use serde::Serialize;
|
||||
|
||||
use mixnet_contract::{Addr, Coin, Layer, MixNode};
|
||||
use mixnet_contract::{Addr, Coin, Layer, MixNode, RawDelegationData};
|
||||
|
||||
use crate::mix_node::models::{NodeDescription, NodeStats};
|
||||
use crate::mix_nodes::{get_mixnode_delegations, Location};
|
||||
use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn mix_node_make_default_routes() -> Vec<Route> {
|
||||
routes_with_openapi![get_delegations, get_description, get_stats, list]
|
||||
routes_with_openapi![
|
||||
get_delegations,
|
||||
get_all_delegations,
|
||||
get_description,
|
||||
get_stats,
|
||||
list
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema)]
|
||||
@@ -54,7 +60,14 @@ pub(crate) async fn list(
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/delegations")]
|
||||
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<mixnet_contract::Delegation>> {
|
||||
Json(get_mixnode_delegations(pubkey).await)
|
||||
Json(get_single_mixnode_delegations(pubkey).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/all_mix_delegations")]
|
||||
pub(crate) async fn get_all_delegations(
|
||||
) -> Json<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>> {
|
||||
Json(get_mixnode_delegations().await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
|
||||
@@ -8,7 +8,7 @@ use rocket::tokio::sync::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code;
|
||||
use mixnet_contract::{Delegation, MixNodeBond};
|
||||
use mixnet_contract::{Delegation, MixNodeBond, RawDelegationData, UnpackedDelegation};
|
||||
use network_defaults::{
|
||||
default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
};
|
||||
@@ -166,10 +166,10 @@ pub(crate) async fn retrieve_mixnodes() -> Vec<MixNodeBond> {
|
||||
bonds
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
|
||||
pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
|
||||
let client = new_nymd_client();
|
||||
let delegates = match client
|
||||
.get_all_nymd_mixnode_delegations(pubkey.to_string())
|
||||
.get_all_nymd_single_mixnode_delegations(pubkey.to_string())
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
@@ -181,6 +181,18 @@ pub(crate) async fn get_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
|
||||
delegates
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnode_delegations() -> Vec<UnpackedDelegation<RawDelegationData>> {
|
||||
let client = new_nymd_client();
|
||||
let delegates = match client.get_all_nymd_mixnode_delegations().await {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
error!("Could not get all mix delegations: {:?}", e);
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
delegates
|
||||
}
|
||||
|
||||
fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
|
||||
let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string();
|
||||
let nymd_url = default_nymd_endpoints()[0].clone();
|
||||
|
||||
@@ -123,7 +123,7 @@ impl<C> Client<C> {
|
||||
self.0
|
||||
.read()
|
||||
.await
|
||||
.get_all_nymd_mixnode_delegations(identity)
|
||||
.get_all_nymd_single_mixnode_delegations(identity)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ impl<C> Client<C> {
|
||||
self.0
|
||||
.read()
|
||||
.await
|
||||
.get_all_nymd_gateway_delegations(identity)
|
||||
.get_all_nymd_single_gateway_delegations(identity)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user