diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 0406695484e..b3e8b969f8b 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1508,6 +1508,20 @@ pub async fn query_rewards( ) } +/// Query validator rewards products for a given validator and epoch. +pub async fn query_validator_rewards_product( + client: &C, + validator: &Address, + epoch: Option, +) -> Vec<(Epoch, Dec)> { + unwrap_client_response::( + RPC.vp() + .pos() + .rewards_products(client, validator, &epoch) + .await, + ) +} + /// Query token total supply. pub async fn query_total_supply( context: &N, diff --git a/crates/proof_of_stake/src/lib.rs b/crates/proof_of_stake/src/lib.rs index 6fe4564861b..1ed51dca965 100644 --- a/crates/proof_of_stake/src/lib.rs +++ b/crates/proof_of_stake/src/lib.rs @@ -2874,6 +2874,46 @@ where Ok(res) } +/// Query the validator's reward product for a given epoch. +pub fn query_validator_rewards_products( + storage: &S, + validator: &Address, + epoch: Option, +) -> Result> +where + S: StorageRead, + Gov: governance::Read, +{ + let rewards_products = validator_rewards_products_handle(validator); + // Query for a specific epoch or all epochs + let result = if let Some(epoch) = epoch { + tracing::debug!("Querying rewards product for epoch {:?}", epoch); + rewards_products + .get(storage, &epoch)? + .map(|product| vec![(epoch, product)]) + .unwrap_or_else(|| { + tracing::debug!( + "No rewards product found for epoch {:?}", + epoch + ); + vec![] + }) + } else { + tracing::debug!("Querying rewards products for all epochs"); + rewards_products + .iter(storage)? + .filter_map(|res| match res { + Ok((epoch, product)) => Some(Ok((epoch, product))), + Err(e) => { + tracing::error!("Failed to read rewards product: {:?}", e); + None + } + }) + .collect::>>()? + }; + Ok(result) +} + /// Jail a validator by removing it from and updating the validator sets and /// changing a its state to `Jailed`. Validators are jailed for liveness and for /// misbehaving. diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index 0ba3228ada8..1a898f34cf0 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -79,6 +79,9 @@ router! {SHELL, // First block height of the current epoch ( "first_block_height_of_current_epoch" ) -> BlockHeight = first_block_height_of_current_epoch, + // First block height of the current epoch + ( "first_block_height_by_epoch" / [epoch: Epoch] ) -> Option = first_block_height_by_epoch, + // Raw storage access - read value ( "value" / [storage_key: storage::Key] ) -> Vec = (with_options storage_value), @@ -427,6 +430,22 @@ where .cloned() } +fn first_block_height_by_epoch( + ctx: RequestCtx<'_, D, H, V, T>, + epoch: Epoch, +) -> namada_storage::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + Ok(ctx + .state + .in_mem() + .block + .pred_epochs + .get_start_height_of_epoch(epoch)) +} + /// Returns data with `vec![]` when the storage key is not found. For all /// borsh-encoded types, it is safe to check `data.is_empty()` to see if the /// value was found, except for unit - see `fn query_storage_value` in diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 883a83bf5a8..8bb99b95a87 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -7,6 +7,7 @@ use namada_core::address::Address; use namada_core::arith::{self, checked}; use namada_core::chain::Epoch; use namada_core::collections::{HashMap, HashSet}; +use namada_core::dec::Dec; use namada_core::key::{common, tm_consensus_key_raw_hash}; use namada_core::token; use namada_proof_of_stake::parameters::PosParams; @@ -34,7 +35,9 @@ use namada_proof_of_stake::types::{ LivenessInfo, Slash, ValidatorLiveness, ValidatorMetaData, WeightedValidator, }; -use namada_proof_of_stake::{bond_amount, query_reward_tokens}; +use namada_proof_of_stake::{ + bond_amount, query_reward_tokens, query_validator_rewards_products, +}; use namada_state::{DB, DBIter, KeySeg, StorageHasher, StorageRead}; use namada_storage::collections::lazy_map; use namada_storage::{OptionExt, ResultExt}; @@ -105,6 +108,9 @@ router! {POS, ( "bond" / [source: Address] / [validator: Address] / [epoch: opt Epoch] ) -> token::Amount = bond, + ( "rewards_products" / [validator: Address] / [epoch: opt Epoch]) + -> Vec<(Epoch, Dec)> = rewards_products, + ( "rewards" / [validator: Address] / [source: opt Address] / [epoch: opt Epoch] ) -> token::Amount = rewards, @@ -586,6 +592,20 @@ where Ok(total) } +fn rewards_products( + ctx: RequestCtx<'_, D, H, V, T>, + validator: Address, + epoch: Option, +) -> namada_storage::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + query_validator_rewards_products::<_, governance::Store<_>>( + ctx.state, &validator, epoch, + ) +} + fn rewards( ctx: RequestCtx<'_, D, H, V, T>, validator: Address,