embedded_td/
tendermint.rs1use 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#[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 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 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}