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, 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(Clone, Copy, Default, Debug, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
27#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
28#[serde(rename_all = "camelCase")]
29pub struct BlockInfo {
30    /// Block time in milliseconds
31    pub time_ms: TimestampMillis,
32
33    /// Block height
34    pub height: BlockHeight,
35
36    /// Core height
37    pub core_height: CoreBlockHeight,
38
39    /// Current fee epoch
40    pub epoch: Epoch,
41}
42
43impl fmt::Display for BlockInfo {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(
46            f,
47            "BlockInfo {{ time_ms: {}, height: {}, core_height: {}, epoch: {} }}",
48            self.time_ms, self.height, self.core_height, self.epoch.index
49        )
50    }
51}
52
53// Implementing PartialOrd for BlockInfo based on height
54impl PartialOrd for BlockInfo {
55    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56        Some(self.cmp(other))
57    }
58}
59
60// Implementing Ord for BlockInfo based on height
61impl Ord for BlockInfo {
62    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
63        self.height.cmp(&other.height)
64    }
65}
66
67impl BlockInfo {
68    // TODO: It's not actually a genesis one. We should use just default to avoid confusion
69    /// Create block info for genesis block
70    pub fn genesis() -> BlockInfo {
71        BlockInfo::default()
72    }
73
74    /// Create default block with specified time
75    pub fn default_with_time(time_ms: TimestampMillis) -> BlockInfo {
76        BlockInfo {
77            time_ms,
78            ..Default::default()
79        }
80    }
81
82    /// Create default block with specified height
83    pub fn default_with_height(height: BlockHeight) -> BlockInfo {
84        BlockInfo {
85            height,
86            ..Default::default()
87        }
88    }
89
90    /// Create default block with specified height and time
91    pub fn default_with_height_and_time(
92        height: BlockHeight,
93        time_ms: TimestampMillis,
94    ) -> BlockInfo {
95        BlockInfo {
96            height,
97            time_ms,
98            ..Default::default()
99        }
100    }
101
102    /// Create default block with specified fee epoch
103    pub fn default_with_epoch(epoch: Epoch) -> BlockInfo {
104        BlockInfo {
105            epoch,
106            ..Default::default()
107        }
108    }
109}
110
111#[cfg(all(test, feature = "json-conversion"))]
112mod tests {
113    use super::*;
114    use crate::block::epoch::Epoch;
115    use crate::serialization::JsonConvertible;
116
117    #[test]
118    fn block_info_json_round_trip() {
119        let block_info = BlockInfo {
120            time_ms: 1_700_000_000_000u64,
121            height: 12345678u64,
122            core_height: 900_000u32,
123            epoch: Epoch::new(42).unwrap(),
124        };
125
126        let json = block_info.to_json().expect("to_json should succeed");
127        assert!(json["timeMs"].is_number());
128        assert_eq!(json["timeMs"].as_u64().unwrap(), 1700000000000);
129        assert!(json["height"].is_number());
130        assert_eq!(json["height"].as_u64().unwrap(), 12345678);
131        assert!(json["coreHeight"].is_number());
132        assert_eq!(json["coreHeight"].as_u64().unwrap(), 900_000);
133
134        let restored = BlockInfo::from_json(json).expect("from_json should succeed");
135        assert_eq!(block_info, restored);
136    }
137
138    #[test]
139    fn block_info_value_round_trip() {
140        let block_info = BlockInfo {
141            time_ms: u64::MAX,
142            height: 999u64,
143            core_height: 100u32,
144            epoch: Epoch::new(0).unwrap(),
145        };
146
147        let obj = block_info.to_object().expect("to_object should succeed");
148        let time_val = obj
149            .get("timeMs")
150            .expect("get should not fail on map")
151            .expect("timeMs key must exist");
152        assert!(
153            time_val.is_integer(),
154            "Value timeMs should be an integer type, got: {:?}",
155            time_val
156        );
157
158        let restored = BlockInfo::from_object(obj).expect("from_object should succeed");
159        assert_eq!(block_info, restored);
160    }
161
162    #[test]
163    fn block_info_max_u64_json_round_trip() {
164        let block_info = BlockInfo {
165            time_ms: u64::MAX,
166            height: u64::MAX,
167            core_height: u32::MAX,
168            epoch: Epoch::new(100).unwrap(),
169        };
170
171        let json = block_info.to_json().expect("to_json should succeed");
172        // u64::MAX > JS MAX_SAFE_INTEGER, serialized as string
173        assert!(json["timeMs"].is_string());
174        assert_eq!(json["timeMs"].as_str().unwrap(), u64::MAX.to_string());
175        assert!(json["height"].is_string());
176        assert_eq!(json["height"].as_str().unwrap(), u64::MAX.to_string());
177
178        let restored = BlockInfo::from_json(json).expect("from_json should succeed");
179        assert_eq!(block_info, restored);
180    }
181}