hopli/network_registry.rs
1//! This module contains arguments and functions to interact with the Network Registry contract for a privileged account.
2//! To participate in the HOPR network, a node must be included in the network registry contract.
3//! Nodes and the staking account (Safe) that manages them should be registered as a pair in the Network registry contrat.
4//! Nodes and safes can be registered by either a manager or by the staking account itself.
5//!
6//! Note the currently only manager wallet can register node-safe pairs. Node runners cannot self-register their nodes.
7//!
8//! A manager (i.e. an account with `MANAGER_ROLE` role), can perform the following actions with `hopli network-registry`,
9//! by specifying the subcommand:
10//! A manager account can register nodes and safes with `manager-regsiter`
11//! A manager account can deregister nodes with `manager-deregsiter`
12//! A manager account can set eligibility of staking accounts with `manager-force-sync`
13//!
14//! Some sample commands:
15//! - Manager registers nodes:
16//! ```text
17//! hopli network-registry manager-register \
18//! --network anvil-localhost \
19//! --contracts-root "../ethereum/contracts" \
20//! --identity-directory "./test" --password-path "./test/pwd" \
21//! --node-address 0x9e820e68f8c024779ebcb6cd2edda1885e1dbe1f,0xb3724772badf4d8fffa186a5ca0bea87693a6c2a \
22//! --safe-address 0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1,0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1,0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1,0xd057604a14982fe8d88c5fc25aac3267ea142a08,0xd057604a14982fe8d88c5fc25aac3267ea142a08 \
23//! --private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
24//! --provider-url "http://localhost:8545"
25//! ```
26//!
27//! - Manager deregisters nodes:
28//! ```text
29//! hopli network-registry manager-deregister \
30//! --network anvil-localhost \
31//! --contracts-root "../ethereum/contracts" \
32//! --node-address 0x9e820e68f8c024779ebcb6cd2edda1885e1dbe1f,0xb3724772badf4d8fffa186a5ca0bea87693a6c2a \
33//! --private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
34//! --provider-url "http://localhost:8545"
35//! ````
36//!
37//! - Manager syncs the eligibility of safes
38//! ```text
39//! hopli network-registry manager-force-sync \
40//! --network anvil-localhost \
41//! --contracts-root "../ethereum/contracts" \
42//! --safe-address 0x9e820e68f8c024779ebcb6cd2edda1885e1dbe1f,0xb3724772badf4d8fffa186a5ca0bea87693a6c2a \
43//! --eligibility true \
44//! --private-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
45//! --provider-url "http://localhost:8545"
46//! ```
47use crate::key_pair::ArgEnvReader;
48use crate::{
49 environment_config::NetworkProviderArgs,
50 key_pair::{IdentityFileArgs, PrivateKeyArgs},
51 methods::{
52 deregister_nodes_from_network_registry, force_sync_safes_on_network_registry,
53 register_safes_and_nodes_on_network_registry,
54 },
55 utils::{Cmd, HelperErrors},
56};
57use clap::Parser;
58use ethers::types::H160;
59use hopr_bindings::hopr_network_registry::HoprNetworkRegistry;
60use std::str::FromStr;
61use tracing::info;
62
63/// CLI arguments for `hopli network-registry`
64#[derive(Clone, Debug, Parser)]
65pub enum NetworkRegistrySubcommands {
66 // Register nodes and safes with a manager account
67 #[command(visible_alias = "mr")]
68 ManagerRegister {
69 /// Network name, contracts config file root, and customized provider, if available
70 #[command(flatten)]
71 network_provider: NetworkProviderArgs,
72
73 /// node addresses
74 #[clap(
75 help = "Comma separated node Ethereum addresses",
76 long,
77 short = 'o',
78 default_value = None
79 )]
80 node_address: Option<String>,
81
82 /// Addresses of the safe proxy instances
83 #[clap(
84 help = "Comma separated Safe Ethereum addresses",
85 long,
86 short,
87 default_value = None
88 )]
89 safe_address: Option<String>,
90
91 /// Arguments to locate identity file(s) of HOPR node(s)
92 #[command(flatten)]
93 local_identity: IdentityFileArgs,
94
95 /// Access to the private key of a manager of Network Registry contract
96 #[command(flatten)]
97 private_key: PrivateKeyArgs,
98 },
99
100 /// Remove nodes and safes with a manager account
101 #[command(visible_alias = "md")]
102 ManagerDeregister {
103 /// Network name, contracts config file root, and customized provider, if available
104 #[command(flatten)]
105 network_provider: NetworkProviderArgs,
106
107 /// node addresses
108 #[clap(
109 help = "Comma separated node Ethereum addresses",
110 long,
111 short = 'o',
112 default_value = None
113 )]
114 node_address: Option<String>,
115
116 /// Arguments to locate identity file(s) of HOPR node(s)
117 #[command(flatten)]
118 local_identity: IdentityFileArgs,
119
120 /// Access to the private key of a manager of Network Registry contract
121 #[command(flatten)]
122 private_key: PrivateKeyArgs,
123 },
124
125 /// Force sync the eligibility of safe accounts
126 #[command(visible_alias = "ms")]
127 ManagerForceSync {
128 /// Network name, contracts config file root, and customized provider, if available
129 #[command(flatten)]
130 network_provider: NetworkProviderArgs,
131
132 /// Addresses of the safe proxy instances
133 #[clap(
134 help = "Comma separated Safe Ethereum addresses",
135 long,
136 short,
137 default_value = None
138 )]
139 safe_address: Option<String>,
140
141 /// Access to the private key of a manager of Network Registry contract
142 #[command(flatten)]
143 private_key: PrivateKeyArgs,
144
145 /// Eligibility of safes when calling `hopli network-registry -a manager-force-sync`
146 #[clap(
147 help = "Desired eligibility of safes",
148 long,
149 short,
150 default_value = None
151 )]
152 eligibility: Option<bool>,
153 },
154}
155
156impl NetworkRegistrySubcommands {
157 /// Execute command to register a node and its staking account (safe) with manager privilege and make the safe eligible.
158 ///
159 /// Manager wallet registers nodes with associated staking accounts
160 pub async fn execute_manager_register(
161 network_provider: NetworkProviderArgs,
162 local_identity: IdentityFileArgs,
163 node_address: Option<String>,
164 safe_address: Option<String>,
165 private_key: PrivateKeyArgs,
166 ) -> Result<(), HelperErrors> {
167 // read all the node addresses
168 let mut node_eth_addresses: Vec<H160> = Vec::new();
169 if let Some(addresses) = node_address {
170 node_eth_addresses.extend(
171 addresses
172 .split(',')
173 .map(|addr| {
174 H160::from_str(addr)
175 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
176 })
177 .collect::<Result<Vec<_>, _>>()?,
178 );
179 }
180 // if local identity dirs/path is provided, read addresses from identity files
181 node_eth_addresses.extend(
182 local_identity
183 .to_addresses()
184 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
185 .into_iter()
186 .map(H160::from),
187 );
188
189 // read all the safe addresses
190 let mut safe_eth_addresses: Vec<H160> = Vec::new();
191 if let Some(addresses) = safe_address {
192 safe_eth_addresses.extend(addresses.split(',').map(|addr| H160::from_str(addr).unwrap()));
193 }
194
195 // Read the private key from arguments or the "MANAGER_PRIVATE_KEY" environment variable
196 let signer_private_key = private_key.read("MANAGER_PRIVATE_KEY")?;
197
198 // get RPC provider for the given network and environment
199 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
200 let contract_addresses = network_provider.get_network_details_from_name()?;
201
202 let hopr_network_registry =
203 HoprNetworkRegistry::new(contract_addresses.addresses.network_registry, rpc_provider.clone());
204
205 // get registered safe of all the nodes
206 // check if any of the node has been registered to a different address than the given safe address.
207 // if the node has been registered to the given safe address, skip any action on it
208 // if the node has not been registered to any safe address, register it.
209 // if the node has been registered to a different safe address, remove the old safe and register the new one
210 let (removed_pairs_num, added_pairs_num) =
211 register_safes_and_nodes_on_network_registry(hopr_network_registry, safe_eth_addresses, node_eth_addresses)
212 .await?;
213 info!(
214 "{:?} pairs have been removed and {:?} pairs have been added to the network registry.",
215 removed_pairs_num, added_pairs_num
216 );
217 Ok(())
218 }
219
220 /// Execute command to deregister a node and its staking account with manager privilege
221 ///
222 /// This action does not need to provide safe_address
223 /// Manager wallet deregisters nodes from associated staking accounts
224 pub async fn execute_manager_deregister(
225 network_provider: NetworkProviderArgs,
226 local_identity: IdentityFileArgs,
227 node_address: Option<String>,
228 private_key: PrivateKeyArgs,
229 ) -> Result<(), HelperErrors> {
230 // read all the node addresses
231 let mut node_eth_addresses: Vec<H160> = Vec::new();
232 if let Some(addresses) = node_address {
233 node_eth_addresses.extend(
234 addresses
235 .split(',')
236 .map(|addr| {
237 H160::from_str(addr)
238 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))
239 })
240 .collect::<Result<Vec<_>, _>>()?,
241 );
242 }
243 // if local identity dirs/path is provided, read addresses from identity files
244 node_eth_addresses.extend(
245 local_identity
246 .to_addresses()
247 .map_err(|e| HelperErrors::InvalidAddress(format!("Invalid node address: {:?}", e)))?
248 .into_iter()
249 .map(H160::from),
250 );
251 info!(
252 "Will deregister {:?} nodes from the network registry",
253 node_eth_addresses.len()
254 );
255
256 // read private key
257 let signer_private_key = private_key.read("MANAGER_PRIVATE_KEY")?;
258
259 // get RPC provider for the given network and environment
260 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
261 let contract_addresses = network_provider.get_network_details_from_name()?;
262
263 let hopr_network_registry =
264 HoprNetworkRegistry::new(contract_addresses.addresses.network_registry, rpc_provider.clone());
265
266 // deregister all the given nodes from the network registry
267 let removed_pairs_num =
268 deregister_nodes_from_network_registry(hopr_network_registry, node_eth_addresses).await?;
269 info!(
270 "{:?} pairs have been removed from the network registry.",
271 removed_pairs_num
272 );
273 Ok(())
274 }
275
276 /// Execute command to force sync eligibility of staking accounts with manager privilege
277 ///
278 /// This action does not need to provide node_address
279 /// Manager wallet sync eligibility of staking accounts to a given value
280 pub async fn execute_manager_force_sync(
281 network_provider: NetworkProviderArgs,
282 safe_address: Option<String>,
283 private_key: PrivateKeyArgs,
284 eligibility: Option<bool>,
285 ) -> Result<(), HelperErrors> {
286 // read all the safe addresses
287 let mut safe_eth_addresses: Vec<H160> = Vec::new();
288 if let Some(addresses) = safe_address {
289 safe_eth_addresses.extend(addresses.split(',').map(|addr| H160::from_str(addr).unwrap()));
290 }
291
292 info!(
293 "Will force sync {:?} safes in the network registry",
294 safe_eth_addresses.len()
295 );
296
297 // read private key
298 let signer_private_key = private_key.read("MANAGER_PRIVATE_KEY")?;
299
300 // get RPC provider for the given network and environment
301 let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
302 let contract_addresses = network_provider.get_network_details_from_name()?;
303
304 let hopr_network_registry =
305 HoprNetworkRegistry::new(contract_addresses.addresses.network_registry, rpc_provider.clone());
306
307 // deregister all the given nodes from the network registry
308 match eligibility {
309 Some(safe_eligibility) => {
310 force_sync_safes_on_network_registry(
311 hopr_network_registry,
312 safe_eth_addresses.clone(),
313 vec![safe_eligibility; safe_eth_addresses.len()],
314 )
315 .await?;
316 info!(
317 "synced the eligibility of {:?} safes in the network registry to {:?}",
318 safe_eth_addresses.len(),
319 safe_eligibility
320 );
321 Ok(())
322 }
323 None => Err(HelperErrors::MissingParameter("eligibility".to_string())),
324 }
325 }
326}
327
328impl Cmd for NetworkRegistrySubcommands {
329 /// Run the execute_register function.
330 /// By default, registration is done by manager wallet
331 fn run(self) -> Result<(), HelperErrors> {
332 Ok(())
333 }
334
335 async fn async_run(self) -> Result<(), HelperErrors> {
336 match self {
337 NetworkRegistrySubcommands::ManagerRegister {
338 network_provider,
339 local_identity,
340 node_address,
341 safe_address,
342 private_key,
343 } => {
344 NetworkRegistrySubcommands::execute_manager_register(
345 network_provider,
346 local_identity,
347 node_address,
348 safe_address,
349 private_key,
350 )
351 .await?;
352 }
353 NetworkRegistrySubcommands::ManagerDeregister {
354 network_provider,
355 local_identity,
356 node_address,
357 private_key,
358 } => {
359 NetworkRegistrySubcommands::execute_manager_deregister(
360 network_provider,
361 local_identity,
362 node_address,
363 private_key,
364 )
365 .await?;
366 }
367 NetworkRegistrySubcommands::ManagerForceSync {
368 network_provider,
369 safe_address,
370 private_key,
371 eligibility,
372 } => {
373 NetworkRegistrySubcommands::execute_manager_force_sync(
374 network_provider,
375 safe_address,
376 private_key,
377 eligibility,
378 )
379 .await?;
380 }
381 }
382 Ok(())
383 }
384}