1use crate::node::anr::Anr;
4use crate::utils::bls12_381;
5pub use crate::utils::bls12_381::generate_sk as gen_sk;
6use crate::utils::ip_resolver::resolve_public_ipv4;
7use crate::utils::version::Ver;
8use crate::utils::{PublicKey, Signature};
9use serde::{Deserialize, Serialize};
10use std::net::Ipv4Addr;
11use std::path::Path;
12use tokio::fs;
13use tracing::{debug, info, warn};
14
15pub const ENTRY_SIZE: usize = 524288; pub const TX_SIZE: usize = 393216; pub const ATTESTATION_SIZE: usize = 512;
19pub const QUORUM: usize = 3; pub const QUORUM_SINGLE: usize = 1; pub const CLEANUP_PERIOD_MILLIS: u64 = 3000; pub const ANR_PERIOD_MILLIS: u64 = 3000; pub const BROADCAST_PERIOD_MILLIS: u64 = 500; pub const AUTOUPDATE_PERIOD_MILLIS: u64 = 1000; pub const CONSENSUS_PERIOD_MILLIS: u64 = 100; pub const CATCHUP_PERIOD_MILLIS: u64 = 1000; pub const VERSION: Ver = parse_version();
29
30pub const BINCODE_CONFIG: bincode::config::Configuration<
32 bincode::config::BigEndian,
33 bincode::config::Fixint,
34 bincode::config::NoLimit,
35> = bincode::config::standard()
36 .with_fixed_int_encoding() .with_big_endian() .with_no_limit(); const fn parse_version() -> Ver {
41 const S: &str = env!("CRATE_VERSION");
42 let bytes = S.as_bytes();
43 let mut out = [0u8; 3];
44 let mut acc = 0u8;
45 let mut part = 0;
46 let mut i = 0;
47 while i < bytes.len() {
48 let b = bytes[i];
49 if b == b'.' {
50 out[part] = acc;
51 part += 1;
52 acc = 0;
53 } else {
54 acc = acc * 10 + (b - b'0');
55 }
56 i += 1;
57 }
58 out[part] = acc;
59 Ver::from_bytes(out)
60}
61
62pub const SEED_NODES: &[&str] = &["72.9.144.110", "167.235.169.185", "37.27.238.30"];
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct SeedANR {
67 pub ip4: String,
68 pub port: u16,
69 pub version: Ver,
70 pub signature: Vec<u8>,
71 pub pop: Signature,
72 pub ts: u32,
73 pub pk: PublicKey,
74}
75
76#[derive(Debug, thiserror::Error)]
77pub enum Error {
78 #[error(transparent)]
79 Io(#[from] std::io::Error),
80 #[error(transparent)]
81 Parse(#[from] std::net::AddrParseError),
82 #[error(transparent)]
83 B58(#[from] bs58::decode::Error),
84 #[error(transparent)]
85 Bls(#[from] bls12_381::Error),
86 #[error("invalid sk length: {0}, expected 64")]
87 InvalidSkLength(usize),
88 #[error(transparent)]
89 Anr(#[from] crate::node::anr::Error),
90}
91
92#[derive(Clone)]
93pub struct Config {
94 pub work_folder: String,
96
97 pub version: Ver,
99
100 pub offline: bool,
102 pub http_ipv4: Ipv4Addr,
103 pub http_port: u16,
104 pub udp_ipv4: Ipv4Addr,
105 pub udp_port: u16,
106 pub public_ipv4: Option<String>,
107
108 pub seed_ips: Vec<Ipv4Addr>,
110 pub seed_anrs: Vec<SeedANR>,
111 pub other_nodes: Vec<String>,
112 pub trust_factor: f64,
113 pub max_peers: usize,
114
115 pub trainer_sk: [u8; 64],
117 pub trainer_pk: PublicKey,
118 pub trainer_pk_b58: String,
119 pub trainer_pop: Vec<u8>,
120
121 pub archival_node: bool,
123 pub autoupdate: bool,
124 pub computor_type: Option<ComputorType>,
125 pub snapshot_height: u64,
126
127 pub anr: Option<Anr>,
129 pub anr_name: Option<String>,
130 pub anr_desc: Option<String>,
131}
132
133#[derive(Debug, Clone, PartialEq)]
134pub enum ComputorType {
135 Trainer,
136 Default,
137}
138
139impl Config {
140 pub fn get_pk(&self) -> PublicKey {
142 self.trainer_pk
143 }
144
145 pub fn get_sk(&self) -> [u8; 64] {
146 self.trainer_sk
147 }
148
149 pub fn get_pop(&self) -> Vec<u8> {
150 self.trainer_pop.clone()
151 }
152
153 pub fn get_root(&self) -> &str {
155 &self.work_folder
156 }
157
158 pub fn get_ver(&self) -> Ver {
159 self.version
160 }
161
162 pub fn get_ver_3b(&self) -> (u8, u8, u8) {
163 (self.version.major(), self.version.minor(), self.version.patch())
164 }
165
166 pub fn get_public_ipv4(&self) -> Ipv4Addr {
167 self.public_ipv4
168 .as_ref()
169 .and_then(|s| s.parse::<Ipv4Addr>().ok())
170 .unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
171 }
172
173 pub fn get_udp_port(&self) -> u16 {
174 self.udp_port
175 }
176
177 pub async fn from_fs(root: Option<&str>, sk: Option<&str>) -> Result<Self, Error> {
179 let work_folder = std::env::var("WORKFOLDER").unwrap_or_else(|_| {
181 let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
182 format!("{}/.amadeusd-rs", home)
183 });
184
185 let work_folder = root.unwrap_or(&work_folder).to_string();
187 fs::create_dir_all(&work_folder).await?;
188
189 let version = VERSION;
190
191 let offline = std::env::var("OFFLINE").is_ok();
193 let http_ipv4 = std::env::var("HTTP_IPV4")
194 .unwrap_or_else(|_| "0.0.0.0".to_string())
195 .parse()
196 .unwrap_or(Ipv4Addr::new(0, 0, 0, 0));
197 let http_port = std::env::var("HTTP_PORT").unwrap_or_else(|_| "80".to_string()).parse().unwrap_or(80);
198 let udp_ipv4 = std::env::var("UDP_IPV4")
199 .unwrap_or_else(|_| "0.0.0.0".to_string())
200 .parse()
201 .unwrap_or(Ipv4Addr::new(0, 0, 0, 0));
202 let udp_port = 36969;
203
204 let seed_ips = SEED_NODES.iter().map(|s| s.parse()).collect::<Result<Vec<Ipv4Addr>, _>>()?;
206 let other_nodes =
207 std::env::var("OTHERNODES").map(|s| s.split(',').map(String::from).collect()).unwrap_or_else(|_| vec![]);
208 let trust_factor = std::env::var("TRUSTFACTOR").ok().and_then(|s| s.parse::<f64>().ok()).unwrap_or(0.8);
209 let max_peers = std::env::var("MAX_PEERS").unwrap_or_else(|_| "300".to_string()).parse().unwrap_or(300);
210
211 if !verify_time_sync() {
213 warn!("time not synced OR systemd-ntp client not found (can cause sync errors)");
214 }
215
216 let default_sk_path = format!("{}/sk", work_folder);
218 let (trainer_sk, trainer_pk, trainer_pk_b58) = if let Some(path) = sk {
219 let sk = read_sk(path).await?;
220 let pk = get_pk(&sk);
221 (sk, pk, bs58::encode(pk).into_string())
222 } else if let Ok(sk) = read_sk(&default_sk_path).await {
223 let pk = get_pk(&sk);
224 (sk, pk, bs58::encode(pk).into_string())
225 } else {
226 debug!("no sk (BLS12-381) found, generating new trainer keys");
227 let sk = gen_sk();
228 let pk = get_pk(&sk);
229 let pk_b58 = bs58::encode(pk).into_string();
230 info!("generated random sk at {default_sk_path}, pk {pk_b58}");
231 write_sk(&default_sk_path, sk).await?;
232 (sk, pk, pk_b58)
233 };
234
235 let trainer_pop = bls12_381::sign(&trainer_sk, trainer_pk.as_ref(), crate::consensus::DST_POP)
237 .map(|sig| sig.to_vec())
238 .unwrap_or_else(|_| vec![0u8; 96]);
239
240 let archival_node = matches!(std::env::var("ARCHIVALNODE").as_deref(), Ok("true") | Ok("y") | Ok("yes"));
242 let autoupdate = matches!(std::env::var("AUTOUPDATE").as_deref(), Ok("true") | Ok("y") | Ok("yes"));
243 let computor_type = match std::env::var("COMPUTOR").as_deref() {
244 Ok("trainer") => Some(ComputorType::Trainer),
245 Ok(_) => Some(ComputorType::Default),
246 Err(_) => None,
247 };
248 let snapshot_height =
249 std::env::var("SNAPSHOT_HEIGHT").unwrap_or_else(|_| "24875547".to_string()).parse().unwrap_or(24875547);
250
251 let my_ip = match std::env::var("PUBLIC_UDP_IPV4").ok().and_then(|i| i.parse::<Ipv4Addr>().ok()) {
253 Some(ip) => Some(ip),
254 None => resolve_public_ipv4().await, };
256
257 let ver = version;
258 let public_ipv4 = my_ip.map(|ip| ip.to_string());
259
260 let anr_name = std::env::var("ANR_NAME").ok();
262 let anr_desc = std::env::var("ANR_DESC").ok();
263
264 let anr = my_ip.and_then(|ip| {
265 Anr::build_with_name_desc(
266 &trainer_sk,
267 &trainer_pk,
268 &trainer_pop,
269 ip,
270 ver,
271 anr_name.clone(),
272 anr_desc.clone(),
273 )
274 .ok()
275 });
276
277 let seed_anrs = vec![
278 SeedANR {
279 ip4: "72.9.144.110".into(),
280 port: 36969,
281 version: "1.2.5".try_into().unwrap(),
282 signature: vec![
283 132, 66, 55, 44, 126, 255, 89, 218, 33, 243, 214, 44, 103, 181, 166, 16, 54, 195, 44, 159, 66, 87,
284 162, 103, 240, 217, 235, 201, 120, 234, 207, 126, 31, 24, 228, 115, 202, 68, 85, 167, 97, 101, 110,
285 201, 220, 161, 239, 95, 15, 174, 251, 185, 62, 16, 69, 231, 99, 94, 189, 94, 210, 122, 150, 220,
286 226, 218, 207, 108, 242, 168, 189, 252, 146, 129, 113, 239, 53, 211, 69, 92, 115, 218, 40, 15, 104,
287 220, 235, 59, 235, 8, 177, 105, 120, 225, 12, 87,
288 ],
289 pop: Signature::from([
290 182, 42, 150, 214, 42, 240, 210, 215, 0, 106, 181, 96, 198, 75, 222, 86, 45, 241, 58, 230, 66, 56,
291 10, 49, 217, 53, 39, 100, 18, 197, 159, 153, 68, 220, 234, 164, 6, 9, 3, 228, 234, 209, 151, 233,
292 122, 209, 101, 73, 16, 190, 135, 172, 85, 106, 80, 99, 225, 214, 141, 245, 66, 170, 177, 163, 247,
293 93, 243, 234, 184, 145, 167, 202, 181, 114, 186, 113, 112, 113, 108, 84, 135, 24, 62, 242, 142,
294 248, 159, 124, 117, 85, 190, 43, 177, 212, 18, 24,
295 ]),
296 ts: 1763051924,
297 pk: PublicKey::from([
298 169, 232, 30, 216, 200, 234, 174, 189, 141, 213, 58, 136, 157, 140, 90, 134, 18, 171, 115, 48, 39,
299 90, 93, 57, 4, 62, 149, 32, 14, 124, 27, 102, 240, 220, 0, 197, 48, 126, 134, 122, 85, 169, 173,
300 158, 122, 228, 185, 240,
301 ]),
302 },
303 SeedANR {
304 ip4: "167.235.169.185".into(),
305 port: 36969,
306 version: "1.2.5".try_into().unwrap(),
307 signature: vec![
308 128, 163, 49, 163, 123, 191, 81, 52, 48, 69, 65, 212, 233, 85, 88, 167, 138, 156, 204, 105, 91,
309 231, 234, 219, 129, 161, 5, 226, 136, 179, 43, 192, 136, 224, 78, 114, 134, 157, 142, 36, 236, 10,
310 83, 4, 102, 209, 48, 133, 0, 116, 212, 250, 22, 218, 156, 26, 50, 86, 197, 56, 110, 252, 252, 108,
311 202, 58, 218, 181, 30, 213, 227, 46, 175, 227, 45, 174, 162, 33, 160, 113, 165, 114, 115, 232, 207,
312 65, 111, 11, 213, 110, 195, 70, 197, 199, 176, 241,
313 ],
314 pop: Signature::from([
315 164, 246, 242, 104, 121, 244, 113, 37, 125, 250, 253, 128, 164, 91, 172, 179, 127, 196, 28, 189,
316 170, 239, 154, 195, 216, 36, 42, 27, 69, 132, 140, 126, 88, 213, 175, 124, 0, 109, 83, 10, 90, 12,
317 56, 188, 226, 219, 163, 219, 6, 28, 138, 155, 129, 47, 145, 171, 20, 95, 100, 57, 188, 175, 139,
318 53, 129, 60, 97, 54, 239, 1, 154, 113, 121, 134, 234, 154, 63, 48, 187, 88, 153, 84, 159, 97, 129,
319 241, 120, 192, 107, 55, 45, 250, 208, 196, 44, 141,
320 ]),
321 ts: 1763065634,
322 pk: PublicKey::from([
323 176, 120, 75, 148, 69, 45, 127, 232, 195, 254, 164, 173, 136, 138, 157, 97, 246, 81, 114, 66, 48,
324 199, 162, 230, 152, 219, 207, 224, 18, 246, 194, 150, 228, 105, 126, 41, 68, 36, 246, 51, 135, 216,
325 105, 117, 245, 98, 93, 140,
326 ]),
327 },
328 SeedANR {
329 ip4: "37.27.238.30".into(),
330 port: 36969,
331 version: "1.2.5".try_into().unwrap(),
332 signature: vec![
333 142, 128, 166, 98, 166, 146, 49, 71, 166, 160, 208, 182, 120, 22, 114, 79, 95, 23, 30, 33, 120, 68,
334 246, 221, 77, 117, 120, 68, 108, 2, 103, 47, 105, 210, 24, 12, 30, 153, 249, 57, 20, 188, 253, 203,
335 90, 37, 214, 84, 9, 155, 80, 94, 127, 0, 67, 16, 11, 17, 24, 86, 230, 23, 63, 13, 39, 139, 63, 123,
336 5, 83, 154, 38, 183, 44, 1, 66, 112, 7, 53, 155, 129, 58, 239, 68, 155, 115, 239, 241, 255, 65, 56,
337 0, 110, 110, 137, 27,
338 ],
339 pop: Signature::from([
340 181, 170, 103, 46, 138, 14, 103, 212, 122, 113, 28, 78, 228, 170, 19, 202, 108, 149, 207, 132, 49,
341 208, 221, 32, 121, 229, 5, 95, 72, 120, 73, 47, 230, 70, 155, 89, 98, 194, 196, 206, 230, 63, 49,
342 205, 160, 135, 90, 246, 7, 93, 79, 237, 127, 111, 189, 136, 122, 225, 135, 54, 45, 95, 36, 186,
343 211, 155, 67, 24, 86, 151, 91, 190, 20, 72, 145, 77, 138, 55, 22, 15, 100, 61, 140, 137, 93, 55,
344 19, 5, 226, 106, 30, 134, 74, 55, 193, 163,
345 ]),
346 ts: 1763066818,
347 pk: PublicKey::from([
348 149, 216, 55, 255, 29, 8, 239, 251, 139, 112, 30, 29, 199, 57, 90, 67, 198, 220, 101, 18, 228, 100,
349 100, 241, 43, 213, 221, 230, 253, 58, 231, 1, 102, 166, 54, 66, 245, 148, 140, 44, 78, 56, 84, 12,
350 222, 205, 57, 210,
351 ]),
352 },
353 ];
354
355 Ok(Self {
356 work_folder,
357 version,
358 offline,
359 http_ipv4,
360 http_port,
361 udp_ipv4,
362 udp_port,
363 public_ipv4,
364 seed_ips,
365 seed_anrs,
366 other_nodes,
367 trust_factor,
368 max_peers,
369 trainer_sk,
370 trainer_pk,
371 trainer_pk_b58,
372 trainer_pop,
373 archival_node,
374 autoupdate,
375 computor_type,
376 snapshot_height,
377 anr,
378 anr_name,
379 anr_desc,
380 })
381 }
382
383 pub async fn ensure_public_ip(&mut self) {
385 if self.public_ipv4.is_none() {
386 self.public_ipv4 = crate::utils::ip_resolver::resolve_public_ipv4_string().await;
387 }
388 }
389
390 pub fn new_daemonless(sk: [u8; 64]) -> Self {
391 let pk = get_pk(&sk);
392 let pk_b58 = bs58::encode(pk).into_string();
393 let pop = bls12_381::sign(&sk, pk.as_ref(), crate::consensus::DST_POP)
394 .map(|sig| sig.to_vec())
395 .unwrap_or_else(|_| vec![0u8; 96]);
396 let seed_ips = SEED_NODES.iter().map(|s| s.parse()).collect::<Result<Vec<Ipv4Addr>, _>>().unwrap_or_default();
397
398 Self {
399 work_folder: ".amadeusd-rs".to_string(),
400 version: VERSION,
401 offline: false,
402 http_ipv4: Ipv4Addr::new(0, 0, 0, 0),
403 http_port: 80,
404 udp_ipv4: Ipv4Addr::new(0, 0, 0, 0),
405 udp_port: 36969,
406 public_ipv4: None,
407 seed_ips,
408 seed_anrs: vec![],
409 other_nodes: vec![],
410 trust_factor: 0.8,
411 max_peers: 300,
412 trainer_sk: sk,
413 trainer_pk: pk,
414 trainer_pk_b58: pk_b58,
415 trainer_pop: pop,
416 archival_node: false,
417 autoupdate: false,
418 computor_type: None,
419 snapshot_height: 24875547,
420 anr: None,
421 anr_name: None,
422 anr_desc: None,
423 }
424 }
425}
426
427pub fn get_pk(sk: &[u8; 64]) -> PublicKey {
428 bls12_381::get_public_key(sk).expect("key generation should not fail with proper key material")
429}
430
431pub async fn write_sk(path: impl AsRef<Path>, sk: [u8; 64]) -> Result<(), Error> {
432 let sk_b58 = bs58::encode(sk).into_string();
433 fs::write(path, sk_b58).await.map_err(Into::into)
434}
435
436pub async fn read_sk(path: impl AsRef<Path>) -> Result<[u8; 64], Error> {
437 let sk_bs58 = fs::read_to_string(path).await?;
438 let sk_vec = bs58::decode(sk_bs58.trim()).into_vec()?;
439 sk_vec.try_into().map_err(|v: Vec<u8>| Error::InvalidSkLength(v.len()))
440}
441
442fn verify_time_sync() -> bool {
444 use std::process::Command;
445
446 match Command::new("timedatectl").arg("status").output() {
448 Ok(output) => {
449 let stdout = String::from_utf8_lossy(&output.stdout);
450 stdout.contains("System clock synchronized: yes") || stdout.contains("NTP synchronized: yes")
452 }
453 Err(_) => {
454 true
456 }
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use std::any::type_name_of_val;
464 use std::path::PathBuf;
465 use std::time::{SystemTime, UNIX_EPOCH};
466
467 struct TmpTestDir {
469 path: PathBuf,
470 }
471
472 impl TmpTestDir {
473 fn for_test<F: ?Sized>(f: &F) -> std::io::Result<Self> {
476 let fq = type_name_of_val(f);
477 let secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
478 let dir_name = format!("{}{}", fq, secs);
479 let path = std::path::Path::new("/tmp").join(dir_name);
480 std::fs::create_dir_all(&path)?;
481 Ok(Self { path })
482 }
483
484 fn to_str(&self) -> &str {
486 self.path.to_str().unwrap_or("")
487 }
488 }
489
490 impl Drop for TmpTestDir {
491 fn drop(&mut self) {
492 let _ = std::fs::remove_dir_all(&self.path);
494 }
495 }
496
497 #[tokio::test]
498 async fn test_config_from_env() {
499 let tmp = TmpTestDir::for_test(&test_config_from_env).unwrap();
501 unsafe {
503 std::env::set_var("WORKFOLDER", tmp.to_str());
504 std::env::set_var("HTTP_PORT", "8080");
505 std::env::set_var("OTHERNODES", "192.168.1.1,192.168.1.2");
506 std::env::set_var("TRUSTFACTOR", "0.9");
507 std::env::set_var("MAX_PEERS", "500");
508 std::env::set_var("ARCHIVALNODE", "true");
509 std::env::set_var("AUTOUPDATE", "yes");
510 std::env::set_var("COMPUTOR", "trainer");
511 std::env::set_var("SNAPSHOT_HEIGHT", "12345678");
512 std::env::set_var("ANR_NAME", "TestNode");
513 std::env::set_var("ANR_DESC", "Test Description");
514 }
515
516 let config = Config::from_fs(Some(tmp.to_str()), None).await.unwrap();
517
518 assert_eq!(config.work_folder, tmp.to_str());
520
521 assert_eq!(config.http_port, 8080);
523 assert_eq!(config.other_nodes, vec!["192.168.1.1", "192.168.1.2"]);
524 assert_eq!(config.trust_factor, 0.9);
525 assert_eq!(config.max_peers, 500);
526
527 assert_eq!(config.trainer_sk.len(), 64);
529 assert_eq!(config.trainer_pk.len(), 48);
530 assert!(!config.trainer_pk_b58.is_empty());
531 assert_eq!(config.trainer_pop.len(), 96);
532
533 assert!(config.archival_node);
535 assert!(config.autoupdate);
536 assert_eq!(config.computor_type, Some(ComputorType::Trainer));
537 assert_eq!(config.snapshot_height, 12345678);
538
539 assert_eq!(config.anr_name, Some("TestNode".to_string()));
541 assert_eq!(config.anr_desc, Some("Test Description".to_string()));
542 }
543
544 #[tokio::test]
545 async fn test_config_from_sk() {
546 let sk = [42u8; 64];
547 let config = Config::new_daemonless(sk);
548
549 assert_eq!(config.trainer_sk, sk);
551
552 assert_eq!(config.trainer_pk.len(), 48);
554 assert!(!config.trainer_pk_b58.is_empty());
555 assert_eq!(config.trainer_pop.len(), 96);
556 }
557
558 #[tokio::test]
559 async fn test_config_env_parsing() {
560 let tmp = TmpTestDir::for_test(&test_config_env_parsing).unwrap();
562 unsafe {
564 std::env::set_var("COMPUTOR", "trainer");
565 }
566 let config = Config::from_fs(Some(tmp.to_str()), None).await.unwrap();
567 assert_eq!(config.computor_type, Some(ComputorType::Trainer));
568
569 unsafe {
570 std::env::set_var("COMPUTOR", "default");
571 }
572 let config = Config::from_fs(Some(tmp.to_str()), None).await.unwrap();
573 assert_eq!(config.computor_type, Some(ComputorType::Default));
574 }
575
576 #[tokio::test]
577 async fn test_config_version_methods() {
578 let tmp = TmpTestDir::for_test(&test_config_version_methods).unwrap();
580 let config = Config::from_fs(Some(tmp.to_str()), None).await.unwrap();
581
582 let version_str = config.get_ver().to_string();
584 let version_3b = config.get_ver_3b();
585
586 let parts: Vec<&str> = version_str.split('.').collect();
588 assert_eq!(parts.len(), 3, "Version string should have 3 parts");
589
590 let expected_major = parts[0].parse::<u8>().unwrap();
591 let expected_minor = parts[1].parse::<u8>().unwrap();
592 let expected_patch = parts[2].parse::<u8>().unwrap();
593
594 assert_eq!(version_3b.0, expected_major);
595 assert_eq!(version_3b.1, expected_minor);
596 assert_eq!(version_3b.2, expected_patch);
597
598 assert_eq!(version_3b, (config.version.major(), config.version.minor(), config.version.patch()));
600 }
601
602 #[test]
603 fn create_keypair() {
604 let sk = gen_sk();
605 let pk = get_pk(&sk);
606 let pk_b58 = bs58::encode(pk).into_string();
607 let sk_b58 = bs58::encode(sk).into_string();
608 println!("generated random sk at {sk_b58}, pk {pk_b58}");
609 }
610}