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#[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 pub time_ms: TimestampMillis,
44
45 pub height: BlockHeight,
47
48 pub core_height: CoreBlockHeight,
50
51 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
65impl PartialOrd for BlockInfo {
67 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
68 Some(self.cmp(other))
69 }
70}
71
72impl Ord for BlockInfo {
74 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
75 self.height.cmp(&other.height)
76 }
77}
78
79impl BlockInfo {
80 pub fn genesis() -> BlockInfo {
83 BlockInfo::default()
84 }
85
86 pub fn default_with_time(time_ms: TimestampMillis) -> BlockInfo {
88 BlockInfo {
89 time_ms,
90 ..Default::default()
91 }
92 }
93
94 pub fn default_with_height(height: BlockHeight) -> BlockInfo {
96 BlockInfo {
97 height,
98 ..Default::default()
99 }
100 }
101
102 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 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 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 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 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 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}