@@ -24,7 +24,9 @@ pub struct AppState {
2424 cardano_transaction_proofs : BTreeMap < String , String > ,
2525 cardano_stake_distribution_list : String ,
2626 cardano_stake_distributions : BTreeMap < String , String > ,
27+ cardano_stake_distributions_per_epoch : BTreeMap < u64 , String > ,
2728 cardano_database_snapshot_list : String ,
29+ cardano_database_snapshot_list_per_epoch : BTreeMap < u64 , String > ,
2830 cardano_database_snapshots : BTreeMap < String , String > ,
2931}
3032
@@ -40,6 +42,17 @@ impl From<AppState> for SharedState {
4042
4143impl Default for AppState {
4244 fn default ( ) -> Self {
45+ let cardano_stake_distributions = default_values:: cardano_stake_distributions ( ) ;
46+ let cardano_stake_distributions_per_epoch =
47+ extract_cardano_stake_distribution_by_epoch ( & cardano_stake_distributions)
48+ . expect ( "Embedded default values are not valid JSON" ) ;
49+
50+ let cardano_database_snapshot_list =
51+ default_values:: cardano_database_snapshot_list ( ) . to_owned ( ) ;
52+ let cardano_database_snapshot_list_per_epoch =
53+ extract_cardano_database_snapshots_for_epoch ( & cardano_database_snapshot_list)
54+ . expect ( "Embedded default values are not valid JSON" ) ;
55+
4356 Self {
4457 status : default_values:: status ( ) . to_owned ( ) ,
4558 epoch_settings : default_values:: epoch_settings ( ) . to_owned ( ) ,
@@ -56,9 +69,10 @@ impl Default for AppState {
5669 cardano_transaction_proofs : default_values:: cardano_transaction_proofs ( ) ,
5770 cardano_stake_distribution_list : default_values:: cardano_stake_distribution_list ( )
5871 . to_owned ( ) ,
59- cardano_stake_distributions : default_values:: cardano_stake_distributions ( ) ,
60- cardano_database_snapshot_list : default_values:: cardano_database_snapshot_list ( )
61- . to_owned ( ) ,
72+ cardano_stake_distributions,
73+ cardano_stake_distributions_per_epoch,
74+ cardano_database_snapshot_list,
75+ cardano_database_snapshot_list_per_epoch,
6276 cardano_database_snapshots : default_values:: cardano_database_snapshots ( ) ,
6377 }
6478 }
@@ -83,6 +97,12 @@ impl AppState {
8397 let ( cardano_database_snapshot_list, cardano_database_snapshots) =
8498 reader. read_files ( "cardano-database" ) ?;
8599
100+ // derived values
101+ let cardano_stake_distributions_per_epoch =
102+ extract_cardano_stake_distribution_by_epoch ( & cardano_stake_distributions) ?;
103+ let cardano_database_snapshot_list_per_epoch =
104+ extract_cardano_database_snapshots_for_epoch ( & cardano_database_snapshot_list) ?;
105+
86106 let instance = Self {
87107 status,
88108 epoch_settings,
@@ -96,8 +116,10 @@ impl AppState {
96116 cardano_transaction_snapshots,
97117 cardano_transaction_proofs,
98118 cardano_stake_distribution_list,
119+ cardano_stake_distributions_per_epoch,
99120 cardano_stake_distributions,
100121 cardano_database_snapshot_list,
122+ cardano_database_snapshot_list_per_epoch,
101123 cardano_database_snapshots,
102124 } ;
103125
@@ -169,11 +191,57 @@ impl AppState {
169191 Ok ( self . cardano_stake_distributions . get ( key) . cloned ( ) )
170192 }
171193
194+ /// return the Cardano stake distribution identified by the given epoch if any.
195+ ///
196+ /// `epoch` can be either a number, `latest`, or `latest-X` where x is a number
197+ pub async fn get_cardano_stake_distribution_by_epoch (
198+ & self ,
199+ epoch : & str ,
200+ ) -> StdResult < Option < String > > {
201+ let stake_distribution = if epoch. starts_with ( "latest" ) {
202+ let offset = parse_epoch_offset ( epoch) ?;
203+ self . cardano_stake_distributions_per_epoch
204+ . values ( )
205+ . rev ( )
206+ . nth ( offset. unwrap_or_default ( ) )
207+ . cloned ( )
208+ } else {
209+ self . cardano_stake_distributions_per_epoch
210+ . get ( & epoch. parse ( ) . with_context ( || "invalid epoch" ) ?)
211+ . cloned ( )
212+ } ;
213+
214+ Ok ( stake_distribution)
215+ }
216+
172217 /// return the list of Cardano database snapshots in the same order as they were read
173218 pub async fn get_cardano_database_snapshots ( & self ) -> StdResult < String > {
174219 Ok ( self . cardano_database_snapshot_list . clone ( ) )
175220 }
176221
222+ /// return the list of Cardano database snapshots for a given epoch in the same order as they were read
223+ ///
224+ /// `epoch` can be either a number, `latest`, or `latest-X` where x is a number
225+ pub async fn get_cardano_database_snapshots_for_epoch (
226+ & self ,
227+ epoch : & str ,
228+ ) -> StdResult < Option < String > > {
229+ let snapshots = if epoch. starts_with ( "latest" ) {
230+ let offset = parse_epoch_offset ( epoch) ?;
231+ self . cardano_database_snapshot_list_per_epoch
232+ . values ( )
233+ . rev ( )
234+ . nth ( offset. unwrap_or_default ( ) )
235+ . cloned ( )
236+ } else {
237+ self . cardano_database_snapshot_list_per_epoch
238+ . get ( & epoch. parse ( ) . with_context ( || "invalid epoch" ) ?)
239+ . cloned ( )
240+ } ;
241+
242+ Ok ( snapshots)
243+ }
244+
177245 /// return the Cardano database snapshot identified by the given key if any.
178246 pub async fn get_cardano_database_snapshot ( & self , key : & str ) -> StdResult < Option < String > > {
179247 Ok ( self . cardano_database_snapshots . get ( key) . cloned ( ) )
@@ -262,6 +330,52 @@ impl DataDir {
262330 }
263331}
264332
333+ fn parse_epoch_offset ( epoch : & str ) -> StdResult < Option < usize > > {
334+ epoch
335+ . strip_prefix ( "latest-" )
336+ . map ( |s| s. parse :: < usize > ( ) )
337+ . transpose ( )
338+ . with_context ( || "invalid epoch offset" )
339+ }
340+
341+ fn extract_cardano_stake_distribution_by_epoch (
342+ source : & BTreeMap < String , String > ,
343+ ) -> StdResult < BTreeMap < u64 , String > > {
344+ let mut res = BTreeMap :: new ( ) ;
345+
346+ for ( key, value) in source {
347+ let parsed_json: serde_json:: Value = serde_json:: from_str ( value)
348+ . with_context ( || format ! ( "Could not parse JSON entity '{key}'" ) ) ?;
349+ let epoch = parsed_json
350+ . pointer ( "/epoch" )
351+ . with_context ( || "missing epoch for a json value" ) ?
352+ . as_u64 ( )
353+ . with_context ( || "epoch is not a number" ) ?;
354+ res. insert ( epoch, value. clone ( ) ) ;
355+ }
356+
357+ Ok ( res)
358+ }
359+
360+ fn extract_cardano_database_snapshots_for_epoch ( source : & str ) -> StdResult < BTreeMap < u64 , String > > {
361+ let parsed_json: Vec < serde_json:: Value > = serde_json:: from_str ( source) ?;
362+ let mut cardano_db_snapshots_per_epoch = BTreeMap :: < u64 , Vec < serde_json:: Value > > :: new ( ) ;
363+
364+ for item in parsed_json {
365+ let epoch = item
366+ . pointer ( "/beacon/epoch" )
367+ . with_context ( || "missing beacon.epoch for a json value" ) ?
368+ . as_u64 ( )
369+ . with_context ( || "beacon.epoch is not a number" ) ?;
370+ cardano_db_snapshots_per_epoch. entry ( epoch) . or_default ( ) . push ( item) ;
371+ }
372+
373+ Ok ( cardano_db_snapshots_per_epoch
374+ . into_iter ( )
375+ . map ( |( k, v) | ( k, serde_json:: to_string ( & v) . unwrap ( ) ) )
376+ . collect ( ) )
377+ }
378+
265379#[ cfg( test) ]
266380mod tests {
267381 use super :: * ;
@@ -271,4 +385,67 @@ mod tests {
271385 AppState :: from_directory ( Path :: new ( "./default_data" ) )
272386 . expect ( "Should be able to construct an AppState from the default_data" ) ;
273387 }
388+
389+ #[ test]
390+ fn test_extract_cardano_stake_distribution_by_epoch ( ) {
391+ let source = BTreeMap :: from ( [
392+ (
393+ "hash1" . to_string ( ) ,
394+ r#"{"bar":4,"epoch":3,"foo":"...","hash":"2"}"# . to_string ( ) ,
395+ ) ,
396+ (
397+ "hash2" . to_string ( ) ,
398+ r#"{"bar":7,"epoch":2,"foo":"...","hash":"1"}"# . to_string ( ) ,
399+ ) ,
400+ ] ) ;
401+ let extracted = extract_cardano_stake_distribution_by_epoch ( & source) . unwrap ( ) ;
402+
403+ // note: values are not re-serialized, so they are kept as is
404+ assert_eq ! (
405+ BTreeMap :: from( [
406+ ( 3 , source. get( "hash1" ) . unwrap( ) . to_string( ) ) ,
407+ ( 2 , source. get( "hash2" ) . unwrap( ) . to_string( ) )
408+ ] ) ,
409+ extracted,
410+ )
411+ }
412+
413+ #[ test]
414+ fn test_extract_cardano_database_snapshots_for_epoch ( ) {
415+ let extracted = extract_cardano_database_snapshots_for_epoch (
416+ r#"[
417+ { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." },
418+ { "beacon": { "epoch": 2}, "hash":"2","foo":"..." },
419+ { "beacon": { "epoch": 1}, "hash":"1","foo":"..." }
420+ ]"# ,
421+ )
422+ . unwrap ( ) ;
423+
424+ // note: values are re-serialized, so serde_json reorders the keys
425+ assert_eq ! (
426+ BTreeMap :: from( [
427+ (
428+ 1 ,
429+ r#"[{"beacon":{"bar":4,"epoch":1},"foo":"...","hash":"3"},{"beacon":{"epoch":1},"foo":"...","hash":"1"}]"#
430+ . to_string( )
431+ ) ,
432+ ( 2 , r#"[{"beacon":{"epoch":2},"foo":"...","hash":"2"}]"# . to_string( ) ) ,
433+ ] ) ,
434+ extracted,
435+ )
436+ }
437+
438+ #[ test]
439+ fn extract_cardano_stake_distribution_by_epoch_from_default_data_dont_panic ( ) {
440+ extract_cardano_stake_distribution_by_epoch ( & default_values:: cardano_stake_distributions ( ) )
441+ . unwrap ( ) ;
442+ }
443+
444+ #[ test]
445+ fn extract_cardano_database_snapshots_for_epoch_from_default_data_dont_panic ( ) {
446+ extract_cardano_database_snapshots_for_epoch (
447+ default_values:: cardano_database_snapshot_list ( ) ,
448+ )
449+ . unwrap ( ) ;
450+ }
274451}
0 commit comments