embedded_td/
tendermint.rs

1use std::{
2    fs::{self, File},
3    io::Write,
4    path::{Path, PathBuf},
5    process::{Child, Command},
6};
7
8use async_abci::ServerXX;
9use rust_embed::RustEmbed;
10use tempfile::tempdir;
11
12use crate::{crypto::Keypair, defined, model, App, Config, Error, Genesis, Result};
13
14#[derive(RustEmbed)]
15#[folder = "$OUT_DIR/build"]
16#[include = "tendermint"]
17pub(crate) struct TendermintEmbed;
18
19/// Tendermint instance
20#[derive(Debug)]
21pub struct Tendermint<A> {
22    #[cfg(not(feature = "__debug_tmp"))]
23    work_dir: tempfile::TempDir,
24
25    #[cfg(feature = "__debug_tmp")]
26    work_dir: PathBuf,
27
28    tendermint_child: Option<Child>,
29
30    app: A,
31}
32
33impl<A> Tendermint<A> {
34    pub fn get_binary_path(&self) -> PathBuf {
35        let path = self.get_work_dir();
36
37        path.join(defined::TENDERMINT_BIN_FILE)
38    }
39
40    pub fn get_config_dir(&self) -> PathBuf {
41        let path = self.get_work_dir();
42
43        path.join(defined::CONFIG_DIR)
44    }
45
46    pub fn get_config_path(&self) -> PathBuf {
47        let path = self.get_work_dir();
48
49        path.join(defined::CONFIG_FILE)
50    }
51
52    pub fn get_node_key_path(&self) -> PathBuf {
53        let path = self.get_work_dir();
54
55        path.join(defined::NODE_KEY_FILE)
56    }
57
58    pub fn get_validator_key_path(&self) -> PathBuf {
59        let path = self.get_work_dir();
60
61        path.join(defined::VALIDATOR_KEY_FILE)
62    }
63
64    pub fn get_socket_dir(&self) -> PathBuf {
65        let path = self.get_work_dir();
66
67        path.join(defined::SOCKET_DIR)
68    }
69
70    pub fn get_p2p_dir(&self) -> PathBuf {
71        let path = self.get_work_dir();
72
73        path.join(defined::P2P_DIR)
74    }
75
76    pub(crate) fn get_work_dir(&self) -> &Path {
77        self.work_dir.as_ref()
78    }
79
80    pub fn get_app_path(&self) -> PathBuf {
81        self.get_work_dir().join(defined::APP_UNIX_SOCKET_FILE)
82    }
83}
84
85impl<A> Tendermint<A> {
86    pub fn new(app: A) -> Result<Self> {
87        #[cfg(feature = "__debug_tmp")]
88        let this = {
89            let work_dir = tempdir()?.into_path();
90            log::info!("Config dir is: {:?}", work_dir.to_str());
91            Self {
92                work_dir,
93                tendermint_child: None,
94                app,
95            }
96        };
97
98        #[cfg(not(feature = "__debug_tmp"))]
99        let this = {
100            let work_dir = tempdir()?;
101
102            Self {
103                work_dir,
104                tendermint_child: None,
105                app,
106            }
107        };
108
109        let ef = TendermintEmbed::get("tendermint").ok_or(Error::NoTendermint)?;
110
111        let bin_path = this.get_binary_path();
112
113        let mut binary_tempfile = File::create(&bin_path)?;
114
115        binary_tempfile.write_all(&ef.data)?;
116
117        #[cfg(unix)]
118        {
119            use std::{
120                fs::{metadata, set_permissions},
121                os::unix::fs::PermissionsExt,
122            };
123
124            let mut permission = metadata(&bin_path)?.permissions();
125            permission.set_mode(0o755);
126            set_permissions(&bin_path, permission)?;
127        }
128
129        fs::create_dir_all(this.get_config_dir())?;
130        fs::create_dir_all(this.get_p2p_dir())?;
131        fs::create_dir_all(this.get_socket_dir())?;
132
133        Ok(this)
134    }
135
136    pub fn version(&self) -> Result<String> {
137        let version = Command::new(self.get_binary_path())
138            .arg("version")
139            .output()?;
140
141        let s = String::from_utf8(version.stdout)?;
142
143        Ok(String::from(s.trim()))
144    }
145
146    /// Start tendermint
147    ///
148    /// Pass ABCI, Config, NodeKey, ValidatorKey, Genesis
149    pub fn start(
150        &mut self,
151        config: Config,
152        node_key: Keypair,
153        validator_key: Keypair,
154        genesis: Genesis<A::AppState>,
155    ) -> Result<()>
156    where
157        A: App + Clone + 'static,
158    {
159        if config.data_dir.is_empty() {
160            fs::create_dir_all(self.get_work_dir().join(defined::DATA_DIR))?;
161        }
162
163        let mut file = File::create(self.get_config_path())?;
164        let cm = config.into_model(self.get_work_dir().to_str().ok_or(Error::PathUtf8Error)?);
165        let cs = toml::to_string_pretty(&cm)?;
166        file.write_all(&cs.into_bytes())?;
167
168        let mut file = File::create(self.get_node_key_path())?;
169        let m = node_key.into_model();
170        let cs = serde_json::to_string_pretty(&m)?;
171        file.write_all(&cs.into_bytes())?;
172
173        let mut file = File::create(self.get_validator_key_path())?;
174        let m = validator_key.into_model();
175        let cs = serde_json::to_string_pretty(&m)?;
176        file.write_all(&cs.into_bytes())?;
177
178        let mut file = File::create(&cm.genesis_file)?;
179        let m = genesis.into_model();
180        let cs = serde_json::to_string_pretty(&m)?;
181        file.write_all(&cs.into_bytes())?;
182
183        let validator_state = model::ValidatorState::default();
184        let mut file = File::create(cm.priv_validator_state_file)?;
185        let m = validator_state.into_model();
186        let cs = serde_json::to_string_pretty(&m)?;
187        file.write_all(&cs.into_bytes())?;
188
189        let app = self.app.clone();
190        let app_path = self.get_app_path();
191
192        let command = Command::new(self.get_binary_path())
193            .arg("--home")
194            .arg(self.get_work_dir())
195            .arg("node")
196            .spawn()?;
197
198        self.tendermint_child = Some(command);
199
200        std::thread::spawn(move || {
201            #[cfg(feature = "async-smol")]
202            smol::block_on(async move {
203                ServerXX::new(app)
204                    .bind_unix(app_path)
205                    .await
206                    .unwrap()
207                    .run()
208                    .await
209                    .unwrap();
210            });
211            #[cfg(feature = "async-tokio")]
212            tokio::block_on(async move {
213                ServerXX::new(app)
214                    .bind_unix(app_path)
215                    .await
216                    .unwrap()
217                    .run()
218                    .await
219                    .unwrap();
220            });
221        });
222
223        Ok(())
224    }
225
226    pub fn stop(&mut self) -> Result<()> {
227        // TODO: Use sigint.
228        let child = self
229            .tendermint_child
230            .as_mut()
231            .ok_or(Error::NoTendermintStart)?;
232
233        child.kill()?;
234
235        Ok(())
236    }
237
238    pub fn kill(&mut self) -> Result<()> {
239        let child = self
240            .tendermint_child
241            .as_mut()
242            .ok_or(Error::NoTendermintStart)?;
243
244        child.kill()?;
245
246        Ok(())
247    }
248
249    pub fn wait(&mut self) -> Result<()> {
250        let child = self
251            .tendermint_child
252            .as_mut()
253            .ok_or(Error::NoTendermintStart)?;
254
255        child.wait()?;
256
257        Ok(())
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use rand::thread_rng;
264    use serde::Serialize;
265
266    use crate::{AlgorithmType, Config, Genesis, Keypair, Tendermint};
267
268    fn init() {
269        let _ = env_logger::builder().is_test(true).try_init();
270    }
271
272    #[test]
273    fn test_version() {
274        let td = Tendermint::<()>::new(()).unwrap();
275        assert_eq!(&td.version().unwrap(), "0.34.21")
276    }
277
278    #[derive(Debug, Serialize)]
279    struct AppState {}
280
281    #[test]
282    fn test_start() {
283        init();
284
285        let rng = thread_rng();
286        let validator_key = Keypair::generate(AlgorithmType::Ed25519, rng.clone());
287
288        let node_key = Keypair::generate(AlgorithmType::Ed25519, rng);
289
290        let genesis = Genesis::<()>::generate(validator_key.public_key.clone());
291
292        let config = Config::default();
293
294        let mut tendermint = Tendermint::<()>::new(()).unwrap();
295
296        tendermint
297            .start(config, node_key, validator_key, genesis)
298            .unwrap();
299
300        tendermint.stop().unwrap();
301    }
302}