Federation Foundations: Topology-Aware Networking and RPC Improvements
Date: 2025-11-13 Phase: Federation Foundation (Phase 1E-1F) + RPC Polish Objective: Implement regional/cluster-based topology awareness with scope-aware gossip fanout, plus RPC receipt tracking and pagination
Overview
This session laid critical groundwork for ICN federation by implementing topology-aware networking that enables efficient regional and cluster-based communication. Additionally, completed RPC server improvements with operation receipts and pagination support.
Key Achievements
- ✅ Topology-Aware Networking - Regional/cluster peer organization with handshake protocol
- ✅ NeighborSets Implementation - Smart peer categorization (LocalCluster, Regional, Backbone, Trusted)
- ✅ Scope-Aware Gossip Fanout - Efficient message propagation based on scope
- ✅ Receipt Tracking - UUID-based operation outcome persistence with 24h TTL
- ✅ Pagination Support - Consistent pagination across all RPC list endpoints
- ✅ All Tests Passing - 240 tests passing across workspace (38 in icn-net, 25 in icn-rpc)
Part 1: Topology-Aware Networking
1. Topology Configuration Schema
File: icn/crates/icn-net/src/topology.rs
Added comprehensive topology types to support regional/cluster-based federation:
/// Topology configuration for regional/cluster-based networking
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopologyConfig {
pub region: String,
pub cluster_id: String,
pub role: NodeRole,
pub neighbor_limits: NeighborLimitsConfig,
pub fanout: FanoutConfig,
}
/// Node role in the network topology
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum NodeRole {
Edge, // Regular node
Rendezvous, // Bootstrap/discovery hub
Archive, // Long-term storage node
}
/// Gossip fanout configuration per scope
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FanoutConfig {
pub local_cluster: usize, // How many peers for cluster-local gossip
pub regional: usize, // How many peers for regional gossip
pub global: usize, // How many peers for global gossip
}
Key Design Decisions:
- Separation of Concerns: Topology types live in
icn-net, re-exported byicn-coreto avoid circular dependencies - Three-Tier Hierarchy: Local cluster → Region → Global for efficient message routing
- Role-Based Behavior: Enables specialized node types (e.g., archives for historical data)
2. NeighborSets: Smart Peer Categorization
File: icn/crates/icn-net/src/topology.rs:60-324
Implemented intelligent peer categorization that combines topology and trust:
pub struct NeighborSets {
local_cluster: HashMap<PeerId, PeerMetadata>,
regional: HashMap<PeerId, PeerMetadata>,
backbone: HashMap<PeerId, PeerMetadata>,
trusted: HashMap<PeerId, PeerMetadata>,
own_topology: TopologyInfo,
}
impl NeighborSets {
/// Add a neighbor to the appropriate set based on topology + trust
pub fn add_neighbor(
&mut self,
peer_id: PeerId,
topology_info: TopologyInfo,
metrics: Option<NeighborMetrics>,
trust_score: f32,
limits: &NeighborLimitsConfig,
) {
let category = self.categorize_peer(&topology_info, trust_score);
// Add to appropriate set with LRU eviction
let (set, limit) = match category {
PeerCategory::LocalCluster => (&mut self.local_cluster, limits.max_local_cluster),
PeerCategory::Regional => (&mut self.regional, limits.max_regional),
PeerCategory::Backbone => (&mut self.backbone, limits.max_backbone),
PeerCategory::Trusted => (&mut self.trusted, limits.max_trusted),
};
// LRU eviction if at capacity
if set.len() >= limit && !set.contains_key(&peer_id) {
if let Some(oldest) = set.iter().min_by_key(|(_, m)| m.connected_at).map(|(id, _)| id.clone()) {
set.remove(&oldest);
}
}
set.insert(peer_id, metadata);
}
}
Categorization Logic:
- Trusted (trust ≥ 0.7): High-trust peers, regardless of location
- LocalCluster: Same region AND cluster_id
- Regional: Same region, different cluster
- Backbone: Different region (cross-regional connectivity)
Key Features:
- LRU Eviction: Bounded memory usage per category
- Metrics Tracking: RTT, connection time for future optimizations
- Prometheus Metrics: Exports neighbor counts per category
3. Handshake Protocol for Topology Exchange
File: icn/crates/icn-net/src/protocol.rs:48-66
Added handshake messages to wire protocol:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessagePayload {
Gossip(GossipMessage),
Ping,
Pong,
Subscribe { topics: Vec<String> },
Unsubscribe { topics: Vec<String> },
SubscribeAck { topics: Vec<String> },
/// NEW: Handshake with topology information
Handshake {
region: String,
cluster_id: String,
role: String,
},
/// NEW: Handshake acknowledgement
HandshakeAck,
}
Handshake Flow (icn/crates/icn-net/src/actor.rs:640-682):
- Connection Established: Peer A dials Peer B
- A → B: Handshake message with (region, cluster_id, role)
- B Processes: Categorizes A into NeighborSets based on topology + trust
- B → A: HandshakeAck
- Both sides: Now aware of peer's location for scope-aware routing
Trust Integration:
let trust_score = if let Some(ref tg) = trust_graph {
tg.read().await.compute_trust_score(&message.from).unwrap_or(0.0) as f32
} else {
0.5 // Default for unknown peers
};
sets.write().await.add_neighbor(
PeerId(message.from.clone()),
peer_topology,
None,
trust_score,
&limits,
);
4. NetworkActor Integration
File: icn/crates/icn-net/src/actor.rs
Extended NetworkActor to support topology:
pub struct NetworkActor {
// ... existing fields ...
neighbor_sets: Option<Arc<RwLock<NeighborSets>>>,
topology_config: Option<TopologyConfig>,
trust_graph: Option<Arc<tokio::sync::RwLock<icn_trust::TrustGraph>>>,
}
pub struct NetworkHandle {
tx: mpsc::Sender<NetworkMsg>,
neighbor_sets: Option<Arc<RwLock<NeighborSets>>>, // NEW: Direct access for sampling
}
impl NetworkHandle {
/// Sample peers based on scope (for gossip fanout)
pub async fn sample_peers(&self, scope: Scope, count: usize) -> Vec<Did> {
if let Some(ref sets) = self.neighbor_sets {
let sets_read = sets.read().await;
sets_read.sample(scope, count).into_iter().map(|peer_id| peer_id.0).collect()
} else {
Vec::new() // Fallback: no topology support
}
}
}
Why Direct Access?
- GossipActor needs fast peer sampling during message propagation
- Storing
Arc<RwLock<NeighborSets>>in NetworkHandle avoids message passing overhead - Async read locks enable concurrent sampling
5. Scope-Aware Gossip Fanout
File: icn/crates/icn-gossip/src/gossip.rs
Added scope-aware message propagation:
/// Callback for sampling peers based on scope
pub type PeerSamplingCallback = Arc<dyn Fn(Scope, usize) -> Vec<Did> + Send + Sync>;
pub struct GossipActor {
// ... existing fields ...
peer_sampling: Option<PeerSamplingCallback>,
}
impl GossipActor {
/// Send a message with scope-aware peer selection
fn send_message_scoped(&self, scope: Scope, fanout: usize, message: GossipMessage) {
if let Some(sampling) = &self.peer_sampling {
let peers = sampling(scope, fanout);
if peers.is_empty() {
debug!("No peers available for scope {:?}", scope);
return;
}
for peer in peers {
self.send_message(Some(peer), message.clone());
}
icn_obs::metrics::topology::gossip_fanout_record(
match scope {
Scope::LocalCluster => "local_cluster",
Scope::Regional => "regional",
Scope::Global => "global",
},
fanout,
);
} else {
// Fallback: broadcast to all peers
self.send_message(None, message);
}
}
}
Fanout Strategy:
- LocalCluster: 8 peers (tight cluster coordination)
- Regional: 6 peers (regional sync)
- Global: 4 peers (efficient global propagation)
Metrics: icn_gossip_fanout_peers_total{scope="local_cluster|regional|global"}
6. Supervisor Integration
File: icn/crates/icn-core/src/supervisor.rs
Wired peer sampling callback from NetworkHandle to GossipActor:
// Set up peer sampling callback for scope-aware gossip fanout
let network_handle_for_sampling = network_handle.clone();
let peer_sampling_callback: icn_gossip::PeerSamplingCallback = Arc::new(move |scope, count| {
let net_handle = network_handle_for_sampling.clone();
tokio::task::block_in_place(move || {
tokio::runtime::Handle::current().block_on(async move {
net_handle.sample_peers(scope, count).await
})
})
});
gossip.set_peer_sampling(peer_sampling_callback);
Sync-to-Async Bridge:
- GossipActor uses sync callbacks for performance
tokio::task::block_in_placesafely bridges to async NetworkHandle- Prevents blocking the Tokio runtime
7. Metrics Implementation
File: icn/crates/icn-obs/src/metrics/topology.rs
Added comprehensive topology metrics:
pub fn neighbor_set_size(category: &str, count: usize) {
NEIGHBOR_SET_SIZE.with_label_values(&[category]).set(count as i64);
}
pub fn neighbor_added(category: &str) {
NEIGHBOR_OPERATIONS.with_label_values(&[category, "added"]).inc();
}
pub fn neighbor_removed(category: &str) {
NEIGHBOR_OPERATIONS.with_label_values(&[category, "removed"]).inc();
}
pub fn gossip_fanout_record(scope: &str, peer_count: usize) {
GOSSIP_FANOUT_PEERS.with_label_values(&[scope]).observe(peer_count as f64);
}
Prometheus Metrics:
icn_neighbor_set_size{category}- Current neighbor count per categoryicn_neighbor_operations_total{category,operation}- Add/remove operationsicn_gossip_fanout_peers{scope}- Peers selected per scope
Part 2: RPC Receipt Tracking and Pagination
1. Receipt Store Integration
Files:
icn/crates/icn-rpc/src/receipt.rs(already existed)icn/crates/icn-rpc/src/server.rs:32,44
Integrated ReceiptStore into RPC server:
pub struct RpcServer {
network_handle: Option<Arc<RwLock<NetworkHandle>>>,
ledger_handle: Option<Arc<RwLock<Ledger>>>,
contract_runtime: Option<Arc<RwLock<ContractRuntime>>>,
gossip_handle: Option<Arc<RwLock<GossipActor>>>,
receipt_store: Arc<ReceiptStore>, // NEW: 10k capacity, 24h TTL
listen_addr: SocketAddr,
}
impl RpcServer {
pub fn new(listen_addr: SocketAddr) -> Self {
RpcServer {
// ...
receipt_store: Arc::new(ReceiptStore::new(10_000, 86400)),
// ...
}
}
}
Configuration:
- Capacity: 10,000 receipts (LRU eviction when exceeded)
- TTL: 86,400 seconds (24 hours)
- Eviction: Both time-based (on insert) and space-based (LRU)
2. Receipt Creation on Operations
Contract Deployment (icn/crates/icn-rpc/src/server.rs:535-568):
match gossip.publish("contracts:deploy", message_bytes) {
Ok(_) => {
// Create receipt for successful deployment
let receipt = Receipt::new(
deployment_msg.installation.installed_by.clone(),
Operation::ContractDeploy {
code_hash: code_hash.to_hex(),
},
Outcome::success(Some(code_hash.to_hex())),
);
let receipt_id = receipt.id.clone();
state.receipt_store.insert(receipt).await;
let response = serde_json::json!({
"code_hash": code_hash.to_hex(),
"receipt_id": receipt_id.to_string(), // Return to client
"success": true,
});
RpcResponse::success(id, response)
}
Err(e) => {
// Create receipt for failed deployment
let receipt = Receipt::new(
deployment_msg.installation.installed_by.clone(),
Operation::ContractDeploy { code_hash: code_hash.to_hex() },
Outcome::failure(e.to_string()),
);
state.receipt_store.insert(receipt).await;
RpcResponse::error(id, -32000, format!("Failed to publish deployment: {}", e))
}
}
Contract Execution (icn/crates/icn-rpc/src/server.rs:628-676):
match runtime.execute_rule(&code_hash, &call_params.rule_name, context, args).await {
Ok(result) => {
// Create receipt with resource tracking
let resources = Resources {
fuel_used: result.fuel_consumed,
bytes_processed: 0, // TODO: Track in future
wall_time_ms: 0, // TODO: Track in future
};
let receipt = Receipt::with_resources(
caller_did.clone(),
Operation::ContractExecute {
code_hash: call_params.code_hash.clone(),
rule: call_params.rule_name.clone(),
},
Outcome::success(None),
resources,
);
let receipt_id = receipt.id.clone();
state.receipt_store.insert(receipt).await;
// Add receipt_id to response
let mut value = serde_json::to_value(&response).unwrap();
value.as_object_mut().unwrap().insert(
"receipt_id".to_string(),
serde_json::Value::String(receipt_id.to_string()),
);
RpcResponse::success(id, value)
}
// ... error case also creates receipt
}
Receipt Fields:
id: UUID v4 (unique identifier)timestamp: Unix timestamp (seconds)caller: DID of requesteroperation: ContractDeploy | ContractExecute | LedgerTransfer | TrustEdgeAddoutcome: Success(commit_hash) | Failure(error)resources: fuel_used, bytes_processed, wall_time_ms
3. Receipt Query Endpoint
File: icn/crates/icn-rpc/src/server.rs:910-938
Added receipt.get RPC method:
async fn handle_receipt_get(
id: u64,
params: &serde_json::Value,
state: &Arc<RpcServer>,
) -> RpcResponse {
#[derive(serde::Deserialize)]
struct GetReceiptParams {
receipt_id: String,
}
let get_params: GetReceiptParams = match serde_json::from_value(params.clone()) {
Ok(p) => p,
Err(e) => return RpcResponse::error(id, -32602, format!("Invalid params: {}", e)),
};
let receipt_id = ReceiptId::from_string(get_params.receipt_id);
match state.receipt_store.get(&receipt_id).await {
Some(receipt) => match serde_json::to_value(&receipt) {
Ok(value) => RpcResponse::success(id, value),
Err(e) => RpcResponse::error(id, -32603, format!("Internal error: {}", e)),
},
None => RpcResponse::error(id, -32000, "Receipt not found".to_string()),
}
}
Example Response:
{
"jsonrpc": "2.0",
"result": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": 1731532800,
"caller": "did:icn:...",
"operation": {
"type": "contract_execute",
"code_hash": "abc123...",
"rule": "process"
},
"outcome": {
"status": "success",
"commit_hash": null
},
"resources": {
"fuel_used": 1234,
"bytes_processed": 0,
"wall_time_ms": 0
}
},
"id": 1
}
4. Pagination Support
Files:
icn/crates/icn-rpc/src/pagination.rs(already existed)icn/crates/icn-rpc/src/server.rs(updated list methods)
Converted three list methods to support pagination:
contract.list (icn/crates/icn-rpc/src/server.rs:679-724):
async fn handle_contract_list(
id: u64,
params: &serde_json::Value,
state: &Arc<RpcServer>,
) -> RpcResponse {
// Parse pagination parameters (defaults to offset=0, limit=100)
let page_request: PageRequest = match serde_json::from_value(params.clone()) {
Ok(p) => p,
Err(_) => PageRequest::default(),
};
let runtime = contract_runtime.read().await;
let contracts = runtime.list_contracts();
// Convert to RPC format
let contracts_rpc: Vec<ContractInfo> = contracts.iter().map(|info| ...).collect();
// Apply pagination
let page = paginate(contracts_rpc, &page_request, DEFAULT_MAX_PAGE_SIZE);
match serde_json::to_value(&page) {
Ok(value) => RpcResponse::success(id, value),
Err(e) => RpcResponse::error(id, -32603, format!("Internal error: {}", e)),
}
}
ledger.quarantine.list (icn/crates/icn-rpc/src/server.rs:726-771):
- Similar pattern: parse PageRequest → get all items → paginate → return PageResponse
ledger.history (icn/crates/icn-rpc/src/server.rs:405-473):
- Reverses entries (most recent first) → paginates → returns PageResponse
PageResponse Format:
{
"items": [...],
"total": 150,
"has_more": true,
"offset": 0,
"limit": 100
}
Server-Side Caps:
DEFAULT_MAX_PAGE_SIZE = 100(prevents unbounded memory usage)ABSOLUTE_MAX_PAGE_SIZE = 1000(hard limit)
Testing
Topology Tests
File: icn/crates/icn-net/src/topology.rs:326-580
Comprehensive test coverage for NeighborSets:
#[test]
fn test_neighbor_placement_local_cluster() {
let own = TopologyInfo { region: "us-west".to_string(), cluster_id: "c1".to_string(), role: NodeRole::Edge };
let mut sets = NeighborSets::new(own);
let same_cluster = TopologyInfo { region: "us-west".to_string(), cluster_id: "c1".to_string(), role: NodeRole::Edge };
sets.add_neighbor(PeerId(did1), same_cluster, None, 0.3, &limits);
assert_eq!(sets.local_cluster.len(), 1);
assert_eq!(sets.regional.len(), 0);
}
#[test]
fn test_sampling_respects_count() {
// ... add 10 peers to local_cluster
let sampled = sets.sample(Scope::LocalCluster, 5);
assert_eq!(sampled.len(), 5); // Exactly 5 peers returned
}
#[test]
fn test_lru_eviction() {
let limits = NeighborLimitsConfig {
max_local_cluster: 3,
max_regional: 5,
max_backbone: 5,
max_trusted: 10,
};
// Add 5 peers to local_cluster (limit = 3)
for i in 0..5 {
sets.add_neighbor(PeerId(did), topology, None, 0.3, &limits);
}
assert_eq!(sets.local_cluster.len(), 3); // Only 3 retained (LRU evicted 2)
}
Test Results:
- ✅
test_neighbor_placement_local_cluster- Verifies LocalCluster categorization - ✅
test_neighbor_placement_regional- Verifies Regional categorization - ✅
test_neighbor_placement_backbone- Verifies Backbone categorization - ✅
test_neighbor_placement_trusted- Verifies high-trust override - ✅
test_sampling_respects_count- Verifies sampling limits - ✅
test_sampling_local_cluster- Verifies scope-based sampling - ✅
test_sampling_regional- Verifies regional peer selection - ✅
test_sampling_global- Verifies global peer selection - ✅
test_lru_eviction- Verifies bounded memory usage - ✅
test_remove_neighbor- Verifies peer removal - ✅
test_metrics- Verifies Prometheus metrics
RPC Tests
File: icn/crates/icn-rpc/src/receipt.rs:284-451
Receipt and pagination test coverage:
- ✅
test_receipt_id_generation- Unique UUID generation - ✅
test_receipt_creation- Basic receipt creation - ✅
test_receipt_with_resources- Resource tracking - ✅
test_receipt_store_insert_and_get- Store operations - ✅
test_receipt_store_size_limit- LRU eviction at capacity - ✅
test_receipt_store_ttl_eviction- Time-based eviction - ✅
test_receipt_store_concurrent_access- Thread safety - ✅
test_paginate_first_page- First page extraction - ✅
test_paginate_middle_page- Middle page extraction - ✅
test_paginate_last_page- Last page extraction - ✅
test_paginate_enforces_max_page_size- Server-side caps - ✅
test_page_response_map- Type transformation
Integration Test Fixes
Files: Fixed 6 test files to include new TopologyConfig parameter:
icn/crates/icn-net/tests/trust_gated_tls_integration.rs(6 spawn calls)icn/crates/icn-core/tests/network_gossip_integration.rsicn/crates/icn-core/tests/subscription_integration.rsicn/crates/icn-core/tests/contract_deployment_integration.rsicn/crates/icn-core/tests/gossip_pull_protocol_integration.rsicn/crates/icn-core/tests/multi_node_gossip_convergence.rsicn/crates/icn-core/tests/trust_propagation_integration.rs
Before:
let network_handle = NetworkActor::spawn(
&keypair,
listen_addr,
shutdown_tx.clone(),
None,
None,
None,
None, // 7 parameters
).await?;
After:
let network_handle = NetworkActor::spawn(
&keypair,
listen_addr,
shutdown_tx.clone(),
None,
None,
None,
None,
None, // 8 parameters (added topology_config)
).await?;
Test Results
Workspace Test Summary (240 tests total):
icn-ccl: 36 passed
icn-core: 26 passed
icn-gossip: 52 passed
icn-identity: 12 passed
icn-ledger: 32 passed
icn-net: 38 passed (includes 11 topology tests)
icn-rpc: 25 passed (includes pagination + receipt tests)
icn-trust: 19 passed
Build Status: ✅ Clean build in 7.68s All Tests: ✅ 240 passing, 0 failing, 4 ignored
Architecture Decisions
1. Why NeighborSets vs. Simple Peer List?
Decision: Categorize peers into sets (LocalCluster, Regional, Backbone, Trusted)
Rationale:
- Efficient Sampling: O(1) category lookup, fast random sampling within category
- Bounded Memory: LRU eviction per category prevents unbounded growth
- Separation of Concerns: Different categories have different retention strategies
- Future-Proof: Enables category-specific optimizations (e.g., different heartbeat intervals)
Alternatives Considered:
- Single peer list with tags: Slower sampling, harder to enforce category limits
- Separate data structures: More complex, harder to maintain invariants
2. Why Handshake Protocol vs. mDNS Broadcast?
Decision: Explicit handshake messages on connection establishment
Rationale:
- Privacy: Don't broadcast cluster membership to entire local network
- Flexibility: Works across WAN, not just LAN
- Extensibility: Can add more fields (capabilities, version negotiation) in future
- Trust Integration: Immediate access to peer's trust score for categorization
Alternatives Considered:
- mDNS TXT records: Works only on LAN, privacy concerns
- Gossip-based discovery: Slower convergence, more bandwidth
3. Why Sync Callback for Peer Sampling?
Decision: GossipActor uses synchronous PeerSamplingCallback
Rationale:
- Performance: Gossip message handling is latency-sensitive
- Simplicity: Avoids async-in-sync complications in GossipActor core logic
- Safe Bridge:
tokio::task::block_in_placesafely bridges to async NetworkHandle
Alternatives Considered:
- Async callback: Would require GossipActor refactor to async, significant churn
- Message passing: Adds latency, complicates error handling
4. Why 24h Receipt TTL?
Decision: Receipts expire after 24 hours
Rationale:
- Auditability Window: Clients have reasonable time to query operation outcomes
- Memory Bounds: Prevents indefinite growth (10k * 24h ≈ reasonable for most deployments)
- Business Logic: Most async operations resolve within minutes; 24h is generous
Alternatives Considered:
- 1 hour: Too short for debugging slow operations
- 7 days: Excessive memory usage for typical deployment rates
- Infinite: Unbounded growth, eventual OOM
Commits
7ab222b - feat: Integrate topology-aware networking into NetworkActor
- Added TopologyConfig, NeighborSets, handshake protocol
- Wired topology into NetworkActor and supervisor
- 221 tests passing
630054f - feat: Add scope-aware gossip fanout using NeighborSets
- Added PeerSamplingCallback to GossipActor
- Implemented send_message_scoped with fanout strategy
- Wired peer sampling from NetworkHandle to GossipActor
- 126 tests passing
ed1b8ea - feat: Add receipt tracking and pagination to RPC server
- Integrated ReceiptStore into RpcServer
- Create receipts for contract.deploy and contract.call
- Added receipt.get RPC method
- Converted contract.list, ledger.quarantine.list, ledger.history to paginated
- Fixed NetworkActor::spawn calls in tests
e126228 - fix: Clean up unused imports in icn-net tests
67e268b - fix: Add TopologyConfig parameter to NetworkActor::spawn in tests
- Fixed all icn-core test files
Next Steps
Immediate (Phase 1 completion)
End-to-End Topology Testing - Multi-node integration test with regional setup
- Deploy 3 regions × 2 clusters × 3 nodes = 18 nodes
- Verify LocalCluster, Regional, Global gossip propagation
- Measure fanout efficiency vs. broadcast baseline
Config File Examples - Update
config/icn.toml.example[topology] region = "us-west" cluster_id = "sfo-1" role = "edge" [topology.neighbor_limits] max_local_cluster = 50 max_regional = 30 max_backbone = 20 max_trusted = 100 [topology.fanout] local_cluster = 8 regional = 6 global = 4Grafana Dashboards - Visualize topology metrics
- Neighbor distribution (LocalCluster vs Regional vs Backbone)
- Gossip fanout per scope
- Handshake success/failure rates
Phase 2: Trust-Based Access Control
TrustPolicy Wiring (deferred from this session)
- Wire
TrustPolicyinto RPC server for operation authorization - Implement
can_deploy_contract(),can_execute_contract()policies - Add audit logging for policy violations
- Wire
Topic-Level Trust Gates
- Enforce minimum trust scores for sensitive topics (e.g.,
ledger:sync) - Implement
TrustGatedTopicConfigin gossip subscriptions
- Enforce minimum trust scores for sensitive topics (e.g.,
Phase 3: Federation Features
Cluster Coordination
- Implement cluster-local consensus for low-latency decisions
- Add cluster leader election (Raft or simplified voting)
- Cross-cluster replication for critical state
Regional Routing
- Optimize inter-region message routing (prefer backbone nodes)
- Implement regional quotas for fairness
Archive Nodes
- Design long-term storage protocol for archive role
- Implement pruning policies for edge nodes (delegate to archives)
Known Issues
TODO Comments: Two resource tracking fields remain unimplemented:
bytes_processedin contract execution receiptswall_time_msin contract execution receipts- These should be tracked in ContractRuntime and passed to RPC layer
No Topology Config Validation: NetworkActor accepts invalid configs (e.g., empty region)
- Should add validation in
TopologyConfig::new()or NetworkActor::spawn
- Should add validation in
Handshake Timing: No retry logic if handshake fails
- Consider adding exponential backoff for handshake retries
Conclusion
This session laid a solid foundation for ICN federation:
Topology-Aware Networking enables efficient regional and cluster-based communication, reducing unnecessary cross-region gossip while maintaining global connectivity. The NeighborSets abstraction provides bounded memory usage and fast peer sampling for scope-aware message propagation.
Receipt Tracking gives clients visibility into async operation outcomes, enabling better error handling and audit trails. Combined with Pagination, the RPC API can now handle large result sets without memory exhaustion.
All 240 tests passing demonstrates the robustness of these changes. The architecture is extensible for future federation features (cluster coordination, regional routing, archive nodes).
Total Lines Changed:
- Added: ~800 lines (topology, receipts, pagination)
- Modified: ~200 lines (integration, tests)
- 5 commits, all tests green
Ready for Phase 2: Trust-Based Access Control and Federation polish.