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