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