Skip to content

Commit 533345d

Browse files
committed
feat: add account rewards REST handler
Signed-off-by: William Hankins <[email protected]>
1 parent 0d25e5f commit 533345d

File tree

7 files changed

+130
-23
lines changed

7 files changed

+130
-23
lines changed

common/src/queries/accounts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub enum AccountsStateQuery {
4949
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
5050
pub enum AccountsStateQueryResponse {
5151
AccountInfo(AccountInfo),
52-
AccountRewardHistory(Vec<RewardHistory>),
52+
AccountRewardHistory(Vec<AccountReward>),
5353
AccountHistory(AccountHistory),
5454
AccountRegistrationHistory(Vec<RegistrationUpdate>),
5555
AccountDelegationHistory(Vec<DelegationUpdate>),
@@ -152,7 +152,7 @@ pub struct AccountWithdrawal {
152152
#[derive(
153153
Debug, Clone, minicbor::Decode, minicbor::Encode, serde::Serialize, serde::Deserialize,
154154
)]
155-
pub struct RewardHistory {
155+
pub struct AccountReward {
156156
#[n(0)]
157157
pub epoch: u32,
158158
#[n(1)]

common/src/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ pub enum RewardType {
230230
PoolRefund,
231231
}
232232

233+
impl fmt::Display for RewardType {
234+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235+
match self {
236+
RewardType::Leader => write!(f, "leader"),
237+
RewardType::Member => write!(f, "member"),
238+
RewardType::PoolRefund => write!(f, "pool_deposit_refund"),
239+
}
240+
}
241+
}
242+
233243
pub type PolicyId = [u8; 28];
234244
pub type NativeAssets = Vec<(PolicyId, Vec<NativeAsset>)>;
235245
pub type NativeAssetsDelta = Vec<(PolicyId, Vec<NativeAssetDelta>)>;

modules/historical_accounts_state/src/immutable_historical_account_store.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{collections::HashMap, path::Path};
22

33
use acropolis_common::{
4-
queries::accounts::{AccountWithdrawal, DelegationUpdate, RegistrationUpdate, RewardHistory},
4+
queries::accounts::{AccountReward, AccountWithdrawal, DelegationUpdate, RegistrationUpdate},
55
ShelleyAddress, StakeAddress,
66
};
77
use anyhow::Result;
@@ -155,9 +155,9 @@ impl ImmutableHistoricalAccountStore {
155155
pub async fn get_reward_history(
156156
&self,
157157
account: &StakeAddress,
158-
) -> Result<Option<Vec<RewardHistory>>> {
158+
) -> Result<Option<Vec<AccountReward>>> {
159159
let mut immutable_rewards =
160-
self.collect_partition::<RewardHistory>(&self.rewards_history, account.get_hash())?;
160+
self.collect_partition::<AccountReward>(&self.rewards_history, account.get_hash())?;
161161

162162
self.merge_pending(
163163
account,

modules/historical_accounts_state/src/state.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use acropolis_common::{
88
AddressDeltasMessage, StakeRewardDeltasMessage, TxCertificatesMessage, WithdrawalsMessage,
99
},
1010
queries::accounts::{
11-
AccountWithdrawal, DelegationUpdate, RegistrationStatus, RegistrationUpdate, RewardHistory,
11+
AccountReward, AccountWithdrawal, DelegationUpdate, RegistrationStatus, RegistrationUpdate,
1212
},
1313
BlockInfo, InstantaneousRewardTarget, PoolId, ShelleyAddress, StakeAddress, TxCertificate,
1414
TxIdentifier,
@@ -24,7 +24,7 @@ use anyhow::Result;
2424

2525
#[derive(Debug, Default, Clone)]
2626
pub struct AccountEntry {
27-
pub reward_history: Option<Vec<RewardHistory>>,
27+
pub reward_history: Option<Vec<AccountReward>>,
2828
pub active_stake_history: Option<Vec<ActiveStakeHistory>>,
2929
pub delegation_history: Option<Vec<DelegationUpdate>>,
3030
pub registration_history: Option<Vec<RegistrationUpdate>>,
@@ -108,7 +108,7 @@ impl State {
108108
let volatile = self.volatile.window.back_mut().expect("window should never be empty");
109109
for reward in reward_deltas.deltas.iter() {
110110
let entry = volatile.entry(reward.stake_address.clone()).or_default();
111-
let update = RewardHistory {
111+
let update = AccountReward {
112112
epoch,
113113
amount: reward.delta,
114114
pool: reward.pool.clone(),
@@ -240,7 +240,7 @@ impl State {
240240
pub async fn get_reward_history(
241241
&self,
242242
account: &StakeAddress,
243-
) -> Result<Option<Vec<RewardHistory>>> {
243+
) -> Result<Option<Vec<AccountReward>>> {
244244
let immutable = self.immutable.get_reward_history(account).await?;
245245

246246
let mut volatile = Vec::new();

modules/rest_blockfrost/src/handlers/accounts.rs

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use anyhow::{anyhow, Result};
1313
use caryatid_sdk::Context;
1414

1515
use crate::handlers_config::HandlersConfig;
16-
use crate::types::{AccountWithdrawalREST, DelegationUpdateREST, RegistrationUpdateREST};
16+
use crate::types::{
17+
AccountRewardREST, AccountWithdrawalREST, DelegationUpdateREST, RegistrationUpdateREST,
18+
};
1719

1820
#[derive(serde::Serialize)]
1921
pub struct StakeAccountRest {
@@ -434,8 +436,8 @@ pub async fn handle_account_withdrawals_blockfrost(
434436
msg,
435437
|message| match message {
436438
Message::StateQueryResponse(StateQueryResponse::Accounts(
437-
AccountsStateQueryResponse::AccountWithdrawalHistory(registrations),
438-
)) => Ok(Some(registrations)),
439+
AccountsStateQueryResponse::AccountWithdrawalHistory(withdrawals),
440+
)) => Ok(Some(withdrawals)),
439441
Message::StateQueryResponse(StateQueryResponse::Accounts(
440442
AccountsStateQueryResponse::NotFound,
441443
)) => Ok(None),
@@ -505,6 +507,71 @@ pub async fn handle_account_withdrawals_blockfrost(
505507
}
506508
}
507509

510+
pub async fn handle_account_rewards_blockfrost(
511+
context: Arc<Context<Message>>,
512+
params: Vec<String>,
513+
handlers_config: Arc<HandlersConfig>,
514+
) -> Result<RESTResponse> {
515+
let stake_address = match parse_stake_address(&params) {
516+
Ok(addr) => addr,
517+
Err(resp) => return Ok(resp),
518+
};
519+
520+
// Prepare the message
521+
let msg = Arc::new(Message::StateQuery(StateQuery::Accounts(
522+
AccountsStateQuery::GetAccountRegistrationHistory {
523+
account: stake_address,
524+
},
525+
)));
526+
527+
// Get rewards from historical accounts state
528+
let rewards = query_state(
529+
&context,
530+
&handlers_config.historical_accounts_query_topic,
531+
msg,
532+
|message| match message {
533+
Message::StateQueryResponse(StateQueryResponse::Accounts(
534+
AccountsStateQueryResponse::AccountRewardHistory(rewards),
535+
)) => Ok(Some(rewards)),
536+
Message::StateQueryResponse(StateQueryResponse::Accounts(
537+
AccountsStateQueryResponse::NotFound,
538+
)) => Ok(None),
539+
Message::StateQueryResponse(StateQueryResponse::Accounts(
540+
AccountsStateQueryResponse::Error(e),
541+
)) => Err(anyhow::anyhow!(
542+
"Internal server error while retrieving account info: {e}"
543+
)),
544+
_ => Err(anyhow::anyhow!(
545+
"Unexpected message type while retrieving account info"
546+
)),
547+
},
548+
)
549+
.await?;
550+
551+
let Some(rewards) = rewards else {
552+
return Ok(RESTResponse::with_text(404, "Account not found"));
553+
};
554+
555+
let rest_response =
556+
match rewards.iter().map(|r| r.try_into()).collect::<Result<Vec<AccountRewardREST>, _>>() {
557+
Ok(v) => v,
558+
Err(e) => {
559+
return Ok(RESTResponse::with_text(
560+
500,
561+
&format!("Failed to convert reward entry: {e}"),
562+
))
563+
}
564+
};
565+
566+
match serde_json::to_string_pretty(&rest_response) {
567+
Ok(json) => Ok(RESTResponse::with_json(200, &json)),
568+
Err(e) => Ok(RESTResponse::with_text(
569+
500,
570+
&format!("Internal server error while serializing withdrawal history: {e}"),
571+
)),
572+
}
573+
}
574+
508575
fn parse_stake_address(params: &[String]) -> Result<StakeAddress, RESTResponse> {
509576
let Some(stake_key) = params.first() else {
510577
return Err(RESTResponse::with_text(

modules/rest_blockfrost/src/rest_blockfrost.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ mod handlers_config;
1616
mod types;
1717
mod utils;
1818
use handlers::{
19-
accounts::handle_single_account_blockfrost,
19+
accounts::{
20+
handle_account_delegations_blockfrost, handle_account_mirs_blockfrost,
21+
handle_account_registrations_blockfrost, handle_account_rewards_blockfrost,
22+
handle_account_withdrawals_blockfrost, handle_single_account_blockfrost,
23+
},
2024
addresses::{
2125
handle_address_asset_utxos_blockfrost, handle_address_extended_blockfrost,
2226
handle_address_single_blockfrost, handle_address_totals_blockfrost,
@@ -58,13 +62,7 @@ use handlers::{
5862
},
5963
};
6064

61-
use crate::{
62-
handlers::accounts::{
63-
handle_account_delegations_blockfrost, handle_account_mirs_blockfrost,
64-
handle_account_registrations_blockfrost, handle_account_withdrawals_blockfrost,
65-
},
66-
handlers_config::HandlersConfig,
67-
};
65+
use crate::handlers_config::HandlersConfig;
6866

6967
// Accounts topics
7068
const DEFAULT_HANDLE_SINGLE_ACCOUNT_TOPIC: (&str, &str) =
@@ -83,6 +81,10 @@ const DEFAULT_HANDLE_ACCOUNT_WITHDRAWALS_TOPIC: (&str, &str) = (
8381
"handle-topic-account-withdrawals",
8482
"rest.get.accounts.*.withdrawals",
8583
);
84+
const DEFAULT_HANDLE_ACCOUNT_REWARDS_TOPIC: (&str, &str) = (
85+
"handle-topic-account-rewards",
86+
"rest.get.accounts.*.rewards",
87+
);
8688

8789
// Blocks topics
8890
const DEFAULT_HANDLE_BLOCKS_LATEST_HASH_NUMBER_TOPIC: (&str, &str) =
@@ -301,6 +303,14 @@ impl BlockfrostREST {
301303
handle_account_withdrawals_blockfrost,
302304
);
303305

306+
// Handler for /accounts/{stake_address}/rewards
307+
register_handler(
308+
context.clone(),
309+
DEFAULT_HANDLE_ACCOUNT_REWARDS_TOPIC,
310+
handlers_config.clone(),
311+
handle_account_rewards_blockfrost,
312+
);
313+
304314
// Handler for /blocks/latest, /blocks/{hash_or_number}
305315
register_handler(
306316
context.clone(),

modules/rest_blockfrost/src/types.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use crate::cost_models::{PLUTUS_V1, PLUTUS_V2, PLUTUS_V3};
22
use acropolis_common::{
33
messages::EpochActivityMessage,
44
protocol_params::{Nonce, NonceVariant, ProtocolParams},
5-
queries::blocks::BlockInfo,
6-
queries::governance::DRepActionUpdate,
5+
queries::{accounts::AccountReward, blocks::BlockInfo, governance::DRepActionUpdate},
76
rest_helper::ToCheckedF64,
8-
serialization::{DisplayFromBech32, PoolPrefix},
7+
serialization::{Bech32WithHrp, DisplayFromBech32, PoolPrefix},
98
AssetAddressEntry, AssetMetadataStandard, AssetMintRecord, KeyHash, PolicyAsset,
109
PoolEpochState, PoolUpdateAction, Relay, TxHash, Vote,
1110
};
@@ -874,3 +873,24 @@ pub struct AccountWithdrawalREST {
874873
pub tx_hash: String,
875874
pub amount: String,
876875
}
876+
877+
#[derive(Serialize)]
878+
pub struct AccountRewardREST {
879+
pub epoch: u32,
880+
pub amount: String,
881+
pub pool_id: String,
882+
#[serde(rename = "type")]
883+
pub reward_type: String,
884+
}
885+
886+
impl TryFrom<&AccountReward> for AccountRewardREST {
887+
type Error = anyhow::Error;
888+
fn try_from(value: &AccountReward) -> Result<Self, Self::Error> {
889+
Ok(Self {
890+
epoch: value.epoch,
891+
amount: value.amount.to_string(),
892+
pool_id: value.pool.to_bech32_with_hrp("pool")?,
893+
reward_type: value.reward_type.to_string(),
894+
})
895+
}
896+
}

0 commit comments

Comments
 (0)