Skip to main content

moonpool_core/
node_address.rs

1//! Node address: identifies a specific node instance in the cluster.
2//!
3//! A [`NodeAddress`] combines a [`NetworkAddress`] with a generation counter,
4//! allowing the system to distinguish between different incarnations of a node
5//! at the same IP:port (e.g., after a restart).
6
7use serde::{Deserialize, Serialize};
8
9use crate::NetworkAddress;
10
11/// Identifies a specific node instance in the cluster.
12///
13/// The `generation` distinguishes node restarts at the same network address.
14/// In simulation, this comes from the RNG seed; in production, from a
15/// monotonic timestamp or similar mechanism.
16///
17/// # Examples
18///
19/// ```
20/// use moonpool_core::{NodeAddress, NetworkAddress};
21/// use std::net::{IpAddr, Ipv4Addr};
22///
23/// let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
24/// let node = NodeAddress::new(addr, 1);
25/// assert_eq!(node.generation(), 1);
26/// ```
27#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub struct NodeAddress {
29    address: NetworkAddress,
30    generation: u64,
31}
32
33impl NodeAddress {
34    /// Create a new node address.
35    pub fn new(address: NetworkAddress, generation: u64) -> Self {
36        Self {
37            address,
38            generation,
39        }
40    }
41
42    /// The network address of this node.
43    pub fn address(&self) -> &NetworkAddress {
44        &self.address
45    }
46
47    /// The generation counter for this node instance.
48    pub fn generation(&self) -> u64 {
49        self.generation
50    }
51}
52
53impl std::fmt::Display for NodeAddress {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "{}@gen{}", self.address, self.generation)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use std::net::{IpAddr, Ipv4Addr};
62
63    use super::*;
64
65    #[test]
66    fn test_node_address_equality() {
67        let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
68        let n1 = NodeAddress::new(addr.clone(), 1);
69        let n2 = NodeAddress::new(addr.clone(), 1);
70        let n3 = NodeAddress::new(addr, 2);
71
72        assert_eq!(n1, n2);
73        assert_ne!(n1, n3);
74    }
75
76    #[test]
77    fn test_node_address_accessors() {
78        let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
79        let node = NodeAddress::new(addr.clone(), 42);
80
81        assert_eq!(node.address(), &addr);
82        assert_eq!(node.generation(), 42);
83    }
84
85    #[test]
86    fn test_node_address_display() {
87        let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
88        let node = NodeAddress::new(addr, 7);
89
90        assert_eq!(node.to_string(), "127.0.0.1:4500@gen7");
91    }
92
93    #[test]
94    fn test_node_address_serde_roundtrip() {
95        let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
96        let node = NodeAddress::new(addr, 42);
97
98        let json = serde_json::to_string(&node).expect("serialize");
99        let decoded: NodeAddress = serde_json::from_str(&json).expect("deserialize");
100        assert_eq!(node, decoded);
101    }
102
103    #[test]
104    fn test_node_address_hash_works_in_collections() {
105        use std::collections::HashSet;
106
107        let addr = NetworkAddress::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4500);
108        let mut set = HashSet::new();
109        set.insert(NodeAddress::new(addr.clone(), 1));
110        set.insert(NodeAddress::new(addr.clone(), 2));
111        set.insert(NodeAddress::new(addr, 1)); // duplicate
112
113        assert_eq!(set.len(), 2);
114    }
115}