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}