Skip to main content

dpp/block/finalized_epoch_info/
mod.rs

1mod getters;
2pub mod v0;
3
4use crate::block::finalized_epoch_info::v0::FinalizedEpochInfoV0;
5use crate::protocol_error::ProtocolError;
6#[cfg(feature = "json-conversion")]
7use crate::serialization::JsonConvertible;
8#[cfg(feature = "value-conversion")]
9use crate::serialization::ValueConvertible;
10use bincode::{Decode, Encode};
11use derive_more::From;
12use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
13use serde::{Deserialize, Serialize};
14
15/// Finalized Epoch information
16#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
17#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
18#[derive(
19    Clone,
20    Debug,
21    PartialEq,
22    Serialize,
23    Deserialize,
24    Encode,
25    Decode,
26    PlatformSerialize,
27    PlatformDeserialize,
28    From,
29)]
30#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
31#[serde(tag = "$formatVersion")]
32pub enum FinalizedEpochInfo {
33    #[serde(rename = "0")]
34    V0(FinalizedEpochInfoV0),
35}
36
37#[cfg(all(test, feature = "json-conversion"))]
38mod tests {
39    use super::*;
40    use crate::serialization::JsonConvertible;
41    use platform_value::Identifier;
42    use std::collections::BTreeMap;
43
44    #[test]
45    fn finalized_epoch_info_json_round_trip() {
46        let proposer_id = Identifier::from([1u8; 32]);
47        let mut block_proposers = BTreeMap::new();
48        block_proposers.insert(proposer_id, 42u64);
49
50        let info = FinalizedEpochInfo::V0(FinalizedEpochInfoV0 {
51            first_block_time: 1_700_000_000_000u64,
52            first_block_height: 100_000u64,
53            total_blocks_in_epoch: 2_000u64,
54            first_core_block_height: 500_000u32,
55            next_epoch_start_core_block_height: 500_200u32,
56            total_processing_fees: 1_000_000u64,
57            total_distributed_storage_fees: 500_000u64,
58            total_created_storage_fees: 600_000u64,
59            core_block_rewards: 10_000_000u64,
60            block_proposers,
61            fee_multiplier_permille: 1_000u64,
62            protocol_version: 3,
63        });
64
65        let json = info.to_json().expect("to_json should succeed");
66        assert!(json["firstBlockTime"].is_number());
67        assert!(json["firstBlockHeight"].is_number());
68        assert!(json["totalBlocksInEpoch"].is_number());
69        assert!(json["totalProcessingFees"].is_number());
70        assert!(json["feeMultiplierPermille"].is_number());
71        assert!(json["firstCoreBlockHeight"].is_number());
72        assert!(json["nextEpochStartCoreBlockHeight"].is_number());
73
74        let proposers = json["blockProposers"]
75            .as_object()
76            .expect("blockProposers should be an object");
77        assert_eq!(proposers.len(), 1);
78
79        let expected_base58 =
80            proposer_id.to_string(platform_value::string_encoding::Encoding::Base58);
81        assert!(
82            proposers.contains_key(&expected_base58),
83            "Expected key {} in blockProposers, got keys: {:?}",
84            expected_base58,
85            proposers.keys().collect::<Vec<_>>()
86        );
87
88        let value = &proposers[&expected_base58];
89        assert!(value.is_number());
90        assert_eq!(value.as_u64().unwrap(), 42);
91
92        let restored = FinalizedEpochInfo::from_json(json).expect("from_json should succeed");
93        assert_eq!(info, restored);
94    }
95
96    #[test]
97    fn finalized_epoch_info_value_round_trip_empty_proposers() {
98        let info = FinalizedEpochInfo::V0(FinalizedEpochInfoV0 {
99            first_block_time: 1_000_000u64,
100            first_block_height: 10_000u64,
101            total_blocks_in_epoch: 500u64,
102            first_core_block_height: 50_000u32,
103            next_epoch_start_core_block_height: 50_200u32,
104            total_processing_fees: 100u64,
105            total_distributed_storage_fees: 50u64,
106            total_created_storage_fees: 60u64,
107            core_block_rewards: 1_000u64,
108            block_proposers: BTreeMap::new(),
109            fee_multiplier_permille: 1_000u64,
110            protocol_version: 2,
111        });
112
113        let obj = info.to_object().expect("to_object should succeed");
114        let restored = FinalizedEpochInfo::from_object(obj).expect("from_object should succeed");
115        assert_eq!(info, restored);
116    }
117}
118
119#[cfg(all(
120    test,
121    feature = "json-conversion",
122    feature = "value-conversion",
123    feature = "serde-conversion"
124))]
125mod json_convertible_tests_finalizedepochinfo {
126    use super::*;
127    use crate::block::finalized_epoch_info::v0::FinalizedEpochInfoV0;
128    use platform_value::Identifier;
129    use std::collections::BTreeMap;
130
131    fn fixture() -> FinalizedEpochInfo {
132        let mut block_proposers = BTreeMap::new();
133        block_proposers.insert(Identifier::new([0xab; 32]), 5u64);
134        FinalizedEpochInfo::V0(FinalizedEpochInfoV0 {
135            first_block_time: 1_700_000_000_000,
136            first_block_height: 100,
137            total_blocks_in_epoch: 250,
138            first_core_block_height: 50,
139            next_epoch_start_core_block_height: 75,
140            total_processing_fees: 1_000_000,
141            total_distributed_storage_fees: 200_000,
142            total_created_storage_fees: 250_000,
143            core_block_rewards: 500_000,
144            block_proposers,
145            fee_multiplier_permille: 1500,
146            protocol_version: 9,
147        })
148    }
149
150    #[test]
151    fn json_round_trip_with_full_wire_shape() {
152        use crate::serialization::JsonConvertible;
153        use serde_json::json;
154        let original = fixture();
155        let json = original.to_json().expect("to_json");
156        // `block_proposers` is `BTreeMap<Identifier, u64>` with the
157        // `json_safe_identifier_u64_map` serde adapter: in JSON HR mode, keys
158        // become base58-encoded `Identifier` strings and values stay numeric
159        // (or string for u64 above JS_MAX_SAFE_INTEGER; 5 is well below).
160        // All Credits / Heights are u64/u32 — JSON erases size; the value-path
161        // assertion uses explicit suffixes to lock the typed variants.
162        assert_eq!(
163            json,
164            json!({
165                "$formatVersion": "0",
166                "firstBlockTime": 1_700_000_000_000_u64,
167                "firstBlockHeight": 100,
168                "totalBlocksInEpoch": 250,
169                "firstCoreBlockHeight": 50,
170                "nextEpochStartCoreBlockHeight": 75,
171                "totalProcessingFees": 1_000_000,
172                "totalDistributedStorageFees": 200_000,
173                "totalCreatedStorageFees": 250_000,
174                "coreBlockRewards": 500_000,
175                "blockProposers": {
176                    "CZ8YUVdk7znjrUmnb5n7kgySk9yRAsQDYmyCxzfSky9t": 5,
177                },
178                "feeMultiplierPermille": 1500,
179                "protocolVersion": 9,
180            })
181        );
182        let recovered = FinalizedEpochInfo::from_json(json).expect("from_json");
183        assert_eq!(original, recovered);
184    }
185
186    #[test]
187    fn value_round_trip_with_full_wire_shape() {
188        use crate::serialization::ValueConvertible;
189        use platform_value::platform_value;
190        let original = fixture();
191        let value = original.to_object().expect("to_object");
192        // In non-HR mode, `block_proposers` keeps `Value::Identifier` keys (no
193        // base58 stringification). Heights/Credits are u64; core heights u32;
194        // protocol_version u32.
195        assert_eq!(
196            value,
197            platform_value!({
198                "$formatVersion": "0",
199                "firstBlockTime": 1_700_000_000_000_u64,
200                "firstBlockHeight": 100u64,
201                "totalBlocksInEpoch": 250u64,
202                "firstCoreBlockHeight": 50u32,
203                "nextEpochStartCoreBlockHeight": 75u32,
204                "totalProcessingFees": 1_000_000u64,
205                "totalDistributedStorageFees": 200_000u64,
206                "totalCreatedStorageFees": 250_000u64,
207                "coreBlockRewards": 500_000u64,
208                "blockProposers": {
209                    Identifier::new([0xab; 32]): 5u64,
210                },
211                "feeMultiplierPermille": 1500u64,
212                "protocolVersion": 9u32,
213            })
214        );
215        let recovered = FinalizedEpochInfo::from_object(value).expect("from_object");
216        assert_eq!(original, recovered);
217    }
218}