Skip to content

Commit d9d274c

Browse files
authored
Merge pull request #362 from input-output-hk/gd/historical-epochs-state
feat: make historical epochs state
2 parents ee73112 + 45ef59f commit d9d274c

34 files changed

+1120
-423
lines changed

Cargo.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ members = [
2323
"modules/accounts_state", # Tracks stake and reward accounts
2424
"modules/assets_state", # Tracks native asset mints and burns
2525
"modules/historical_accounts_state", # Tracks historical account information
26+
"modules/historical_epochs_state", # Tracks historical epochs information
2627
"modules/consensus", # Chooses favoured chain across multiple options
2728
"modules/chain_store", # Tracks historical information about blocks and TXs
2829
"modules/tx_submitter", # Submits TXs to peers

common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ hex = { workspace = true }
2626
memmap2 = "0.9"
2727
num-rational = { version = "0.4.2", features = ["serde"] }
2828
regex = "1"
29-
serde = { workspace = true }
29+
serde = { workspace = true, features = ["rc"] }
3030
serde_json = { workspace = true }
3131
serde_with = { workspace = true, features = ["base64"] }
3232
tempfile = "3"

common/src/cbor.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Custom codec module for u128 using CBOR bignum encoding
2+
pub mod u128_cbor_codec {
3+
use minicbor::{Decoder, Encoder};
4+
5+
/// Encode u128 as CBOR Tag 2 (positive bignum)
6+
/// For use with `#[cbor(with = "u128_cbor_codec")]`
7+
pub fn encode<C, W: minicbor::encode::Write>(
8+
v: &u128,
9+
e: &mut Encoder<W>,
10+
_ctx: &mut C,
11+
) -> Result<(), minicbor::encode::Error<W::Error>> {
12+
// Tag 2 = positive bignum
13+
e.tag(minicbor::data::Tag::new(2))?;
14+
15+
// Optimize: only encode non-zero leading bytes
16+
let bytes = v.to_be_bytes();
17+
let first_nonzero = bytes.iter().position(|&b| b != 0).unwrap_or(15);
18+
e.bytes(&bytes[first_nonzero..])?;
19+
Ok(())
20+
}
21+
22+
/// Decode u128 from CBOR Tag 2 (positive bignum)
23+
pub fn decode<'b, C>(
24+
d: &mut Decoder<'b>,
25+
_ctx: &mut C,
26+
) -> Result<u128, minicbor::decode::Error> {
27+
// Expect Tag 2
28+
let tag = d.tag()?;
29+
if tag != minicbor::data::Tag::new(2) {
30+
return Err(minicbor::decode::Error::message(
31+
"Expected CBOR Tag 2 (positive bignum) for u128",
32+
));
33+
}
34+
35+
let bytes = d.bytes()?;
36+
if bytes.len() > 16 {
37+
return Err(minicbor::decode::Error::message(
38+
"Bignum too large for u128 (max 16 bytes)",
39+
));
40+
}
41+
42+
// Pad with leading zeros to make 16 bytes (big-endian)
43+
let mut arr = [0u8; 16];
44+
arr[16 - bytes.len()..].copy_from_slice(bytes);
45+
Ok(u128::from_be_bytes(arr))
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::u128_cbor_codec;
52+
use minicbor::{Decode, Encode};
53+
54+
#[derive(Debug, PartialEq, Encode, Decode)]
55+
struct TestStruct {
56+
#[cbor(n(0), with = "u128_cbor_codec")]
57+
value: u128,
58+
}
59+
60+
#[test]
61+
fn test_u128_zero() {
62+
let original = TestStruct { value: 0 };
63+
let encoded = minicbor::to_vec(&original).unwrap();
64+
let decoded: TestStruct = minicbor::decode(&encoded).unwrap();
65+
assert_eq!(original, decoded);
66+
}
67+
68+
#[test]
69+
fn test_u128_max() {
70+
let original = TestStruct { value: u128::MAX };
71+
let encoded = minicbor::to_vec(&original).unwrap();
72+
let decoded: TestStruct = minicbor::decode(&encoded).unwrap();
73+
assert_eq!(original, decoded);
74+
}
75+
76+
#[test]
77+
fn test_u128_boundary_values() {
78+
let test_values = [
79+
0u128,
80+
1,
81+
127, // Max 1-byte value
82+
u64::MAX as u128, // 18446744073709551615
83+
(u64::MAX as u128) + 1, // First value needing >64 bits
84+
u128::MAX - 1, // Near max
85+
u128::MAX, // Maximum u128 value
86+
];
87+
88+
for &val in &test_values {
89+
let original = TestStruct { value: val };
90+
let encoded = minicbor::to_vec(&original).unwrap();
91+
let decoded: TestStruct = minicbor::decode(&encoded).unwrap();
92+
assert_eq!(original, decoded, "Failed for value {}", val);
93+
}
94+
}
95+
}

common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
pub mod address;
44
pub mod calculations;
5+
pub mod cbor;
56
pub mod cip19;
67
pub mod commands;
78
pub mod crypto;

common/src/messages.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::queries::{
2626
transactions::{TransactionsStateQuery, TransactionsStateQueryResponse},
2727
};
2828

29+
use crate::cbor::u128_cbor_codec;
2930
use crate::types::*;
3031
use crate::validation::ValidationStatus;
3132

@@ -141,47 +142,68 @@ pub struct BlockTxsMessage {
141142
}
142143

143144
/// Epoch activity - sent at end of epoch
144-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
145+
#[derive(
146+
Debug,
147+
Clone,
148+
serde::Serialize,
149+
serde::Deserialize,
150+
minicbor::Encode,
151+
minicbor::Decode,
152+
PartialEq,
153+
)]
145154
pub struct EpochActivityMessage {
146155
/// Epoch which has ended
156+
#[n(0)]
147157
pub epoch: u64,
148158

149159
/// Epoch start time
150160
/// UNIX timestamp
161+
#[n(1)]
151162
pub epoch_start_time: u64,
152163

153164
/// Epoch end time
154165
/// UNIX timestamp
166+
#[n(2)]
155167
pub epoch_end_time: u64,
156168

157169
/// When first block of this epoch was created
170+
#[n(3)]
158171
pub first_block_time: u64,
159172

160173
/// Block height of first block of this epoch
174+
#[n(4)]
161175
pub first_block_height: u64,
162176

163177
/// When last block of this epoch was created
178+
#[n(5)]
164179
pub last_block_time: u64,
165180

166181
/// Block height of last block of this epoch
182+
#[n(6)]
167183
pub last_block_height: u64,
168184

169185
/// Total blocks in this epoch
186+
#[n(7)]
170187
pub total_blocks: usize,
171188

172189
/// Total txs in this epoch
190+
#[n(8)]
173191
pub total_txs: u64,
174192

175193
/// Total outputs of all txs in this epoch
194+
#[cbor(n(9), with = "u128_cbor_codec")]
176195
pub total_outputs: u128,
177196

178197
/// Total fees in this epoch
198+
#[n(10)]
179199
pub total_fees: u64,
180200

181201
/// Map of SPO IDs to blocks produced
202+
#[n(11)]
182203
pub spo_blocks: Vec<(PoolId, usize)>,
183204

184205
/// Nonce
206+
#[n(12)]
185207
pub nonce: Option<Nonce>,
186208
}
187209

