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}