tendermint_testgen/
light_block.rs

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/// A light block is the core data structure used by the light client.
14/// It records everything the light client needs to know about a block.
15/// NOTE: This struct & associated `impl` below are a copy of light-client's `LightBlock`.
16/// The copy is necessary here to avoid a circular dependency.
17/// Cf. <https://github.com/informalsystems/tendermint-rs/issues/605>
18/// TODO: fix redundant code without introducing cyclic dependency.
19///
20/// To convert `TmLightBlock` to the Domain type `LightBlock` used in light-client crate
21/// You'll need to implement the `From` trait like below:
22///
23/// ```rust,ignore
24/// impl From<TmLightBlock> for LightBlock {
25///     fn from(tm_lb: TmLightBlock) -> Self {
26///         Self {
27///             signed_header: tm_lb.signed_header,
28///             validators: tm_lb.validators,
29///             next_validators: tm_lb.next_validators,
30///             provider: tm_lb.provider,
31///         }
32///     }
33/// }
34/// ```
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct TmLightBlock {
37    /// Header and commit of this block
38    pub signed_header: SignedHeader,
39    /// Validator set at the block height
40    pub validators: ValidatorSet,
41    /// Validator set at the next block height
42    pub next_validators: ValidatorSet,
43    /// The peer ID of the node that provided this block
44    pub provider: PeerId,
45}
46
47/// We use this data structure as a simplistic representation of LightClient's LightBlock
48#[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    /// Constructs a new Testgen-specific light block
70    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()); // just wanted to initialize time with some value
97
98        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    /// Produces a subsequent, i.e. at (height+1), light block to the supplied one
126    // TODO: figure how to represent the currently ignored details in header
127    // TODO: and commit like last_block_id and other hashes
128    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    /// returns the height of LightBlock's header
143    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    /// returns the chain_id of LightBlock's header
152    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    /// returns the last_block_id hash of LightBlock's header
163    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
236/// A helper function to generate SignedHeader used by TmLightBlock
237pub 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}