basic network established

This commit is contained in:
geoffsee
2025-06-14 11:21:01 -04:00
commit 9747912595
33 changed files with 8377 additions and 0 deletions

View 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
View 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]

View 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()
}
}

View File

@@ -0,0 +1,2 @@
pub mod ledger;
pub mod p2p;

View 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
View 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(),
}
}
}

View 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);
}

View 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()));
}

View 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");
}