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