1use core::time::Duration;
2use std::{
3 convert::{TryFrom, TryInto},
4 str::FromStr,
5};
6
7use cometbft::{block, chain, validator, AppHash, Hash, Time};
8use gumdrop::Options;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use simple_error::*;
11use time::OffsetDateTime;
12
13use crate::{helpers::*, validator::generate_validators, Generator, Validator};
14
15#[derive(Debug, Options, Serialize, Deserialize, Clone)]
16pub struct Header {
17 #[options(
18 help = "validators (required), encoded as array of 'validator' parameters",
19 parse(try_from_str = "parse_as::<Vec<Validator>>")
20 )]
21 pub validators: Option<Vec<Validator>>,
22 #[options(
23 help = "next validators (default: same as validators), encoded as array of 'validator' parameters",
24 parse(try_from_str = "parse_as::<Vec<Validator>>")
25 )]
26 pub next_validators: Option<Vec<Validator>>,
27 #[options(help = "chain id (default: test-chain)")]
28 pub chain_id: Option<String>,
29 #[options(help = "block height (default: 1)")]
30 pub height: Option<u64>,
31 #[options(help = "time (default: now)")]
32 #[serde(deserialize_with = "deserialize_time")]
33 #[serde(serialize_with = "serialize_time")]
34 pub time: Option<Time>,
35 #[options(help = "proposer index (default: 0)")]
36 pub proposer: Option<usize>,
37 #[options(help = "last block id hash (default: Hash::None)")]
38 pub last_block_id_hash: Option<Hash>,
39 #[options(help = "application hash (default: AppHash(vec![])")]
40 #[serde(default, with = "app_hash_serde")]
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub app_hash: Option<AppHash>,
43}
44
45fn deserialize_time<'de, D>(deserializer: D) -> Result<Option<Time>, D::Error>
50where
51 D: Deserializer<'de>,
52{
53 let m_secs = <Option<i64>>::deserialize(deserializer)?;
54 let m_time = m_secs.map(|secs| Time::from_unix_timestamp(secs, 0).unwrap());
55
56 Ok(m_time)
57}
58
59fn serialize_time<S>(m_time: &Option<Time>, serializer: S) -> Result<S::Ok, S::Error>
60where
61 S: Serializer,
62{
63 let m_secs = m_time.map(|time| {
64 let datetime: OffsetDateTime = time.into();
65 datetime.unix_timestamp()
66 });
67
68 m_secs.serialize(serializer)
69}
70
71mod app_hash_serde {
74 use super::*;
75 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AppHash>, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 cometbft::serializers::apphash::deserialize(deserializer).map(Some)
80 }
81
82 pub fn serialize<S>(value: &Option<AppHash>, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: Serializer,
85 {
86 cometbft::serializers::apphash::serialize(value.as_ref().unwrap(), serializer)
87 }
88}
89
90impl Header {
91 pub fn new(validators: &[Validator]) -> Self {
92 Header {
93 validators: Some(validators.to_vec()),
94 next_validators: None,
95 chain_id: None,
96 height: None,
97 time: None,
98 proposer: None,
99 last_block_id_hash: None,
100 app_hash: None,
101 }
102 }
103 set_option!(validators, &[Validator], Some(validators.to_vec()));
104 set_option!(
105 next_validators,
106 &[Validator],
107 Some(next_validators.to_vec())
108 );
109 set_option!(chain_id, &str, Some(chain_id.to_string()));
110 set_option!(height, u64);
111 set_option!(time, Time);
112 set_option!(proposer, usize);
113 set_option!(last_block_id_hash, Hash);
114 set_option!(app_hash, AppHash);
115
116 pub fn next(&self) -> Self {
117 let height = self.height.expect("Missing previous header's height");
118 let time = self
120 .time
121 .unwrap_or_else(|| Time::from_unix_timestamp(height.try_into().unwrap(), 0).unwrap());
122 let validators = self.validators.clone().expect("Missing validators");
123 let next_validators = self.next_validators.clone().unwrap_or(validators);
124
125 let prev_header = self.generate().unwrap();
126 let last_block_id_hash = prev_header.hash();
127
128 Self {
129 validators: Some(next_validators.clone()),
130 next_validators: Some(next_validators),
131 chain_id: self.chain_id.clone(),
132 height: Some(height + 1),
133 time: Some((time + Duration::from_secs(1)).unwrap()),
134 proposer: self.proposer, last_block_id_hash: Some(last_block_id_hash),
136 app_hash: self.app_hash.clone(),
137 }
138 }
139}
140
141impl std::str::FromStr for Header {
142 type Err = SimpleError;
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 let header = match parse_as::<Header>(s) {
145 Ok(input) => input,
146 Err(_) => Header::new(&parse_as::<Vec<Validator>>(s)?),
147 };
148 Ok(header)
149 }
150}
151
152impl Generator<block::Header> for Header {
153 fn merge_with_default(self, default: Self) -> Self {
154 Header {
155 validators: self.validators.or(default.validators),
156 next_validators: self.next_validators.or(default.next_validators),
157 chain_id: self.chain_id.or(default.chain_id),
158 height: self.height.or(default.height),
159 time: self.time.or(default.time),
160 proposer: self.proposer.or(default.proposer),
161 last_block_id_hash: self.last_block_id_hash.or(default.last_block_id_hash),
162 app_hash: self.app_hash.or(default.app_hash),
163 }
164 }
165
166 fn generate(&self) -> Result<block::Header, SimpleError> {
167 let vals = match &self.validators {
168 None => bail!("validator array is missing"),
169 Some(vals) => vals,
170 };
171 let vals = generate_validators(vals)?;
172 let proposer_index = self.proposer.unwrap_or(0);
173 let proposer_address = if !vals.is_empty() {
174 vals[proposer_index].address
175 } else {
176 Validator::new("a").generate().unwrap().address
177 };
178 let valset = validator::Set::without_proposer(vals);
179 let validators_hash = valset.hash();
180 let next_valset = match &self.next_validators {
181 Some(next_vals) => validator::Set::without_proposer(generate_validators(next_vals)?),
182 None => valset,
183 };
184 let chain_id = match chain::Id::from_str(
185 self.chain_id
186 .clone()
187 .unwrap_or_else(|| "test-chain".to_string())
188 .as_str(),
189 ) {
190 Ok(id) => id,
191 Err(_) => bail!("failed to construct header's chain_id"),
192 };
193
194 let time: Time = self.time.unwrap_or_else(Time::now);
195
196 let last_block_id = self.last_block_id_hash.map(|hash| block::Id {
197 hash,
198 part_set_header: Default::default(),
199 });
200
201 let header = block::Header {
202 version: block::header::Version { block: 11, app: 0 },
205 chain_id,
206 height: block::Height::try_from(self.height.unwrap_or(1))
207 .map_err(|_| SimpleError::new("height out of bounds"))?,
208 time,
209 last_block_id,
210 last_commit_hash: None,
211 data_hash: None,
212 validators_hash,
213 next_validators_hash: next_valset.hash(),
214 consensus_hash: validators_hash, app_hash: self.app_hash.clone().unwrap_or_default(),
216 last_results_hash: None,
217 evidence_hash: None,
218 proposer_address,
219 };
220 Ok(header)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use core::time::Duration;
227
228 use cometbft::Time;
229
230 use super::*;
231
232 #[test]
233 fn test_header() {
234 let valset1 = [
235 Validator::new("a"),
236 Validator::new("b"),
237 Validator::new("c"),
238 ];
239 let valset2 = [
240 Validator::new("b"),
241 Validator::new("c"),
242 Validator::new("d"),
243 ];
244
245 let now1 = Time::now();
246 let header1 = Header::new(&valset1)
247 .next_validators(&valset2)
248 .height(10)
249 .time(now1);
250
251 let now2 = (now1 + Duration::from_secs(1)).unwrap();
252 let header2 = Header::new(&valset1)
253 .next_validators(&valset2)
254 .height(10)
255 .time(now2);
256 assert_ne!(header1.generate(), header2.generate());
257
258 let header2 = header2.time(now1);
259 assert_eq!(header1.generate(), header2.generate());
260
261 let header3 = header2.clone().height(11);
262 assert_ne!(header1.generate(), header3.generate());
263
264 let header3 = header2.clone().validators(&valset2);
265 assert_ne!(header1.generate(), header3.generate());
266
267 let header3 = header2.clone().next_validators(&valset1);
268 assert_ne!(header1.generate(), header3.generate());
269
270 let mut block_header = header2.generate().unwrap();
271
272 block_header.chain_id = chain::Id::from_str("chain1").unwrap();
273 let header = header2.chain_id("chain1");
274 assert_eq!(header.generate().unwrap(), block_header);
275
276 block_header.proposer_address = Validator::new("c").generate().unwrap().address;
277 assert_ne!(header.generate().unwrap(), block_header);
278
279 let header = header.proposer(1);
280 assert_eq!(header.generate().unwrap(), block_header);
281 }
282}