common/src/protocol_params.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,21 +254,46 @@ impl ProtocolVersion {
254254
}
255255

256256
#[derive(
257-
Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
257+
Default,
258+
Debug,
259+
Clone,
260+
PartialEq,
261+
Eq,
262+
PartialOrd,
263+
Ord,
264+
serde::Serialize,
265+
serde::Deserialize,
266+
minicbor::Encode,
267+
minicbor::Decode,
258268
)]
259269
#[serde(rename_all = "PascalCase")]
260270
pub enum NonceVariant {
271+
#[n(0)]
261272
#[default]
262273
NeutralNonce,
274+
#[n(1)]
263275
Nonce,
264276
}
265277

266278
pub type NonceHash = [u8; 32];
267279

268-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
280+
#[derive(
281+
Debug,
282+
Clone,
283+
PartialEq,
284+
Eq,
285+
PartialOrd,
286+
Ord,
287+
serde::Serialize,
288+
serde::Deserialize,
289+
minicbor::Encode,
290+
minicbor::Decode,
291+
)]
269292
#[serde(rename_all = "camelCase")]
270293
pub struct Nonce {
294+
#[n(0)]
271295
pub tag: NonceVariant,
296+
#[n(1)]
272297
pub hash: Option<NonceHash>,
273298
}
274299

