Skip to content

Commit d722769

Browse files
authored
Merge pull request #244 from input-output-hk/golddydev/epochs-blocks
feat: epoch blocks endpoints
2 parents c983831 + e60501e commit d722769

File tree

13 files changed

+229
-88
lines changed

13 files changed

+229
-88
lines changed

common/src/messages.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,25 @@ pub struct EpochActivityMessage {
148148
pub epoch: u64,
149149

150150
/// Epoch start time
151+
/// UNIX timestamp
151152
pub epoch_start_time: u64,
152153

153154
/// Epoch end time
155+
/// UNIX timestamp
154156
pub epoch_end_time: u64,
155157

156-
/// First block time
158+
/// When first block of this epoch was created
157159
pub first_block_time: u64,
158160

159-
/// Last block time
161+
/// Block height of first block of this epoch
162+
pub first_block_height: u64,
163+
164+
/// When last block of this epoch was created
160165
pub last_block_time: u64,
161166

167+
/// Block height of last block of this epoch
168+
pub last_block_height: u64,
169+
162170
/// Total blocks in this epoch
163171
pub total_blocks: usize,
164172

common/src/queries/epochs.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ pub enum EpochsStateQuery {
1111
GetPreviousEpochs { epoch_number: u64 },
1212
GetEpochStakeDistribution { epoch_number: u64 },
1313
GetEpochStakeDistributionByPool { epoch_number: u64 },
14-
GetEpochBlockDistribution { epoch_number: u64 },
15-
GetEpochBlockDistributionByPool { epoch_number: u64 },
1614
GetLatestEpochBlocksMintedByPool { vrf_key_hash: KeyHash },
1715
}
1816

@@ -24,8 +22,6 @@ pub enum EpochsStateQueryResponse {
2422
PreviousEpochs(PreviousEpochs),
2523
EpochStakeDistribution(EpochStakeDistribution),
2624
EpochStakeDistributionByPool(EpochStakeDistributionByPool),
27-
EpochBlockDistribution(EpochBlockDistribution),
28-
EpochBlockDistributionByPool(EpochBlockDistributionByPool),
2925
LatestEpochBlocksMintedByPool(u64),
3026

3127
NotFound,
@@ -62,9 +58,3 @@ pub struct EpochStakeDistribution {}
6258

6359
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
6460
pub struct EpochStakeDistributionByPool {}
65-
66-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
67-
pub struct EpochBlockDistribution {}
68-
69-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
70-
pub struct EpochBlockDistributionByPool {}

common/src/queries/pools.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
queries::governance::VoteRecord, rational_number::RationalNumber, BlockHash, KeyHash,
3-
PoolEpochState, PoolMetadata, PoolRegistration, PoolRetirement, PoolUpdateEvent, Relay,
2+
queries::governance::VoteRecord, rational_number::RationalNumber, KeyHash, PoolEpochState,
3+
PoolMetadata, PoolRegistration, PoolRetirement, PoolUpdateEvent, Relay,
44
};
55

66
pub const DEFAULT_POOLS_QUERY_TOPIC: (&str, &str) =
@@ -41,9 +41,13 @@ pub enum PoolsStateQuery {
4141
GetPoolTotalBlocksMinted {
4242
pool_id: KeyHash,
4343
},
44-
GetPoolBlockHashes {
44+
GetBlocksByPool {
4545
pool_id: KeyHash,
4646
},
47+
GetBlocksByPoolAndEpoch {
48+
pool_id: KeyHash,
49+
epoch: u64,
50+
},
4751
GetPoolUpdates {
4852
pool_id: KeyHash,
4953
},
@@ -67,7 +71,10 @@ pub enum PoolsStateQueryResponse {
6771
PoolRelays(Vec<Relay>),
6872
PoolDelegators(PoolDelegators),
6973
PoolTotalBlocksMinted(u64),
70-
PoolBlockHashes(Vec<BlockHash>),
74+
// Vector of Block Heights
75+
BlocksByPool(Vec<u64>),
76+
// Vector of Block Heights
77+
BlocksByPoolAndEpoch(Vec<u64>),
7178
PoolUpdates(Vec<PoolUpdateEvent>),
7279
PoolVotes(Vec<VoteRecord>),
7380
NotFound,

modules/epochs_state/src/epochs_history.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ mod tests {
117117
epoch_start_time: 0,
118118
epoch_end_time: 0,
119119
first_block_time: 0,
120+
first_block_height: 0,
120121
last_block_time: 0,
122+
last_block_height: 0,
121123
total_blocks: 1,
122124
total_txs: 1,
123125
total_outputs: 100,
@@ -147,7 +149,9 @@ mod tests {
147149
epoch_start_time: 0,
148150
epoch_end_time: 0,
149151
first_block_time: 0,
152+
first_block_height: 0,
150153
last_block_time: 0,
154+
last_block_height: 0,
151155
total_blocks: 1,
152156
total_txs: 1,
153157
total_outputs: 100,
@@ -165,7 +169,9 @@ mod tests {
165169
epoch_start_time: 0,
166170
epoch_end_time: 0,
167171
first_block_time: 0,
172+
first_block_height: 0,
168173
last_block_time: 0,
174+
last_block_height: 0,
169175
total_blocks: 1,
170176
total_txs: 1,
171177
total_outputs: 100,

modules/epochs_state/src/state.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ pub struct State {
2929
// UNIX timestamp
3030
first_block_time: u64,
3131

32+
// first block height
33+
first_block_height: u64,
34+
3235
// last block time
3336
// UNIX timestamp
3437
last_block_time: u64,
3538

39+
// last block height
40+
last_block_height: u64,
41+
3642
// Map of counts by VRF key hashes
3743
blocks_minted: HashMap<KeyHash, usize>,
3844

@@ -63,7 +69,9 @@ impl State {
6369
epoch: 0,
6470
epoch_start_time: genesis.byron_timestamp,
6571
first_block_time: genesis.byron_timestamp,
72+
first_block_height: 0,
6673
last_block_time: 0,
74+
last_block_height: 0,
6775
blocks_minted: HashMap::new(),
6876
epoch_blocks: 0,
6977
epoch_txs: 0,
@@ -178,6 +186,7 @@ impl State {
178186
// This will update last block time
179187
pub fn handle_mint(&mut self, block_info: &BlockInfo, vrf_vkey: Option<&[u8]>) {
180188
self.last_block_time = block_info.timestamp;
189+
self.last_block_height = block_info.number;
181190
self.epoch_blocks += 1;
182191

183192
if let Some(vrf_vkey) = vrf_vkey {
@@ -215,7 +224,9 @@ impl State {
215224
self.epoch = block_info.epoch;
216225
self.epoch_start_time = block_info.timestamp;
217226
self.first_block_time = block_info.timestamp;
227+
self.first_block_height = block_info.number;
218228
self.last_block_time = block_info.timestamp;
229+
self.last_block_height = block_info.number;
219230
self.blocks_minted.clear();
220231
self.epoch_blocks = 0;
221232
self.epoch_txs = 0;
@@ -231,7 +242,9 @@ impl State {
231242
epoch_start_time: self.epoch_start_time,
232243
epoch_end_time: self.epoch_start_time + EPOCH_LENGTH,
233244
first_block_time: self.first_block_time,
245+
first_block_height: self.first_block_height,
234246
last_block_time: self.last_block_time,
247+
last_block_height: self.last_block_height,
235248
// NOTE:
236249
// total_blocks will be missing one
237250
// This is only because we now ignore EBBs
@@ -422,7 +435,9 @@ mod tests {
422435
assert!(state.blocks_minted.is_empty());
423436
assert_eq!(state.epoch_start_time, block.timestamp);
424437
assert_eq!(state.first_block_time, block.timestamp);
438+
assert_eq!(state.first_block_height, block.number);
425439
assert_eq!(state.last_block_time, block.timestamp);
440+
assert_eq!(state.last_block_height, block.number);
426441

427442
let blocks_minted = state.get_latest_epoch_blocks_minted_by_pool(&keyhash(b"vrf_1"));
428443
assert_eq!(blocks_minted, 0);

modules/rest_blockfrost/src/handlers/epochs.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ use acropolis_common::{
88
accounts::{AccountsStateQuery, AccountsStateQueryResponse},
99
epochs::{EpochsStateQuery, EpochsStateQueryResponse},
1010
parameters::{ParametersStateQuery, ParametersStateQueryResponse},
11+
pools::{PoolsStateQuery, PoolsStateQueryResponse},
1112
spdd::{SPDDStateQuery, SPDDStateQueryResponse},
1213
utils::query_state,
1314
},
15+
serialization::Bech32WithHrp,
1416
};
1517
use anyhow::{anyhow, Result};
1618
use caryatid_sdk::Context;
@@ -417,9 +419,71 @@ pub async fn handle_epoch_total_blocks_blockfrost(
417419
}
418420

419421
pub async fn handle_epoch_pool_blocks_blockfrost(
420-
_context: Arc<Context<Message>>,
421-
_params: Vec<String>,
422-
_handlers_config: Arc<HandlersConfig>,
422+
context: Arc<Context<Message>>,
423+
params: Vec<String>,
424+
handlers_config: Arc<HandlersConfig>,
423425
) -> Result<RESTResponse> {
424-
Ok(RESTResponse::with_text(501, "Not implemented"))
426+
if params.len() != 2 {
427+
return Ok(RESTResponse::with_text(
428+
400,
429+
"Expected two parameters: an epoch number and a pool ID",
430+
));
431+
}
432+
let epoch_number_param = &params[0];
433+
let pool_id_param = &params[1];
434+
435+
let epoch_number = match epoch_number_param.parse::<u64>() {
436+
Ok(num) => num,
437+
Err(_) => {
438+
return Ok(RESTResponse::with_text(
439+
400,
440+
"Invalid epoch number parameter",
441+
));
442+
}
443+
};
444+
445+
let Ok(spo) = Vec::<u8>::from_bech32_with_hrp(pool_id_param, "pool") else {
446+
return Ok(RESTResponse::with_text(
447+
400,
448+
&format!("Invalid Bech32 stake pool ID: {pool_id_param}"),
449+
));
450+
};
451+
452+
// query Pool's Blocks by epoch from spo-state
453+
let msg = Arc::new(Message::StateQuery(StateQuery::Pools(
454+
PoolsStateQuery::GetBlocksByPoolAndEpoch {
455+
pool_id: spo.clone(),
456+
epoch: epoch_number,
457+
},
458+
)));
459+
460+
let blocks = query_state(
461+
&context,
462+
&handlers_config.pools_query_topic,
463+
msg,
464+
|message| match message {
465+
Message::StateQueryResponse(StateQueryResponse::Pools(
466+
PoolsStateQueryResponse::BlocksByPoolAndEpoch(blocks),
467+
)) => Ok(blocks),
468+
Message::StateQueryResponse(StateQueryResponse::Pools(
469+
PoolsStateQueryResponse::Error(e),
470+
)) => Err(anyhow::anyhow!(
471+
"Internal server error while retrieving pool block hashes by epoch: {e}"
472+
)),
473+
_ => Err(anyhow::anyhow!("Unexpected message type")),
474+
},
475+
)
476+
.await?;
477+
478+
// NOTE:
479+
// Need to query chain_store
480+
// to get block_hash for each block height
481+
482+
match serde_json::to_string_pretty(&blocks) {
483+
Ok(json) => Ok(RESTResponse::with_json(200, &json)),
484+
Err(e) => Ok(RESTResponse::with_text(
485+
500,
486+
&format!("Internal server error while retrieving pool block hashes by epoch: {e}"),
487+
)),
488+
}
425489
}

modules/rest_blockfrost/src/handlers/pools.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,9 +1062,9 @@ pub async fn handle_pool_blocks_blockfrost(
10621062
));
10631063
};
10641064

1065-
// Get block hashes by pool_id from spo_state
1065+
// Get blocks by pool_id from spo_state
10661066
let pool_blocks_msg = Arc::new(Message::StateQuery(StateQuery::Pools(
1067-
PoolsStateQuery::GetPoolBlockHashes {
1067+
PoolsStateQuery::GetBlocksByPool {
10681068
pool_id: spo.clone(),
10691069
},
10701070
)));
@@ -1075,18 +1075,21 @@ pub async fn handle_pool_blocks_blockfrost(
10751075
pool_blocks_msg,
10761076
|message| match message {
10771077
Message::StateQueryResponse(StateQueryResponse::Pools(
1078-
PoolsStateQueryResponse::PoolBlockHashes(pool_blocks),
1078+
PoolsStateQueryResponse::BlocksByPool(pool_blocks),
10791079
)) => Ok(pool_blocks),
10801080
Message::StateQueryResponse(StateQueryResponse::Pools(
10811081
PoolsStateQueryResponse::Error(_),
1082-
)) => Err(anyhow::anyhow!("Block hashes are not enabled")),
1082+
)) => Err(anyhow::anyhow!("Blocks are not enabled")),
10831083
_ => Err(anyhow::anyhow!("Unexpected message type")),
10841084
},
10851085
)
10861086
.await?;
10871087

1088-
let pool_blocks_rest = pool_blocks.into_iter().map(|b| hex::encode(b)).collect::<Vec<_>>();
1089-
match serde_json::to_string(&pool_blocks_rest) {
1088+
// NOTE:
1089+
// Need to query chain_store
1090+
// to get block_hash for each block height
1091+
1092+
match serde_json::to_string_pretty(&pool_blocks) {
10901093
Ok(json) => Ok(RESTResponse::with_json(200, &json)),
10911094
Err(e) => Ok(RESTResponse::with_text(
10921095
500,

modules/spo_state/src/historical_spo_state.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use acropolis_common::{
22
queries::governance::VoteRecord, KeyHash, PoolRegistration, PoolUpdateEvent,
33
};
4-
use imbl::HashSet;
4+
use imbl::{HashSet, OrdMap, Vector};
55
use serde::{Deserialize, Serialize};
66

77
use crate::store_config::StoreConfig;
@@ -17,6 +17,10 @@ pub struct HistoricalSPOState {
1717
pub delegators: Option<HashSet<KeyHash>>,
1818
// SPO's votes
1919
pub votes: Option<Vec<VoteRecord>>,
20+
21+
// blocks
22+
// <Epoch Number, Block Heights>
23+
pub blocks: Option<OrdMap<u64, Vector<u64>>>,
2024
}
2125

2226
impl HistoricalSPOState {
@@ -27,6 +31,7 @@ impl HistoricalSPOState {
2731
updates: store_config.store_updates.then(Vec::new),
2832
delegators: store_config.store_delegators.then(HashSet::new),
2933
votes: store_config.store_votes.then(Vec::new),
34+
blocks: store_config.store_blocks.then(OrdMap::new),
3035
}
3136
}
3237

@@ -55,4 +60,21 @@ impl HistoricalSPOState {
5560
pub fn remove_delegator(&mut self, delegator: &KeyHash) -> Option<bool> {
5661
self.delegators.as_mut().and_then(|delegators| Some(delegators.remove(delegator).is_some()))
5762
}
63+
64+
pub fn get_all_blocks(&self) -> Option<Vec<u64>> {
65+
self.blocks.as_ref().map(|blocks| blocks.values().flatten().cloned().collect())
66+
}
67+
68+
pub fn get_blocks_by_epoch(&self, epoch: u64) -> Option<Vec<u64>> {
69+
self.blocks
70+
.as_ref()
71+
.and_then(|blocks| blocks.get(&epoch).map(|blocks| blocks.iter().cloned().collect()))
72+
}
73+
74+
pub fn add_block(&mut self, epoch: u64, block_number: u64) -> Option<()> {
75+
self.blocks.as_mut().and_then(|blocks| {
76+
blocks.entry(epoch).or_insert_with(Vector::new).push_back(block_number);
77+
Some(())
78+
})
79+
}
5880
}

modules/spo_state/src/spo_state.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -634,12 +634,19 @@ impl SPOState {
634634
PoolsStateQueryResponse::PoolTotalBlocksMinted(state.get_total_blocks_minted_by_pool(&pool_id))
635635
}
636636

637-
PoolsStateQuery::GetPoolBlockHashes { pool_id } => {
638-
if state.is_block_hashes_enabled() {
639-
PoolsStateQueryResponse::PoolBlockHashes(state.get_pool_block_hashes(pool_id).unwrap_or_default())
640-
} else {
641-
PoolsStateQueryResponse::Error("Block hashes are not enabled".into())
642-
}
637+
PoolsStateQuery::GetBlocksByPool { pool_id } => {
638+
state
639+
.is_historical_blocks_enabled()
640+
.then(|| PoolsStateQueryResponse::BlocksByPool(state.get_blocks_by_pool(pool_id).unwrap_or_default()))
641+
.unwrap_or(PoolsStateQueryResponse::Error("Blocks are not enabled".into()))
642+
}
643+
644+
PoolsStateQuery::GetBlocksByPoolAndEpoch { pool_id, epoch } => {
645+
state
646+
.is_historical_blocks_enabled()
647+
.then(|| PoolsStateQueryResponse::BlocksByPoolAndEpoch(state.get_blocks_by_pool_and_epoch(pool_id, *epoch)
648+
.unwrap_or_default()))
649+
.unwrap_or(PoolsStateQueryResponse::Error("Blocks are not enabled".into()))
643650
}
644651

645652
PoolsStateQuery::GetPoolUpdates { pool_id } => {

0 commit comments

Comments
 (0)