Skip to content

Add node_bonded field to delegations #5759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions nym-api/src/unstable_routes/account/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ impl AddressInfoCache {
address: account_id.to_string(),
balance: balance.into(),
delegations: delegation_data
.delegations()
.into_iter()
.map(|d| NyxAccountDelegationDetails {
delegated: d.amount,
height: d.height,
node_id: d.node_id,
proxy: d.proxy,
delegated: d.details().amount.clone(),
height: d.details().height,
node_id: d.details().node_id,
proxy: d.details().proxy.clone(),
node_bonded: d.is_node_bonded(),
})
.collect(),
accumulated_rewards,
Expand Down
136 changes: 80 additions & 56 deletions nym-api/src/unstable_routes/account/data_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use crate::{
};
use cosmwasm_std::{Coin, Decimal};
use nym_mixnet_contract_common::NodeRewarding;
use nym_topology::NodeId;
use nym_validator_client::nyxd::AccountId;
use std::collections::{HashMap, HashSet};
use tracing::warn;
use tracing::error;

pub(crate) struct AddressDataCollector {
nyxd_client: crate::nyxd::Client,
Expand Down Expand Up @@ -56,18 +55,18 @@ impl AddressDataCollector {
Ok(balance)
}

pub(crate) async fn get_delegations(&mut self) -> AxumResult<AddressDelegationInfo> {
pub(crate) async fn get_delegations(&mut self) -> AxumResult<Vec<AddressDelegationInfo>> {
let og_delegations = self
.nyxd_client
.get_all_delegator_delegations(&self.account_id)
.await?;
.await?
.into_iter()
.map(|delegation| (delegation.node_id, delegation))
.collect::<HashMap<_, _>>();

let delegated_to_nodes = og_delegations
.iter()
.map(|d| d.node_id)
.collect::<HashSet<_>>();
let mut node_delegation_info = Vec::new();

let nym_nodes = self
let delegated_to_nodes_bonded = self
.nym_contract_cache
.all_cached_nym_nodes()
.await
Expand All @@ -85,61 +84,75 @@ impl AddressDataCollector {
// add to totals
self.total_value += pending_operator_reward;
}
if delegated_to_nodes.contains(&node_details.node_id()) {
Some((
node_details.node_id(),
// avoid cloning node data which we don't need
(
node_details.rewarding_details.clone(),
node_details.is_unbonding(),
),
))
if let Some(delegation) = og_delegations.get(&node_details.node_id()) {
node_delegation_info.push(AddressDelegationInfo {
details: delegation.clone(),
node_reward_info: NodeBondStatus::Bonded {
rewarding_info: node_details.rewarding_details.to_owned(),
unbonding: node_details.is_unbonding(),
},
});

Some(node_details.node_id())
} else {
None
}
})
.collect::<HashMap<_, _>>();
.collect::<HashSet<_>>();

Ok(AddressDelegationInfo {
delegations: og_delegations,
delegated_to_nodes: nym_nodes,
})
for (node_id, delegation) in og_delegations {
if !delegated_to_nodes_bonded.contains(&node_id) {
node_delegation_info.push(AddressDelegationInfo {
details: delegation.clone(),
node_reward_info: NodeBondStatus::UnBonded,
});
}
}

Ok(node_delegation_info)
}

pub(crate) async fn calculate_rewards(
&mut self,
delegation_data: &AddressDelegationInfo,
delegation_data: &Vec<AddressDelegationInfo>,
) -> AxumResult<Vec<NyxAccountDelegationRewardDetails>> {
let mut accumulated_rewards = Vec::new();
for delegation in delegation_data.delegations.iter() {
let node_id = &delegation.node_id;

if let Some((rewarding_details, is_unbonding)) =
delegation_data.delegated_to_nodes.get(node_id)
{
match rewarding_details.determine_delegation_reward(delegation) {
Ok(delegation_reward) => {
let reward = NyxAccountDelegationRewardDetails {
node_id: delegation.node_id,
rewards: decimal_to_coin(delegation_reward, &self.base_denom),
amount_staked: delegation.amount.clone(),
node_still_fully_bonded: !is_unbonding,
};
// 4. sum the rewards and delegations
self.total_delegations += delegation.amount.amount.u128();
self.total_value += delegation.amount.amount.u128();
self.total_value += reward.rewards.amount.u128();
self.claimable_rewards += reward.rewards.amount.u128();

accumulated_rewards.push(reward);
}
Err(err) => {
warn!(
"Couldn't determine delegations for {} on node {}: {}",
&self.account_id, node_id, err
)
for delegation in delegation_data {
let node_id = delegation.details.node_id;

match &delegation.node_reward_info {
NodeBondStatus::Bonded {
rewarding_info,
unbonding,
} => {
match rewarding_info.determine_delegation_reward(&delegation.details) {
Ok(delegation_reward) => {
let reward = NyxAccountDelegationRewardDetails {
node_id,
rewards: decimal_to_coin(delegation_reward, &self.base_denom),
amount_staked: delegation.details.amount.clone(),
node_still_fully_bonded: !unbonding,
};
// 4. sum the rewards and delegations
self.total_delegations += delegation.details.amount.amount.u128();
self.total_value += delegation.details.amount.amount.u128();
self.total_value += reward.rewards.amount.u128();
self.claimable_rewards += reward.rewards.amount.u128();

accumulated_rewards.push(reward);
}
Err(err) => {
error!(
"Couldn't determine delegations for {} on node {}: {}",
&self.account_id, node_id, err
)
}
}
}
NodeBondStatus::UnBonded => {
// directory cache doesn't store node details required to
// calculate rewarding for unbonded nodes
}
}
}

Expand Down Expand Up @@ -171,24 +184,35 @@ impl AddressDataCollector {
}

pub(crate) struct AddressDelegationInfo {
delegations: Vec<nym_mixnet_contract_common::Delegation>,
delegated_to_nodes: HashMap<NodeId, RewardAndBondInfo>,
details: nym_mixnet_contract_common::Delegation,
node_reward_info: NodeBondStatus,
}

impl AddressDelegationInfo {
pub(crate) fn delegations(self) -> Vec<nym_mixnet_contract_common::Delegation> {
self.delegations
pub(crate) fn details(&self) -> &nym_mixnet_contract_common::Delegation {
&self.details
}

pub(crate) fn is_node_bonded(&self) -> bool {
matches!(self.node_reward_info, NodeBondStatus::Bonded { .. })
}
}

type RewardAndBondInfo = (NodeRewarding, bool);
pub(crate) enum NodeBondStatus {
Bonded {
rewarding_info: NodeRewarding,
unbonding: bool,
},
UnBonded,
}

fn decimal_to_coin(decimal: Decimal, denom: impl Into<String>) -> Coin {
Coin::new(decimal.to_uint_floor(), denom)
}

#[cfg(test)]
mod test {

use super::*;

#[tokio::test]
Expand Down
4 changes: 4 additions & 0 deletions nym-api/src/unstable_routes/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct NyxAccountDelegationDetails {
pub height: u64,
#[schema(value_type = Option<String>)]
pub proxy: Option<Addr>,
pub node_bonded: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)]
Expand All @@ -41,6 +42,9 @@ pub struct NyxAccountDetails {
#[schema(value_type = CoinSchema)]
pub total_value: Coin,
pub delegations: Vec<NyxAccountDelegationDetails>,
/// Shows rewards from delegations to **currently** bonded nodes.
/// Rewards from nodes that user delegated to, but were later unbonded,
/// are claimable, but not shown here.
pub accumulated_rewards: Vec<NyxAccountDelegationRewardDetails>,
#[schema(value_type = String)]
pub total_delegations: Coin,
Expand Down
Loading