mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-11 17:17:54 +00:00
Standardize naming conventions across docs and source
Rename NodeId to NodeAddr and npub to pubkey throughout documentation and source code for consistency with the established identity model. Add FIPS API vs IPv6 adapter overview to session protocol document. Add transport address terminology note to wire protocol document.
This commit is contained in:
+1
-1
@@ -83,7 +83,7 @@ async fn main() {
|
||||
// Log node information
|
||||
info!("Node created:");
|
||||
info!(" npub: {}", node.npub());
|
||||
info!(" node_id: {}", hex::encode(node.node_id().as_bytes()));
|
||||
info!(" node_addr: {}", hex::encode(node.node_addr().as_bytes()));
|
||||
info!(" address: {}", node.identity().address());
|
||||
info!(" state: {}", node.state());
|
||||
info!(" leaf_only: {}", node.is_leaf_only());
|
||||
|
||||
+79
-79
@@ -15,7 +15,7 @@
|
||||
//! overcounted by assuming mesh connectivity vs tree structure with TTL-bounded
|
||||
//! propagation. Actual filter occupancy is ~250-800 entries for typical nodes.
|
||||
|
||||
use crate::NodeId;
|
||||
use crate::NodeAddr;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
@@ -114,10 +114,10 @@ impl BloomFilter {
|
||||
Self::from_bytes(bytes.to_vec(), hash_count)
|
||||
}
|
||||
|
||||
/// Insert a NodeId into the filter.
|
||||
pub fn insert(&mut self, node_id: &NodeId) {
|
||||
/// Insert a NodeAddr into the filter.
|
||||
pub fn insert(&mut self, node_addr: &NodeAddr) {
|
||||
for i in 0..self.hash_count {
|
||||
let bit_index = self.hash(node_id.as_bytes(), i);
|
||||
let bit_index = self.hash(node_addr.as_bytes(), i);
|
||||
self.set_bit(bit_index);
|
||||
}
|
||||
}
|
||||
@@ -130,12 +130,12 @@ impl BloomFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the filter might contain a NodeId.
|
||||
/// Check if the filter might contain a NodeAddr.
|
||||
///
|
||||
/// Returns `true` if the item might be in the set (possible false positive).
|
||||
/// Returns `false` if the item is definitely not in the set.
|
||||
pub fn contains(&self, node_id: &NodeId) -> bool {
|
||||
self.contains_bytes(node_id.as_bytes())
|
||||
pub fn contains(&self, node_addr: &NodeAddr) -> bool {
|
||||
self.contains_bytes(node_addr.as_bytes())
|
||||
}
|
||||
|
||||
/// Check if the filter might contain raw bytes.
|
||||
@@ -293,27 +293,27 @@ impl fmt::Debug for BloomFilter {
|
||||
/// Tracks local filter state and what needs to be sent to peers.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BloomState {
|
||||
/// This node's NodeId (always included in outgoing filters).
|
||||
own_node_id: NodeId,
|
||||
/// This node's NodeAddr (always included in outgoing filters).
|
||||
own_node_addr: NodeAddr,
|
||||
/// Leaf-only nodes we speak for (included in our filter).
|
||||
leaf_dependents: HashSet<NodeId>,
|
||||
leaf_dependents: HashSet<NodeAddr>,
|
||||
/// Whether this node operates in leaf-only mode.
|
||||
is_leaf_only: bool,
|
||||
/// Rate limiting: minimum interval between outgoing updates (milliseconds).
|
||||
update_debounce_ms: u64,
|
||||
/// Timestamp of last update sent (per peer, in milliseconds).
|
||||
last_update_sent: HashMap<NodeId, u64>,
|
||||
last_update_sent: HashMap<NodeAddr, u64>,
|
||||
/// Peers that need a filter update.
|
||||
pending_updates: HashSet<NodeId>,
|
||||
pending_updates: HashSet<NodeAddr>,
|
||||
/// Current sequence number for outgoing filters.
|
||||
sequence: u64,
|
||||
}
|
||||
|
||||
impl BloomState {
|
||||
/// Create new Bloom state for a node.
|
||||
pub fn new(own_node_id: NodeId) -> Self {
|
||||
pub fn new(own_node_addr: NodeAddr) -> Self {
|
||||
Self {
|
||||
own_node_id,
|
||||
own_node_addr,
|
||||
leaf_dependents: HashSet::new(),
|
||||
is_leaf_only: false,
|
||||
update_debounce_ms: 500,
|
||||
@@ -324,15 +324,15 @@ impl BloomState {
|
||||
}
|
||||
|
||||
/// Create state for a leaf-only node.
|
||||
pub fn leaf_only(own_node_id: NodeId) -> Self {
|
||||
let mut state = Self::new(own_node_id);
|
||||
pub fn leaf_only(own_node_addr: NodeAddr) -> Self {
|
||||
let mut state = Self::new(own_node_addr);
|
||||
state.is_leaf_only = true;
|
||||
state
|
||||
}
|
||||
|
||||
/// Get the node's own ID.
|
||||
pub fn own_node_id(&self) -> &NodeId {
|
||||
&self.own_node_id
|
||||
pub fn own_node_addr(&self) -> &NodeAddr {
|
||||
&self.own_node_addr
|
||||
}
|
||||
|
||||
/// Check if this is a leaf-only node.
|
||||
@@ -362,17 +362,17 @@ impl BloomState {
|
||||
}
|
||||
|
||||
/// Add a leaf dependent that we'll include in our filter.
|
||||
pub fn add_leaf_dependent(&mut self, node_id: NodeId) {
|
||||
self.leaf_dependents.insert(node_id);
|
||||
pub fn add_leaf_dependent(&mut self, node_addr: NodeAddr) {
|
||||
self.leaf_dependents.insert(node_addr);
|
||||
}
|
||||
|
||||
/// Remove a leaf dependent.
|
||||
pub fn remove_leaf_dependent(&mut self, node_id: &NodeId) -> bool {
|
||||
self.leaf_dependents.remove(node_id)
|
||||
pub fn remove_leaf_dependent(&mut self, node_addr: &NodeAddr) -> bool {
|
||||
self.leaf_dependents.remove(node_addr)
|
||||
}
|
||||
|
||||
/// Get the set of leaf dependents.
|
||||
pub fn leaf_dependents(&self) -> &HashSet<NodeId> {
|
||||
pub fn leaf_dependents(&self) -> &HashSet<NodeAddr> {
|
||||
&self.leaf_dependents
|
||||
}
|
||||
|
||||
@@ -382,22 +382,22 @@ impl BloomState {
|
||||
}
|
||||
|
||||
/// Mark that a peer needs an update.
|
||||
pub fn mark_update_needed(&mut self, peer_id: NodeId) {
|
||||
pub fn mark_update_needed(&mut self, peer_id: NodeAddr) {
|
||||
self.pending_updates.insert(peer_id);
|
||||
}
|
||||
|
||||
/// Mark all peers as needing updates.
|
||||
pub fn mark_all_updates_needed(&mut self, peer_ids: impl IntoIterator<Item = NodeId>) {
|
||||
pub fn mark_all_updates_needed(&mut self, peer_ids: impl IntoIterator<Item = NodeAddr>) {
|
||||
self.pending_updates.extend(peer_ids);
|
||||
}
|
||||
|
||||
/// Check if a peer needs an update.
|
||||
pub fn needs_update(&self, peer_id: &NodeId) -> bool {
|
||||
pub fn needs_update(&self, peer_id: &NodeAddr) -> bool {
|
||||
self.pending_updates.contains(peer_id)
|
||||
}
|
||||
|
||||
/// Check if we should send an update to a peer (respecting debounce).
|
||||
pub fn should_send_update(&self, peer_id: &NodeId, current_time_ms: u64) -> bool {
|
||||
pub fn should_send_update(&self, peer_id: &NodeAddr, current_time_ms: u64) -> bool {
|
||||
if !self.pending_updates.contains(peer_id) {
|
||||
return false;
|
||||
}
|
||||
@@ -409,7 +409,7 @@ impl BloomState {
|
||||
}
|
||||
|
||||
/// Record that we sent an update to a peer.
|
||||
pub fn record_update_sent(&mut self, peer_id: NodeId, current_time_ms: u64) {
|
||||
pub fn record_update_sent(&mut self, peer_id: NodeAddr, current_time_ms: u64) {
|
||||
self.last_update_sent.insert(peer_id, current_time_ms);
|
||||
self.pending_updates.remove(&peer_id);
|
||||
}
|
||||
@@ -430,13 +430,13 @@ impl BloomState {
|
||||
/// The filter for `exclude_peer` is excluded to prevent routing loops.
|
||||
pub fn compute_outgoing_filter(
|
||||
&self,
|
||||
exclude_peer: &NodeId,
|
||||
peer_filters: &HashMap<NodeId, (BloomFilter, u8)>, // (filter, ttl)
|
||||
exclude_peer: &NodeAddr,
|
||||
peer_filters: &HashMap<NodeAddr, (BloomFilter, u8)>, // (filter, ttl)
|
||||
) -> BloomFilter {
|
||||
let mut filter = BloomFilter::new();
|
||||
|
||||
// Always include ourselves
|
||||
filter.insert(&self.own_node_id);
|
||||
filter.insert(&self.own_node_addr);
|
||||
|
||||
// Include leaf dependents
|
||||
for dep in &self.leaf_dependents {
|
||||
@@ -457,7 +457,7 @@ impl BloomState {
|
||||
/// Create a base filter containing just this node and its dependents.
|
||||
pub fn base_filter(&self) -> BloomFilter {
|
||||
let mut filter = BloomFilter::new();
|
||||
filter.insert(&self.own_node_id);
|
||||
filter.insert(&self.own_node_addr);
|
||||
for dep in &self.leaf_dependents {
|
||||
filter.insert(dep);
|
||||
}
|
||||
@@ -469,10 +469,10 @@ impl BloomState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
// ===== BloomFilter Tests =====
|
||||
@@ -489,8 +489,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_bloom_filter_insert_contains() {
|
||||
let mut filter = BloomFilter::new();
|
||||
let node1 = make_node_id(1);
|
||||
let node2 = make_node_id(2);
|
||||
let node1 = make_node_addr(1);
|
||||
let node2 = make_node_addr(2);
|
||||
|
||||
assert!(!filter.contains(&node1));
|
||||
assert!(!filter.contains(&node2));
|
||||
@@ -507,13 +507,13 @@ mod tests {
|
||||
let mut filter = BloomFilter::new();
|
||||
|
||||
for i in 0..100 {
|
||||
let node = make_node_id(i);
|
||||
let node = make_node_addr(i);
|
||||
filter.insert(&node);
|
||||
}
|
||||
|
||||
// All inserted items should be found
|
||||
for i in 0..100 {
|
||||
let node = make_node_id(i);
|
||||
let node = make_node_addr(i);
|
||||
assert!(filter.contains(&node), "Node {} not found", i);
|
||||
}
|
||||
|
||||
@@ -527,8 +527,8 @@ mod tests {
|
||||
let mut filter1 = BloomFilter::new();
|
||||
let mut filter2 = BloomFilter::new();
|
||||
|
||||
let node1 = make_node_id(1);
|
||||
let node2 = make_node_id(2);
|
||||
let node1 = make_node_addr(1);
|
||||
let node2 = make_node_addr(2);
|
||||
|
||||
filter1.insert(&node1);
|
||||
filter2.insert(&node2);
|
||||
@@ -544,8 +544,8 @@ mod tests {
|
||||
let mut filter1 = BloomFilter::new();
|
||||
let mut filter2 = BloomFilter::new();
|
||||
|
||||
let node1 = make_node_id(1);
|
||||
let node2 = make_node_id(2);
|
||||
let node1 = make_node_addr(1);
|
||||
let node2 = make_node_addr(2);
|
||||
|
||||
filter1.insert(&node1);
|
||||
filter2.insert(&node2);
|
||||
@@ -562,7 +562,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_bloom_filter_clear() {
|
||||
let mut filter = BloomFilter::new();
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
|
||||
filter.insert(&node);
|
||||
assert!(!filter.is_empty());
|
||||
@@ -631,7 +631,7 @@ mod tests {
|
||||
|
||||
// Insert some items
|
||||
for i in 0..50 {
|
||||
filter.insert(&make_node_id(i));
|
||||
filter.insert(&make_node_addr(i));
|
||||
}
|
||||
|
||||
// Estimate should be reasonably close to 50
|
||||
@@ -650,10 +650,10 @@ mod tests {
|
||||
|
||||
assert_eq!(filter1, filter2);
|
||||
|
||||
filter1.insert(&make_node_id(1));
|
||||
filter1.insert(&make_node_addr(1));
|
||||
assert_ne!(filter1, filter2);
|
||||
|
||||
filter2.insert(&make_node_id(1));
|
||||
filter2.insert(&make_node_addr(1));
|
||||
assert_eq!(filter1, filter2);
|
||||
}
|
||||
|
||||
@@ -661,10 +661,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_new() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let state = BloomState::new(node);
|
||||
|
||||
assert_eq!(state.own_node_id(), &node);
|
||||
assert_eq!(state.own_node_addr(), &node);
|
||||
assert!(!state.is_leaf_only());
|
||||
assert_eq!(state.sequence(), 0);
|
||||
assert_eq!(state.leaf_dependent_count(), 0);
|
||||
@@ -672,7 +672,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_leaf_only() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let state = BloomState::leaf_only(node);
|
||||
|
||||
assert!(state.is_leaf_only());
|
||||
@@ -680,11 +680,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_leaf_dependents() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let leaf1 = make_node_id(1);
|
||||
let leaf2 = make_node_id(2);
|
||||
let leaf1 = make_node_addr(1);
|
||||
let leaf2 = make_node_addr(2);
|
||||
|
||||
state.add_leaf_dependent(leaf1);
|
||||
state.add_leaf_dependent(leaf2);
|
||||
@@ -698,8 +698,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_debounce() {
|
||||
let node = make_node_id(0);
|
||||
let peer = make_node_id(1);
|
||||
let node = make_node_addr(0);
|
||||
let peer = make_node_addr(1);
|
||||
let mut state = BloomState::new(node);
|
||||
state.set_update_debounce_ms(500);
|
||||
|
||||
@@ -721,7 +721,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_sequence() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
assert_eq!(state.sequence(), 0);
|
||||
@@ -732,11 +732,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_pending_updates() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer1 = make_node_id(1);
|
||||
let peer2 = make_node_id(2);
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer2 = make_node_addr(2);
|
||||
|
||||
assert!(!state.needs_update(&peer1));
|
||||
|
||||
@@ -755,37 +755,37 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_base_filter() {
|
||||
let node = make_node_id(0);
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let leaf = make_node_id(1);
|
||||
let leaf = make_node_addr(1);
|
||||
state.add_leaf_dependent(leaf);
|
||||
|
||||
let filter = state.base_filter();
|
||||
|
||||
assert!(filter.contains(&node));
|
||||
assert!(filter.contains(&leaf));
|
||||
assert!(!filter.contains(&make_node_id(99)));
|
||||
assert!(!filter.contains(&make_node_addr(99)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_compute_outgoing_filter() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = BloomState::new(my_node);
|
||||
|
||||
let leaf = make_node_id(1);
|
||||
let leaf = make_node_addr(1);
|
||||
state.add_leaf_dependent(leaf);
|
||||
|
||||
let peer1 = make_node_id(10);
|
||||
let peer2 = make_node_id(20);
|
||||
let peer1 = make_node_addr(10);
|
||||
let peer2 = make_node_addr(20);
|
||||
|
||||
// Create peer filters
|
||||
let mut filter1 = BloomFilter::new();
|
||||
filter1.insert(&make_node_id(100));
|
||||
filter1.insert(&make_node_id(101));
|
||||
filter1.insert(&make_node_addr(100));
|
||||
filter1.insert(&make_node_addr(101));
|
||||
|
||||
let mut filter2 = BloomFilter::new();
|
||||
filter2.insert(&make_node_id(200));
|
||||
filter2.insert(&make_node_addr(200));
|
||||
|
||||
let mut peer_filters = HashMap::new();
|
||||
peer_filters.insert(peer1, (filter1, 2)); // TTL 2
|
||||
@@ -795,38 +795,38 @@ mod tests {
|
||||
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
|
||||
assert!(outgoing1.contains(&my_node)); // self
|
||||
assert!(outgoing1.contains(&leaf)); // leaf dependent
|
||||
assert!(outgoing1.contains(&make_node_id(200))); // from peer2
|
||||
assert!(outgoing1.contains(&make_node_addr(200))); // from peer2
|
||||
// peer1's nodes may or may not be present (depends on split brain)
|
||||
|
||||
// Filter for peer2 should exclude peer2's contributions
|
||||
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
|
||||
assert!(outgoing2.contains(&my_node));
|
||||
assert!(outgoing2.contains(&leaf));
|
||||
assert!(outgoing2.contains(&make_node_id(100))); // from peer1
|
||||
assert!(outgoing2.contains(&make_node_id(101))); // from peer1
|
||||
assert!(outgoing2.contains(&make_node_addr(100))); // from peer1
|
||||
assert!(outgoing2.contains(&make_node_addr(101))); // from peer1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_ttl_filtering() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let state = BloomState::new(my_node);
|
||||
|
||||
let peer1 = make_node_id(10);
|
||||
let peer2 = make_node_id(20);
|
||||
let peer1 = make_node_addr(10);
|
||||
let peer2 = make_node_addr(20);
|
||||
|
||||
let mut filter1 = BloomFilter::new();
|
||||
filter1.insert(&make_node_id(100));
|
||||
filter1.insert(&make_node_addr(100));
|
||||
|
||||
let mut filter2 = BloomFilter::new();
|
||||
filter2.insert(&make_node_id(200));
|
||||
filter2.insert(&make_node_addr(200));
|
||||
|
||||
let mut peer_filters = HashMap::new();
|
||||
peer_filters.insert(peer1, (filter1, 1)); // TTL 1 - included
|
||||
peer_filters.insert(peer2, (filter2, 0)); // TTL 0 - excluded
|
||||
|
||||
let outgoing = state.compute_outgoing_filter(&make_node_id(99), &peer_filters);
|
||||
let outgoing = state.compute_outgoing_filter(&make_node_addr(99), &peer_filters);
|
||||
|
||||
assert!(outgoing.contains(&make_node_id(100))); // TTL 1
|
||||
assert!(!outgoing.contains(&make_node_id(200))); // TTL 0 excluded
|
||||
assert!(outgoing.contains(&make_node_addr(100))); // TTL 1
|
||||
assert!(!outgoing.contains(&make_node_addr(200))); // TTL 0 excluded
|
||||
}
|
||||
}
|
||||
|
||||
+27
-27
@@ -5,7 +5,7 @@
|
||||
//! RouteCache stores coordinates learned from discovery queries.
|
||||
|
||||
use crate::tree::TreeCoordinate;
|
||||
use crate::{FipsAddress, NodeId};
|
||||
use crate::{FipsAddress, NodeAddr};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -413,11 +413,11 @@ impl CachedCoords {
|
||||
///
|
||||
/// Separate from CoordCache, this stores routes learned from the discovery
|
||||
/// protocol (LookupRequest/LookupResponse) rather than session establishment.
|
||||
/// Keyed by NodeId rather than FipsAddress.
|
||||
/// Keyed by NodeAddr rather than FipsAddress.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RouteCache {
|
||||
/// NodeId -> discovered coordinates.
|
||||
entries: HashMap<NodeId, CachedCoords>,
|
||||
/// NodeAddr -> discovered coordinates.
|
||||
entries: HashMap<NodeAddr, CachedCoords>,
|
||||
/// Maximum entries.
|
||||
max_entries: usize,
|
||||
}
|
||||
@@ -442,9 +442,9 @@ impl RouteCache {
|
||||
}
|
||||
|
||||
/// Insert a discovered route.
|
||||
pub fn insert(&mut self, node_id: NodeId, coords: TreeCoordinate, current_time_ms: u64) {
|
||||
pub fn insert(&mut self, node_addr: NodeAddr, coords: TreeCoordinate, current_time_ms: u64) {
|
||||
// Update existing
|
||||
if let Some(entry) = self.entries.get_mut(&node_id) {
|
||||
if let Some(entry) = self.entries.get_mut(&node_addr) {
|
||||
entry.update(coords, current_time_ms);
|
||||
return;
|
||||
}
|
||||
@@ -455,21 +455,21 @@ impl RouteCache {
|
||||
}
|
||||
|
||||
self.entries
|
||||
.insert(node_id, CachedCoords::new(coords, current_time_ms));
|
||||
.insert(node_addr, CachedCoords::new(coords, current_time_ms));
|
||||
}
|
||||
|
||||
/// Look up a route (without touching).
|
||||
pub fn get(&self, node_id: &NodeId) -> Option<&CachedCoords> {
|
||||
self.entries.get(node_id)
|
||||
pub fn get(&self, node_addr: &NodeAddr) -> Option<&CachedCoords> {
|
||||
self.entries.get(node_addr)
|
||||
}
|
||||
|
||||
/// Look up and touch.
|
||||
pub fn get_and_touch(
|
||||
&mut self,
|
||||
node_id: &NodeId,
|
||||
node_addr: &NodeAddr,
|
||||
current_time_ms: u64,
|
||||
) -> Option<&TreeCoordinate> {
|
||||
if let Some(entry) = self.entries.get_mut(node_id) {
|
||||
if let Some(entry) = self.entries.get_mut(node_addr) {
|
||||
entry.touch(current_time_ms);
|
||||
Some(entry.coords())
|
||||
} else {
|
||||
@@ -478,13 +478,13 @@ impl RouteCache {
|
||||
}
|
||||
|
||||
/// Remove a route (e.g., after route failure).
|
||||
pub fn invalidate(&mut self, node_id: &NodeId) -> Option<CachedCoords> {
|
||||
self.entries.remove(node_id)
|
||||
pub fn invalidate(&mut self, node_addr: &NodeAddr) -> Option<CachedCoords> {
|
||||
self.entries.remove(node_addr)
|
||||
}
|
||||
|
||||
/// Check if a node is cached.
|
||||
pub fn contains(&self, node_id: &NodeId) -> bool {
|
||||
self.entries.contains_key(node_id)
|
||||
pub fn contains(&self, node_addr: &NodeAddr) -> bool {
|
||||
self.entries.contains_key(node_addr)
|
||||
}
|
||||
|
||||
/// Number of cached routes.
|
||||
@@ -533,10 +533,10 @@ impl Default for RouteCache {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn make_address(val: u8) -> FipsAddress {
|
||||
@@ -546,7 +546,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn make_coords(ids: &[u8]) -> TreeCoordinate {
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_id(v)).collect()).unwrap()
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
|
||||
}
|
||||
|
||||
// ===== CacheEntry Tests =====
|
||||
@@ -723,7 +723,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_route_cache_basic() {
|
||||
let mut cache = RouteCache::new(100);
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
let coords = make_coords(&[1, 0]);
|
||||
|
||||
cache.insert(node, coords.clone(), 0);
|
||||
@@ -735,7 +735,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_route_cache_invalidate() {
|
||||
let mut cache = RouteCache::new(100);
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
let coords = make_coords(&[1, 0]);
|
||||
|
||||
cache.insert(node, coords, 0);
|
||||
@@ -749,9 +749,9 @@ mod tests {
|
||||
fn test_route_cache_lru_eviction() {
|
||||
let mut cache = RouteCache::new(2);
|
||||
|
||||
let node1 = make_node_id(1);
|
||||
let node2 = make_node_id(2);
|
||||
let node3 = make_node_id(3);
|
||||
let node1 = make_node_addr(1);
|
||||
let node2 = make_node_addr(2);
|
||||
let node3 = make_node_addr(3);
|
||||
|
||||
cache.insert(node1, make_coords(&[1, 0]), 0);
|
||||
cache.insert(node2, make_coords(&[2, 0]), 100);
|
||||
@@ -772,9 +772,9 @@ mod tests {
|
||||
fn test_route_cache_evict_older_than() {
|
||||
let mut cache = RouteCache::new(100);
|
||||
|
||||
cache.insert(make_node_id(1), make_coords(&[1, 0]), 0);
|
||||
cache.insert(make_node_id(2), make_coords(&[2, 0]), 500);
|
||||
cache.insert(make_node_id(3), make_coords(&[3, 0]), 1000);
|
||||
cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
|
||||
cache.insert(make_node_addr(2), make_coords(&[2, 0]), 500);
|
||||
cache.insert(make_node_addr(3), make_coords(&[3, 0]), 1000);
|
||||
|
||||
let evicted = cache.evict_older_than(600, 1000);
|
||||
|
||||
@@ -785,7 +785,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_route_cache_update() {
|
||||
let mut cache = RouteCache::new(100);
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
|
||||
cache.insert(node, make_coords(&[1, 0]), 0);
|
||||
cache.insert(node, make_coords(&[1, 2, 0]), 500);
|
||||
|
||||
+61
-61
@@ -1,6 +1,6 @@
|
||||
//! FIPS Identity System
|
||||
//!
|
||||
//! Node identity based on Nostr keypairs (secp256k1). The node_id is derived
|
||||
//! Node identity based on Nostr keypairs (secp256k1). The node_addr is derived
|
||||
//! from the public key via SHA-256, and the FIPS address uses an IPv6-compatible
|
||||
//! format with the 0xfd prefix.
|
||||
|
||||
@@ -33,8 +33,8 @@ pub enum IdentityError {
|
||||
#[error("signature verification failed")]
|
||||
SignatureVerificationFailed,
|
||||
|
||||
#[error("invalid node_id length: expected 32, got {0}")]
|
||||
InvalidNodeIdLength(usize),
|
||||
#[error("invalid node_addr length: expected 32, got {0}")]
|
||||
InvalidNodeAddrLength(usize),
|
||||
|
||||
#[error("invalid address length: expected 16, got {0}")]
|
||||
InvalidAddressLength(usize),
|
||||
@@ -64,31 +64,31 @@ pub enum IdentityError {
|
||||
InvalidHex(#[from] hex::FromHexError),
|
||||
}
|
||||
|
||||
/// 32-byte node identifier derived from SHA-256(npub).
|
||||
/// 32-byte node identifier derived from SHA-256(pubkey).
|
||||
///
|
||||
/// The node_id is used in protocol messages and bloom filters. Hashing the
|
||||
/// The node_addr is used in protocol messages and bloom filters. Hashing the
|
||||
/// public key prevents grinding attacks that exploit secp256k1's algebraic
|
||||
/// structure.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct NodeId([u8; 32]);
|
||||
pub struct NodeAddr([u8; 32]);
|
||||
|
||||
impl NodeId {
|
||||
/// Create a NodeId from a 32-byte array.
|
||||
impl NodeAddr {
|
||||
/// Create a NodeAddr from a 32-byte array.
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
/// Create a NodeId from a slice.
|
||||
/// Create a NodeAddr from a slice.
|
||||
pub fn from_slice(slice: &[u8]) -> Result<Self, IdentityError> {
|
||||
if slice.len() != 32 {
|
||||
return Err(IdentityError::InvalidNodeIdLength(slice.len()));
|
||||
return Err(IdentityError::InvalidNodeAddrLength(slice.len()));
|
||||
}
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes.copy_from_slice(slice);
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
/// Derive a NodeId from an x-only public key (npub).
|
||||
/// Derive a NodeAddr from an x-only public key (npub).
|
||||
pub fn from_pubkey(pubkey: &XOnlyPublicKey) -> Self {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(pubkey.serialize());
|
||||
@@ -109,19 +109,19 @@ impl NodeId {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NodeId {
|
||||
impl fmt::Debug for NodeAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "NodeId({})", hex_encode(&self.0[..8]))
|
||||
write!(f, "NodeAddr({})", hex_encode(&self.0[..8]))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NodeId {
|
||||
impl fmt::Display for NodeAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", hex_encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for NodeId {
|
||||
impl AsRef<[u8]> for NodeAddr {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
@@ -130,7 +130,7 @@ impl AsRef<[u8]> for NodeId {
|
||||
/// 128-bit FIPS address with IPv6-compatible format.
|
||||
///
|
||||
/// The address uses the IPv6 Unique Local Address (ULA) prefix `fd00::/8`,
|
||||
/// providing 120 bits for the node_id hash. This format allows applications
|
||||
/// providing 120 bits for the node_addr hash. This format allows applications
|
||||
/// designed for IP transports to bind to FIPS addresses via a TUN interface.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct FipsAddress([u8; 16]);
|
||||
@@ -154,13 +154,13 @@ impl FipsAddress {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Derive a FipsAddress from a NodeId.
|
||||
/// Derive a FipsAddress from a NodeAddr.
|
||||
///
|
||||
/// Takes the first 15 bytes of the node_id and prepends the 0xfd prefix.
|
||||
pub fn from_node_id(node_id: &NodeId) -> Self {
|
||||
/// Takes the first 15 bytes of the node_addr and prepends the 0xfd prefix.
|
||||
pub fn from_node_addr(node_addr: &NodeAddr) -> Self {
|
||||
let mut bytes = [0u8; 16];
|
||||
bytes[0] = FIPS_ADDRESS_PREFIX;
|
||||
bytes[1..16].copy_from_slice(&node_id.0[0..15]);
|
||||
bytes[1..16].copy_from_slice(&node_addr.0[0..15]);
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ pub struct PeerIdentity {
|
||||
pubkey: XOnlyPublicKey,
|
||||
/// Full public key if known (includes parity for ECDH operations).
|
||||
pubkey_full: Option<PublicKey>,
|
||||
node_id: NodeId,
|
||||
node_addr: NodeAddr,
|
||||
address: FipsAddress,
|
||||
}
|
||||
|
||||
@@ -212,12 +212,12 @@ impl PeerIdentity {
|
||||
/// Note: When only the x-only key is available, the full public key
|
||||
/// will be derived assuming even parity for ECDH operations.
|
||||
pub fn from_pubkey(pubkey: XOnlyPublicKey) -> Self {
|
||||
let node_id = NodeId::from_pubkey(&pubkey);
|
||||
let address = FipsAddress::from_node_id(&node_id);
|
||||
let node_addr = NodeAddr::from_pubkey(&pubkey);
|
||||
let address = FipsAddress::from_node_addr(&node_addr);
|
||||
Self {
|
||||
pubkey,
|
||||
pubkey_full: None,
|
||||
node_id,
|
||||
node_addr,
|
||||
address,
|
||||
}
|
||||
}
|
||||
@@ -228,12 +228,12 @@ impl PeerIdentity {
|
||||
/// handshake) to preserve parity information for ECDH operations.
|
||||
pub fn from_pubkey_full(pubkey: PublicKey) -> Self {
|
||||
let (x_only, _parity) = pubkey.x_only_public_key();
|
||||
let node_id = NodeId::from_pubkey(&x_only);
|
||||
let address = FipsAddress::from_node_id(&node_id);
|
||||
let node_addr = NodeAddr::from_pubkey(&x_only);
|
||||
let address = FipsAddress::from_node_addr(&node_addr);
|
||||
Self {
|
||||
pubkey: x_only,
|
||||
pubkey_full: Some(pubkey),
|
||||
node_id,
|
||||
node_addr,
|
||||
address,
|
||||
}
|
||||
}
|
||||
@@ -266,8 +266,8 @@ impl PeerIdentity {
|
||||
}
|
||||
|
||||
/// Return the node ID.
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
&self.node_id
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
&self.node_addr
|
||||
}
|
||||
|
||||
/// Return the FIPS address.
|
||||
@@ -286,7 +286,7 @@ impl PeerIdentity {
|
||||
impl fmt::Debug for PeerIdentity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PeerIdentity")
|
||||
.field("node_id", &self.node_id)
|
||||
.field("node_addr", &self.node_addr)
|
||||
.field("address", &self.address)
|
||||
.finish()
|
||||
}
|
||||
@@ -304,7 +304,7 @@ impl fmt::Display for PeerIdentity {
|
||||
/// and verifying protocol messages.
|
||||
pub struct Identity {
|
||||
keypair: Keypair,
|
||||
node_id: NodeId,
|
||||
node_addr: NodeAddr,
|
||||
address: FipsAddress,
|
||||
}
|
||||
|
||||
@@ -319,11 +319,11 @@ impl Identity {
|
||||
/// Create an identity from an existing keypair.
|
||||
pub fn from_keypair(keypair: Keypair) -> Self {
|
||||
let (pubkey, _parity) = keypair.x_only_public_key();
|
||||
let node_id = NodeId::from_pubkey(&pubkey);
|
||||
let address = FipsAddress::from_node_id(&node_id);
|
||||
let node_addr = NodeAddr::from_pubkey(&pubkey);
|
||||
let address = FipsAddress::from_node_addr(&node_addr);
|
||||
Self {
|
||||
keypair,
|
||||
node_id,
|
||||
node_addr,
|
||||
address,
|
||||
}
|
||||
}
|
||||
@@ -370,8 +370,8 @@ impl Identity {
|
||||
}
|
||||
|
||||
/// Return the node ID.
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
&self.node_id
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
&self.node_addr
|
||||
}
|
||||
|
||||
/// Return the FIPS address.
|
||||
@@ -404,7 +404,7 @@ impl Identity {
|
||||
impl fmt::Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Identity")
|
||||
.field("node_id", &self.node_id)
|
||||
.field("node_addr", &self.node_addr)
|
||||
.field("address", &self.address)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
@@ -433,14 +433,14 @@ impl AuthChallenge {
|
||||
}
|
||||
|
||||
/// Verify a response to this challenge.
|
||||
pub fn verify(&self, response: &AuthResponse) -> Result<NodeId, IdentityError> {
|
||||
pub fn verify(&self, response: &AuthResponse) -> Result<NodeAddr, IdentityError> {
|
||||
let digest = auth_challenge_digest(&self.0, response.timestamp);
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
secp.verify_schnorr(&response.signature, &digest, &response.pubkey)
|
||||
.map_err(|_| IdentityError::SignatureVerificationFailed)?;
|
||||
|
||||
Ok(NodeId::from_pubkey(&response.pubkey))
|
||||
Ok(NodeAddr::from_pubkey(&response.pubkey))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,28 +547,28 @@ mod tests {
|
||||
fn test_identity_generation() {
|
||||
let identity = Identity::generate();
|
||||
|
||||
// NodeId should be 32 bytes
|
||||
assert_eq!(identity.node_id().as_bytes().len(), 32);
|
||||
// NodeAddr should be 32 bytes
|
||||
assert_eq!(identity.node_addr().as_bytes().len(), 32);
|
||||
|
||||
// Address should start with 0xfd
|
||||
assert_eq!(identity.address().as_bytes()[0], 0xfd);
|
||||
|
||||
// Address bytes 1-15 should match node_id bytes 0-14
|
||||
// Address bytes 1-15 should match node_addr bytes 0-14
|
||||
assert_eq!(
|
||||
&identity.address().as_bytes()[1..16],
|
||||
&identity.node_id().as_bytes()[0..15]
|
||||
&identity.node_addr().as_bytes()[0..15]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_id_from_pubkey_deterministic() {
|
||||
fn test_node_addr_from_pubkey_deterministic() {
|
||||
let identity = Identity::generate();
|
||||
let pubkey = identity.pubkey();
|
||||
|
||||
let node_id1 = NodeId::from_pubkey(&pubkey);
|
||||
let node_id2 = NodeId::from_pubkey(&pubkey);
|
||||
let node_addr1 = NodeAddr::from_pubkey(&pubkey);
|
||||
let node_addr2 = NodeAddr::from_pubkey(&pubkey);
|
||||
|
||||
assert_eq!(node_id1, node_id2);
|
||||
assert_eq!(node_addr1, node_addr2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -595,7 +595,7 @@ mod tests {
|
||||
let result = challenge.verify(&response);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), *identity.node_id());
|
||||
assert_eq!(result.unwrap(), *identity.node_addr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -636,12 +636,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_id_ordering() {
|
||||
fn test_node_addr_ordering() {
|
||||
let id1 = Identity::generate();
|
||||
let id2 = Identity::generate();
|
||||
|
||||
// NodeIds should be comparable for root election
|
||||
let _cmp = id1.node_id().cmp(id2.node_id());
|
||||
// NodeAddrs should be comparable for root election
|
||||
let _cmp = id1.node_addr().cmp(id2.node_addr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -656,22 +656,22 @@ mod tests {
|
||||
let identity1 = Identity::from_secret_bytes(&secret_bytes).unwrap();
|
||||
let identity2 = Identity::from_secret_bytes(&secret_bytes).unwrap();
|
||||
|
||||
// Same secret key should produce same node_id
|
||||
assert_eq!(identity1.node_id(), identity2.node_id());
|
||||
// Same secret key should produce same node_addr
|
||||
assert_eq!(identity1.node_addr(), identity2.node_addr());
|
||||
assert_eq!(identity1.address(), identity2.address());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_id_from_slice() {
|
||||
fn test_node_addr_from_slice() {
|
||||
let bytes = [0u8; 32];
|
||||
let node_id = NodeId::from_slice(&bytes).unwrap();
|
||||
assert_eq!(node_id.as_bytes(), &bytes);
|
||||
let node_addr = NodeAddr::from_slice(&bytes).unwrap();
|
||||
assert_eq!(node_addr.as_bytes(), &bytes);
|
||||
|
||||
// Wrong length should fail
|
||||
let short = [0u8; 16];
|
||||
assert!(matches!(
|
||||
NodeId::from_slice(&short),
|
||||
Err(IdentityError::InvalidNodeIdLength(16))
|
||||
NodeAddr::from_slice(&short),
|
||||
Err(IdentityError::InvalidNodeAddrLength(16))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -772,7 +772,7 @@ mod tests {
|
||||
let peer = PeerIdentity::from_npub(&npub).unwrap();
|
||||
|
||||
assert_eq!(peer.pubkey(), identity.pubkey());
|
||||
assert_eq!(peer.node_id(), identity.node_id());
|
||||
assert_eq!(peer.node_addr(), identity.node_addr());
|
||||
assert_eq!(peer.address(), identity.address());
|
||||
assert_eq!(peer.npub(), npub);
|
||||
}
|
||||
@@ -877,7 +877,7 @@ mod tests {
|
||||
let identity = Identity::from_secret_str(&nsec).unwrap();
|
||||
let identity_from_bytes = Identity::from_secret_bytes(&secret_bytes).unwrap();
|
||||
|
||||
assert_eq!(identity.node_id(), identity_from_bytes.node_id());
|
||||
assert_eq!(identity.node_addr(), identity_from_bytes.node_addr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -892,6 +892,6 @@ mod tests {
|
||||
let identity = Identity::from_secret_str(hex_str).unwrap();
|
||||
let identity_from_bytes = Identity::from_secret_bytes(&secret_bytes).unwrap();
|
||||
|
||||
assert_eq!(identity.node_id(), identity_from_bytes.node_id());
|
||||
assert_eq!(identity.node_addr(), identity_from_bytes.node_addr());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ pub mod wire;
|
||||
// Re-export identity types
|
||||
pub use identity::{
|
||||
decode_npub, decode_nsec, decode_secret, encode_npub, encode_nsec, AuthChallenge, AuthResponse,
|
||||
FipsAddress, Identity, IdentityError, NodeId, PeerIdentity,
|
||||
FipsAddress, Identity, IdentityError, NodeAddr, PeerIdentity,
|
||||
};
|
||||
|
||||
// Re-export config types
|
||||
|
||||
+79
-79
@@ -23,7 +23,7 @@ use crate::wire::{
|
||||
build_msg1, build_msg2, EncryptedHeader, Msg1Header, Msg2Header,
|
||||
DISCRIMINATOR_ENCRYPTED, DISCRIMINATOR_MSG1, DISCRIMINATOR_MSG2,
|
||||
};
|
||||
use crate::{Config, ConfigError, Identity, IdentityError, NodeId, PeerIdentity};
|
||||
use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::thread::{self, JoinHandle};
|
||||
@@ -56,10 +56,10 @@ pub enum NodeError {
|
||||
ConnectionNotFound(LinkId),
|
||||
|
||||
#[error("peer not found: {0:?}")]
|
||||
PeerNotFound(NodeId),
|
||||
PeerNotFound(NodeAddr),
|
||||
|
||||
#[error("peer already exists: {0:?}")]
|
||||
PeerAlreadyExists(NodeId),
|
||||
PeerAlreadyExists(NodeAddr),
|
||||
|
||||
#[error("connection already exists for link: {0}")]
|
||||
ConnectionAlreadyExists(LinkId),
|
||||
@@ -142,7 +142,7 @@ type AddrKey = (TransportId, TransportAddr);
|
||||
///
|
||||
/// Peers go through two phases:
|
||||
/// 1. **Connection phase** (`connections`): Handshake in progress, indexed by LinkId
|
||||
/// 2. **Active phase** (`peers`): Authenticated, indexed by NodeId
|
||||
/// 2. **Active phase** (`peers`): Authenticated, indexed by NodeAddr
|
||||
///
|
||||
/// The `addr_to_link` map enables dispatching incoming packets to the right
|
||||
/// connection before authentication completes.
|
||||
@@ -195,8 +195,8 @@ pub struct Node {
|
||||
|
||||
// === Peers (Active Phase) ===
|
||||
/// Authenticated peers.
|
||||
/// Indexed by NodeId (verified identity).
|
||||
peers: HashMap<NodeId, ActivePeer>,
|
||||
/// Indexed by NodeAddr (verified identity).
|
||||
peers: HashMap<NodeAddr, ActivePeer>,
|
||||
|
||||
// === Resource Limits ===
|
||||
/// Maximum connections (0 = unlimited).
|
||||
@@ -227,9 +227,9 @@ pub struct Node {
|
||||
// === Index-Based Session Dispatch ===
|
||||
/// Allocator for session indices.
|
||||
index_allocator: IndexAllocator,
|
||||
/// O(1) lookup: (transport_id, our_index) → NodeId.
|
||||
/// O(1) lookup: (transport_id, our_index) → NodeAddr.
|
||||
/// This maps our session index to the peer that uses it.
|
||||
peers_by_index: HashMap<(TransportId, u32), NodeId>,
|
||||
peers_by_index: HashMap<(TransportId, u32), NodeAddr>,
|
||||
/// Pending outbound handshakes by our sender_idx.
|
||||
/// Tracks which LinkId corresponds to which session index.
|
||||
pending_outbound: HashMap<(TransportId, u32), LinkId>,
|
||||
@@ -243,13 +243,13 @@ impl Node {
|
||||
/// Create a new node from configuration.
|
||||
pub fn new(config: Config) -> Result<Self, NodeError> {
|
||||
let identity = config.create_identity()?;
|
||||
let node_id = *identity.node_id();
|
||||
let node_addr = *identity.node_addr();
|
||||
let is_leaf_only = config.is_leaf_only();
|
||||
|
||||
let bloom_state = if is_leaf_only {
|
||||
BloomState::leaf_only(node_id)
|
||||
BloomState::leaf_only(node_addr)
|
||||
} else {
|
||||
BloomState::new(node_id)
|
||||
BloomState::new(node_addr)
|
||||
};
|
||||
|
||||
let tun_state = if config.tun.enabled {
|
||||
@@ -259,7 +259,7 @@ impl Node {
|
||||
};
|
||||
|
||||
// Initialize tree state with signed self-declaration
|
||||
let mut tree_state = TreeState::new(node_id);
|
||||
let mut tree_state = TreeState::new(node_addr);
|
||||
tree_state
|
||||
.sign_declaration(&identity)
|
||||
.expect("signing own declaration should never fail");
|
||||
@@ -298,7 +298,7 @@ impl Node {
|
||||
|
||||
/// Create a node with a specific identity.
|
||||
pub fn with_identity(identity: Identity, config: Config) -> Self {
|
||||
let node_id = *identity.node_id();
|
||||
let node_addr = *identity.node_addr();
|
||||
let tun_state = if config.tun.enabled {
|
||||
TunState::Configured
|
||||
} else {
|
||||
@@ -306,7 +306,7 @@ impl Node {
|
||||
};
|
||||
|
||||
// Initialize tree state with signed self-declaration
|
||||
let mut tree_state = TreeState::new(node_id);
|
||||
let mut tree_state = TreeState::new(node_addr);
|
||||
tree_state
|
||||
.sign_declaration(&identity)
|
||||
.expect("signing own declaration should never fail");
|
||||
@@ -317,7 +317,7 @@ impl Node {
|
||||
state: NodeState::Created,
|
||||
is_leaf_only: false,
|
||||
tree_state,
|
||||
bloom_state: BloomState::new(node_id),
|
||||
bloom_state: BloomState::new(node_addr),
|
||||
coord_cache: CoordCache::with_defaults(),
|
||||
transports: HashMap::new(),
|
||||
links: HashMap::new(),
|
||||
@@ -347,7 +347,7 @@ impl Node {
|
||||
pub fn leaf_only(config: Config) -> Result<Self, NodeError> {
|
||||
let mut node = Self::new(config)?;
|
||||
node.is_leaf_only = true;
|
||||
node.bloom_state = BloomState::leaf_only(*node.identity.node_id());
|
||||
node.bloom_state = BloomState::leaf_only(*node.identity.node_addr());
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
@@ -428,10 +428,10 @@ impl Node {
|
||||
}
|
||||
})?;
|
||||
|
||||
let peer_node_id = *peer_identity.node_id();
|
||||
let peer_node_addr = *peer_identity.node_addr();
|
||||
|
||||
// Check if peer already exists (fully authenticated)
|
||||
if self.peers.contains_key(&peer_node_id) {
|
||||
if self.peers.contains_key(&peer_node_addr) {
|
||||
debug!(
|
||||
npub = %peer_config.npub,
|
||||
"Peer already exists, skipping"
|
||||
@@ -442,7 +442,7 @@ impl Node {
|
||||
// Check if connection already in progress to this peer
|
||||
let already_connecting = self.connections.values().any(|conn| {
|
||||
conn.expected_identity()
|
||||
.map(|id| id.node_id() == &peer_node_id)
|
||||
.map(|id| id.node_addr() == &peer_node_addr)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
if already_connecting {
|
||||
@@ -545,7 +545,7 @@ impl Node {
|
||||
|
||||
info!("Peer connection initiated{}", alias_display);
|
||||
info!(" npub: {}", peer_config.npub);
|
||||
info!(" node_id: {}", peer_node_id);
|
||||
info!(" node_addr: {}", peer_node_addr);
|
||||
info!(" transport: {}", addr.transport);
|
||||
info!(" addr: {}", addr.addr);
|
||||
info!(" link_id: {}", link_id);
|
||||
@@ -599,9 +599,9 @@ impl Node {
|
||||
&self.identity
|
||||
}
|
||||
|
||||
/// Get this node's NodeId.
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
self.identity.node_id()
|
||||
/// Get this node's NodeAddr.
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
self.identity.node_addr()
|
||||
}
|
||||
|
||||
/// Get this node's npub.
|
||||
@@ -855,23 +855,23 @@ impl Node {
|
||||
.remove(&link_id)
|
||||
.ok_or(NodeError::ConnectionNotFound(link_id))?;
|
||||
|
||||
let peer_node_id = *verified_identity.node_id();
|
||||
let peer_node_addr = *verified_identity.node_addr();
|
||||
let is_outbound = connection.is_outbound();
|
||||
|
||||
// Check for cross-connection
|
||||
if let Some(existing_peer) = self.peers.get(&peer_node_id) {
|
||||
if let Some(existing_peer) = self.peers.get(&peer_node_addr) {
|
||||
let existing_link_id = existing_peer.link_id();
|
||||
|
||||
// Determine which connection wins
|
||||
let this_wins = cross_connection_winner(
|
||||
self.identity.node_id(),
|
||||
&peer_node_id,
|
||||
self.identity.node_addr(),
|
||||
&peer_node_addr,
|
||||
is_outbound,
|
||||
);
|
||||
|
||||
if this_wins {
|
||||
// This connection wins, replace the existing peer
|
||||
let old_peer = self.peers.remove(&peer_node_id).unwrap();
|
||||
let old_peer = self.peers.remove(&peer_node_addr).unwrap();
|
||||
let loser_link_id = old_peer.link_id();
|
||||
|
||||
// Create new active peer with stats from handshake
|
||||
@@ -882,10 +882,10 @@ impl Node {
|
||||
connection.link_stats().clone(),
|
||||
);
|
||||
|
||||
self.peers.insert(peer_node_id, new_peer);
|
||||
self.peers.insert(peer_node_addr, new_peer);
|
||||
|
||||
info!(
|
||||
node_id = %peer_node_id,
|
||||
node_addr = %peer_node_addr,
|
||||
winner_link = %link_id,
|
||||
loser_link = %loser_link_id,
|
||||
"Cross-connection resolved: this connection won"
|
||||
@@ -893,12 +893,12 @@ impl Node {
|
||||
|
||||
Ok(PromotionResult::CrossConnectionWon {
|
||||
loser_link_id,
|
||||
node_id: peer_node_id,
|
||||
node_addr: peer_node_addr,
|
||||
})
|
||||
} else {
|
||||
// This connection loses, keep existing
|
||||
info!(
|
||||
node_id = %peer_node_id,
|
||||
node_addr = %peer_node_addr,
|
||||
winner_link = %existing_link_id,
|
||||
loser_link = %link_id,
|
||||
"Cross-connection resolved: this connection lost"
|
||||
@@ -921,33 +921,33 @@ impl Node {
|
||||
connection.link_stats().clone(),
|
||||
);
|
||||
|
||||
self.peers.insert(peer_node_id, new_peer);
|
||||
self.peers.insert(peer_node_addr, new_peer);
|
||||
|
||||
info!(
|
||||
node_id = %peer_node_id,
|
||||
node_addr = %peer_node_addr,
|
||||
link_id = %link_id,
|
||||
"Connection promoted to active peer"
|
||||
);
|
||||
|
||||
Ok(PromotionResult::Promoted(peer_node_id))
|
||||
Ok(PromotionResult::Promoted(peer_node_addr))
|
||||
}
|
||||
}
|
||||
|
||||
// === Peer Management (Active Phase) ===
|
||||
|
||||
/// Get a peer by NodeId.
|
||||
pub fn get_peer(&self, node_id: &NodeId) -> Option<&ActivePeer> {
|
||||
self.peers.get(node_id)
|
||||
/// Get a peer by NodeAddr.
|
||||
pub fn get_peer(&self, node_addr: &NodeAddr) -> Option<&ActivePeer> {
|
||||
self.peers.get(node_addr)
|
||||
}
|
||||
|
||||
/// Get a mutable peer by NodeId.
|
||||
pub fn get_peer_mut(&mut self, node_id: &NodeId) -> Option<&mut ActivePeer> {
|
||||
self.peers.get_mut(node_id)
|
||||
/// Get a mutable peer by NodeAddr.
|
||||
pub fn get_peer_mut(&mut self, node_addr: &NodeAddr) -> Option<&mut ActivePeer> {
|
||||
self.peers.get_mut(node_addr)
|
||||
}
|
||||
|
||||
/// Remove a peer.
|
||||
pub fn remove_peer(&mut self, node_id: &NodeId) -> Option<ActivePeer> {
|
||||
self.peers.remove(node_id)
|
||||
pub fn remove_peer(&mut self, node_addr: &NodeAddr) -> Option<ActivePeer> {
|
||||
self.peers.remove(node_addr)
|
||||
}
|
||||
|
||||
/// Iterate over all peers.
|
||||
@@ -956,7 +956,7 @@ impl Node {
|
||||
}
|
||||
|
||||
/// Iterate over all peer node IDs.
|
||||
pub fn peer_ids(&self) -> impl Iterator<Item = &NodeId> {
|
||||
pub fn peer_ids(&self) -> impl Iterator<Item = &NodeAddr> {
|
||||
self.peers.keys()
|
||||
}
|
||||
|
||||
@@ -975,13 +975,13 @@ impl Node {
|
||||
/// Find next hop for a destination (stub).
|
||||
///
|
||||
/// Returns the peer that minimizes tree distance to the destination.
|
||||
pub fn find_next_hop(&self, _dest_node_id: &NodeId) -> Option<&ActivePeer> {
|
||||
pub fn find_next_hop(&self, _dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
|
||||
// Stub: would implement greedy tree routing
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if a destination is in any peer's bloom filter.
|
||||
pub fn destination_in_filters(&self, dest: &NodeId) -> Vec<&ActivePeer> {
|
||||
pub fn destination_in_filters(&self, dest: &NodeAddr) -> Vec<&ActivePeer> {
|
||||
self.peers.values().filter(|p| p.may_reach(dest)).collect()
|
||||
}
|
||||
|
||||
@@ -1153,7 +1153,7 @@ impl Node {
|
||||
|
||||
// O(1) session lookup by our receiver index
|
||||
let key = (packet.transport_id, header.receiver_idx.as_u32());
|
||||
let node_id = match self.peers_by_index.get(&key) {
|
||||
let node_addr = match self.peers_by_index.get(&key) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
// Unknown index - could be stale session or attack
|
||||
@@ -1166,7 +1166,7 @@ impl Node {
|
||||
}
|
||||
};
|
||||
|
||||
let peer = match self.peers.get_mut(&node_id) {
|
||||
let peer = match self.peers.get_mut(&node_addr) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
// Peer removed but index not cleaned up - fix it
|
||||
@@ -1180,7 +1180,7 @@ impl Node {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
warn!(
|
||||
node_id = %node_id,
|
||||
node_addr = %node_addr,
|
||||
"Peer in index map has no session"
|
||||
);
|
||||
return;
|
||||
@@ -1193,7 +1193,7 @@ impl Node {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
node_id = %node_id,
|
||||
node_addr = %node_addr,
|
||||
counter = header.counter,
|
||||
error = %e,
|
||||
"Decryption failed"
|
||||
@@ -1212,7 +1212,7 @@ impl Node {
|
||||
peer.touch(packet.timestamp_ms);
|
||||
|
||||
// Dispatch to link message handler
|
||||
self.dispatch_link_message(&node_id, &plaintext).await;
|
||||
self.dispatch_link_message(&node_addr, &plaintext).await;
|
||||
}
|
||||
|
||||
/// Handle handshake message 1 (discriminator 0x01).
|
||||
@@ -1284,14 +1284,14 @@ impl Node {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let peer_node_id = *peer_identity.node_id();
|
||||
let peer_node_addr = *peer_identity.node_addr();
|
||||
|
||||
// Check if this peer is already connected
|
||||
if self.peers.contains_key(&peer_node_id) {
|
||||
if self.peers.contains_key(&peer_node_addr) {
|
||||
// TODO: Handle reconnection case (future: session replacement)
|
||||
self.msg1_rate_limiter.complete_handshake();
|
||||
debug!(
|
||||
node_id = %peer_node_id,
|
||||
node_addr = %peer_node_addr,
|
||||
"Peer already connected, ignoring msg1"
|
||||
);
|
||||
return;
|
||||
@@ -1355,7 +1355,7 @@ impl Node {
|
||||
}
|
||||
|
||||
info!(
|
||||
node_id = %peer_node_id,
|
||||
node_addr = %peer_node_addr,
|
||||
link_id = %link_id,
|
||||
our_index = %our_index,
|
||||
"Inbound handshake initiated"
|
||||
@@ -1425,7 +1425,7 @@ impl Node {
|
||||
};
|
||||
|
||||
info!(
|
||||
node_id = %peer_identity.node_id(),
|
||||
node_addr = %peer_identity.node_addr(),
|
||||
link_id = %link_id,
|
||||
their_index = %header.sender_idx,
|
||||
"Outbound handshake completed"
|
||||
@@ -1439,15 +1439,15 @@ impl Node {
|
||||
self.pending_outbound.remove(&key);
|
||||
|
||||
match result {
|
||||
PromotionResult::Promoted(node_id) => {
|
||||
PromotionResult::Promoted(node_addr) => {
|
||||
info!(
|
||||
node_id = %node_id,
|
||||
node_addr = %node_addr,
|
||||
"Peer promoted to active"
|
||||
);
|
||||
}
|
||||
PromotionResult::CrossConnectionWon { loser_link_id, node_id } => {
|
||||
PromotionResult::CrossConnectionWon { loser_link_id, node_addr } => {
|
||||
info!(
|
||||
node_id = %node_id,
|
||||
node_addr = %node_addr,
|
||||
loser_link_id = %loser_link_id,
|
||||
"Cross-connection won"
|
||||
);
|
||||
@@ -1473,7 +1473,7 @@ impl Node {
|
||||
/// Dispatch a decrypted link message to the appropriate handler.
|
||||
///
|
||||
/// Link messages are protocol messages exchanged between authenticated peers.
|
||||
async fn dispatch_link_message(&mut self, _from: &NodeId, plaintext: &[u8]) {
|
||||
async fn dispatch_link_message(&mut self, _from: &NodeAddr, plaintext: &[u8]) {
|
||||
if plaintext.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -1584,7 +1584,7 @@ impl Node {
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Node")
|
||||
.field("node_id", self.node_id())
|
||||
.field("node_addr", self.node_addr())
|
||||
.field("state", &self.state)
|
||||
.field("is_leaf_only", &self.is_leaf_only)
|
||||
.field("connections", &self.connection_count())
|
||||
@@ -1607,10 +1607,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn make_peer_identity() -> PeerIdentity {
|
||||
@@ -1632,12 +1632,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_node_with_identity() {
|
||||
let identity = Identity::generate();
|
||||
let expected_node_id = *identity.node_id();
|
||||
let expected_node_addr = *identity.node_addr();
|
||||
let config = Config::new();
|
||||
|
||||
let node = Node::with_identity(identity, config);
|
||||
|
||||
assert_eq!(node.node_id(), &expected_node_id);
|
||||
assert_eq!(node.node_addr(), &expected_node_addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1783,7 +1783,7 @@ mod tests {
|
||||
let mut node = make_node();
|
||||
|
||||
let identity = make_peer_identity();
|
||||
let node_id = *identity.node_id();
|
||||
let node_addr = *identity.node_addr();
|
||||
let link_id = LinkId::new(1);
|
||||
let conn = PeerConnection::outbound(link_id, identity.clone(), 1000);
|
||||
|
||||
@@ -1797,7 +1797,7 @@ mod tests {
|
||||
assert_eq!(node.connection_count(), 0);
|
||||
assert_eq!(node.peer_count(), 1);
|
||||
|
||||
let peer = node.get_peer(&node_id).unwrap();
|
||||
let peer = node.get_peer(&node_addr).unwrap();
|
||||
assert_eq!(peer.authenticated_at(), 2000);
|
||||
}
|
||||
|
||||
@@ -1807,7 +1807,7 @@ mod tests {
|
||||
|
||||
// First connection and promotion (becomes active peer)
|
||||
let identity = make_peer_identity();
|
||||
let node_id = *identity.node_id();
|
||||
let node_addr = *identity.node_addr();
|
||||
let link_id1 = LinkId::new(1);
|
||||
let conn1 = PeerConnection::outbound(link_id1, identity.clone(), 1000);
|
||||
|
||||
@@ -1815,7 +1815,7 @@ mod tests {
|
||||
node.promote_connection(link_id1, identity.clone(), 1500).unwrap();
|
||||
|
||||
assert_eq!(node.peer_count(), 1);
|
||||
assert_eq!(node.get_peer(&node_id).unwrap().link_id(), link_id1);
|
||||
assert_eq!(node.get_peer(&node_addr).unwrap().link_id(), link_id1);
|
||||
|
||||
// Second connection (simulates cross-connection scenario)
|
||||
let link_id2 = LinkId::new(2);
|
||||
@@ -1830,11 +1830,11 @@ mod tests {
|
||||
match result {
|
||||
PromotionResult::CrossConnectionWon { loser_link_id, .. } => {
|
||||
assert_eq!(loser_link_id, link_id1);
|
||||
assert_eq!(node.get_peer(&node_id).unwrap().link_id(), link_id2);
|
||||
assert_eq!(node.get_peer(&node_addr).unwrap().link_id(), link_id2);
|
||||
}
|
||||
PromotionResult::CrossConnectionLost { winner_link_id } => {
|
||||
assert_eq!(winner_link_id, link_id1);
|
||||
assert_eq!(node.get_peer(&node_id).unwrap().link_id(), link_id1);
|
||||
assert_eq!(node.get_peer(&node_addr).unwrap().link_id(), link_id1);
|
||||
}
|
||||
PromotionResult::Promoted(_) => {
|
||||
panic!("Expected cross-connection, got normal promotion");
|
||||
@@ -1912,7 +1912,7 @@ mod tests {
|
||||
|
||||
// Add a healthy peer
|
||||
let identity1 = make_peer_identity();
|
||||
let node_id1 = *identity1.node_id();
|
||||
let node_addr1 = *identity1.node_addr();
|
||||
let link_id1 = LinkId::new(1);
|
||||
let conn1 = PeerConnection::outbound(link_id1, identity1.clone(), 1000);
|
||||
node.add_connection(conn1).unwrap();
|
||||
@@ -1927,19 +1927,19 @@ mod tests {
|
||||
|
||||
// Add a third peer and mark it disconnected (not sendable)
|
||||
let identity3 = make_peer_identity();
|
||||
let node_id3 = *identity3.node_id();
|
||||
let node_addr3 = *identity3.node_addr();
|
||||
let link_id3 = LinkId::new(3);
|
||||
let conn3 = PeerConnection::outbound(link_id3, identity3.clone(), 1000);
|
||||
node.add_connection(conn3).unwrap();
|
||||
node.promote_connection(link_id3, identity3, 2000).unwrap();
|
||||
node.get_peer_mut(&node_id3).unwrap().mark_disconnected();
|
||||
node.get_peer_mut(&node_addr3).unwrap().mark_disconnected();
|
||||
|
||||
assert_eq!(node.peer_count(), 3);
|
||||
assert_eq!(node.sendable_peer_count(), 2);
|
||||
|
||||
let sendable: Vec<_> = node.sendable_peers().collect();
|
||||
assert_eq!(sendable.len(), 2);
|
||||
assert!(sendable.iter().any(|p| p.node_id() == &node_id1));
|
||||
assert!(sendable.iter().any(|p| p.node_addr() == &node_addr1));
|
||||
}
|
||||
|
||||
// === RX Loop Tests ===
|
||||
@@ -1979,17 +1979,17 @@ mod tests {
|
||||
fn test_node_peers_by_index_tracking() {
|
||||
let mut node = make_node();
|
||||
let transport_id = TransportId::new(1);
|
||||
let node_id = make_node_id(42);
|
||||
let node_addr = make_node_addr(42);
|
||||
|
||||
// Allocate an index
|
||||
let index = node.index_allocator.allocate().unwrap();
|
||||
|
||||
// Track in peers_by_index
|
||||
node.peers_by_index.insert((transport_id, index.as_u32()), node_id);
|
||||
node.peers_by_index.insert((transport_id, index.as_u32()), node_addr);
|
||||
|
||||
// Verify lookup
|
||||
let found = node.peers_by_index.get(&(transport_id, index.as_u32()));
|
||||
assert_eq!(found, Some(&node_id));
|
||||
assert_eq!(found, Some(&node_addr));
|
||||
|
||||
// Clean up
|
||||
node.peers_by_index.remove(&(transport_id, index.as_u32()));
|
||||
|
||||
+13
-13
@@ -8,7 +8,7 @@ use crate::index::SessionIndex;
|
||||
use crate::noise::NoiseSession;
|
||||
use crate::transport::{LinkId, LinkStats, TransportAddr, TransportId};
|
||||
use crate::tree::{ParentDeclaration, TreeCoordinate};
|
||||
use crate::{FipsAddress, NodeId, PeerIdentity};
|
||||
use crate::{FipsAddress, NodeAddr, PeerIdentity};
|
||||
use secp256k1::XOnlyPublicKey;
|
||||
use std::fmt;
|
||||
|
||||
@@ -203,9 +203,9 @@ impl ActivePeer {
|
||||
&self.identity
|
||||
}
|
||||
|
||||
/// Get the peer's NodeId.
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
self.identity.node_id()
|
||||
/// Get the peer's NodeAddr.
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
self.identity.node_addr()
|
||||
}
|
||||
|
||||
/// Get the peer's FIPS address.
|
||||
@@ -338,9 +338,9 @@ impl ActivePeer {
|
||||
}
|
||||
|
||||
/// Check if a destination might be reachable through this peer.
|
||||
pub fn may_reach(&self, node_id: &NodeId) -> bool {
|
||||
pub fn may_reach(&self, node_addr: &NodeAddr) -> bool {
|
||||
match &self.inbound_filter {
|
||||
Some(filter) => filter.contains(node_id),
|
||||
Some(filter) => filter.contains(node_addr),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@@ -487,14 +487,14 @@ mod tests {
|
||||
PeerIdentity::from_pubkey(identity.pubkey())
|
||||
}
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn make_coords(ids: &[u8]) -> TreeCoordinate {
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_id(v)).collect()).unwrap()
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -516,7 +516,7 @@ mod tests {
|
||||
let identity = make_peer_identity();
|
||||
let peer = ActivePeer::new(identity.clone(), LinkId::new(1), 1000);
|
||||
|
||||
assert_eq!(peer.identity().node_id(), identity.node_id());
|
||||
assert_eq!(peer.identity().node_addr(), identity.node_addr());
|
||||
assert_eq!(peer.link_id(), LinkId::new(1));
|
||||
assert!(peer.is_healthy());
|
||||
assert!(peer.can_send());
|
||||
@@ -558,8 +558,8 @@ mod tests {
|
||||
assert!(!peer.has_tree_position());
|
||||
assert!(peer.coords().is_none());
|
||||
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
let decl = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
let coords = make_coords(&[1, 2, 0]);
|
||||
|
||||
@@ -574,7 +574,7 @@ mod tests {
|
||||
fn test_bloom_filter() {
|
||||
let identity = make_peer_identity();
|
||||
let mut peer = ActivePeer::new(identity, LinkId::new(1), 1000);
|
||||
let target = make_node_id(42);
|
||||
let target = make_node_addr(42);
|
||||
|
||||
assert!(!peer.may_reach(&target));
|
||||
assert!(peer.filter_is_stale(2000, 500));
|
||||
|
||||
+43
-43
@@ -14,7 +14,7 @@ pub use active::{ActivePeer, ConnectivityState};
|
||||
pub use connection::{HandshakeState, PeerConnection};
|
||||
|
||||
use crate::transport::LinkId;
|
||||
use crate::NodeId;
|
||||
use crate::NodeAddr;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -29,13 +29,13 @@ pub enum PeerError {
|
||||
NotAuthenticated,
|
||||
|
||||
#[error("peer not found: {0:?}")]
|
||||
NotFound(NodeId),
|
||||
NotFound(NodeAddr),
|
||||
|
||||
#[error("connection not found: {0}")]
|
||||
ConnectionNotFound(LinkId),
|
||||
|
||||
#[error("peer already exists: {0:?}")]
|
||||
AlreadyExists(NodeId),
|
||||
AlreadyExists(NodeAddr),
|
||||
|
||||
#[error("handshake failed: {0}")]
|
||||
HandshakeFailed(String),
|
||||
@@ -44,7 +44,7 @@ pub enum PeerError {
|
||||
HandshakeTimeout,
|
||||
|
||||
#[error("identity mismatch: expected {expected:?}, got {actual:?}")]
|
||||
IdentityMismatch { expected: NodeId, actual: NodeId },
|
||||
IdentityMismatch { expected: NodeAddr, actual: NodeAddr },
|
||||
|
||||
#[error("peer disconnected")]
|
||||
Disconnected,
|
||||
@@ -66,13 +66,13 @@ pub enum PeerError {
|
||||
/// connection to this peer (cross-connection). The tie-breaker rule
|
||||
/// determines which connection survives.
|
||||
///
|
||||
/// Note: Returns NodeId instead of ActivePeer because ActivePeer cannot
|
||||
/// Note: Returns NodeAddr instead of ActivePeer because ActivePeer cannot
|
||||
/// be cloned (it contains NoiseSession which has cryptographic state).
|
||||
/// Callers can look up the peer from the peers map using the NodeId.
|
||||
/// Callers can look up the peer from the peers map using the NodeAddr.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PromotionResult {
|
||||
/// New peer created successfully.
|
||||
Promoted(NodeId),
|
||||
Promoted(NodeAddr),
|
||||
|
||||
/// Cross-connection detected. This connection lost the tie-breaker
|
||||
/// and should be closed.
|
||||
@@ -87,16 +87,16 @@ pub enum PromotionResult {
|
||||
/// The link that lost (previous connection, now closed).
|
||||
loser_link_id: LinkId,
|
||||
/// The node ID of the peer.
|
||||
node_id: NodeId,
|
||||
node_addr: NodeAddr,
|
||||
},
|
||||
}
|
||||
|
||||
impl PromotionResult {
|
||||
/// Get the node ID if promotion succeeded.
|
||||
pub fn node_id(&self) -> Option<NodeId> {
|
||||
pub fn node_addr(&self) -> Option<NodeAddr> {
|
||||
match self {
|
||||
PromotionResult::Promoted(node_id) => Some(*node_id),
|
||||
PromotionResult::CrossConnectionWon { node_id, .. } => Some(*node_id),
|
||||
PromotionResult::Promoted(node_addr) => Some(*node_addr),
|
||||
PromotionResult::CrossConnectionWon { node_addr, .. } => Some(*node_addr),
|
||||
PromotionResult::CrossConnectionLost { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -118,22 +118,22 @@ impl PromotionResult {
|
||||
|
||||
/// Determine winner of cross-connection tie-breaker.
|
||||
///
|
||||
/// Rule: The node with the smaller node_id prefers its OUTBOUND connection.
|
||||
/// Rule: The node with the smaller node_addr prefers its OUTBOUND connection.
|
||||
/// This is deterministic and symmetric: both nodes will reach the same conclusion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `our_node_id` - Our node's ID
|
||||
/// * `their_node_id` - The peer's node ID
|
||||
/// * `our_node_addr` - Our node's ID
|
||||
/// * `their_node_addr` - The peer's node ID
|
||||
/// * `this_is_outbound` - Whether the connection being evaluated is our outbound
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if this connection should win (survive), `false` if it should close.
|
||||
pub fn cross_connection_winner(
|
||||
our_node_id: &NodeId,
|
||||
their_node_id: &NodeId,
|
||||
our_node_addr: &NodeAddr,
|
||||
their_node_addr: &NodeAddr,
|
||||
this_is_outbound: bool,
|
||||
) -> bool {
|
||||
let we_are_smaller = our_node_id < their_node_id;
|
||||
let we_are_smaller = our_node_addr < their_node_addr;
|
||||
|
||||
// Smaller node's outbound wins
|
||||
// If we're smaller: our outbound wins, our inbound loses
|
||||
@@ -224,14 +224,14 @@ impl PeerSlot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the known node_id, if any.
|
||||
/// Get the known node_addr, if any.
|
||||
///
|
||||
/// For connections, this is the expected identity (may be None for inbound).
|
||||
/// For active peers, this is always known.
|
||||
pub fn node_id(&self) -> Option<&NodeId> {
|
||||
pub fn node_addr(&self) -> Option<&NodeAddr> {
|
||||
match self {
|
||||
PeerSlot::Connecting(conn) => conn.expected_identity().map(|id| id.node_id()),
|
||||
PeerSlot::Active(peer) => Some(peer.node_id()),
|
||||
PeerSlot::Connecting(conn) => conn.expected_identity().map(|id| id.node_addr()),
|
||||
PeerSlot::Active(peer) => Some(peer.node_addr()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ impl fmt::Display for PeerSlot {
|
||||
write!(f, "connecting(link={}, state={})", conn.link_id(), conn.handshake_state())
|
||||
}
|
||||
PeerSlot::Active(peer) => {
|
||||
write!(f, "active(node={:?}, link={})", peer.node_id(), peer.link_id())
|
||||
write!(f, "active(node={:?}, link={})", peer.node_addr(), peer.link_id())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,10 +259,10 @@ mod tests {
|
||||
use crate::transport::LinkId;
|
||||
use crate::{Identity, PeerIdentity};
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn make_peer_identity() -> PeerIdentity {
|
||||
@@ -272,8 +272,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_cross_connection_smaller_node_wins_outbound() {
|
||||
let node_a = make_node_id(1); // smaller
|
||||
let node_b = make_node_id(2); // larger
|
||||
let node_a = make_node_addr(1); // smaller
|
||||
let node_b = make_node_addr(2); // larger
|
||||
|
||||
// Node A's perspective
|
||||
assert!(cross_connection_winner(&node_a, &node_b, true)); // A's outbound wins
|
||||
@@ -286,8 +286,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_cross_connection_symmetric() {
|
||||
let node_a = make_node_id(1);
|
||||
let node_b = make_node_id(2);
|
||||
let node_a = make_node_addr(1);
|
||||
let node_b = make_node_addr(2);
|
||||
|
||||
// A's outbound = B's inbound
|
||||
let a_outbound_wins = cross_connection_winner(&node_a, &node_b, true);
|
||||
@@ -332,11 +332,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_promotion_result_promoted() {
|
||||
let identity = make_peer_identity();
|
||||
let node_id = *identity.node_id();
|
||||
let result = PromotionResult::Promoted(node_id);
|
||||
let node_addr = *identity.node_addr();
|
||||
let result = PromotionResult::Promoted(node_addr);
|
||||
|
||||
assert!(result.node_id().is_some());
|
||||
assert_eq!(result.node_id(), Some(node_id));
|
||||
assert!(result.node_addr().is_some());
|
||||
assert_eq!(result.node_addr(), Some(node_addr));
|
||||
assert!(!result.should_close_this_connection());
|
||||
assert!(result.link_to_close().is_none());
|
||||
}
|
||||
@@ -347,7 +347,7 @@ mod tests {
|
||||
winner_link_id: LinkId::new(1),
|
||||
};
|
||||
|
||||
assert!(result.node_id().is_none());
|
||||
assert!(result.node_addr().is_none());
|
||||
assert!(result.should_close_this_connection());
|
||||
assert!(result.link_to_close().is_none()); // Caller closes their own
|
||||
}
|
||||
@@ -355,37 +355,37 @@ mod tests {
|
||||
#[test]
|
||||
fn test_promotion_result_cross_won() {
|
||||
let identity = make_peer_identity();
|
||||
let node_id = *identity.node_id();
|
||||
let node_addr = *identity.node_addr();
|
||||
let result = PromotionResult::CrossConnectionWon {
|
||||
loser_link_id: LinkId::new(1),
|
||||
node_id,
|
||||
node_addr,
|
||||
};
|
||||
|
||||
assert!(result.node_id().is_some());
|
||||
assert_eq!(result.node_id(), Some(node_id));
|
||||
assert!(result.node_addr().is_some());
|
||||
assert_eq!(result.node_addr(), Some(node_addr));
|
||||
assert!(!result.should_close_this_connection());
|
||||
assert_eq!(result.link_to_close(), Some(LinkId::new(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_slot_node_id() {
|
||||
fn test_peer_slot_node_addr() {
|
||||
// Outbound connection knows expected identity
|
||||
let identity = make_peer_identity();
|
||||
let expected_node_id = *identity.node_id();
|
||||
let expected_node_addr = *identity.node_addr();
|
||||
let conn = PeerConnection::outbound(LinkId::new(1), identity, 1000);
|
||||
let slot = PeerSlot::Connecting(conn);
|
||||
assert_eq!(slot.node_id(), Some(&expected_node_id));
|
||||
assert_eq!(slot.node_addr(), Some(&expected_node_addr));
|
||||
|
||||
// Inbound connection doesn't know identity yet
|
||||
let conn_inbound = PeerConnection::inbound(LinkId::new(2), 2000);
|
||||
let slot_inbound = PeerSlot::Connecting(conn_inbound);
|
||||
assert!(slot_inbound.node_id().is_none());
|
||||
assert!(slot_inbound.node_addr().is_none());
|
||||
|
||||
// Active peer always knows identity
|
||||
let identity2 = make_peer_identity();
|
||||
let active_node_id = *identity2.node_id();
|
||||
let active_node_addr = *identity2.node_addr();
|
||||
let peer = ActivePeer::new(identity2, LinkId::new(3), 3000);
|
||||
let slot_active = PeerSlot::Active(peer);
|
||||
assert_eq!(slot_active.node_id(), Some(&active_node_id));
|
||||
assert_eq!(slot_active.node_addr(), Some(&active_node_addr));
|
||||
}
|
||||
}
|
||||
|
||||
+37
-37
@@ -22,7 +22,7 @@
|
||||
|
||||
use crate::bloom::BloomFilter;
|
||||
use crate::tree::{ParentDeclaration, TreeCoordinate};
|
||||
use crate::{FipsAddress, NodeId};
|
||||
use crate::{FipsAddress, NodeAddr};
|
||||
use secp256k1::schnorr::Signature;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
@@ -378,9 +378,9 @@ pub struct LookupRequest {
|
||||
/// Unique request identifier.
|
||||
pub request_id: u64,
|
||||
/// Node we're looking for.
|
||||
pub target: NodeId,
|
||||
pub target: NodeAddr,
|
||||
/// Who's asking (for response routing).
|
||||
pub origin: NodeId,
|
||||
pub origin: NodeAddr,
|
||||
/// Origin's coordinates (for return path).
|
||||
pub origin_coords: TreeCoordinate,
|
||||
/// Remaining propagation hops.
|
||||
@@ -393,8 +393,8 @@ impl LookupRequest {
|
||||
/// Create a new lookup request.
|
||||
pub fn new(
|
||||
request_id: u64,
|
||||
target: NodeId,
|
||||
origin: NodeId,
|
||||
target: NodeAddr,
|
||||
origin: NodeAddr,
|
||||
origin_coords: TreeCoordinate,
|
||||
ttl: u8,
|
||||
) -> Self {
|
||||
@@ -412,8 +412,8 @@ impl LookupRequest {
|
||||
|
||||
/// Generate a new request with a random ID.
|
||||
pub fn generate(
|
||||
target: NodeId,
|
||||
origin: NodeId,
|
||||
target: NodeAddr,
|
||||
origin: NodeAddr,
|
||||
origin_coords: TreeCoordinate,
|
||||
ttl: u8,
|
||||
) -> Self {
|
||||
@@ -425,12 +425,12 @@ impl LookupRequest {
|
||||
/// Decrement TTL and add self to visited.
|
||||
///
|
||||
/// Returns false if TTL was already 0.
|
||||
pub fn forward(&mut self, my_node_id: &NodeId) -> bool {
|
||||
pub fn forward(&mut self, my_node_addr: &NodeAddr) -> bool {
|
||||
if self.ttl == 0 {
|
||||
return false;
|
||||
}
|
||||
self.ttl -= 1;
|
||||
self.visited.insert(my_node_id);
|
||||
self.visited.insert(my_node_addr);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -440,8 +440,8 @@ impl LookupRequest {
|
||||
}
|
||||
|
||||
/// Check if a node was already visited.
|
||||
pub fn was_visited(&self, node_id: &NodeId) -> bool {
|
||||
self.visited.contains(node_id)
|
||||
pub fn was_visited(&self, node_addr: &NodeAddr) -> bool {
|
||||
self.visited.contains(node_addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ pub struct LookupResponse {
|
||||
/// Echoed request identifier.
|
||||
pub request_id: u64,
|
||||
/// The target node.
|
||||
pub target: NodeId,
|
||||
pub target: NodeAddr,
|
||||
/// Target's coordinates in the tree.
|
||||
pub target_coords: TreeCoordinate,
|
||||
/// Proof that target authorized this response (signature over request).
|
||||
@@ -464,7 +464,7 @@ impl LookupResponse {
|
||||
/// Create a new lookup response.
|
||||
pub fn new(
|
||||
request_id: u64,
|
||||
target: NodeId,
|
||||
target: NodeAddr,
|
||||
target_coords: TreeCoordinate,
|
||||
proof: Signature,
|
||||
) -> Self {
|
||||
@@ -479,7 +479,7 @@ impl LookupResponse {
|
||||
/// Get the bytes that should be signed as proof.
|
||||
///
|
||||
/// Format: request_id (8) || target (32)
|
||||
pub fn proof_bytes(request_id: u64, target: &NodeId) -> Vec<u8> {
|
||||
pub fn proof_bytes(request_id: u64, target: &NodeAddr) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(40);
|
||||
bytes.extend_from_slice(&request_id.to_le_bytes());
|
||||
bytes.extend_from_slice(target.as_bytes());
|
||||
@@ -803,12 +803,12 @@ pub struct CoordsRequired {
|
||||
/// Destination that couldn't be routed.
|
||||
pub dest_addr: FipsAddress,
|
||||
/// Router reporting the miss.
|
||||
pub reporter: NodeId,
|
||||
pub reporter: NodeAddr,
|
||||
}
|
||||
|
||||
impl CoordsRequired {
|
||||
/// Create a new CoordsRequired error.
|
||||
pub fn new(dest_addr: FipsAddress, reporter: NodeId) -> Self {
|
||||
pub fn new(dest_addr: FipsAddress, reporter: NodeAddr) -> Self {
|
||||
Self { dest_addr, reporter }
|
||||
}
|
||||
}
|
||||
@@ -823,14 +823,14 @@ pub struct PathBroken {
|
||||
/// Destination that couldn't be reached.
|
||||
pub dest_addr: FipsAddress,
|
||||
/// Node that detected the failure.
|
||||
pub reporter: NodeId,
|
||||
pub reporter: NodeAddr,
|
||||
/// Optional: last known coordinates of destination.
|
||||
pub last_known_coords: Option<TreeCoordinate>,
|
||||
}
|
||||
|
||||
impl PathBroken {
|
||||
/// Create a new PathBroken error.
|
||||
pub fn new(original_src: FipsAddress, dest_addr: FipsAddress, reporter: NodeId) -> Self {
|
||||
pub fn new(original_src: FipsAddress, dest_addr: FipsAddress, reporter: NodeAddr) -> Self {
|
||||
Self {
|
||||
original_src,
|
||||
dest_addr,
|
||||
@@ -850,10 +850,10 @@ impl PathBroken {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn make_address(val: u8) -> FipsAddress {
|
||||
@@ -863,7 +863,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn make_coords(ids: &[u8]) -> TreeCoordinate {
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_id(v)).collect()).unwrap()
|
||||
TreeCoordinate::new(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
|
||||
}
|
||||
|
||||
// ===== HandshakeMessageType Tests =====
|
||||
@@ -1042,10 +1042,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lookup_request_forward() {
|
||||
let target = make_node_id(1);
|
||||
let origin = make_node_id(2);
|
||||
let target = make_node_addr(1);
|
||||
let origin = make_node_addr(2);
|
||||
let coords = make_coords(&[2, 0]);
|
||||
let forwarder = make_node_id(3);
|
||||
let forwarder = make_node_addr(3);
|
||||
|
||||
let mut request = LookupRequest::new(123, target, origin, coords, 5);
|
||||
|
||||
@@ -1060,21 +1060,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lookup_request_ttl_exhausted() {
|
||||
let target = make_node_id(1);
|
||||
let origin = make_node_id(2);
|
||||
let target = make_node_addr(1);
|
||||
let origin = make_node_addr(2);
|
||||
let coords = make_coords(&[2, 0]);
|
||||
|
||||
let mut request = LookupRequest::new(123, target, origin, coords, 1);
|
||||
|
||||
assert!(request.forward(&make_node_id(3)));
|
||||
assert!(request.forward(&make_node_addr(3)));
|
||||
assert!(!request.can_forward());
|
||||
assert!(!request.forward(&make_node_id(4)));
|
||||
assert!(!request.forward(&make_node_addr(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_request_generate() {
|
||||
let target = make_node_id(1);
|
||||
let origin = make_node_id(2);
|
||||
let target = make_node_addr(1);
|
||||
let origin = make_node_addr(2);
|
||||
let coords = make_coords(&[2, 0]);
|
||||
|
||||
let req1 = LookupRequest::generate(target, origin, coords.clone(), 5);
|
||||
@@ -1088,7 +1088,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lookup_response_proof_bytes() {
|
||||
let target = make_node_id(42);
|
||||
let target = make_node_addr(42);
|
||||
let bytes = LookupResponse::proof_bytes(12345, &target);
|
||||
|
||||
assert_eq!(bytes.len(), 40); // 8 + 32
|
||||
@@ -1166,17 +1166,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_coords_required() {
|
||||
let err = CoordsRequired::new(make_address(1), make_node_id(2));
|
||||
let err = CoordsRequired::new(make_address(1), make_node_addr(2));
|
||||
|
||||
assert_eq!(err.dest_addr, make_address(1));
|
||||
assert_eq!(err.reporter, make_node_id(2));
|
||||
assert_eq!(err.reporter, make_node_addr(2));
|
||||
}
|
||||
|
||||
// ===== PathBroken Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_path_broken() {
|
||||
let err = PathBroken::new(make_address(1), make_address(2), make_node_id(3))
|
||||
let err = PathBroken::new(make_address(1), make_address(2), make_node_addr(3))
|
||||
.with_last_coords(make_coords(&[2, 0]));
|
||||
|
||||
assert!(err.last_known_coords.is_some());
|
||||
@@ -1186,14 +1186,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_announce() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
let decl = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
let ancestry = make_coords(&[1, 2, 0]);
|
||||
|
||||
let announce = TreeAnnounce::new(decl, ancestry);
|
||||
|
||||
assert_eq!(announce.declaration.node_id(), &node);
|
||||
assert_eq!(announce.declaration.node_addr(), &node);
|
||||
assert_eq!(announce.ancestry.depth(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
+136
-136
@@ -4,7 +4,7 @@
|
||||
//! The spanning tree provides a routing topology where each node maintains
|
||||
//! a path to a common root, enabling greedy distance-based routing.
|
||||
|
||||
use crate::{Identity, IdentityError, NodeId};
|
||||
use crate::{Identity, IdentityError, NodeAddr};
|
||||
use secp256k1::schnorr::Signature;
|
||||
use secp256k1::XOnlyPublicKey;
|
||||
use std::collections::HashMap;
|
||||
@@ -21,13 +21,13 @@ pub enum TreeError {
|
||||
AncestryNotToRoot,
|
||||
|
||||
#[error("signature verification failed for node {0:?}")]
|
||||
InvalidSignature(NodeId),
|
||||
InvalidSignature(NodeAddr),
|
||||
|
||||
#[error("sequence number regression: got {got}, expected > {expected}")]
|
||||
SequenceRegression { got: u64, expected: u64 },
|
||||
|
||||
#[error("parent not in peers: {0:?}")]
|
||||
ParentNotPeer(NodeId),
|
||||
ParentNotPeer(NodeAddr),
|
||||
|
||||
#[error("identity error: {0}")]
|
||||
Identity(#[from] IdentityError),
|
||||
@@ -37,14 +37,14 @@ pub enum TreeError {
|
||||
///
|
||||
/// Each node periodically announces its parent selection. The declaration
|
||||
/// includes a monotonic sequence number for freshness and a signature
|
||||
/// for authenticity. When `parent_id == node_id`, the node declares itself
|
||||
/// for authenticity. When `parent_id == node_addr`, the node declares itself
|
||||
/// as a root candidate.
|
||||
#[derive(Clone)]
|
||||
pub struct ParentDeclaration {
|
||||
/// The node making this declaration.
|
||||
node_id: NodeId,
|
||||
/// The selected parent (equals node_id if self-declaring as root).
|
||||
parent_id: NodeId,
|
||||
node_addr: NodeAddr,
|
||||
/// The selected parent (equals node_addr if self-declaring as root).
|
||||
parent_id: NodeAddr,
|
||||
/// Monotonically increasing sequence number.
|
||||
sequence: u64,
|
||||
/// Timestamp when this declaration was created (Unix seconds).
|
||||
@@ -57,9 +57,9 @@ impl ParentDeclaration {
|
||||
/// Create a new unsigned parent declaration.
|
||||
///
|
||||
/// The declaration must be signed before transmission using `set_signature()`.
|
||||
pub fn new(node_id: NodeId, parent_id: NodeId, sequence: u64, timestamp: u64) -> Self {
|
||||
pub fn new(node_addr: NodeAddr, parent_id: NodeAddr, sequence: u64, timestamp: u64) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
node_addr,
|
||||
parent_id,
|
||||
sequence,
|
||||
timestamp,
|
||||
@@ -68,20 +68,20 @@ impl ParentDeclaration {
|
||||
}
|
||||
|
||||
/// Create a self-declaration (node is root candidate).
|
||||
pub fn self_root(node_id: NodeId, sequence: u64, timestamp: u64) -> Self {
|
||||
Self::new(node_id, node_id, sequence, timestamp)
|
||||
pub fn self_root(node_addr: NodeAddr, sequence: u64, timestamp: u64) -> Self {
|
||||
Self::new(node_addr, node_addr, sequence, timestamp)
|
||||
}
|
||||
|
||||
/// Create a declaration with a pre-computed signature.
|
||||
pub fn with_signature(
|
||||
node_id: NodeId,
|
||||
parent_id: NodeId,
|
||||
node_addr: NodeAddr,
|
||||
parent_id: NodeAddr,
|
||||
sequence: u64,
|
||||
timestamp: u64,
|
||||
signature: Signature,
|
||||
) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
node_addr,
|
||||
parent_id,
|
||||
sequence,
|
||||
timestamp,
|
||||
@@ -90,12 +90,12 @@ impl ParentDeclaration {
|
||||
}
|
||||
|
||||
/// Get the declaring node's ID.
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
&self.node_id
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
&self.node_addr
|
||||
}
|
||||
|
||||
/// Get the parent node's ID.
|
||||
pub fn parent_id(&self) -> &NodeId {
|
||||
pub fn parent_id(&self) -> &NodeAddr {
|
||||
&self.parent_id
|
||||
}
|
||||
|
||||
@@ -121,11 +121,11 @@ impl ParentDeclaration {
|
||||
|
||||
/// Sign this declaration with the given identity.
|
||||
///
|
||||
/// The identity's node_id must match this declaration's node_id.
|
||||
/// Returns an error if the node_ids don't match.
|
||||
/// The identity's node_addr must match this declaration's node_addr.
|
||||
/// Returns an error if the node_addrs don't match.
|
||||
pub fn sign(&mut self, identity: &Identity) -> Result<(), TreeError> {
|
||||
if identity.node_id() != &self.node_id {
|
||||
return Err(TreeError::InvalidSignature(self.node_id));
|
||||
if identity.node_addr() != &self.node_addr {
|
||||
return Err(TreeError::InvalidSignature(self.node_addr));
|
||||
}
|
||||
let signature = identity.sign(&self.signing_bytes());
|
||||
self.signature = Some(signature);
|
||||
@@ -134,7 +134,7 @@ impl ParentDeclaration {
|
||||
|
||||
/// Check if this is a root declaration (parent == self).
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.node_id == self.parent_id
|
||||
self.node_addr == self.parent_id
|
||||
}
|
||||
|
||||
/// Check if this declaration is signed.
|
||||
@@ -144,10 +144,10 @@ impl ParentDeclaration {
|
||||
|
||||
/// Get the bytes that should be signed.
|
||||
///
|
||||
/// Format: node_id (32) || parent_id (32) || sequence (8) || timestamp (8)
|
||||
/// Format: node_addr (32) || parent_id (32) || sequence (8) || timestamp (8)
|
||||
pub fn signing_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(80);
|
||||
bytes.extend_from_slice(self.node_id.as_bytes());
|
||||
bytes.extend_from_slice(self.node_addr.as_bytes());
|
||||
bytes.extend_from_slice(self.parent_id.as_bytes());
|
||||
bytes.extend_from_slice(&self.sequence.to_le_bytes());
|
||||
bytes.extend_from_slice(&self.timestamp.to_le_bytes());
|
||||
@@ -161,13 +161,13 @@ impl ParentDeclaration {
|
||||
let signature = self
|
||||
.signature
|
||||
.as_ref()
|
||||
.ok_or(TreeError::InvalidSignature(self.node_id))?;
|
||||
.ok_or(TreeError::InvalidSignature(self.node_addr))?;
|
||||
|
||||
let secp = secp256k1::Secp256k1::verification_only();
|
||||
let hash = self.signing_hash();
|
||||
|
||||
secp.verify_schnorr(signature, &hash, pubkey)
|
||||
.map_err(|_| TreeError::InvalidSignature(self.node_id))
|
||||
.map_err(|_| TreeError::InvalidSignature(self.node_addr))
|
||||
}
|
||||
|
||||
/// Compute the SHA-256 hash of the signing bytes.
|
||||
@@ -187,7 +187,7 @@ impl ParentDeclaration {
|
||||
impl fmt::Debug for ParentDeclaration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ParentDeclaration")
|
||||
.field("node_id", &self.node_id)
|
||||
.field("node_addr", &self.node_addr)
|
||||
.field("parent_id", &self.parent_id)
|
||||
.field("sequence", &self.sequence)
|
||||
.field("is_root", &self.is_root())
|
||||
@@ -198,7 +198,7 @@ impl fmt::Debug for ParentDeclaration {
|
||||
|
||||
impl PartialEq for ParentDeclaration {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node_id == other.node_id
|
||||
self.node_addr == other.node_addr
|
||||
&& self.parent_id == other.parent_id
|
||||
&& self.sequence == other.sequence
|
||||
&& self.timestamp == other.timestamp
|
||||
@@ -216,13 +216,13 @@ impl Eq for ParentDeclaration {}
|
||||
/// Two nodes can compute the hops between them by finding their lowest
|
||||
/// common ancestor (LCA) in the tree.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct TreeCoordinate(Vec<NodeId>);
|
||||
pub struct TreeCoordinate(Vec<NodeAddr>);
|
||||
|
||||
impl TreeCoordinate {
|
||||
/// Create a coordinate from a path (self to root).
|
||||
///
|
||||
/// The path must be non-empty and ordered from the node to the root.
|
||||
pub fn new(path: Vec<NodeId>) -> Result<Self, TreeError> {
|
||||
pub fn new(path: Vec<NodeAddr>) -> Result<Self, TreeError> {
|
||||
if path.is_empty() {
|
||||
return Err(TreeError::EmptyCoordinate);
|
||||
}
|
||||
@@ -230,22 +230,22 @@ impl TreeCoordinate {
|
||||
}
|
||||
|
||||
/// Create a coordinate for a root node.
|
||||
pub fn root(node_id: NodeId) -> Self {
|
||||
Self(vec![node_id])
|
||||
pub fn root(node_addr: NodeAddr) -> Self {
|
||||
Self(vec![node_addr])
|
||||
}
|
||||
|
||||
/// The node this coordinate belongs to (first element).
|
||||
pub fn node_id(&self) -> &NodeId {
|
||||
pub fn node_addr(&self) -> &NodeAddr {
|
||||
&self.0[0]
|
||||
}
|
||||
|
||||
/// The root of the tree (last element).
|
||||
pub fn root_id(&self) -> &NodeId {
|
||||
pub fn root_id(&self) -> &NodeAddr {
|
||||
self.0.last().expect("coordinate never empty")
|
||||
}
|
||||
|
||||
/// The immediate parent (second element, or self if root).
|
||||
pub fn parent_id(&self) -> &NodeId {
|
||||
pub fn parent_id(&self) -> &NodeAddr {
|
||||
self.0.get(1).unwrap_or(&self.0[0])
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ impl TreeCoordinate {
|
||||
}
|
||||
|
||||
/// The full ancestry path.
|
||||
pub fn path(&self) -> &[NodeId] {
|
||||
pub fn path(&self) -> &[NodeAddr] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ impl TreeCoordinate {
|
||||
}
|
||||
|
||||
/// Get the lowest common ancestor node ID.
|
||||
pub fn lca(&self, other: &TreeCoordinate) -> Option<&NodeId> {
|
||||
pub fn lca(&self, other: &TreeCoordinate) -> Option<&NodeAddr> {
|
||||
let self_rev: Vec<_> = self.0.iter().rev().collect();
|
||||
let other_rev: Vec<_> = other.0.iter().rev().collect();
|
||||
|
||||
@@ -318,19 +318,19 @@ impl TreeCoordinate {
|
||||
}
|
||||
|
||||
/// Check if `other` is an ancestor (appears in our path after self).
|
||||
pub fn has_ancestor(&self, other: &NodeId) -> bool {
|
||||
pub fn has_ancestor(&self, other: &NodeAddr) -> bool {
|
||||
self.0.iter().skip(1).any(|id| id == other)
|
||||
}
|
||||
|
||||
/// Check if `other` is in our ancestry (including self).
|
||||
pub fn contains(&self, other: &NodeId) -> bool {
|
||||
pub fn contains(&self, other: &NodeAddr) -> bool {
|
||||
self.0.iter().any(|id| id == other)
|
||||
}
|
||||
|
||||
/// Get the ancestor at a specific depth from self.
|
||||
///
|
||||
/// `ancestor_at(0)` returns self, `ancestor_at(1)` returns parent, etc.
|
||||
pub fn ancestor_at(&self, depth: usize) -> Option<&NodeId> {
|
||||
pub fn ancestor_at(&self, depth: usize) -> Option<&NodeAddr> {
|
||||
self.0.get(depth)
|
||||
}
|
||||
}
|
||||
@@ -349,8 +349,8 @@ impl fmt::Debug for TreeCoordinate {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[NodeId]> for TreeCoordinate {
|
||||
fn as_ref(&self) -> &[NodeId] {
|
||||
impl AsRef<[NodeAddr]> for TreeCoordinate {
|
||||
fn as_ref(&self) -> &[NodeAddr] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -361,46 +361,46 @@ impl AsRef<[NodeId]> for TreeCoordinate {
|
||||
/// tree positions. State is bounded by O(P × D) where P is peer count
|
||||
/// and D is tree depth.
|
||||
pub struct TreeState {
|
||||
/// This node's NodeId.
|
||||
my_node_id: NodeId,
|
||||
/// This node's NodeAddr.
|
||||
my_node_addr: NodeAddr,
|
||||
/// This node's current parent declaration.
|
||||
my_declaration: ParentDeclaration,
|
||||
/// This node's current coordinates (computed from declaration chain).
|
||||
my_coords: TreeCoordinate,
|
||||
/// The current elected root (smallest reachable node_id).
|
||||
root: NodeId,
|
||||
/// The current elected root (smallest reachable node_addr).
|
||||
root: NodeAddr,
|
||||
/// Each peer's most recent parent declaration.
|
||||
peer_declarations: HashMap<NodeId, ParentDeclaration>,
|
||||
peer_declarations: HashMap<NodeAddr, ParentDeclaration>,
|
||||
/// Each peer's full ancestry to root.
|
||||
peer_ancestry: HashMap<NodeId, TreeCoordinate>,
|
||||
peer_ancestry: HashMap<NodeAddr, TreeCoordinate>,
|
||||
}
|
||||
|
||||
impl TreeState {
|
||||
/// Create initial tree state for a node (as root candidate).
|
||||
///
|
||||
/// The node starts as its own root until it learns of a smaller node_id.
|
||||
/// The node starts as its own root until it learns of a smaller node_addr.
|
||||
/// Initial sequence is 1 per protocol spec; timestamp is current Unix time.
|
||||
pub fn new(my_node_id: NodeId) -> Self {
|
||||
pub fn new(my_node_addr: NodeAddr) -> Self {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0);
|
||||
let my_declaration = ParentDeclaration::self_root(my_node_id, 1, timestamp);
|
||||
let my_coords = TreeCoordinate::root(my_node_id);
|
||||
let my_declaration = ParentDeclaration::self_root(my_node_addr, 1, timestamp);
|
||||
let my_coords = TreeCoordinate::root(my_node_addr);
|
||||
|
||||
Self {
|
||||
my_node_id,
|
||||
my_node_addr,
|
||||
my_declaration,
|
||||
my_coords,
|
||||
root: my_node_id,
|
||||
root: my_node_addr,
|
||||
peer_declarations: HashMap::new(),
|
||||
peer_ancestry: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get this node's NodeId.
|
||||
pub fn my_node_id(&self) -> &NodeId {
|
||||
&self.my_node_id
|
||||
/// Get this node's NodeAddr.
|
||||
pub fn my_node_addr(&self) -> &NodeAddr {
|
||||
&self.my_node_addr
|
||||
}
|
||||
|
||||
/// Get this node's current declaration.
|
||||
@@ -414,22 +414,22 @@ impl TreeState {
|
||||
}
|
||||
|
||||
/// Get the current root.
|
||||
pub fn root(&self) -> &NodeId {
|
||||
pub fn root(&self) -> &NodeAddr {
|
||||
&self.root
|
||||
}
|
||||
|
||||
/// Check if this node is currently the root.
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.root == self.my_node_id
|
||||
self.root == self.my_node_addr
|
||||
}
|
||||
|
||||
/// Get coordinates for a peer, if known.
|
||||
pub fn peer_coords(&self, peer_id: &NodeId) -> Option<&TreeCoordinate> {
|
||||
pub fn peer_coords(&self, peer_id: &NodeAddr) -> Option<&TreeCoordinate> {
|
||||
self.peer_ancestry.get(peer_id)
|
||||
}
|
||||
|
||||
/// Get declaration for a peer, if known.
|
||||
pub fn peer_declaration(&self, peer_id: &NodeId) -> Option<&ParentDeclaration> {
|
||||
pub fn peer_declaration(&self, peer_id: &NodeAddr) -> Option<&ParentDeclaration> {
|
||||
self.peer_declarations.get(peer_id)
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ impl TreeState {
|
||||
}
|
||||
|
||||
/// Iterate over all peer node IDs.
|
||||
pub fn peer_ids(&self) -> impl Iterator<Item = &NodeId> {
|
||||
pub fn peer_ids(&self) -> impl Iterator<Item = &NodeAddr> {
|
||||
self.peer_declarations.keys()
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ impl TreeState {
|
||||
declaration: ParentDeclaration,
|
||||
ancestry: TreeCoordinate,
|
||||
) -> bool {
|
||||
let peer_id = *declaration.node_id();
|
||||
let peer_id = *declaration.node_addr();
|
||||
|
||||
// Check if this is a fresh update
|
||||
if let Some(existing) = self.peer_declarations.get(&peer_id)
|
||||
@@ -466,7 +466,7 @@ impl TreeState {
|
||||
}
|
||||
|
||||
/// Remove a peer from the tree state.
|
||||
pub fn remove_peer(&mut self, peer_id: &NodeId) {
|
||||
pub fn remove_peer(&mut self, peer_id: &NodeAddr) {
|
||||
self.peer_declarations.remove(peer_id);
|
||||
self.peer_ancestry.remove(peer_id);
|
||||
}
|
||||
@@ -474,23 +474,23 @@ impl TreeState {
|
||||
/// Update this node's parent selection.
|
||||
///
|
||||
/// Call this when switching parents. Updates the declaration and coordinates.
|
||||
pub fn set_parent(&mut self, parent_id: NodeId, sequence: u64, timestamp: u64) {
|
||||
self.my_declaration = ParentDeclaration::new(self.my_node_id, parent_id, sequence, timestamp);
|
||||
pub fn set_parent(&mut self, parent_id: NodeAddr, sequence: u64, timestamp: u64) {
|
||||
self.my_declaration = ParentDeclaration::new(self.my_node_addr, parent_id, sequence, timestamp);
|
||||
// Coordinates will be recomputed when ancestry is available
|
||||
}
|
||||
|
||||
/// Update this node's coordinates based on current parent's ancestry.
|
||||
pub fn recompute_coords(&mut self) {
|
||||
if self.my_declaration.is_root() {
|
||||
self.my_coords = TreeCoordinate::root(self.my_node_id);
|
||||
self.root = self.my_node_id;
|
||||
self.my_coords = TreeCoordinate::root(self.my_node_addr);
|
||||
self.root = self.my_node_addr;
|
||||
return;
|
||||
}
|
||||
|
||||
let parent_id = self.my_declaration.parent_id();
|
||||
if let Some(parent_coords) = self.peer_ancestry.get(parent_id) {
|
||||
// Our coords = [self] ++ parent_coords
|
||||
let mut path = vec![self.my_node_id];
|
||||
let mut path = vec![self.my_node_addr];
|
||||
path.extend_from_slice(parent_coords.path());
|
||||
self.my_coords = TreeCoordinate::new(path).expect("non-empty path");
|
||||
self.root = *self.my_coords.root_id();
|
||||
@@ -498,7 +498,7 @@ impl TreeState {
|
||||
}
|
||||
|
||||
/// Calculate tree distance to a peer.
|
||||
pub fn distance_to_peer(&self, peer_id: &NodeId) -> Option<usize> {
|
||||
pub fn distance_to_peer(&self, peer_id: &NodeAddr) -> Option<usize> {
|
||||
self.peer_ancestry
|
||||
.get(peer_id)
|
||||
.map(|coords| self.my_coords.distance_to(coords))
|
||||
@@ -508,7 +508,7 @@ impl TreeState {
|
||||
///
|
||||
/// Returns the peer that minimizes tree distance to the destination.
|
||||
/// This is a stub - full implementation requires greedy routing logic.
|
||||
pub fn find_next_hop(&self, _dest_coords: &TreeCoordinate) -> Option<NodeId> {
|
||||
pub fn find_next_hop(&self, _dest_coords: &TreeCoordinate) -> Option<NodeAddr> {
|
||||
// Stub: would implement greedy tree routing
|
||||
None
|
||||
}
|
||||
@@ -516,14 +516,14 @@ impl TreeState {
|
||||
/// Check if a parent switch to `candidate` would be beneficial.
|
||||
///
|
||||
/// This is a stub - full implementation requires policy decisions.
|
||||
pub fn should_switch_parent(&self, _candidate: &NodeId) -> bool {
|
||||
pub fn should_switch_parent(&self, _candidate: &NodeAddr) -> bool {
|
||||
// Stub: would evaluate parent switch criteria
|
||||
false
|
||||
}
|
||||
|
||||
/// Sign this node's declaration with the given identity.
|
||||
///
|
||||
/// The identity's node_id must match this TreeState's node_id.
|
||||
/// The identity's node_addr must match this TreeState's node_addr.
|
||||
pub fn sign_declaration(&mut self, identity: &Identity) -> Result<(), TreeError> {
|
||||
self.my_declaration.sign(identity)
|
||||
}
|
||||
@@ -537,7 +537,7 @@ impl TreeState {
|
||||
impl fmt::Debug for TreeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TreeState")
|
||||
.field("my_node_id", &self.my_node_id)
|
||||
.field("my_node_addr", &self.my_node_addr)
|
||||
.field("root", &self.root)
|
||||
.field("is_root", &self.is_root())
|
||||
.field("depth", &self.my_coords.depth())
|
||||
@@ -550,37 +550,37 @@ impl fmt::Debug for TreeState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_node_id(val: u8) -> NodeId {
|
||||
fn make_node_addr(val: u8) -> NodeAddr {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes[0] = val;
|
||||
NodeId::from_bytes(bytes)
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
// ===== TreeCoordinate Tests =====
|
||||
|
||||
#[test]
|
||||
fn test_tree_coordinate_root() {
|
||||
let root_id = make_node_id(1);
|
||||
let root_id = make_node_addr(1);
|
||||
let coord = TreeCoordinate::root(root_id);
|
||||
|
||||
assert!(coord.is_root());
|
||||
assert_eq!(coord.depth(), 0);
|
||||
assert_eq!(coord.node_id(), &root_id);
|
||||
assert_eq!(coord.node_addr(), &root_id);
|
||||
assert_eq!(coord.root_id(), &root_id);
|
||||
assert_eq!(coord.parent_id(), &root_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_coordinate_path() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let root = make_node_id(3);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
let root = make_node_addr(3);
|
||||
|
||||
let coord = TreeCoordinate::new(vec![node, parent, root]).unwrap();
|
||||
|
||||
assert!(!coord.is_root());
|
||||
assert_eq!(coord.depth(), 2);
|
||||
assert_eq!(coord.node_id(), &node);
|
||||
assert_eq!(coord.node_addr(), &node);
|
||||
assert_eq!(coord.parent_id(), &parent);
|
||||
assert_eq!(coord.root_id(), &root);
|
||||
}
|
||||
@@ -593,7 +593,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_distance_same_node() {
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
let coord = TreeCoordinate::root(node);
|
||||
|
||||
assert_eq!(coord.distance_to(&coord), 0);
|
||||
@@ -601,9 +601,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_distance_siblings() {
|
||||
let root = make_node_id(0);
|
||||
let a = make_node_id(1);
|
||||
let b = make_node_id(2);
|
||||
let root = make_node_addr(0);
|
||||
let a = make_node_addr(1);
|
||||
let b = make_node_addr(2);
|
||||
|
||||
let coord_a = TreeCoordinate::new(vec![a, root]).unwrap();
|
||||
let coord_b = TreeCoordinate::new(vec![b, root]).unwrap();
|
||||
@@ -614,9 +614,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_distance_ancestor() {
|
||||
let root = make_node_id(0);
|
||||
let parent = make_node_id(1);
|
||||
let child = make_node_id(2);
|
||||
let root = make_node_addr(0);
|
||||
let parent = make_node_addr(1);
|
||||
let child = make_node_addr(2);
|
||||
|
||||
let coord_parent = TreeCoordinate::new(vec![parent, root]).unwrap();
|
||||
let coord_child = TreeCoordinate::new(vec![child, parent, root]).unwrap();
|
||||
@@ -633,11 +633,11 @@ mod tests {
|
||||
// a b
|
||||
// / \
|
||||
// c d
|
||||
let root = make_node_id(0);
|
||||
let a = make_node_id(1);
|
||||
let b = make_node_id(2);
|
||||
let c = make_node_id(3);
|
||||
let d = make_node_id(4);
|
||||
let root = make_node_addr(0);
|
||||
let a = make_node_addr(1);
|
||||
let b = make_node_addr(2);
|
||||
let c = make_node_addr(3);
|
||||
let d = make_node_addr(4);
|
||||
|
||||
let coord_c = TreeCoordinate::new(vec![c, a, root]).unwrap();
|
||||
let coord_d = TreeCoordinate::new(vec![d, b, root]).unwrap();
|
||||
@@ -648,8 +648,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_distance_different_roots() {
|
||||
let root1 = make_node_id(1);
|
||||
let root2 = make_node_id(2);
|
||||
let root1 = make_node_addr(1);
|
||||
let root2 = make_node_addr(2);
|
||||
|
||||
let coord1 = TreeCoordinate::root(root1);
|
||||
let coord2 = TreeCoordinate::root(root2);
|
||||
@@ -659,9 +659,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_has_ancestor() {
|
||||
let root = make_node_id(0);
|
||||
let parent = make_node_id(1);
|
||||
let child = make_node_id(2);
|
||||
let root = make_node_addr(0);
|
||||
let parent = make_node_addr(1);
|
||||
let child = make_node_addr(2);
|
||||
|
||||
let coord = TreeCoordinate::new(vec![child, parent, root]).unwrap();
|
||||
|
||||
@@ -672,10 +672,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_contains() {
|
||||
let root = make_node_id(0);
|
||||
let parent = make_node_id(1);
|
||||
let child = make_node_id(2);
|
||||
let other = make_node_id(99);
|
||||
let root = make_node_addr(0);
|
||||
let parent = make_node_addr(1);
|
||||
let child = make_node_addr(2);
|
||||
let other = make_node_addr(99);
|
||||
|
||||
let coord = TreeCoordinate::new(vec![child, parent, root]).unwrap();
|
||||
|
||||
@@ -687,9 +687,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_ancestor_at() {
|
||||
let root = make_node_id(0);
|
||||
let parent = make_node_id(1);
|
||||
let child = make_node_id(2);
|
||||
let root = make_node_addr(0);
|
||||
let parent = make_node_addr(1);
|
||||
let child = make_node_addr(2);
|
||||
|
||||
let coord = TreeCoordinate::new(vec![child, parent, root]).unwrap();
|
||||
|
||||
@@ -701,11 +701,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lca() {
|
||||
let root = make_node_id(0);
|
||||
let a = make_node_id(1);
|
||||
let b = make_node_id(2);
|
||||
let c = make_node_id(3);
|
||||
let d = make_node_id(4);
|
||||
let root = make_node_addr(0);
|
||||
let a = make_node_addr(1);
|
||||
let b = make_node_addr(2);
|
||||
let c = make_node_addr(3);
|
||||
let d = make_node_addr(4);
|
||||
|
||||
// c under a, d under b, both under root
|
||||
let coord_c = TreeCoordinate::new(vec![c, a, root]).unwrap();
|
||||
@@ -722,12 +722,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parent_declaration_new() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
|
||||
let decl = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
|
||||
assert_eq!(decl.node_id(), &node);
|
||||
assert_eq!(decl.node_addr(), &node);
|
||||
assert_eq!(decl.parent_id(), &parent);
|
||||
assert_eq!(decl.sequence(), 1);
|
||||
assert_eq!(decl.timestamp(), 1000);
|
||||
@@ -737,18 +737,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parent_declaration_self_root() {
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
|
||||
let decl = ParentDeclaration::self_root(node, 5, 2000);
|
||||
|
||||
assert!(decl.is_root());
|
||||
assert_eq!(decl.node_id(), decl.parent_id());
|
||||
assert_eq!(decl.node_addr(), decl.parent_id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parent_declaration_freshness() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
|
||||
let old_decl = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
let new_decl = ParentDeclaration::new(node, parent, 2, 2000);
|
||||
@@ -760,8 +760,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parent_declaration_signing_bytes() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
|
||||
let decl = ParentDeclaration::new(node, parent, 100, 1234567890);
|
||||
let bytes = decl.signing_bytes();
|
||||
@@ -778,8 +778,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parent_declaration_equality() {
|
||||
let node = make_node_id(1);
|
||||
let parent = make_node_id(2);
|
||||
let node = make_node_addr(1);
|
||||
let parent = make_node_addr(2);
|
||||
|
||||
let decl1 = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
let decl2 = ParentDeclaration::new(node, parent, 1, 1000);
|
||||
@@ -793,10 +793,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_state_new() {
|
||||
let node = make_node_id(1);
|
||||
let node = make_node_addr(1);
|
||||
let state = TreeState::new(node);
|
||||
|
||||
assert_eq!(state.my_node_id(), &node);
|
||||
assert_eq!(state.my_node_addr(), &node);
|
||||
assert!(state.is_root());
|
||||
assert_eq!(state.root(), &node);
|
||||
assert_eq!(state.my_coords().depth(), 0);
|
||||
@@ -805,11 +805,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_state_update_peer() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = TreeState::new(my_node);
|
||||
|
||||
let peer = make_node_id(1);
|
||||
let root = make_node_id(2);
|
||||
let peer = make_node_addr(1);
|
||||
let root = make_node_addr(2);
|
||||
|
||||
let decl = ParentDeclaration::new(peer, root, 1, 1000);
|
||||
let coords = TreeCoordinate::new(vec![peer, root]).unwrap();
|
||||
@@ -830,11 +830,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_state_remove_peer() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = TreeState::new(my_node);
|
||||
|
||||
let peer = make_node_id(1);
|
||||
let root = make_node_id(2);
|
||||
let peer = make_node_addr(1);
|
||||
let root = make_node_addr(2);
|
||||
|
||||
let decl = ParentDeclaration::new(peer, root, 1, 1000);
|
||||
let coords = TreeCoordinate::new(vec![peer, root]).unwrap();
|
||||
@@ -849,10 +849,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_state_distance_to_peer() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = TreeState::new(my_node);
|
||||
|
||||
let peer = make_node_id(1);
|
||||
let peer = make_node_addr(1);
|
||||
|
||||
// Both are roots in their own trees initially - different roots
|
||||
let peer_coords = TreeCoordinate::root(peer);
|
||||
@@ -863,7 +863,7 @@ mod tests {
|
||||
assert_eq!(state.distance_to_peer(&peer), Some(usize::MAX));
|
||||
|
||||
// If they share a root, distance should be finite
|
||||
let shared_root = make_node_id(99);
|
||||
let shared_root = make_node_addr(99);
|
||||
|
||||
// Update my state to have shared root
|
||||
state.set_parent(shared_root, 1, 1000);
|
||||
@@ -883,11 +883,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tree_state_peer_ids() {
|
||||
let my_node = make_node_id(0);
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = TreeState::new(my_node);
|
||||
|
||||
let peer1 = make_node_id(1);
|
||||
let peer2 = make_node_id(2);
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer2 = make_node_addr(2);
|
||||
|
||||
state.update_peer(
|
||||
ParentDeclaration::self_root(peer1, 1, 1000),
|
||||
|
||||
Reference in New Issue
Block a user