hopr_internal_types/
account.rs

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