Skip to main content

dpp/block/block_info/
mod.rs

1use crate::block::epoch::{Epoch, EPOCH_0};
2use crate::prelude::{BlockHeight, CoreBlockHeight, TimestampMillis};
3#[cfg(feature = "json-conversion")]
4use crate::serialization::json_safe_fields;
5#[cfg(feature = "json-conversion")]
6use crate::serialization::JsonConvertible;
7#[cfg(feature = "value-conversion")]
8use crate::serialization::ValueConvertible;
9use bincode::{Decode, DecodeUntrusted, Encode};
10use serde::{Deserialize, Serialize};
11use std::fmt;
12
13pub const DEFAULT_BLOCK_INFO: BlockInfo = BlockInfo {
14    time_ms: 0,
15    height: 0,
16    core_height: 0,
17    epoch: EPOCH_0,
18};
19
20// We make this immutable because it should never be changed or updated
21// Extended block info however is not immutable
22// @immutable
23/// Block information
24#[cfg_attr(feature = "json-conversion", json_safe_fields)]
25#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
26#[derive(
27    Clone,
28    Copy,
29    Default,
30    Debug,
31    PartialEq,
32    Eq,
33    Encode,
34    Decode,
35    Serialize,
36    Deserialize,
37    DecodeUntrusted,
38)]
39#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
40#[serde(rename_all = "camelCase")]
41pub struct BlockInfo {
42    /// Block time in milliseconds
43    pub time_ms: TimestampMillis,
44
45    /// Block height
46    pub height: BlockHeight,
47
48    /// Core height
49    pub core_height: CoreBlockHeight,
50
51    /// Current fee epoch
52    pub epoch: Epoch,
53}
54
55impl fmt::Display for BlockInfo {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(
58            f,
59            "BlockInfo {{ time_ms: {}, height: {}, core_height: {}, epoch: {} }}",
60            self.time_ms, self.height, self.core_height, self.epoch.index
61        )
62    }
63}
64
65// Implementing PartialOrd for BlockInfo based on height
66impl PartialOrd for BlockInfo {
67    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
68        Some(self.cmp(other))
69    }
70}
71
72// Implementing Ord for BlockInfo based on height
73impl Ord for BlockInfo {
74    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
75        self.height.cmp(&other.height)
76    }
77}
78
79impl BlockInfo {
80    // TODO: It's not actually a genesis one. We should use just default to avoid confusion
81    /// Create block info for genesis block
82    pub fn genesis() -> BlockInfo {
83        BlockInfo::default()
84    }
85
86    /// Create default block with specified time
87    pub fn default_with_time(time_ms: TimestampMillis) -> BlockInfo {
88        BlockInfo {
89            time_ms,
90            ..Default::default()
91        }
92    }
93
94    /// Create default block with specified height
95    pub fn default_with_height(height: BlockHeight) -> BlockInfo {
96        BlockInfo {
97            height,
98            ..Default::default()
99        }
100    }
101
102    /// Create default block with specified height and time
103    pub fn default_with_height_and_time(
104        height: BlockHeight,
105        time_ms: TimestampMillis,
106    ) -> BlockInfo {
107        BlockInfo {
108            height,
109            time_ms,
110            ..Default::default()
111        }
112    }
113
114    /// Create default block with specified fee epoch
115    pub fn default_with_epoch(epoch: Epoch) -> BlockInfo {
116        BlockInfo {
117            epoch,
118            ..Default::default()
119        }
120    }
121}
122
123#[cfg(all(test, feature = "json-conversion"))]
124mod tests {
125    use super::*;
126    use crate::block::epoch::Epoch;
127    use crate::serialization::JsonConvertible;
128
129    #[test]
130    fn block_info_json_round_trip() {
131        let block_info = BlockInfo {
132            time_ms: 1_700_000_000_000u64,
133            height: 12345678u64,
134            core_height: 900_000u32,
135            epoch: Epoch::new(42).unwrap(),
136        };
137
138        let json = block_info.to_json().expect("to_json should succeed");
139        assert!(json["timeMs"].is_number());
140        assert_eq!(json["timeMs"].as_u64().unwrap(), 1700000000000);
141        assert!(json["height"].is_number());
142        assert_eq!(json["height"].as_u64().unwrap(), 12345678);
143        assert!(json["coreHeight"].is_number());
144        assert_eq!(json["coreHeight"].as_u64().unwrap(), 900_000);
145
146        let restored = BlockInfo::from_json(json).expect("from_json should succeed");
147        assert_eq!(block_info, restored);
148    }
149
150    #[test]
151    fn block_info_value_round_trip() {
152        let block_info = BlockInfo {
153            time_ms: u64::MAX,
154            height: 999u64,
155            core_height: 100u32,
156            epoch: Epoch::new(0).unwrap(),
157        };
158
159        let obj = block_info.to_object().expect("to_object should succeed");
160        let time_val = obj
161            .get("timeMs")
162            .expect("get should not fail on map")
163            .expect("timeMs key must exist");
164        assert!(
165            time_val.is_integer(),
166            "Value timeMs should be an integer type, got: {:?}",
167            time_val
168        );
169
170        let restored = BlockInfo::from_object(obj).expect("from_object should succeed");
171        assert_eq!(block_info, restored);
172    }
173
174    #[test]
175    fn block_info_max_u64_json_round_trip() {
176        let block_info = BlockInfo {
177            time_ms: u64::MAX,
178            height: u64::MAX,
179            core_height: u32::MAX,
180            epoch: Epoch::new(100).unwrap(),
181        };
182
183        let json = block_info.to_json().expect("to_json should succeed");
184        // u64::MAX > JS MAX_SAFE_INTEGER, serialized as string
185        assert!(json["timeMs"].is_string());
186        assert_eq!(json["timeMs"].as_str().unwrap(), u64::MAX.to_string());
187        assert!(json["height"].is_string());
188        assert_eq!(json["height"].as_str().unwrap(), u64::MAX.to_string());
189
190        let restored = BlockInfo::from_json(json).expect("from_json should succeed");
191        assert_eq!(block_info, restored);
192    }
193}
194
195#[cfg(all(
196    test,
197    feature = "json-conversion",
198    feature = "value-conversion",
199    feature = "serde-conversion"
200))]
201mod json_convertible_tests_blockinfo {
202    use super::*;
203    use platform_value::platform_value;
204    use serde_json::json;
205
206    // Distinct per-field values (not `default()`'s all-zeros) so the fixture
207    // catches field-name renames, field swaps, and integer-size changes.
208    fn fixture() -> BlockInfo {
209        BlockInfo {
210            time_ms: 1_700_000_000_000,
211            height: 123,
212            core_height: 456,
213            epoch: Epoch::new(7).expect("epoch"),
214        }
215    }
216
217    #[test]
218    fn json_round_trip_blockinfo_with_full_wire_shape() {
219        use crate::serialization::JsonConvertible;
220        let original = fixture();
221        let json = original.to_json().expect("to_json");
222        // `#[serde(rename_all = "camelCase")]` → `timeMs` / `coreHeight`.
223        // `#[json_safe_fields]` keeps `timeMs` / `height` (u64) as numbers
224        // below 2^53. Nested `Epoch` serializes as `{"index": <u16>}` (`key`
225        // is `#[serde(skip)]`, reconstructed from `index` on deserialize).
226        assert_eq!(
227            json,
228            json!({
229                "timeMs": 1_700_000_000_000u64,
230                "height": 123,
231                "coreHeight": 456,
232                "epoch": {"index": 7},
233            })
234        );
235        let recovered = BlockInfo::from_json(json).expect("from_json");
236        assert_eq!(original, recovered);
237    }
238
239    #[test]
240    fn value_round_trip_blockinfo_with_full_wire_shape() {
241        use crate::serialization::ValueConvertible;
242        let original = fixture();
243        let value = original.to_object().expect("to_object");
244        // Non-HR `Value` keeps integers typed: `timeMs` / `height` are u64,
245        // `coreHeight` is u32, and `Epoch::index` is u16.
246        assert_eq!(
247            value,
248            platform_value!({
249                "timeMs": 1_700_000_000_000u64,
250                "height": 123u64,
251                "coreHeight": 456u32,
252                "epoch": {"index": 7u16},
253            })
254        );
255        let recovered = BlockInfo::from_object(value).expect("from_object");
256        assert_eq!(original, recovered);
257    }
258}