1use gumdrop::Options;
2use serde::{Deserialize, Serialize};
3use simple_error::*;
4use tendermint::{
5 block::signed_header::SignedHeader, node::Id as PeerId, validator,
6 validator::Set as ValidatorSet, Hash, Time,
7};
8
9use crate::{
10 helpers::parse_as, validator::generate_validators, Commit, Generator, Header, Validator,
11};
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct TmLightBlock {
37 pub signed_header: SignedHeader,
39 pub validators: ValidatorSet,
41 pub next_validators: ValidatorSet,
43 pub provider: PeerId,
45}
46
47#[derive(Debug, Options, Serialize, Deserialize, Clone)]
49pub struct LightBlock {
50 #[options(help = "header (required)", parse(try_from_str = "parse_as::<Header>"))]
51 pub header: Option<Header>,
52 #[options(help = "commit (required)", parse(try_from_str = "parse_as::<Commit>"))]
53 pub commit: Option<Commit>,
54 #[options(
55 help = "validators (required), encoded as array of 'validator' parameters",
56 parse(try_from_str = "parse_as::<Vec<Validator>>")
57 )]
58 pub validators: Option<Vec<Validator>>,
59 #[options(
60 help = "next validators (default: same as validators), encoded as array of 'validator' parameters",
61 parse(try_from_str = "parse_as::<Vec<Validator>>")
62 )]
63 pub next_validators: Option<Vec<Validator>>,
64 #[options(help = "peer id (default: default_peer_id())")]
65 pub provider: Option<PeerId>,
66}
67
68impl LightBlock {
69 pub fn new(header: Header, commit: Commit) -> Self {
71 Self {
72 header: Some(header),
73 commit: Some(commit),
74 validators: None,
75 next_validators: None,
76 provider: None,
77 }
78 }
79 set_option!(validators, &[Validator], Some(validators.to_vec()));
80 set_option!(
81 next_validators,
82 &[Validator],
83 Some(next_validators.to_vec())
84 );
85 set_option!(provider, &str, Some(provider.parse().unwrap()));
86
87 pub fn new_default(height: u64) -> Self {
88 let validators = [
89 Validator::new("1").voting_power(50),
90 Validator::new("2").voting_power(50),
91 ];
92 let header = Header::new(&validators)
93 .height(height)
94 .chain_id("test-chain")
95 .next_validators(&validators)
96 .time(Time::from_unix_timestamp(height as i64, 0).unwrap()); Self::new_default_with_header(header)
99 }
100
101 pub fn new_default_with_time_and_chain_id(chain_id: String, time: Time, height: u64) -> Self {
102 let validators = [
103 Validator::new("1").voting_power(50),
104 Validator::new("2").voting_power(50),
105 ];
106 let header = Header::new(&validators)
107 .height(height)
108 .chain_id(&chain_id)
109 .next_validators(&validators)
110 .time(time);
111
112 Self::new_default_with_header(header)
113 }
114
115 pub fn new_default_with_header(header: Header) -> Self {
116 Self {
117 header: Some(header.clone()),
118 commit: Some(Commit::new(header.clone(), 1)),
119 validators: header.validators.clone(),
120 next_validators: header.validators,
121 provider: Some(default_peer_id()),
122 }
123 }
124
125 pub fn next(&self) -> Self {
129 let header = self.header.as_ref().expect("header is missing").next();
130
131 let commit = Commit::new(header.clone(), 1);
132
133 Self {
134 header: Some(header),
135 commit: Some(commit),
136 validators: self.next_validators.clone(),
137 next_validators: self.next_validators.clone(),
138 provider: self.provider,
139 }
140 }
141
142 pub fn height(&self) -> u64 {
144 self.header
145 .as_ref()
146 .expect("header is missing")
147 .height
148 .expect("header height is missing")
149 }
150
151 pub fn chain_id(&self) -> String {
153 self.header
154 .as_ref()
155 .expect("header is missing")
156 .chain_id
157 .as_ref()
158 .expect("chain_id is missing")
159 .to_string()
160 }
161
162 pub fn last_block_id_hash(&self) -> Option<Hash> {
164 self.header
165 .as_ref()
166 .expect("header is missing")
167 .last_block_id_hash
168 }
169}
170
171impl std::str::FromStr for LightBlock {
172 type Err = SimpleError;
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 let light_block = match parse_as::<LightBlock>(s) {
175 Ok(input) => input,
176 Err(_) => LightBlock::new(parse_as::<Header>(s)?, Commit::from_str(s)?),
177 };
178 Ok(light_block)
179 }
180}
181
182impl Generator<TmLightBlock> for LightBlock {
183 fn merge_with_default(self, default: Self) -> Self {
184 Self {
185 header: self.header.or(default.header),
186 commit: self.commit.or(default.commit),
187 validators: self.validators.or(default.validators),
188 next_validators: self.next_validators.or(default.next_validators),
189 provider: self.provider.or(default.provider),
190 }
191 }
192
193 fn generate(&self) -> Result<TmLightBlock, SimpleError> {
194 let header = match &self.header {
195 None => bail!("header is missing"),
196 Some(h) => h,
197 };
198 let commit = match &self.commit {
199 None => bail!("commit is missing"),
200 Some(c) => c,
201 };
202 let signed_header =
203 generate_signed_header(header, commit).expect("Could not generate signed header");
204
205 let validators = match &self.validators {
206 None => validator::Set::without_proposer(generate_validators(
207 header
208 .validators
209 .as_ref()
210 .expect("missing validators in header"),
211 )?),
212 Some(vals) => validator::Set::without_proposer(generate_validators(vals)?),
213 };
214
215 let next_validators = match &self.next_validators {
216 Some(next_vals) => validator::Set::without_proposer(generate_validators(next_vals)?),
217 None => validators.clone(),
218 };
219
220 let provider = match self.provider {
221 Some(peer) => peer,
222 None => default_peer_id(),
223 };
224
225 let light_block = TmLightBlock {
226 signed_header,
227 validators,
228 next_validators,
229 provider,
230 };
231
232 Ok(light_block)
233 }
234}
235
236pub fn generate_signed_header(
238 raw_header: &Header,
239 raw_commit: &Commit,
240) -> Result<SignedHeader, SimpleError> {
241 let header = match raw_header.generate() {
242 Err(e) => bail!("Failed to generate header with error: {}", e),
243 Ok(h) => h,
244 };
245
246 let commit = match raw_commit.generate() {
247 Err(e) => bail!("Failed to generate commit with error: {}", e),
248 Ok(c) => c,
249 };
250
251 Ok(SignedHeader::new(header, commit).unwrap())
252}
253
254pub fn default_peer_id() -> PeerId {
255 "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap()
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_light_block() {
264 let light_block_1 = LightBlock::new_default(1);
265 let light_block_2 = LightBlock::new_default(1);
266
267 assert_eq!(light_block_1.generate(), light_block_2.generate());
268
269 let validators = [Validator::new("10"), Validator::new("20")];
270
271 let light_block_3 = LightBlock::new_default(1).validators(&validators);
272
273 assert_ne!(light_block_2.generate(), light_block_3.generate());
274
275 let light_block_4 = LightBlock::new_default(4).validators(&validators);
276
277 assert_ne!(light_block_3.generate(), light_block_4.generate());
278
279 let light_block_5 = light_block_3.next();
280 let lb_5_height: u64 = light_block_5
281 .generate()
282 .unwrap()
283 .signed_header
284 .header
285 .height
286 .into();
287
288 assert_eq!(2, lb_5_height);
289
290 let header_6 = Header::new(&validators)
291 .next_validators(&validators)
292 .height(10)
293 .time(Time::from_unix_timestamp(10, 0).unwrap())
294 .chain_id("test-chain");
295 let commit_6 = Commit::new(header_6.clone(), 1);
296 let light_block_6 = LightBlock::new(header_6.clone(), commit_6);
297
298 let header_7 = header_6.next();
299 let commit_7 = Commit::new(header_7.clone(), 1);
300 let light_block_7 = LightBlock::new(header_7, commit_7);
301
302 assert_eq!(light_block_7.height(), 11);
303 assert_eq!(light_block_7.chain_id(), "test-chain");
304 assert_ne!(light_block_6.generate(), light_block_7.generate());
305 }
306}