common/src/queries/epochs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ use crate::{messages::EpochActivityMessage, protocol_params::ProtocolParams, Poo
44
pub const DEFAULT_EPOCHS_QUERY_TOPIC: (&str, &str) =
55
("epochs-state-query-topic", "cardano.query.epochs");
66

7+
pub const DEFAULT_HISTORICAL_EPOCHS_QUERY_TOPIC: (&str, &str) = (
8+
"historical-epochs-state-query-topic",
9+
"cardano.query.historical.epochs",
10+
);
11+
712
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
813
pub enum EpochsStateQuery {
914
GetLatestEpoch,
15+
16+
// Served from historical epochs state
1017
GetEpochInfo { epoch_number: u64 },
1118
GetNextEpochs { epoch_number: u64 },
1219
GetPreviousEpochs { epoch_number: u64 },
20+
1321
GetEpochStakeDistribution { epoch_number: u64 },
1422
GetEpochStakeDistributionByPool { epoch_number: u64 },
1523
GetLatestEpochBlocksMintedByPool { spo_id: PoolId },

modules/accounts_state/src/accounts_state.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const DEFAULT_SPO_REWARDS_TOPIC: &str = "cardano.spo.rewards";
5151
const DEFAULT_PROTOCOL_PARAMETERS_TOPIC: &str = "cardano.protocol.parameters";
5252
const DEFAULT_STAKE_REWARD_DELTAS_TOPIC: &str = "cardano.stake.reward.deltas";
5353

54-
const DEFAULT_SPDD_DB_PATH: (&str, &str) = ("spdd-db-path", "./spdd_db");
54+
const DEFAULT_SPDD_DB_PATH: (&str, &str) = ("spdd-db-path", "./fjall-spdd");
5555
const DEFAULT_SPDD_RETENTION_EPOCHS: (&str, u64) = ("spdd-retention-epochs", 0);
5656

5757
/// Accounts State module
@@ -403,24 +403,28 @@ impl AccountsState {
403403
let parameters_topic = config
404404
.get_string("protocol-parameters-topic")
405405
.unwrap_or(DEFAULT_PROTOCOL_PARAMETERS_TOPIC.to_string());
406+
info!("Creating protocol parameters subscriber on '{parameters_topic}'");
406407

407408
// Publishing topics
408409
let drep_distribution_topic = config
409410
.get_string("publish-drep-distribution-topic")
410411
.unwrap_or(DEFAULT_DREP_DISTRIBUTION_TOPIC.to_string());
412+
info!("Creating DRep distribution publisher on '{drep_distribution_topic}'");
411413

412414
let spo_distribution_topic = config
413415
.get_string("publish-spo-distribution-topic")
414416
.unwrap_or(DEFAULT_SPO_DISTRIBUTION_TOPIC.to_string());
417+
info!("Creating SPO distribution publisher on '{spo_distribution_topic}'");
415418

416419
let spo_rewards_topic = config
417420
.get_string("publish-spo-rewards-topic")
418421
.unwrap_or(DEFAULT_SPO_REWARDS_TOPIC.to_string());
422+
info!("Creating SPO rewards publisher on '{spo_rewards_topic}'");
419423

420424
let stake_reward_deltas_topic = config
421425
.get_string("publish-stake-reward-deltas-topic")
422426
.unwrap_or(DEFAULT_STAKE_REWARD_DELTAS_TOPIC.to_string());
423-
info!("Creating stake reward deltas subscriber on '{stake_reward_deltas_topic}'");
427+
info!("Creating stake reward deltas publisher on '{stake_reward_deltas_topic}'");
424428

425429
let spdd_db_path =
426430
config.get_string(DEFAULT_SPDD_DB_PATH.0).unwrap_or(DEFAULT_SPDD_DB_PATH.1.to_string());

modules/address_state/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
db/
1+
# fjall immutable db
2+
fjall-*/

0 commit comments

Comments
 (0)