hopr_internal_types/
account.rs

1use std::fmt::{Display, Formatter};
2
3use hopr_crypto_types::prelude::*;
4use hopr_primitive_types::prelude::*;
5use multiaddr::Multiaddr;
6
7/// Type of the node account.
8#[derive(Clone, Debug, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum AccountType {
11    /// Node is not announced.
12    NotAnnounced,
13    /// Node is announced with a multi-address
14    Announced(Vec<Multiaddr>),
15}
16
17impl Display for AccountType {
18    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
19        match &self {
20            Self::NotAnnounced => {
21                write!(f, "not announced")
22            }
23            Self::Announced(multiaddr) => write!(f, "announced via {multiaddr:?}"),
24        }
25    }
26}
27
28/// Represents a node announcement entry on the blockchain.
29/// This contains the node's public key and optional announcement information (multiaddress).
30#[derive(Clone, Debug, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct AccountEntry {
33    pub public_key: OffchainPublicKey,
34    pub chain_addr: Address,
35    pub entry_type: AccountType,
36    pub safe_address: Option<Address>,
37    pub key_id: KeyIdent<4>,
38}
39
40impl AccountEntry {
41    /// Is the node announced?
42    pub fn has_announced(&self) -> bool {
43        matches!(&self.entry_type, AccountType::Announced(addrs) if !addrs.is_empty())
44    }
45
46    /// If the node has announced, did it announce with routing information?
47    pub fn has_announced_with_routing_info(&self) -> bool {
48        match &self.entry_type {
49            AccountType::NotAnnounced => false,
50            AccountType::Announced(multiaddrs) => {
51                !multiaddrs.is_empty()
52                    && multiaddrs.iter().all(|multiaddr| {
53                        multiaddr
54                            .protocol_stack()
55                            .any(|p| p == "ip4" || p == "dns4" || p == "ip6" || p == "dns6")
56                            && multiaddr.protocol_stack().any(|p| p == "tcp" || p == "udp")
57                    })
58            }
59        }
60    }
61
62    pub fn get_multiaddrs(&self) -> &[Multiaddr] {
63        match &self.entry_type {
64            AccountType::NotAnnounced => &[],
65            AccountType::Announced(multiaddrs) => multiaddrs.as_slice(),
66        }
67    }
68
69    /// Used to either set a PRN or revoke being a PRN
70    ///
71    /// Examples:
72    /// - a node transitions from being a PRN to an edge node
73    /// - a node becomes a PRN
74    /// - the IP of a PRN has changed, e.g., due to relocation
75    pub fn update(&mut self, new_entry_type: AccountType) {
76        self.entry_type = new_entry_type;
77    }
78}
79
80impl Display for AccountEntry {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        write!(
83            f,
84            "account {} ({}:{}) (safe: {:?}) {}",
85            self.key_id,
86            self.chain_addr,
87            self.public_key.to_peerid_str(),
88            self.safe_address,
89            self.entry_type
90        )
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use hex_literal::hex;
97    use hopr_crypto_types::types::OffchainPublicKey;
98    use hopr_primitive_types::prelude::*;
99
100    use crate::account::{
101        AccountEntry,
102        AccountType::{Announced, NotAnnounced},
103    };
104
105    const PRIVATE_KEY: [u8; 32] = hex!("c14b8faa0a9b8a5fa4453664996f23a7e7de606d42297d723fc4a794f375e260");
106    const CHAIN_ADDR: [u8; 20] = hex!("2cDD13ddB0346E0F620C8E5826Da5d7230341c6E");
107
108    #[test]
109    fn test_account_entry_non_routable() -> anyhow::Result<()> {
110        let public_key = OffchainPublicKey::from_privkey(&PRIVATE_KEY)?;
111        let chain_addr = Address::try_from(CHAIN_ADDR.as_ref())?;
112
113        let ae1 = AccountEntry {
114            public_key,
115            chain_addr,
116            key_id: 1.into(),
117            entry_type: Announced(vec![
118                "/p2p/16Uiu2HAm3rUQdpCz53tK1MVUUq9NdMAU6mFgtcXrf71Ltw6AStzk".parse()?,
119            ]),
120            safe_address: None,
121        };
122
123        assert!(ae1.has_announced());
124        assert!(!ae1.has_announced_with_routing_info());
125
126        Ok(())
127    }
128
129    #[test]
130    fn test_account_entry_routable() -> anyhow::Result<()> {
131        let public_key = OffchainPublicKey::from_privkey(&PRIVATE_KEY)?;
132        let chain_addr = Address::try_from(CHAIN_ADDR.as_ref())?;
133
134        let ae1 = AccountEntry {
135            public_key,
136            chain_addr,
137            key_id: 1.into(),
138            entry_type: Announced(vec![
139                "/ip4/34.65.237.196/tcp/9091/p2p/16Uiu2HAm3rUQdpCz53tK1MVUUq9NdMAU6mFgtcXrf71Ltw6AStzk".parse()?,
140            ]),
141            safe_address: None,
142        };
143
144        assert!(ae1.has_announced());
145        assert!(ae1.has_announced_with_routing_info());
146
147        let ae1 = AccountEntry {
148            public_key,
149            chain_addr,
150            key_id: 1.into(),
151            entry_type: Announced(vec![
152                "/ip4/34.65.237.196/udp/9091/p2p/16Uiu2HAm3rUQdpCz53tK1MVUUq9NdMAU6mFgtcXrf71Ltw6AStzk".parse()?,
153            ]),
154            safe_address: None,
155        };
156
157        assert!(ae1.has_announced());
158        assert!(ae1.has_announced_with_routing_info());
159
160        Ok(())
161    }
162
163    #[test]
164    fn test_account_entry_not_announced() -> anyhow::Result<()> {
165        let public_key = OffchainPublicKey::from_privkey(&PRIVATE_KEY)?;
166        let chain_addr = Address::try_from(CHAIN_ADDR.as_ref())?;
167
168        let ae1 = AccountEntry {
169            public_key,
170            chain_addr,
171            key_id: 0.into(),
172            entry_type: NotAnnounced,
173            safe_address: None,
174        };
175
176        assert!(!ae1.has_announced());
177        assert!(!ae1.has_announced_with_routing_info());
178
179        Ok(())
180    }
181}