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#[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 pub time_ms: TimestampMillis,
32
33 pub height: BlockHeight,
35
36 pub core_height: CoreBlockHeight,
38
39 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
53impl PartialOrd for BlockInfo {
55 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56 Some(self.cmp(other))
57 }
58}
59
60impl Ord for BlockInfo {
62 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
63 self.height.cmp(&other.height)
64 }
65}
66
67impl BlockInfo {
68 pub fn genesis() -> BlockInfo {
71 BlockInfo::default()
72 }
73
74 pub fn default_with_time(time_ms: TimestampMillis) -> BlockInfo {
76 BlockInfo {
77 time_ms,
78 ..Default::default()
79 }
80 }
81
82 pub fn default_with_height(height: BlockHeight) -> BlockInfo {
84 BlockInfo {
85 height,
86 ..Default::default()
87 }
88 }
89
90 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 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 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}
182
183#[cfg(all(
184 test,
185 feature = "json-conversion",
186 feature = "value-conversion",
187 feature = "serde-conversion"
188))]
189mod json_convertible_tests_blockinfo {
190 use super::*;
191 use platform_value::platform_value;
192 use serde_json::json;
193
194 fn fixture() -> BlockInfo {
197 BlockInfo {
198 time_ms: 1_700_000_000_000,
199 height: 123,
200 core_height: 456,
201 epoch: Epoch::new(7).expect("epoch"),
202 }
203 }
204
205 #[test]
206 fn json_round_trip_blockinfo_with_full_wire_shape() {
207 use crate::serialization::JsonConvertible;
208 let original = fixture();
209 let json = original.to_json().expect("to_json");
210 assert_eq!(
215 json,
216 json!({
217 "timeMs": 1_700_000_000_000u64,
218 "height": 123,
219 "coreHeight": 456,
220 "epoch": {"index": 7},
221 })
222 );
223 let recovered = BlockInfo::from_json(json).expect("from_json");
224 assert_eq!(original, recovered);
225 }
226
227 #[test]
228 fn value_round_trip_blockinfo_with_full_wire_shape() {
229 use crate::serialization::ValueConvertible;
230 let original = fixture();
231 let value = original.to_object().expect("to_object");
232 assert_eq!(
235 value,
236 platform_value!({
237 "timeMs": 1_700_000_000_000u64,
238 "height": 123u64,
239 "coreHeight": 456u32,
240 "epoch": {"index": 7u16},
241 })
242 );
243 let recovered = BlockInfo::from_object(value).expect("from_object");
244 assert_eq!(original, recovered);
245 }
246}