penumbra_sdk_mock_consensus/builder/
init_chain.rs

1use {
2    super::*,
3    anyhow::{anyhow, bail},
4    bytes::Bytes,
5    prost::Message,
6    sha2::Digest as _,
7    std::{collections::BTreeMap, time},
8    tap::TapFallible,
9    tendermint::{
10        abci::request::InitChain,
11        block::{self, Height},
12        consensus::{
13            self,
14            params::{AbciParams, ValidatorParams, VersionParams},
15        },
16        evidence,
17        v0_37::abci::{ConsensusRequest, ConsensusResponse},
18        validator::Update,
19        vote::Power,
20    },
21    tendermint_proto::v0_37::types::HashedParams,
22    tower::{BoxError, Service, ServiceExt},
23    tracing::{debug, error},
24};
25
26impl Builder {
27    /// Consumes this builder, using the provided consensus service.
28    ///
29    /// This function returns an error if the builder was not fully initialized, or if the
30    /// application could not successfully perform the chain initialization.
31    ///
32    /// See [`TestNode`] for more information on the consensus service.
33    pub async fn init_chain<C>(self, mut consensus: C) -> Result<TestNode<C>, anyhow::Error>
34    where
35        C: Service<ConsensusRequest, Response = ConsensusResponse, Error = BoxError>
36            + Send
37            + Clone
38            + 'static,
39        C::Future: Send + 'static,
40        C::Error: Sized,
41    {
42        use tendermint::v0_37::abci::response;
43
44        let Self {
45            app_state: Some(app_state),
46            keyring,
47            on_block,
48            initial_timestamp,
49            ts_callback,
50            chain_id,
51            keys: _,
52            hardcoded_genesis,
53        } = self
54        else {
55            bail!("builder was not fully initialized")
56        };
57
58        let chain_id = tendermint::chain::Id::try_from(
59            chain_id.unwrap_or(TestNode::<()>::CHAIN_ID.to_string()),
60        )?;
61
62        let timestamp = initial_timestamp.unwrap_or(Time::now());
63        let request = match hardcoded_genesis {
64            // If there is a hardcoded genesis, ignore whatever else was configured on the builder.
65            Some(genesis) => ConsensusRequest::InitChain(InitChain {
66                time: genesis.genesis_time,
67                chain_id: genesis.chain_id.into(),
68                consensus_params: genesis.consensus_params,
69                validators: genesis
70                    .validators
71                    .iter()
72                    .map(|v| Update {
73                        pub_key: v.pub_key.clone(),
74                        power: v.power,
75                    })
76                    .collect::<Vec<_>>(),
77                app_state_bytes: serde_json::to_vec(&genesis.app_state).unwrap().into(),
78                initial_height: Height::try_from(genesis.initial_height)?,
79            }),
80            // Use whatever state was configured on the builder.
81            None => {
82                Self::init_chain_request(app_state, &keyring, chain_id.clone(), timestamp.clone())?
83            }
84        };
85        let service = consensus
86            .ready()
87            .await
88            .tap_err(|error| error!(?error, "failed waiting for consensus service"))
89            .map_err(|_| anyhow!("failed waiting for consensus service"))?;
90
91        let response::InitChain {
92            app_hash,
93            consensus_params,
94            validators,
95            ..
96        } = match service
97            .call(request)
98            .await
99            .tap_ok(|resp| debug!(?resp, "received response from consensus service"))
100            .tap_err(|error| error!(?error, "consensus service returned error"))
101            .map_err(|_| anyhow!("consensus service returned error"))?
102        {
103            ConsensusResponse::InitChain(resp) => resp,
104            response => {
105                error!(?response, "unexpected InitChain response");
106                bail!("unexpected InitChain response");
107            }
108        };
109
110        // handle validators
111        {
112            // TODO: implement
113            tracing::debug!(?validators, "init_chain ignoring validator updates");
114        }
115
116        // The validators aren't properly handled by the mock tendermint
117        // and it just maintains this value for the life of the chain right now
118        let pk = keyring.iter().next().expect("validator key in keyring").0;
119        let pub_key =
120            tendermint::PublicKey::from_raw_ed25519(pk.as_bytes()).expect("pub key present");
121        let proposer_address = tendermint::validator::Info {
122            address: tendermint::account::Id::new(
123                <sha2::Sha256 as sha2::Digest>::digest(pk).as_slice()[0..20]
124                    .try_into()
125                    .expect(""),
126            )
127            .try_into()?,
128            pub_key,
129            power: 1i64.try_into()?,
130            name: Some("test validator".to_string()),
131            proposer_priority: 1i64.try_into()?,
132        };
133
134        let hashed_params = HashedParams {
135            block_max_bytes: consensus_params
136                .as_ref()
137                .unwrap()
138                .block
139                .max_bytes
140                .try_into()?,
141            block_max_gas: consensus_params.unwrap().block.max_gas,
142        };
143
144        Ok(TestNode {
145            consensus,
146            height: block::Height::from(0_u8),
147            last_app_hash: app_hash.as_bytes().to_owned(),
148            // TODO: hook this up correctly
149            last_validator_set_hash: Some(
150                tendermint::validator::Set::new(
151                    vec![tendermint::validator::Info {
152                        address: proposer_address.address,
153                        pub_key,
154                        power: Power::try_from(25_000 * 10i64.pow(6))?,
155                        name: Some("test validator".to_string()),
156                        proposer_priority: 1i64.try_into()?,
157                    }],
158                    // Same validator as proposer?
159                    Some(tendermint::validator::Info {
160                        address: proposer_address.address,
161                        pub_key,
162                        power: Power::try_from(25_000 * 10i64.pow(6))?,
163                        name: Some("test validator".to_string()),
164                        proposer_priority: 1i64.try_into()?,
165                    }),
166                )
167                .hash(),
168            ),
169            keyring,
170            on_block,
171            timestamp,
172            ts_callback: ts_callback.unwrap_or(Box::new(default_ts_callback)),
173            chain_id,
174            consensus_params_hash: sha2::Sha256::digest(hashed_params.encode_to_vec()).to_vec(),
175            // No last commit for the genesis block.
176            last_commit: None,
177        })
178    }
179
180    fn init_chain_request(
181        app_state_bytes: Bytes,
182        keyring: &BTreeMap<ed25519_consensus::VerificationKey, ed25519_consensus::SigningKey>,
183        chain_id: tendermint::chain::Id,
184        timestamp: Time,
185    ) -> Result<ConsensusRequest, anyhow::Error> {
186        let consensus_params = Self::consensus_params();
187
188        // TODO: add this to an impl on a keyring
189        let pub_keys = keyring
190            .iter()
191            .map(|(pk, _)| pk)
192            .map(|pk| {
193                tendermint::PublicKey::from_raw_ed25519(pk.as_bytes()).expect("pub key present")
194            })
195            .collect::<Vec<_>>();
196
197        Ok(ConsensusRequest::InitChain(InitChain {
198            time: timestamp,
199            chain_id: chain_id.into(),
200            consensus_params,
201            validators: pub_keys
202                .into_iter()
203                .map(|pub_key| tendermint::validator::Update {
204                    pub_key,
205                    power: 1u64.try_into().unwrap(),
206                })
207                .collect::<Vec<_>>(),
208            app_state_bytes,
209            initial_height: 0_u32.into(),
210        }))
211    }
212
213    fn consensus_params() -> consensus::Params {
214        consensus::Params {
215            block: block::Size {
216                max_bytes: 1,
217                max_gas: 1,
218                time_iota_ms: 1,
219            },
220            evidence: evidence::Params {
221                max_age_num_blocks: 1,
222                max_age_duration: evidence::Duration(time::Duration::from_secs(1)),
223                max_bytes: 1,
224            },
225            validator: ValidatorParams {
226                pub_key_types: vec![],
227            },
228            version: Some(VersionParams { app: 1 }),
229            abci: AbciParams {
230                vote_extensions_enable_height: None,
231            },
232        }
233    }
234}