basic network established
This commit is contained in:
23
crates/gsio-node/Cargo.toml
Normal file
23
crates/gsio-node/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "gsio-node"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3.31" }
|
||||
libp2p = { version = "0.55.0", features = ["identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux"] }
|
||||
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time", "net"] }
|
||||
tracing = { version = "0.1.41" }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
axum = { version = "0.8.4", features = ["json", "tracing"] }
|
||||
socketioxide = { version = "0.17.2", features = ["tracing", "v4", "extensions"] }
|
||||
rmpv = { version = "1.3.0", features = ["serde"] }
|
||||
tower-http = { version = "0.6.6", features = ["trace"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4.35", features = ["serde"] }
|
||||
sha2 = "0.10.8"
|
133
crates/gsio-node/README.md
Normal file
133
crates/gsio-node/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# gsio-node
|
||||
|
||||
A Rust-based Socket.IO server that serves as a node in the GSIO-Net distributed ledger system.
|
||||
|
||||
## Overview
|
||||
|
||||
The gsio-node component is a Socket.IO server that:
|
||||
- Maintains a distributed ledger with cryptographic verification
|
||||
- Handles real-time communication with clients
|
||||
- Participates in a peer-to-peer network with other nodes
|
||||
- Provides APIs for adding and retrieving ledger entries
|
||||
|
||||
## Features
|
||||
|
||||
- **Socket.IO Server**: Provides real-time bidirectional communication
|
||||
- **Distributed Ledger**: Maintains a chain of cryptographically linked entries
|
||||
- **P2P Networking**: Communicates with other nodes to synchronize the ledger
|
||||
- **Node Discovery**: Automatically discovers and connects to other nodes
|
||||
- **Consensus Mechanism**: Ensures all nodes converge to the same ledger state
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust (latest stable version)
|
||||
- Cargo (comes with Rust)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Clone the repository (if you haven't already)
|
||||
git clone <repository-url>
|
||||
cd gsio-net
|
||||
|
||||
# Build the gsio-node component
|
||||
cd crates/gsio-node
|
||||
cargo build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Running the Server
|
||||
|
||||
```bash
|
||||
# Run in development mode
|
||||
cargo run
|
||||
|
||||
# Run in release mode
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
The server will start on port 3000 by default.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
The gsio-node server provides the following Socket.IO events:
|
||||
|
||||
#### Client Events (Namespace: "/")
|
||||
|
||||
| Event | Description | Parameters | Response Event |
|
||||
|-------|-------------|------------|----------------|
|
||||
| `add_ledger_entry` | Add a new entry to the ledger | JSON data to store | `ledger_entry_added` |
|
||||
| `get_ledger` | Get all entries in the ledger | None | `ledger_entries` |
|
||||
| `get_known_nodes` | Get all known nodes in the network | None | `known_nodes` |
|
||||
| `ping` | Simple ping to check connection | Any data | `pong` |
|
||||
| `message` | Send a message to the server | Any data | `message-back` |
|
||||
| `message-with-ack` | Send a message with acknowledgement | Any data | Acknowledgement with same data |
|
||||
|
||||
#### P2P Events (Namespace: "/p2p")
|
||||
|
||||
| Event | Description | Parameters | Response Event |
|
||||
|-------|-------------|------------|----------------|
|
||||
| `p2p_message` | Send a message to other nodes | P2P message object | Varies based on message type |
|
||||
|
||||
## Examples
|
||||
|
||||
### Adding a Ledger Entry
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("add_ledger_entry", {
|
||||
message: "Hello, ledger!",
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Handle the response
|
||||
socket.on("ledger_entry_added", (entry) => {
|
||||
console.log("Entry added:", entry);
|
||||
});
|
||||
```
|
||||
|
||||
### Getting Ledger Entries
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("get_ledger");
|
||||
|
||||
// Handle the response
|
||||
socket.on("ledger_entries", (entries) => {
|
||||
console.log("Ledger entries:", entries);
|
||||
});
|
||||
```
|
||||
|
||||
### Getting Known Nodes
|
||||
|
||||
```javascript
|
||||
// Using Socket.IO client
|
||||
socket.emit("get_known_nodes");
|
||||
|
||||
// Handle the response
|
||||
socket.on("known_nodes", (data) => {
|
||||
console.log("Known nodes:", data.nodes);
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The gsio-node component consists of the following modules:
|
||||
|
||||
- **main.rs**: Entry point and Socket.IO server setup
|
||||
- **ledger.rs**: Implementation of the distributed ledger
|
||||
- **p2p.rs**: Implementation of peer-to-peer communication
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
cargo test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Add license information here]
|
242
crates/gsio-node/src/ledger.rs
Normal file
242
crates/gsio-node/src/ledger.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Sha256, Digest};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// Represents a single entry in the distributed ledger
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LedgerEntry {
|
||||
/// Unique identifier for the entry
|
||||
pub id: String,
|
||||
/// Timestamp when the entry was created
|
||||
pub timestamp: DateTime<Utc>,
|
||||
/// The actual data stored in the entry
|
||||
pub data: serde_json::Value,
|
||||
/// Hash of the previous entry in the chain
|
||||
pub previous_hash: String,
|
||||
/// Hash of this entry
|
||||
pub hash: String,
|
||||
/// Node ID that created this entry
|
||||
pub creator_node_id: String,
|
||||
/// Signatures from nodes that have validated this entry
|
||||
pub signatures: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl LedgerEntry {
|
||||
/// Create a new ledger entry
|
||||
pub fn new(
|
||||
data: serde_json::Value,
|
||||
previous_hash: String,
|
||||
creator_node_id: String,
|
||||
) -> Self {
|
||||
let timestamp = Utc::now();
|
||||
let id = format!("{}-{}", creator_node_id, timestamp.timestamp_millis());
|
||||
|
||||
let mut entry = Self {
|
||||
id,
|
||||
timestamp,
|
||||
data,
|
||||
previous_hash,
|
||||
hash: String::new(),
|
||||
creator_node_id,
|
||||
signatures: HashMap::new(),
|
||||
};
|
||||
|
||||
// Calculate the hash of this entry
|
||||
entry.hash = entry.calculate_hash();
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
/// Calculate the hash of this entry
|
||||
pub fn calculate_hash(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
// Hash the entry fields
|
||||
hasher.update(self.id.as_bytes());
|
||||
hasher.update(self.timestamp.to_rfc3339().as_bytes());
|
||||
hasher.update(self.data.to_string().as_bytes());
|
||||
hasher.update(self.previous_hash.as_bytes());
|
||||
hasher.update(self.creator_node_id.as_bytes());
|
||||
|
||||
// Convert the hash to a hex string
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
/// Add a signature from a node that has validated this entry
|
||||
pub fn add_signature(&mut self, node_id: String, signature: String) {
|
||||
self.signatures.insert(node_id, signature);
|
||||
}
|
||||
|
||||
/// Verify that this entry is valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
// Check that the hash is correct
|
||||
self.hash == self.calculate_hash()
|
||||
}
|
||||
}
|
||||
|
||||
/// The distributed ledger
|
||||
#[derive(Debug)]
|
||||
pub struct Ledger {
|
||||
/// The chain of entries in the ledger
|
||||
entries: Vec<LedgerEntry>,
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// Pending entries that have been received but not yet added to the chain
|
||||
pending_entries: HashMap<String, LedgerEntry>,
|
||||
/// Set of node IDs that are known to this node
|
||||
known_nodes: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Ledger {
|
||||
/// Create a new ledger
|
||||
pub fn new(node_id: String) -> Self {
|
||||
let mut known_nodes = HashSet::new();
|
||||
known_nodes.insert(node_id.clone());
|
||||
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
node_id,
|
||||
pending_entries: HashMap::new(),
|
||||
known_nodes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new entry to the ledger
|
||||
pub fn add_entry(&mut self, data: serde_json::Value) -> Result<LedgerEntry, String> {
|
||||
let previous_hash = match self.entries.last() {
|
||||
Some(entry) => entry.hash.clone(),
|
||||
None => "0".repeat(64), // Genesis block has a hash of all zeros
|
||||
};
|
||||
|
||||
let entry = LedgerEntry::new(data, previous_hash, self.node_id.clone());
|
||||
|
||||
// Add the entry to the chain
|
||||
self.entries.push(entry.clone());
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Get all entries in the ledger
|
||||
pub fn get_entries(&self) -> &Vec<LedgerEntry> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
/// Get the last entry in the ledger
|
||||
pub fn get_last_entry(&self) -> Option<&LedgerEntry> {
|
||||
self.entries.last()
|
||||
}
|
||||
|
||||
/// Add a pending entry that has been received from another node
|
||||
pub fn add_pending_entry(&mut self, entry: LedgerEntry) {
|
||||
self.pending_entries.insert(entry.id.clone(), entry);
|
||||
}
|
||||
|
||||
/// Process pending entries and add them to the chain if they are valid
|
||||
pub fn process_pending_entries(&mut self) -> Vec<LedgerEntry> {
|
||||
let mut added_entries = Vec::new();
|
||||
|
||||
// Get the current last entry in the chain
|
||||
let last_entry = match self.entries.last() {
|
||||
Some(entry) => entry.clone(),
|
||||
None => return added_entries,
|
||||
};
|
||||
|
||||
// Find pending entries that link to the last entry
|
||||
let mut entries_to_process: Vec<LedgerEntry> = self.pending_entries
|
||||
.values()
|
||||
.filter(|e| e.previous_hash == last_entry.hash)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Sort by timestamp to ensure deterministic ordering
|
||||
entries_to_process.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
||||
|
||||
// Process each entry
|
||||
for entry in entries_to_process {
|
||||
if entry.is_valid() {
|
||||
// Add the entry to the chain
|
||||
self.entries.push(entry.clone());
|
||||
// Remove from pending
|
||||
self.pending_entries.remove(&entry.id);
|
||||
// Add to the list of added entries
|
||||
added_entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
added_entries
|
||||
}
|
||||
|
||||
/// Add a known node to the network
|
||||
pub fn add_known_node(&mut self, node_id: String) {
|
||||
self.known_nodes.insert(node_id);
|
||||
}
|
||||
|
||||
/// Get all known nodes in the network
|
||||
pub fn get_known_nodes(&self) -> &HashSet<String> {
|
||||
&self.known_nodes
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe wrapper around the ledger
|
||||
#[derive(Clone)]
|
||||
pub struct SharedLedger {
|
||||
ledger: Arc<Mutex<Ledger>>,
|
||||
}
|
||||
|
||||
impl SharedLedger {
|
||||
/// Create a new shared ledger
|
||||
pub fn new(node_id: String) -> Self {
|
||||
Self {
|
||||
ledger: Arc::new(Mutex::new(Ledger::new(node_id))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a clone of the ledger Arc
|
||||
pub fn clone_ledger(&self) -> Arc<Mutex<Ledger>> {
|
||||
self.ledger.clone()
|
||||
}
|
||||
|
||||
/// Add a new entry to the ledger
|
||||
pub fn add_entry(&self, data: serde_json::Value) -> Result<LedgerEntry, String> {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_entry(data)
|
||||
}
|
||||
|
||||
/// Get all entries in the ledger
|
||||
pub fn get_entries(&self) -> Vec<LedgerEntry> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_entries().clone()
|
||||
}
|
||||
|
||||
/// Get the last entry in the ledger
|
||||
pub fn get_last_entry(&self) -> Option<LedgerEntry> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_last_entry().cloned()
|
||||
}
|
||||
|
||||
/// Add a pending entry that has been received from another node
|
||||
pub fn add_pending_entry(&self, entry: LedgerEntry) {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_pending_entry(entry);
|
||||
}
|
||||
|
||||
/// Process pending entries and add them to the chain if they are valid
|
||||
pub fn process_pending_entries(&self) -> Vec<LedgerEntry> {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.process_pending_entries()
|
||||
}
|
||||
|
||||
/// Add a known node to the network
|
||||
pub fn add_known_node(&self, node_id: String) {
|
||||
let mut ledger = self.ledger.lock().unwrap();
|
||||
ledger.add_known_node(node_id);
|
||||
}
|
||||
|
||||
/// Get all known nodes in the network
|
||||
pub fn get_known_nodes(&self) -> HashSet<String> {
|
||||
let ledger = self.ledger.lock().unwrap();
|
||||
ledger.get_known_nodes().clone()
|
||||
}
|
||||
}
|
2
crates/gsio-node/src/lib.rs
Normal file
2
crates/gsio-node/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod ledger;
|
||||
pub mod p2p;
|
137
crates/gsio-node/src/main.rs
Normal file
137
crates/gsio-node/src/main.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use axum::routing::get;
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use socketioxide::{
|
||||
extract::{AckSender, Data, SocketRef},
|
||||
SocketIo,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod ledger;
|
||||
mod p2p;
|
||||
|
||||
use ledger::SharedLedger;
|
||||
use p2p::P2PManager;
|
||||
|
||||
// Handle regular client connections
|
||||
async fn on_connect(socket: SocketRef, Data(data): Data<JsonValue>, p2p_manager: Arc<P2PManager>) {
|
||||
info!(ns = socket.ns(), ?socket.id, "Socket.IO client connected");
|
||||
socket.emit("auth", &data).ok();
|
||||
|
||||
// Set up basic message handlers
|
||||
socket.on("message", |socket: SocketRef, Data(data): Data<JsonValue>| async move {
|
||||
info!(?data, "Received event:");
|
||||
socket.emit("message-back", &data).ok();
|
||||
});
|
||||
|
||||
socket.on("ping", |socket: SocketRef, Data(data): Data<JsonValue>| async move {
|
||||
socket.emit("pong", &data).ok();
|
||||
});
|
||||
|
||||
socket.on(
|
||||
"message-with-ack",
|
||||
|Data(data): Data<JsonValue>, ack: AckSender| async move {
|
||||
info!(?data, "Received event");
|
||||
ack.send(&data).ok();
|
||||
},
|
||||
);
|
||||
|
||||
// Set up ledger-related handlers
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on(
|
||||
"add_ledger_entry",
|
||||
move |socket: SocketRef, Data(data): Data<JsonValue>| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!(?data, "Adding ledger entry");
|
||||
|
||||
// Add the entry to the ledger
|
||||
match p2p_manager.ledger.add_entry(data) {
|
||||
Ok(entry) => {
|
||||
// Broadcast the entry to all connected nodes
|
||||
p2p_manager.broadcast_entry(entry.clone());
|
||||
|
||||
// Send the entry back to the client
|
||||
socket.emit("ledger_entry_added", &serde_json::to_value(entry).unwrap()).ok();
|
||||
},
|
||||
Err(e) => {
|
||||
socket.emit("error", &json!({ "error": e })).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on("get_ledger", move |socket: SocketRef| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!("Getting ledger entries");
|
||||
|
||||
// Get all entries in the ledger
|
||||
let entries = p2p_manager.ledger.get_entries();
|
||||
|
||||
// Send the entries to the client
|
||||
socket.emit("ledger_entries", &serde_json::to_value(entries).unwrap()).ok();
|
||||
}
|
||||
});
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
socket.on("get_known_nodes", move |socket: SocketRef| {
|
||||
let p2p_manager = p2p_manager_clone.clone();
|
||||
async move {
|
||||
info!("Getting known nodes");
|
||||
|
||||
// Get all known nodes
|
||||
let nodes = p2p_manager.ledger.get_known_nodes();
|
||||
|
||||
// Send the nodes to the client
|
||||
socket.emit("known_nodes", &json!({ "nodes": nodes })).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle p2p node connections
|
||||
async fn on_p2p_connect(socket: SocketRef, Data(data): Data<JsonValue>, p2p_manager: Arc<P2PManager>) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Handle the connection in the p2p manager
|
||||
p2p_manager.handle_connection(socket, data);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing::subscriber::set_global_default(FmtSubscriber::default())?;
|
||||
|
||||
// Generate a unique ID for this node
|
||||
let node_id = Uuid::new_v4().to_string();
|
||||
info!("Starting node with ID: {}", node_id);
|
||||
|
||||
// Create the shared ledger
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Create the p2p manager
|
||||
let p2p_manager = Arc::new(P2PManager::new(node_id, ledger));
|
||||
|
||||
let (layer, io) = SocketIo::new_layer();
|
||||
|
||||
// Set up namespaces
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
io.ns("/", move |s, d| on_connect(s, d, p2p_manager_clone.clone()));
|
||||
|
||||
let p2p_manager_clone = p2p_manager.clone();
|
||||
io.ns("/p2p", move |s, d| on_p2p_connect(s, d, p2p_manager_clone.clone()));
|
||||
|
||||
let app = axum::Router::new()
|
||||
.route("/", get(|| async { "GSIO-Net Distributed Ledger Node" }))
|
||||
.layer(layer);
|
||||
|
||||
info!("Starting server on port 3000");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
332
crates/gsio-node/src/p2p.rs
Normal file
332
crates/gsio-node/src/p2p.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use socketioxide::extract::{Data, SocketRef};
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ledger::{LedgerEntry, SharedLedger};
|
||||
|
||||
/// Types of messages that can be sent between nodes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum MessageType {
|
||||
/// Announce a new node joining the network
|
||||
NodeAnnounce,
|
||||
/// Request the list of known nodes
|
||||
NodeListRequest,
|
||||
/// Response with the list of known nodes
|
||||
NodeListResponse,
|
||||
/// Announce a new ledger entry
|
||||
EntryAnnounce,
|
||||
/// Request a specific ledger entry
|
||||
EntryRequest,
|
||||
/// Response with a requested ledger entry
|
||||
EntryResponse,
|
||||
/// Request all ledger entries
|
||||
LedgerSyncRequest,
|
||||
/// Response with all ledger entries
|
||||
LedgerSyncResponse,
|
||||
}
|
||||
|
||||
/// A message sent between nodes in the p2p network
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct P2PMessage {
|
||||
/// Type of message
|
||||
pub message_type: MessageType,
|
||||
/// Unique ID for this message
|
||||
pub message_id: String,
|
||||
/// ID of the node that sent this message
|
||||
pub sender_id: String,
|
||||
/// ID of the node that should receive this message (empty for broadcast)
|
||||
pub recipient_id: String,
|
||||
/// The actual message payload
|
||||
pub payload: JsonValue,
|
||||
}
|
||||
|
||||
impl P2PMessage {
|
||||
/// Create a new p2p message
|
||||
pub fn new(
|
||||
message_type: MessageType,
|
||||
sender_id: String,
|
||||
recipient_id: String,
|
||||
payload: JsonValue,
|
||||
) -> Self {
|
||||
Self {
|
||||
message_type,
|
||||
message_id: Uuid::new_v4().to_string(),
|
||||
sender_id,
|
||||
recipient_id,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages p2p communication between nodes
|
||||
pub struct P2PManager {
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// The shared ledger
|
||||
pub ledger: SharedLedger,
|
||||
/// Connected sockets by node ID
|
||||
connected_nodes: Arc<Mutex<HashMap<String, SocketRef>>>,
|
||||
}
|
||||
|
||||
impl P2PManager {
|
||||
/// Create a new p2p manager
|
||||
pub fn new(node_id: String, ledger: SharedLedger) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
ledger,
|
||||
connected_nodes: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the node ID
|
||||
pub fn node_id(&self) -> &str {
|
||||
&self.node_id
|
||||
}
|
||||
|
||||
/// Get a clone of the connected nodes Arc
|
||||
pub fn clone_connected_nodes(&self) -> Arc<Mutex<HashMap<String, SocketRef>>> {
|
||||
self.connected_nodes.clone()
|
||||
}
|
||||
|
||||
/// Handle a new connection from another node
|
||||
pub fn handle_connection(&self, socket: SocketRef, data: JsonValue) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Extract the node ID from the connection data
|
||||
let node_id = match data.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the connected nodes
|
||||
{
|
||||
let mut connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
connected_nodes.insert(node_id.clone(), socket.clone());
|
||||
}
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id.clone());
|
||||
|
||||
// Send a node announce message to all other nodes
|
||||
self.broadcast_message(P2PMessage::new(
|
||||
MessageType::NodeAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
json!({ "node_id": node_id }),
|
||||
));
|
||||
|
||||
// Set up event handlers for this socket
|
||||
self.setup_socket_handlers(socket);
|
||||
}
|
||||
|
||||
/// Set up event handlers for a socket
|
||||
fn setup_socket_handlers(&self, socket: SocketRef) {
|
||||
let p2p_manager = self.clone();
|
||||
|
||||
// Handle p2p messages
|
||||
socket.on("p2p_message", move |socket: SocketRef, Data(data): Data<JsonValue>| {
|
||||
let p2p_manager = p2p_manager.clone();
|
||||
async move {
|
||||
info!(?data, "Received p2p message");
|
||||
|
||||
// Parse the message
|
||||
let message: P2PMessage = match serde_json::from_value(data) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
info!("Error parsing p2p message: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the message
|
||||
p2p_manager.handle_message(socket, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle a p2p message
|
||||
fn handle_message(&self, socket: SocketRef, message: P2PMessage) {
|
||||
match message.message_type {
|
||||
MessageType::NodeAnnounce => self.handle_node_announce(message),
|
||||
MessageType::NodeListRequest => self.handle_node_list_request(socket, message),
|
||||
MessageType::EntryAnnounce => self.handle_entry_announce(message),
|
||||
MessageType::EntryRequest => self.handle_entry_request(socket, message),
|
||||
MessageType::LedgerSyncRequest => self.handle_ledger_sync_request(socket, message),
|
||||
_ => info!("Unhandled message type: {:?}", message.message_type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a node announce message
|
||||
fn handle_node_announce(&self, message: P2PMessage) {
|
||||
// Extract the node ID from the message
|
||||
let node_id = match message.payload.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id);
|
||||
}
|
||||
|
||||
/// Handle a node list request message
|
||||
fn handle_node_list_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Get the list of known nodes
|
||||
let known_nodes = self.ledger.get_known_nodes();
|
||||
|
||||
// Send the response
|
||||
let response = P2PMessage::new(
|
||||
MessageType::NodeListResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
json!({ "nodes": known_nodes }),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
|
||||
/// Handle an entry announce message
|
||||
fn handle_entry_announce(&self, message: P2PMessage) {
|
||||
// Extract the entry from the message
|
||||
let entry: LedgerEntry = match serde_json::from_value(message.payload.clone()) {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
info!("Error parsing entry announce: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Add the entry to the pending entries
|
||||
self.ledger.add_pending_entry(entry);
|
||||
|
||||
// Process pending entries
|
||||
let added_entries = self.ledger.process_pending_entries();
|
||||
|
||||
// Announce any new entries that were added
|
||||
for entry in added_entries {
|
||||
self.broadcast_entry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an entry request message
|
||||
fn handle_entry_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Extract the entry ID from the message
|
||||
let entry_id = match message.payload.get("entry_id") {
|
||||
Some(id) => id.as_str().unwrap_or("").to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
// Find the entry in the ledger
|
||||
let entries = self.ledger.get_entries();
|
||||
let entry = entries.iter().find(|e| e.id == entry_id);
|
||||
|
||||
// Send the response
|
||||
if let Some(entry) = entry {
|
||||
let response = P2PMessage::new(
|
||||
MessageType::EntryResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a ledger sync request message
|
||||
fn handle_ledger_sync_request(&self, socket: SocketRef, message: P2PMessage) {
|
||||
// Get all entries in the ledger
|
||||
let entries = self.ledger.get_entries();
|
||||
|
||||
// Send the response
|
||||
let response = P2PMessage::new(
|
||||
MessageType::LedgerSyncResponse,
|
||||
self.node_id.clone(),
|
||||
message.sender_id,
|
||||
serde_json::to_value(entries).unwrap(),
|
||||
);
|
||||
|
||||
socket.emit("p2p_message", &serde_json::to_value(response).unwrap()).ok();
|
||||
}
|
||||
|
||||
/// Broadcast a message to all connected nodes
|
||||
pub fn broadcast_message(&self, message: P2PMessage) {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
for (_, socket) in connected_nodes.iter() {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message.clone()).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a new ledger entry to all connected nodes
|
||||
pub fn broadcast_entry(&self, entry: LedgerEntry) {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
self.broadcast_message(message);
|
||||
}
|
||||
|
||||
/// Send a message to a specific node
|
||||
pub fn send_message(&self, recipient_id: String, message: P2PMessage) -> bool {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
if let Some(socket) = connected_nodes.get(&recipient_id) {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message).unwrap()).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Request the list of known nodes from a specific node
|
||||
pub fn request_node_list(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request a specific ledger entry from a specific node
|
||||
pub fn request_entry(&self, recipient_id: String, entry_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({ "entry_id": entry_id }),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request all ledger entries from a specific node
|
||||
pub fn request_ledger_sync(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::LedgerSyncRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for P2PManager {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
node_id: self.node_id.clone(),
|
||||
ledger: self.ledger.clone(),
|
||||
connected_nodes: self.connected_nodes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
41
crates/gsio-node/tests/basic_test.rs
Normal file
41
crates/gsio-node/tests/basic_test.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use axum::routing::get;
|
||||
use serde_json::json;
|
||||
use socketioxide::{
|
||||
extract::{Data, SocketRef},
|
||||
SocketIo,
|
||||
};
|
||||
|
||||
// Test the Socket.IO server setup
|
||||
#[test]
|
||||
fn test_socketio_setup() {
|
||||
let (_, io) = SocketIo::new_layer();
|
||||
// Set up a namespace to verify it works
|
||||
io.ns("/", |_socket: SocketRef, _data: Data<serde_json::Value>| async move {});
|
||||
// If we got here without errors, the setup is successful
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
// Test the on_connect handler
|
||||
#[tokio::test]
|
||||
async fn test_on_connect_handler() {
|
||||
let (_, io) = SocketIo::new_layer();
|
||||
|
||||
// Define a simple handler for testing
|
||||
io.ns("/", |socket: SocketRef, Data(data): Data<serde_json::Value>| async move {
|
||||
// Echo back the auth data
|
||||
socket.emit("auth", &data).ok();
|
||||
});
|
||||
|
||||
// If we got here without errors, the namespace was set up successfully
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
// Test the Socket.IO layer creation
|
||||
#[test]
|
||||
fn test_socketio_layer() {
|
||||
// Just test that we can create a Socket.IO layer
|
||||
let (layer, _) = SocketIo::new_layer();
|
||||
|
||||
// If we got here without errors, the layer was created successfully
|
||||
assert!(true);
|
||||
}
|
150
crates/gsio-node/tests/ledger_test.rs
Normal file
150
crates/gsio-node/tests/ledger_test.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use gsio_node::ledger::{LedgerEntry, Ledger, SharedLedger};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_ledger_entry_creation() {
|
||||
// Create a new ledger entry
|
||||
let data = json!({ "message": "Test entry" });
|
||||
let previous_hash = "0".repeat(64);
|
||||
let creator_node_id = "test-node-1".to_string();
|
||||
|
||||
let entry = LedgerEntry::new(data.clone(), previous_hash.clone(), creator_node_id.clone());
|
||||
|
||||
// Verify the entry fields
|
||||
assert_eq!(entry.data, data);
|
||||
assert_eq!(entry.previous_hash, previous_hash);
|
||||
assert_eq!(entry.creator_node_id, creator_node_id);
|
||||
assert!(!entry.hash.is_empty());
|
||||
assert!(entry.signatures.is_empty());
|
||||
|
||||
// Verify the entry is valid
|
||||
assert!(entry.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ledger_add_entry() {
|
||||
// Create a new ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let mut ledger = Ledger::new(node_id.clone());
|
||||
|
||||
// Add an entry to the ledger
|
||||
let data = json!({ "message": "Test entry 1" });
|
||||
let result = ledger.add_entry(data.clone());
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok());
|
||||
let entry = result.unwrap();
|
||||
assert_eq!(entry.data, data);
|
||||
assert_eq!(entry.creator_node_id, node_id);
|
||||
|
||||
// Verify the ledger state
|
||||
let entries = ledger.get_entries();
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert_eq!(entries[0].data, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ledger_chain_integrity() {
|
||||
// Create a new ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let mut ledger = Ledger::new(node_id.clone());
|
||||
|
||||
// Add multiple entries to the ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
let data3 = json!({ "message": "Test entry 3" });
|
||||
|
||||
let _entry1 = ledger.add_entry(data1.clone()).unwrap();
|
||||
let _entry2 = ledger.add_entry(data2.clone()).unwrap();
|
||||
let _entry3 = ledger.add_entry(data3.clone()).unwrap();
|
||||
|
||||
// Verify the chain integrity
|
||||
let entries = ledger.get_entries();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
||||
// First entry should have a previous hash of all zeros
|
||||
assert_eq!(entries[0].previous_hash, "0".repeat(64));
|
||||
|
||||
// Each entry's hash should match the next entry's previous_hash
|
||||
assert_eq!(entries[1].previous_hash, entries[0].hash);
|
||||
assert_eq!(entries[2].previous_hash, entries[1].hash);
|
||||
|
||||
// All entries should be valid
|
||||
for entry in entries {
|
||||
assert!(entry.is_valid());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_ledger() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add entries to the shared ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
|
||||
let _entry1 = shared_ledger.add_entry(data1.clone()).unwrap();
|
||||
let _entry2 = shared_ledger.add_entry(data2.clone()).unwrap();
|
||||
|
||||
// Verify the entries
|
||||
let entries = shared_ledger.get_entries();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(entries[0].data, data1);
|
||||
assert_eq!(entries[1].data, data2);
|
||||
|
||||
// Verify the last entry
|
||||
let last_entry = shared_ledger.get_last_entry().unwrap();
|
||||
assert_eq!(last_entry.data, data2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pending_entries() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add an entry to the ledger
|
||||
let data1 = json!({ "message": "Test entry 1" });
|
||||
let entry1 = shared_ledger.add_entry(data1.clone()).unwrap();
|
||||
|
||||
// Create a pending entry that links to the first entry
|
||||
let data2 = json!({ "message": "Test entry 2" });
|
||||
let entry2 = LedgerEntry::new(data2.clone(), entry1.hash.clone(), "test-node-2".to_string());
|
||||
|
||||
// Add the pending entry
|
||||
shared_ledger.add_pending_entry(entry2.clone());
|
||||
|
||||
// Process pending entries
|
||||
let added_entries = shared_ledger.process_pending_entries();
|
||||
|
||||
// Verify the pending entry was added
|
||||
assert_eq!(added_entries.len(), 1);
|
||||
assert_eq!(added_entries[0].data, data2);
|
||||
|
||||
// Verify the ledger state
|
||||
let entries = shared_ledger.get_entries();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(entries[0].data, data1);
|
||||
assert_eq!(entries[1].data, data2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_known_nodes() {
|
||||
// Create a new shared ledger
|
||||
let node_id = "test-node-1".to_string();
|
||||
let shared_ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
// Add known nodes
|
||||
shared_ledger.add_known_node("test-node-2".to_string());
|
||||
shared_ledger.add_known_node("test-node-3".to_string());
|
||||
shared_ledger.add_known_node("test-node-2".to_string()); // Duplicate should be ignored
|
||||
|
||||
// Verify the known nodes
|
||||
let known_nodes = shared_ledger.get_known_nodes();
|
||||
assert_eq!(known_nodes.len(), 3); // Including the original node
|
||||
assert!(known_nodes.contains(&node_id));
|
||||
assert!(known_nodes.contains(&"test-node-2".to_string()));
|
||||
assert!(known_nodes.contains(&"test-node-3".to_string()));
|
||||
}
|
392
crates/gsio-node/tests/p2p_test.rs
Normal file
392
crates/gsio-node/tests/p2p_test.rs
Normal file
@@ -0,0 +1,392 @@
|
||||
use gsio_node::p2p::{P2PMessage, MessageType};
|
||||
use gsio_node::ledger::{LedgerEntry, SharedLedger};
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
use tracing::info;
|
||||
|
||||
// Mock SocketRef for testing
|
||||
#[derive(Clone)]
|
||||
struct MockSocketRef {
|
||||
id: String,
|
||||
sent_messages: Arc<Mutex<Vec<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
impl MockSocketRef {
|
||||
fn new(id: &str) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
sent_messages: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit<T: serde::Serialize>(&self, _event: &str, data: &T) -> Result<(), String> {
|
||||
let value = serde_json::to_value(data).unwrap();
|
||||
let mut messages = self.sent_messages.lock().unwrap();
|
||||
messages.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_sent_messages(&self) -> Vec<serde_json::Value> {
|
||||
let messages = self.sent_messages.lock().unwrap();
|
||||
messages.clone()
|
||||
}
|
||||
|
||||
fn ns(&self) -> &str {
|
||||
"/"
|
||||
}
|
||||
}
|
||||
|
||||
// Test-specific P2PManager implementation
|
||||
struct P2PManager {
|
||||
/// The ID of this node
|
||||
node_id: String,
|
||||
/// The shared ledger
|
||||
pub ledger: SharedLedger,
|
||||
/// Connected sockets by node ID
|
||||
connected_nodes: Arc<Mutex<HashMap<String, MockSocketRef>>>,
|
||||
}
|
||||
|
||||
impl P2PManager {
|
||||
/// Create a new p2p manager
|
||||
pub fn new(node_id: String, ledger: SharedLedger) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
ledger,
|
||||
connected_nodes: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the node ID
|
||||
pub fn node_id(&self) -> &str {
|
||||
&self.node_id
|
||||
}
|
||||
|
||||
/// Get a clone of the connected nodes Arc
|
||||
pub fn clone_connected_nodes(&self) -> Arc<Mutex<HashMap<String, MockSocketRef>>> {
|
||||
self.connected_nodes.clone()
|
||||
}
|
||||
|
||||
/// Handle a new connection from another node
|
||||
pub fn handle_connection(&self, socket: MockSocketRef, data: JsonValue) {
|
||||
info!(ns = socket.ns(), ?socket.id, "P2P node connected");
|
||||
|
||||
// Extract the node ID from the connection data
|
||||
let node_id = match data.get("node_id") {
|
||||
Some(id) => id.as_str().unwrap_or("unknown").to_string(),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
|
||||
// Add the node to the connected nodes
|
||||
{
|
||||
let mut connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
connected_nodes.insert(node_id.clone(), socket.clone());
|
||||
}
|
||||
|
||||
// Add the node to the known nodes in the ledger
|
||||
self.ledger.add_known_node(node_id.clone());
|
||||
|
||||
// In the real implementation, we would set up event handlers for this socket
|
||||
// but for testing purposes, we don't need to do that
|
||||
}
|
||||
|
||||
/// Broadcast a message to all connected nodes
|
||||
pub fn broadcast_message(&self, message: P2PMessage) {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
for (_, socket) in connected_nodes.iter() {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message.clone()).unwrap()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a new ledger entry to all connected nodes
|
||||
pub fn broadcast_entry(&self, entry: LedgerEntry) {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryAnnounce,
|
||||
self.node_id.clone(),
|
||||
"".to_string(),
|
||||
serde_json::to_value(entry).unwrap(),
|
||||
);
|
||||
|
||||
self.broadcast_message(message);
|
||||
}
|
||||
|
||||
/// Send a message to a specific node
|
||||
pub fn send_message(&self, recipient_id: String, message: P2PMessage) -> bool {
|
||||
let connected_nodes = self.connected_nodes.lock().unwrap();
|
||||
|
||||
if let Some(socket) = connected_nodes.get(&recipient_id) {
|
||||
socket.emit("p2p_message", &serde_json::to_value(message).unwrap()).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Request the list of known nodes from a specific node
|
||||
pub fn request_node_list(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request a specific ledger entry from a specific node
|
||||
pub fn request_entry(&self, recipient_id: String, entry_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::EntryRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({ "entry_id": entry_id }),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
|
||||
/// Request all ledger entries from a specific node
|
||||
pub fn request_ledger_sync(&self, recipient_id: String) -> bool {
|
||||
let message = P2PMessage::new(
|
||||
MessageType::LedgerSyncRequest,
|
||||
self.node_id.clone(),
|
||||
recipient_id.clone(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
self.send_message(recipient_id, message)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_p2p_message_creation() {
|
||||
let message_type = MessageType::NodeAnnounce;
|
||||
let sender_id = "test-node-1".to_string();
|
||||
let recipient_id = "test-node-2".to_string();
|
||||
let payload = json!({ "node_id": "test-node-1" });
|
||||
|
||||
let message = P2PMessage::new(
|
||||
message_type,
|
||||
sender_id.clone(),
|
||||
recipient_id.clone(),
|
||||
payload.clone(),
|
||||
);
|
||||
|
||||
// Verify the message fields
|
||||
assert!(matches!(message.message_type, MessageType::NodeAnnounce));
|
||||
assert_eq!(message.sender_id, sender_id);
|
||||
assert_eq!(message.recipient_id, recipient_id);
|
||||
assert_eq!(message.payload, payload);
|
||||
assert!(!message.message_id.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_p2p_manager_creation() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Verify the manager fields
|
||||
assert_eq!(p2p_manager.node_id(), &node_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_connection() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Create a mock socket
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
// Handle a connection
|
||||
let connection_data = json!({
|
||||
"node_id": "test-node-2"
|
||||
});
|
||||
|
||||
p2p_manager.handle_connection(socket.clone(), connection_data);
|
||||
|
||||
// Verify the node was added to known nodes
|
||||
let known_nodes = p2p_manager.ledger.get_known_nodes();
|
||||
assert!(known_nodes.contains(&"test-node-2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_broadcast_message() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add some connected nodes
|
||||
let socket1 = MockSocketRef::new("socket-1");
|
||||
let socket2 = MockSocketRef::new("socket-2");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket1.clone());
|
||||
connected_nodes.insert("test-node-3".to_string(), socket2.clone());
|
||||
}
|
||||
|
||||
// Create a message to broadcast
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeAnnounce,
|
||||
node_id.clone(),
|
||||
"".to_string(),
|
||||
json!({ "node_id": node_id }),
|
||||
);
|
||||
|
||||
// Broadcast the message
|
||||
p2p_manager.broadcast_message(message);
|
||||
|
||||
// Verify the message was sent to all connected nodes
|
||||
let messages1 = socket1.get_sent_messages();
|
||||
let messages2 = socket2.get_sent_messages();
|
||||
|
||||
assert_eq!(messages1.len(), 1);
|
||||
assert_eq!(messages2.len(), 1);
|
||||
|
||||
let sent_message1 = &messages1[0];
|
||||
let sent_message2 = &messages2[0];
|
||||
|
||||
assert_eq!(sent_message1["sender_id"], node_id);
|
||||
assert_eq!(sent_message2["sender_id"], node_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_message() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Create a message to send
|
||||
let message = P2PMessage::new(
|
||||
MessageType::NodeListRequest,
|
||||
node_id.clone(),
|
||||
"test-node-2".to_string(),
|
||||
json!({}),
|
||||
);
|
||||
|
||||
// Send the message
|
||||
let result = p2p_manager.send_message("test-node-2".to_string(), message);
|
||||
|
||||
// Verify the message was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_node_list() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request the node list
|
||||
let result = p2p_manager.request_node_list("test-node-2".to_string());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "NodeListRequest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_entry() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request an entry
|
||||
let entry_id = "test-entry-1".to_string();
|
||||
let result = p2p_manager.request_entry("test-node-2".to_string(), entry_id.clone());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "EntryRequest");
|
||||
assert_eq!(sent_message["payload"]["entry_id"], entry_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_ledger_sync() {
|
||||
let node_id = "test-node-1".to_string();
|
||||
let ledger = SharedLedger::new(node_id.clone());
|
||||
|
||||
let p2p_manager = P2PManager::new(node_id.clone(), ledger);
|
||||
|
||||
// Add a connected node
|
||||
let socket = MockSocketRef::new("socket-1");
|
||||
|
||||
{
|
||||
let connected_nodes_arc = p2p_manager.clone_connected_nodes();
|
||||
let mut connected_nodes = connected_nodes_arc.lock().unwrap();
|
||||
connected_nodes.insert("test-node-2".to_string(), socket.clone());
|
||||
}
|
||||
|
||||
// Request ledger sync
|
||||
let result = p2p_manager.request_ledger_sync("test-node-2".to_string());
|
||||
|
||||
// Verify the request was sent
|
||||
assert!(result);
|
||||
|
||||
let messages = socket.get_sent_messages();
|
||||
assert_eq!(messages.len(), 1);
|
||||
|
||||
let sent_message = &messages[0];
|
||||
assert_eq!(sent_message["sender_id"], node_id);
|
||||
assert_eq!(sent_message["recipient_id"], "test-node-2");
|
||||
assert_eq!(sent_message["message_type"], "LedgerSyncRequest");
|
||||
}
|
Reference in New Issue
Block a user