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:
Bogdan-Ștefan Neacşu
2021-09-30 15:04:33 +03:00
committed by GitHub
parent 12637d93ff
commit 2a98f7482d
13 changed files with 995 additions and 124 deletions
@@ -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,
+33 -1
View File
@@ -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,
}
}
}
+3 -2
View File
@@ -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};
+8
View File
@@ -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>,
+38 -2
View File
@@ -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())
}
+328 -1
View File
@@ -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
View File
@@ -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::*;
+85 -1
View File
@@ -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::*;
+43 -65
View File
@@ -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::*;
+17 -4
View File
@@ -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")]
+15 -3
View File
@@ -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();
+2 -2
View File
@@ -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
}