hoprd_localcluster/
identity.rs1use std::path::PathBuf;
2
3use anyhow::Context;
4use hopr_chain_connector::{
5 BlockchainConnectorConfig,
6 api::*,
7 blokli_client::{BlokliClient, BlokliClientConfig, BlokliQueryClient},
8 create_trustful_safeless_hopr_blokli_connector,
9 reexports::hopr_chain_types::exports::alloy::hex,
10};
11use hopr_lib::{ChainKeypair, HoprKeys, Keypair, SafeModule, XDaiBalance, crypto_traits::Randomizable};
12use hoprd::config::{Db, HoprdConfig, Identity, SessionIpForwardingConfig, UserHoprLibConfig, UserHoprNetworkConfig};
13use hoprd_api::config::{Api, Auth};
14
15pub const DEFAULT_BLOKLI_URL: &str = "http://localhost:8080";
16pub const DEFAULT_PRIVATE_KEY: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
17pub const DEFAULT_CONFIG_HOME: &str = "/tmp/hopr-nodes";
18pub const DEFAULT_IDENTITY_PASSWORD: &str = "password";
19pub const DEFAULT_NUM_NODES: usize = 3;
20
21#[derive(Clone, Debug)]
22pub struct GenerationConfig {
23 pub blokli_url: String,
24 pub private_key: String,
25 pub num_nodes: usize,
26 pub config_home: PathBuf,
27 pub identity_password: String,
28 pub random_identities: bool,
29}
30
31impl Default for GenerationConfig {
32 fn default() -> Self {
33 Self {
34 blokli_url: DEFAULT_BLOKLI_URL.to_string(),
35 private_key: DEFAULT_PRIVATE_KEY.to_string(),
36 num_nodes: DEFAULT_NUM_NODES,
37 config_home: PathBuf::from(DEFAULT_CONFIG_HOME),
38 identity_password: DEFAULT_IDENTITY_PASSWORD.to_string(),
39 random_identities: false,
40 }
41 }
42}
43
44lazy_static::lazy_static! {
45 static ref NODE_KEYS: [HoprKeys; 5] = [
46 (
47 hex!("76a4edbc3f595d4d07671779a0055e30b2b8477ecfd5d23c37afd7b5aa83781d"),
48 hex!("71bf1f42ebbfcd89c3e197a3fd7cda79b92499e509b6fefa0fe44d02821d146a")
49 ).try_into().unwrap(),
50 (
51 hex!("c90f09e849aa512be3dd007452977e32c7cfdc1e3de1a62bd92ba6592bcc9e90"),
52 hex!("c3659450e994f3ad086373440e4e7070629a1bfbd555387237ccb28d17acbfc8")
53 ).try_into().unwrap(),
54 (
55 hex!("40d4749a620d1a4278d030a3153b5b94d6fcd4f9677f6ce8e37e6ebb1987ad53"),
56 hex!("4a14c5aeb53629a2dd45058a8d233f24dd90192189e8200a1e5f10069868f963")
57 ).try_into().unwrap(),
58 (
59 hex!("e539f1ac48270be4e84b6acfe35252df5e141a29b50ddb07b50670271bb574ee"),
60 hex!("8c1edcdebfe508031e4124168bb4a133180e8ee68207a7946fcdc4ad0068ef0d")
61 ).try_into().unwrap(),
62 (
63 hex!("9ab557eb14d8b081c7e1750eb87407d8c421aa79bdeb420f38980829e7dbf936"),
64 hex!("6075c595103667537c33cdb954e3e5189921cab942e5fc0ba9ec27fe6d7787d1")
65 ).try_into().unwrap()
66 ];
67}
68
69pub async fn generate(config: &GenerationConfig) -> anyhow::Result<()> {
73 std::fs::create_dir_all(&config.config_home)?;
74 let home_path = &config.config_home;
75 let private_key = hex::decode(&config.private_key).context("invalid private key")?;
76
77 let blokli_client = BlokliClient::new(config.blokli_url.parse()?, BlokliClientConfig::default());
78 let status = blokli_client.query_health().await?;
79 if !status.eq_ignore_ascii_case("ok") {
80 return Err(anyhow::anyhow!("Blokli is not usable: {status}"));
81 }
82
83 let mut anvil_connector = create_trustful_safeless_hopr_blokli_connector(
85 &ChainKeypair::from_secret(&private_key)?,
86 Default::default(),
87 blokli_client.clone(),
88 )
89 .await?;
90 anvil_connector.connect().await?;
91
92 let initial_token_balance: HoprBalance = "1000 wxHOPR".parse()?;
93 let initial_native_balance: XDaiBalance = "1 xDai".parse()?;
94
95 for id in 0..config.num_nodes.clamp(1, NODE_KEYS.len()) {
96 let kp = if config.random_identities {
97 HoprKeys::random()
98 } else {
99 NODE_KEYS[id].clone()
100 };
101 let node_address = kp.chain_key.public().to_address();
102 eprintln!("Node {id}: Address {node_address}");
103
104 let node_connector = std::sync::Arc::new(
105 create_trustful_safeless_hopr_blokli_connector(
106 &kp.chain_key,
107 BlockchainConnectorConfig::default(),
108 blokli_client.clone(),
109 )
110 .await?,
111 );
112
113 eprint!("Node {id}: Checking balances...");
114
115 let node_native_balance: XDaiBalance = node_connector.balance(node_address).await?;
117 if node_native_balance < initial_native_balance {
118 let top_up = initial_native_balance - node_native_balance;
119 if anvil_connector.balance(*anvil_connector.me()).await? < top_up {
120 return Err(anyhow::anyhow!(
121 "Account {} must have at least {top_up}.",
122 anvil_connector.me()
123 ));
124 }
125
126 anvil_connector.withdraw(top_up, &node_address).await?.await?;
127 eprint!("\x1b[2K\rNode {id}: {top_up} transferred to {node_address}");
128 } else {
129 eprint!("\x1b[2K\rNode {id}: {node_address} already has {node_native_balance} xDai tokens");
130 }
131
132 eprint!("\x1b[2K\rNode {id}: Checking Safe deployment...");
133 let safe = if let Some(safe) = node_connector.safe_info(SafeSelector::Owner(node_address)).await? {
134 safe
135 } else {
136 eprint!("\x1b[2K\rNode {id}: Topping up to {initial_token_balance}...");
138 let node_token_balance: HoprBalance = node_connector.balance(node_address).await?;
139 if node_token_balance < initial_token_balance {
140 let top_up = initial_token_balance - node_token_balance;
141 if anvil_connector.balance(*anvil_connector.me()).await? < top_up {
142 return Err(anyhow::anyhow!(
143 "Account {} must have at least {top_up}.",
144 anvil_connector.me()
145 ));
146 }
147
148 anvil_connector.withdraw(top_up, &node_address).await?.await?;
149 eprint!("\x1b[2K\rNode {id}: {top_up} transferred to {node_address}");
150 } else {
151 eprint!("\x1b[2K\rNode {id}: {node_address} already has {node_token_balance} wxHOPR tokens");
152 }
153
154 eprint!("\x1b[2K\rNode {id}: Deploying Safe...");
155 let node_connector_clone = node_connector.clone();
156 let jh = tokio::task::spawn(async move {
157 node_connector_clone
158 .await_safe_deployment(SafeSelector::Owner(node_address), std::time::Duration::from_secs(10))
159 .await
160 });
161 node_connector.deploy_safe(initial_token_balance).await?.await?;
162 jh.await??
163 };
164
165 let id_file = home_path
166 .join(format!("node_id_{id}.id"))
167 .to_str()
168 .ok_or(anyhow::anyhow!("Invalid path"))?
169 .to_owned();
170
171 let node_cfg = HoprdConfig {
172 hopr: UserHoprLibConfig {
173 announce: true,
174 network: UserHoprNetworkConfig {
175 announce_local_addresses: true,
176 prefer_local_addresses: true,
177 ..Default::default()
178 },
179 safe_module: SafeModule {
180 safe_address: safe.address,
181 module_address: safe.module,
182 },
183 ..Default::default()
184 },
185 identity: Identity {
186 file: id_file.clone(),
187 password: config.identity_password.clone(),
188 private_key: None,
189 },
190 db: Db {
191 data: home_path
192 .join(format!("db_{id}"))
193 .to_str()
194 .ok_or(anyhow::anyhow!("Invalid path"))?
195 .to_owned(),
196 initialize: true,
197 force_initialize: true,
198 },
199 api: Api {
200 enable: true,
201 auth: Auth::None,
202 ..Default::default()
203 },
204 blokli_url: Some(config.blokli_url.clone()),
205 session_ip_forwarding: SessionIpForwardingConfig {
206 use_target_allow_list: false,
207 ..Default::default()
208 },
209 ..Default::default()
210 };
211
212 let cfg_file = home_path
213 .join(format!("hoprd_cfg_{id}.yaml"))
214 .to_str()
215 .ok_or(anyhow::anyhow!("Invalid path"))?
216 .to_owned();
217 std::fs::write(&cfg_file, serde_saphyr::to_string(&node_cfg)?)?;
218 kp.write_eth_keystore(&id_file, &config.identity_password)?;
219
220 eprintln!("\x1b[2K\rNode {id}: Node config written to {cfg_file}");
221 }
222
223 Ok(())
224}