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

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
target/
node_modules/
.wrangler/
.idea/

4347
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

5
Cargo.toml Normal file
View File

@@ -0,0 +1,5 @@
[workspace]
members = [
"crates/gsio-relay",
'crates/gsio-node'
]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 gsio-net Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

357
README.md Normal file
View File

@@ -0,0 +1,357 @@
# gsio-net
gsio-net is a distributed ledger system with three main components:
1. **gsio-node**: A Rust-based Socket.IO server that handles real-time communication
2. **gsio-relay**: A Rust-based WebSocket server implemented as a Cloudflare Worker
3. **gsio-node-client**: A TypeScript client for connecting to the gsio-node server
## Project Overview
`gsio-net` provides a foundation for building distributed ledger applications with real-time communication capabilities. The system uses Socket.IO for client-server communication and WebSockets for peer-to-peer relay.
## Project Status
### Why it exists
- **1 AM couch spark → next-morning code.**
- **Big idea:** a **multipurpose private network**—built only with open-web tech—that gives any group trust (tamper-proof), transparency (everyone can verify), **and** anonymity (no personal data needed).
### What works today
1. **Real-time sync** add a note on one computer, see it on every other in under a second.
2. **Cryptographic chain** each note is linked to the last, so history can't be secretly edited.
3. **Auto-peer discovery** nodes find each other through a tiny Cloudflare Worker relay.
4. **Runs almost anywhere** thanks to **Socket.IO** (via Rust crate **`socketoxide`**) which swaps between WebSockets, HTTP long-polling, etc., to punch through most firewalls.
### What's next
- **Direct peer-to-peer connections with iroh**
- When a node or client first connects, it will generate an *iroh dial-out address* (think of it as its "phone number").
- That address is broadcast over the existing Socket.IO channel.
- Other peers will then call that number to form a direct, encrypted link—skipping the relay for faster, more private transfers.
- A slick **web UI** so non-coders can join with one click.
- **One-command deploy** scripts so anyone can spin up their own private micro-network.
- A visual **history viewer** that shows who posted what (optionally under nicknames).
### Key Features
- Distributed ledger with cryptographic verification
- Peer-to-peer networking with node discovery and message propagation
- Consensus mechanism for ledger synchronization
- Real-time bidirectional communication using Socket.IO
- WebSocket relay for peer-to-peer messaging
- TypeScript client for easy integration with web applications
- Rust-based servers for high performance and reliability
## Network Architecture
`gsio-net` implements a hybrid network architecture combining client-server and peer-to-peer models:
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ gsio-node │◄────┤ gsio-relay │◄────│ gsio-node │
│ (Server A) │ │ (WebSocket │ │ (Server B) │
└──────┬──────┘ │ Relay) │ └──────┬──────┘
│ └─────────────┘ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ gsio-client │ │ gsio-client │
│ (Client A) │ │ (Client B) │
└─────────────┘ └─────────────┘
```
### How the Network Works
1. **Node-Client Communication**:
- Clients connect to gsio-node servers using Socket.IO
- Each server maintains its own distributed ledger
- Clients can add entries to the ledger and retrieve ledger data
2. **Node-Node Communication**:
- Nodes communicate with each other through the gsio-relay server
- The relay server acts as a message broker using WebSockets
- Nodes can discover each other and synchronize their ledgers
3. **Distributed Ledger**:
- Each ledger entry contains data, timestamps, and cryptographic hashes
- Entries form a chain where each entry references the previous entry's hash
- The ledger is synchronized across nodes using the P2P network
4. **Message Flow**:
- When a client adds an entry to a node's ledger, the node broadcasts it to other nodes
- Nodes validate incoming entries and add them to their ledgers
- Nodes can request missing entries from other nodes to maintain synchronization
### Protocol Details
The P2P protocol uses the following message types:
1. **NodeAnnounce**: Announces a new node joining the network. Empty recipient_id indicates a broadcast message.
```json
{
"message_type": "NodeAnnounce",
"message_id": "uuid-string",
"sender_id": "node-id",
"recipient_id": "",
"payload": { "node_id": "node-id" }
}
```
2. **NodeListRequest/Response**: Request and response for the list of known nodes
```json
{
"message_type": "NodeListResponse",
"message_id": "uuid-string",
"sender_id": "node-id",
"recipient_id": "requesting-node-id",
"payload": { "nodes": ["node-id-1", "node-id-2"] }
}
```
3. **EntryAnnounce**: Announces a new ledger entry. Empty recipient_id indicates a broadcast message.
```json
{
"message_type": "EntryAnnounce",
"message_id": "uuid-string",
"sender_id": "node-id",
"recipient_id": "",
"payload": {
"id": "entry-id",
"timestamp": "2023-05-20T15:30:45Z",
"data": { "message": "Example data" },
"previous_hash": "hash-string",
"hash": "hash-string",
"creator_node_id": "node-id",
"signatures": {}
}
}
```
4. **LedgerSyncRequest/Response**: Request and response for synchronizing the entire ledger. The payload contains an array of ledger entries.
```json
{
"message_type": "LedgerSyncResponse",
"message_id": "uuid-string",
"sender_id": "node-id",
"recipient_id": "requesting-node-id",
"payload": []
}
```
### Security Considerations
1. **Cryptographic Verification**:
- Each ledger entry is hashed using SHA-256
- The hash includes the entry's ID, timestamp, data, previous hash, and creator node ID
- This ensures the integrity of the ledger chain
2. **Chain Integrity**:
- Each entry references the hash of the previous entry
- This creates a cryptographic chain that is difficult to tamper with
- Nodes validate the hash of each entry before adding it to their ledger
3. **Consensus Mechanism**:
- Entries are ordered by timestamp to ensure deterministic ordering
- Nodes only accept entries that link to their current last entry
- This ensures that all nodes converge to the same ledger state
4. **Authentication**:
- The current implementation uses simple node IDs
- For production use, implement proper authentication and authorization
- Consider adding digital signatures for entry validation
## Getting Started
### Prerequisites
- Rust (latest stable version)
- Node.js (latest LTS version)
- Bun (for running TypeScript code and tests)
- Wrangler CLI (for Cloudflare Workers development)
### Installation
1. Clone the repository:
```bash
git clone https://github.com/seemueller-io/gsio-net.git
cd gsio-net
```
2. Install dependencies:
```bash
npm install
```
This will also build the Rust components due to the `postinstall` script.
### Running the Components
1. **gsio-node** (Socket.IO server):
```bash
cd crates/gsio-node
cargo run
```
The server will start on port 3000. Each node has a unique ID and maintains its own distributed ledger.
2. **gsio-relay** (WebSocket relay):
```bash
cd crates/gsio-relay
wrangler dev
```
The WebSocket server will start on port 8787. This relay enables p2p communication between nodes.
3. **gsio-node-client** (Socket.IO client):
```bash
cd packages/gsio-node-client
bun src/listeners/node_listener.ts
```
This will connect to the gsio-node server and interact with the distributed ledger.
4. **gsio-node-client** (WebSocket p2p client):
```bash
cd packages/gsio-node-client
bun src/listeners/ws_client.ts
```
This will connect to the relay server and participate in the p2p network.
### Using the Distributed Ledger
The Socket.IO client provides the following functions for interacting with the distributed ledger:
- **Adding an entry**:
```typescript
// Add a new entry to the ledger
socket.emit("add_ledger_entry", { message: "Hello, ledger!", timestamp: new Date().toISOString() });
```
- **Getting all entries**:
```typescript
// Request all ledger entries
socket.emit("get_ledger");
// Handle the response
socket.on("ledger_entries", (entries) => {
console.log("Ledger entries:", entries);
});
```
- **Getting known nodes**:
```typescript
// Request all known nodes
socket.emit("get_known_nodes");
// Handle the response
socket.on("known_nodes", (data) => {
console.log("Known nodes:", data.nodes);
});
```
### Using the P2P Network
The WebSocket client provides the following functions for participating in the p2p network:
- **Sending a p2p message**:
```typescript
// Send a p2p message
sendP2PMessage({
message_type: "NodeAnnounce",
sender_id: nodeId,
recipient_id: "",
payload: { node_id: nodeId }
});
```
- **Handling p2p messages**:
```typescript
// Handle incoming p2p messages
websocket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
console.log("Received p2p message:", message);
});
```
## Development
### Project Structure
- **crates/**: Contains Rust crates
- **gsio-node/**: Socket.IO server implementation
- **src/ledger.rs**: Distributed ledger implementation
- **src/p2p.rs**: Peer-to-peer networking implementation
- **gsio-relay/**: WebSocket server implemented as a Cloudflare Worker
- **packages/**: Contains TypeScript packages
- **gsio-node-client/**: Client for connecting to the gsio-node server
- **src/listeners/node_listener.ts**: Socket.IO client for ledger operations
- **src/listeners/ws_client.ts**: WebSocket client for p2p communication
- **scripts/**: Utility scripts for the project
### Distributed Ledger
The distributed ledger is implemented in `crates/gsio-node/src/ledger.rs` and provides the following features:
- **Ledger Entries**: Each entry contains data, timestamps, and cryptographic hashes
- **Chain Integrity**: Each entry references the hash of the previous entry
- **Validation**: Entries are validated using cryptographic hashes
- **Consensus**: Entries are synchronized across nodes in the network
### Peer-to-Peer Networking
The p2p networking is implemented in `crates/gsio-node/src/p2p.rs` and provides the following features:
- **Node Discovery**: Nodes announce themselves to the network
- **Message Propagation**: Messages are propagated to all connected nodes
- **Connection Management**: Connections are managed using Socket.IO
- **Ledger Synchronization**: Nodes can request and receive ledger entries from other nodes
### Building Individual Components
- **gsio-node**:
```bash
cd crates/gsio-node
cargo build
```
- **gsio-relay**:
```bash
cd crates/gsio-relay
cargo install -q worker-build && worker-build --release
```
- **gsio-node-client**:
```bash
cd packages/gsio-node-client
bun install
```
### Running Tests
1. **Rust Tests**:
```bash
cd crates/gsio-node
cargo test
```
2. **TypeScript Tests**:
```bash
cd packages/gsio-node-client
bun test
```
### Debugging
- **gsio-node**: Use `RUST_LOG=debug cargo run` for verbose logging.
- **gsio-relay**: Use `wrangler dev --verbose` for detailed logs.
- **gsio-node-client**: Add `console.log` statements for debugging.
### Cleaning the Project
To clean the project, run:
```bash
npm run clean
```
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under the MIT License - see the LICENSE file for details.

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

999
crates/gsio-relay/Cargo.lock generated Normal file
View File

@@ -0,0 +1,999 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "async-trait"
version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bumpalo"
version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"zerovec",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "realscheme"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"futures",
"http",
"worker",
"worker-macros",
]
[[package]]
name = "rustc-demangle"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"pin-project-lite",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "worker"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727789ca7eff9733efbea9d0e97779edc1cf1926e98aee7d7d8afe32805458aa"
dependencies = [
"async-trait",
"bytes",
"chrono",
"futures-channel",
"futures-util",
"http",
"http-body",
"js-sys",
"matchit",
"pin-project",
"serde",
"serde-wasm-bindgen 0.6.5",
"serde_json",
"serde_urlencoded",
"tokio",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"worker-kv",
"worker-macros",
"worker-sys",
]
[[package]]
name = "worker-kv"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f06d4d1416a9f8346ee9123b0d9a11b3cfa38e6cfb5a139698017d1597c4d41"
dependencies = [
"js-sys",
"serde",
"serde-wasm-bindgen 0.5.0",
"serde_json",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "worker-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d625c24570ba9207a2617476013335f28a95cbe513e59bb814ffba092a18058"
dependencies = [
"async-trait",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-macro-support",
"worker-sys",
]
[[package]]
name = "worker-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34563340d41016b4381257c5a16b0d2bc590dbe00500ecfbebcaa16f5f85ce90"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,25 @@
[package]
name = "gsio-relay"
version = "0.1.0"
edition = "2024"
authors = [ "geoffsee <>" ]
[package.metadata.release]
release = false
# https://github.com/rustwasm/wasm-pack/issues/1247
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[lib]
crate-type = ["cdylib"]
[dependencies]
worker = { version = "0.5.0", features = ['http'] }
worker-macros = { version = "0.5.0", features = ['http'] }
console_error_panic_hook = { version = "0.1.1" }
http = "1.1"
futures = "0.3.31"
[dev-dependencies]
wasm-bindgen-test = "0.3.37"

134
crates/gsio-relay/README.md Normal file
View File

@@ -0,0 +1,134 @@
# gsio-relay
A Rust-based WebSocket server implemented as a Cloudflare Worker that serves as a relay in the GSIO-Net distributed ledger system.
## Overview
The gsio-relay component is a WebSocket server that:
- Accepts WebSocket connections from gsio-node instances
- Relays messages between nodes to enable peer-to-peer communication
- Runs as a Cloudflare Worker for global distribution and reliability
- Facilitates the synchronization of distributed ledgers across nodes
## Features
- **WebSocket Server**: Provides real-time bidirectional communication
- **Cloudflare Worker**: Runs on Cloudflare's edge network for low latency
- **Message Relay**: Forwards messages between nodes in the network
- **Lightweight**: Minimal implementation focused on efficient message passing
- **Scalable**: Can handle many concurrent connections
## Installation
### Prerequisites
- Rust (latest stable version)
- Wrangler CLI (for Cloudflare Workers development)
- Node.js (for running Wrangler)
### Building
```bash
# Clone the repository (if you haven't already)
git clone <repository-url>
cd gsio-net
# Install Wrangler CLI if you haven't already
npm install -g wrangler
# Build the gsio-relay component
cd crates/gsio-relay
cargo install -q worker-build && worker-build --release
```
## Usage
### Running Locally
```bash
# Run the worker locally
wrangler dev
```
The WebSocket server will start on port 8787 by default.
### Deploying to Cloudflare
```bash
# Deploy to Cloudflare
wrangler publish
```
### Configuration
The worker can be configured using the `wrangler.toml` file:
```toml
name = "gsio-relay"
type = "javascript"
account_id = "<your-account-id>"
workers_dev = true
compatibility_date = "2023-01-01"
[build]
command = "cargo install -q worker-build && worker-build --release"
[build.upload]
format = "modules"
main = "./worker/worker.mjs"
```
## WebSocket Protocol
The gsio-relay server accepts WebSocket connections and relays messages between connected clients. The protocol is simple:
1. Connect to the WebSocket server
2. Send messages as text
3. Receive echoed messages from the server
In the GSIO-Net system, nodes use this relay to exchange P2P messages for ledger synchronization and node discovery.
## Examples
### Connecting to the Relay
```javascript
// Using browser WebSocket API
const websocket = new WebSocket("wss://gsio-relay.your-worker.workers.dev");
websocket.addEventListener("open", () => {
console.log("Connected to relay server");
// Send a message
websocket.send(JSON.stringify({
message_type: "NodeAnnounce",
sender_id: "node-id",
recipient_id: "",
payload: { node_id: "node-id" }
}));
});
websocket.addEventListener("message", (event) => {
console.log("Message received:", event.data);
});
```
## Architecture
The gsio-relay component is a simple WebSocket server that:
1. Accepts incoming WebSocket connections
2. Receives messages from connected clients
3. Echoes messages back to the sender (in the current implementation)
4. In a more advanced implementation, it would relay messages to the appropriate recipients
## Testing
```bash
# Run tests
wrangler dev --test
```
## License
[Add license information here]

222
crates/gsio-relay/bun.lock Normal file
View File

@@ -0,0 +1,222 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "realscheme",
"devDependencies": {
"@types/bun": "latest",
"wrangler": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg=="],
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250525.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-L5l+7sSJJT2+riR5rS3Q3PKNNySPjWfRIeaNGMVRi1dPO6QPi4lwuxfRUFNoeUdilZJUVPfSZvTtj9RedsKznQ=="],
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250525.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3IbIdrF/vJWh/WBvshwcSyUh175VAiLRW7963S1dXChrZ1N5wuKGQm9xY69cIGVtitpMJWWW3jLq7J/Xxwm0Q=="],
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250525.0", "", { "os": "linux", "cpu": "x64" }, "sha512-KSyQPAby+c6cpENoO0ayCQlY6QIh28l/+QID7VC1SLXfiNHy+hPNsH1vVBTST6CilHVAQSsy9tCZ9O9XECB8yg=="],
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250525.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nt0FUxS2kQhJUea4hMCNPaetkrAFDhPnNX/ntwcqVlGgnGt75iaAhupWJbU0GB+gIWlKeuClUUnDZqKbicoKyg=="],
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250525.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mwTj+9f3uIa4NEXR1cOa82PjLa6dbrb3J+KCVJFYIaq7e63VxEzOchCXS4tublT2pmOhmFqkgBMXrxozxNkR2Q=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
"@types/node": ["@types/node@24.0.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg=="],
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
"as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
"exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"miniflare": ["miniflare@4.20250525.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250525.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-4PJlT5WA+hfclFU5Q7xnpG1G1VGYTXaf/3iu6iKQ8IsbSi9QvPTA2bSZ5goCFxmJXDjV4cxttVxB0Wl1CLuQ0w=="],
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
"stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="],
"workerd": ["workerd@1.20250525.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250525.0", "@cloudflare/workerd-darwin-arm64": "1.20250525.0", "@cloudflare/workerd-linux-64": "1.20250525.0", "@cloudflare/workerd-linux-arm64": "1.20250525.0", "@cloudflare/workerd-windows-64": "1.20250525.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-SXJgLREy/Aqw2J71Oah0Pbu+SShbqbTExjVQyRBTM1r7MG7fS5NUlknhnt6sikjA/t4cO09Bi8OJqHdTkrcnYQ=="],
"wrangler": ["wrangler@4.19.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250525.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250525.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250525.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-b+ed2SJKauHgndl4Im1wHE+FeSSlrdlEZNuvpc8q/94k4EmRxRkXnwBAsVWuicBxG3HStFLQPGGlvL8wGKTtHw=="],
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
"zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
}
}

View File

@@ -0,0 +1,19 @@
{
"name": "realscheme",
"module": "index.ts",
"type": "module",
"private": true,
"scripts": {
"dev": "wrangler dev",
"ws:client": "bun ./ws_client.ts",
"wscat": "wscat -c ws:///localhost:3004/ws",
"wscat:ping": "echo 'ping' | wscat -c ws:///localhost:3004/ws"
},
"devDependencies": {
"@types/bun": "latest",
"wrangler": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}

View File

@@ -0,0 +1,29 @@
use futures::StreamExt;
use worker::*;
#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<worker::Response> {
let upgrade_header = match req.headers().get("Upgrade") {
Some(h) => h.to_str().unwrap(),
None => "",
};
if upgrade_header != "websocket" {
return worker::Response::error("Expected Upgrade: websocket", 426);
}
let ws = WebSocketPair::new()?;
let client = ws.client;
let server = ws.server;
server.accept()?;
wasm_bindgen_futures::spawn_local(async move {
let mut event_stream = server.events().expect("could not open stream");
while let Some(event) = event_stream.next().await {
match event.expect("received error in websocket") {
WebsocketEvent::Message(msg) => server.send(&format!("echo: {:?}", &msg.text().unwrap())).unwrap(),
WebsocketEvent::Close(event) => console_log!("{:?}", event),
}
}
});
worker::Response::from_websocket(client)
}

View File

@@ -0,0 +1,78 @@
// Integration testing must be written in tandem with Miniflare
// See https://github.com/cloudflare/workers-rs?tab=readme-ov-file#testing-with-miniflare
// use worker::*;
// use wasm_bindgen_test::*;
//
// wasm_bindgen_test_configure!(run_in_browser);
//
// // Test the WebSocket upgrade check
// #[wasm_bindgen_test]
// async fn test_websocket_upgrade_check() {
// // Create a mock request without the Upgrade header
// let mut headers = Headers::new();
// headers.set("Content-Type", "text/plain").unwrap();
//
// let req = Request::new_with_init(
// "https://example.com/",
// RequestInit::new()
// .with_method(Method::Get)
// .with_headers(headers),
// ).unwrap();
//
// // Create a mock environment and context
// let env = worker::Env::new();
// let ctx = worker::Context::new();
//
// // Call the fetch function
// let result = fetch(req, env, ctx).await;
//
// // Verify that the response is an error with status 426
// assert!(result.is_ok());
// let response = result.unwrap();
// assert_eq!(response.status_code(), 426);
// }
//
// // Test the WebSocket pair creation
// #[wasm_bindgen_test]
// async fn test_websocket_pair_creation() {
// // Create a WebSocketPair
// let ws_pair = WebSocketPair::new();
//
// // Verify that the pair was created successfully
// assert!(ws_pair.is_ok());
//
// let pair = ws_pair.unwrap();
// assert!(Some(pair.client).is_some());
// assert!(Some(pair.server).is_some());
// }
//
// // Test the WebSocket server accept
// #[wasm_bindgen_test]
// async fn test_websocket_server_accept() {
// // Create a WebSocketPair
// let ws_pair = WebSocketPair::new().unwrap();
// let server = ws_pair.server;
//
// // Accept the connection
// let result = server.accept();
//
// // Verify that the connection was accepted successfully
// assert!(result.is_ok());
// }
//
// // Test the WebSocket response creation
// #[wasm_bindgen_test]
// async fn test_websocket_response_creation() {
// // Create a WebSocketPair
// let ws_pair = WebSocketPair::new().unwrap();
// let client = ws_pair.client;
//
// // Create a response from the WebSocket
// let response = Response::from_websocket(client);
//
// // Verify that the response was created successfully
// assert!(response.is_ok());
// let ws_response = response.unwrap();
// assert_eq!(ws_response.status_code(), 101); // Switching Protocols
// }

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

View File

@@ -0,0 +1,7 @@
name = "gsio-relay"
main = "build/worker/shim.mjs"
compatibility_date = "2025-06-10"
dev.port = 3001
[build]
command = "cargo install -q worker-build && worker-build --release"

6
package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"scripts": {
"clean": "packages/scripts/cleanup.sh",
"postinstall": "cargo build"
}
}

34
packages/gsio-node-client/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@@ -0,0 +1,231 @@
# gsio-node-client
A TypeScript client library for connecting to the GSIO-Net distributed ledger system.
## Overview
The gsio-node-client component is a TypeScript client that:
- Connects to gsio-node servers using Socket.IO
- Interacts with the distributed ledger
- Participates in the peer-to-peer network
- Provides a simple API for adding and retrieving ledger entries
## Features
- **Socket.IO Client**: Connects to gsio-node servers for real-time communication
- **WebSocket Client**: Connects to gsio-relay for peer-to-peer communication
- **Ledger Interaction**: Add entries to and retrieve entries from the distributed ledger
- **Node Discovery**: Discover other nodes in the network
- **Event-Based API**: Simple event-based API for easy integration
## Installation
### Prerequisites
- Node.js (latest LTS version)
- Bun (for running TypeScript code and tests)
### Installing
```bash
# Clone the repository (if you haven't already)
git clone <repository-url>
cd gsio-net
# Install dependencies
cd packages/gsio-node-client
bun install
```
## Usage
### Connecting to a Node
```typescript
import { Manager as SocketIO } from "socket.io-client";
// Create a Socket.IO manager
const socketIO = new SocketIO("http://localhost:3000");
// Connect to the root namespace
const socket = socketIO.socket("/", {
auth: {
token: "your-auth-token"
}
});
// Basic Socket.IO event handlers
socket.on("connect", () => {
console.log("Connected to GSIO-Net node");
});
socket.on("disconnect", () => {
console.log("Disconnected from GSIO-Net node");
});
```
### Interacting with the Ledger
```typescript
// Function to add a new entry to the ledger
function addLedgerEntry(data) {
socket.emit("add_ledger_entry", data);
console.log("Adding ledger entry:", data);
}
// Function to get all entries in the ledger
function getLedgerEntries() {
socket.emit("get_ledger");
console.log("Requesting ledger entries");
}
// Function to get all known nodes in the network
function getKnownNodes() {
socket.emit("get_known_nodes");
console.log("Requesting known nodes");
}
// Handle ledger-related events
socket.on("ledger_entry_added", (entry) => {
console.log("New ledger entry added:", entry);
});
socket.on("ledger_entries", (entries) => {
console.log("Received ledger entries:", entries);
});
socket.on("known_nodes", (data) => {
console.log("Known nodes in the network:", data.nodes);
});
// Example usage
addLedgerEntry({
message: "Hello, distributed ledger!",
timestamp: new Date().toISOString()
});
```
### Connecting to the P2P Network
```typescript
import { v4 as uuidv4 } from 'uuid';
// Generate a unique ID for this node
const nodeId = uuidv4();
// Connect to the relay server
const websocket = new WebSocket("wss://localhost:8787");
// Set up event handlers
websocket.addEventListener("open", () => {
console.log("Connected to relay server");
// Announce this node to the network
sendP2PMessage({
message_type: "NodeAnnounce",
sender_id: nodeId,
recipient_id: "",
payload: { node_id: nodeId }
});
});
websocket.addEventListener("message", (event) => {
console.log("Message received from relay server");
try {
const message = JSON.parse(event.data);
handleP2PMessage(message);
} catch (error) {
console.error("Error parsing message:", error);
}
});
// Function to send a p2p message
function sendP2PMessage(message) {
// Add a unique message ID if not provided
if (!message.message_id) {
message.message_id = uuidv4();
}
websocket.send(JSON.stringify(message));
}
// Function to handle incoming p2p messages
function handleP2PMessage(message) {
console.log("Handling p2p message:", message);
switch (message.message_type) {
case "NodeAnnounce":
console.log(`Node announced: ${message.payload.node_id}`);
break;
case "NodeListResponse":
console.log("Received node list:", message.payload.nodes);
break;
case "EntryAnnounce":
console.log("New ledger entry announced:", message.payload);
break;
// Handle other message types...
}
}
```
## API Reference
### Socket.IO Events (Client to Server)
| Event | Description | Parameters |
|-------|-------------|------------|
| `add_ledger_entry` | Add a new entry to the ledger | JSON data to store |
| `get_ledger` | Get all entries in the ledger | None |
| `get_known_nodes` | Get all known nodes in the network | None |
| `ping` | Simple ping to check connection | Any data |
### Socket.IO Events (Server to Client)
| Event | Description | Data |
|-------|-------------|------|
| `ledger_entry_added` | Notification of a new ledger entry | The added entry |
| `ledger_entries` | Response with all ledger entries | Array of entries |
| `known_nodes` | Response with all known nodes | Object with nodes array |
| `pong` | Response to ping | Same data as ping |
### P2P Message Types
| Message Type | Description | Payload |
|--------------|-------------|---------|
| `NodeAnnounce` | Announce a new node | `{ node_id: string }` |
| `NodeListRequest` | Request the list of known nodes | `{}` |
| `NodeListResponse` | Response with the list of known nodes | `{ nodes: string[] }` |
| `EntryAnnounce` | Announce a new ledger entry | Ledger entry object |
| `EntryRequest` | Request a specific ledger entry | `{ entry_id: string }` |
| `EntryResponse` | Response with a requested ledger entry | Ledger entry object |
| `LedgerSyncRequest` | Request all ledger entries | `{}` |
| `LedgerSyncResponse` | Response with all ledger entries | Array of ledger entries |
## Examples
The package includes example scripts:
- `node_listener.ts`: Demonstrates connecting to a gsio-node server
- `ws_client.ts`: Demonstrates connecting to the gsio-relay server
To run these examples:
```bash
# Run the Socket.IO client example
bun src/listeners/node_listener.ts
# Run the WebSocket client example
bun src/listeners/ws_client.ts
```
## Testing
```bash
# Run tests
bun test
```
## License
[Add license information here]

View File

@@ -0,0 +1,46 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "ts-sdk",
"dependencies": {
"socket.io-client": "^4.8.1",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@types/node": ["@types/node@24.0.1", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw=="],
"bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="],
"socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "gsio-node-client",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"socket.io-client": "^4.8.1"
}
}

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bun
import { Manager as SocketIO } from "socket.io-client";
// Create a Socket.IO manager
const socketIO = new SocketIO("http://localhost:3000");
// Connect to the root namespace
const socket = socketIO.socket("/", {
auth: {
token: "abcd"
}
});
// Basic Socket.IO event handlers
socket.on("connect", () => {
console.log("Connected to GSIO-Net node");
});
socket.on("disconnect", () => {
console.log("Disconnected from GSIO-Net node");
});
socket.on("error", (error) => {
console.log("Socket error:", error);
});
socket.on("reconnect", (attemptNumber) => {
console.log("Reconnected after", attemptNumber, "attempts");
});
socket.on("connect_error", (error) => {
console.log("Connection error:", error);
});
// Ledger-related event handlers
socket.on("ledger_entry_added", (entry) => {
console.log("New ledger entry added:");
console.log(entry);
});
socket.on("ledger_entries", (entries) => {
console.log("Received ledger entries:");
console.log(entries);
});
socket.on("known_nodes", (data) => {
console.log("Known nodes in the network:");
console.log(data.nodes);
});
// Send a ping message every 4 seconds
setInterval(() => {
socket.emit("ping", "ping message");
console.log("Ping sent to server");
}, 4000);
// Function to add a new entry to the ledger
function addLedgerEntry(data: any) {
socket.emit("add_ledger_entry", data);
console.log("Adding ledger entry:", data);
}
// Function to get all entries in the ledger
function getLedgerEntries() {
socket.emit("get_ledger");
console.log("Requesting ledger entries");
}
// Function to get all known nodes in the network
function getKnownNodes() {
socket.emit("get_known_nodes");
console.log("Requesting known nodes");
}
// Example usage
setTimeout(() => {
// Add a sample ledger entry after 2 seconds
addLedgerEntry({ message: "Hello, distributed ledger!", timestamp: new Date().toISOString() });
// Get all ledger entries after 3 seconds
setTimeout(getLedgerEntries, 1000);
// Get all known nodes after 4 seconds
setTimeout(getKnownNodes, 2000);
}, 2000);

View File

@@ -0,0 +1,116 @@
#!/usr/bin/env bun
import { v4 as uuidv4 } from 'uuid';
// Generate a unique ID for this node
const nodeId = uuidv4();
console.log(`Starting p2p node with ID: ${nodeId}`);
// Connect to the relay server
const websocket = new WebSocket(
"wss://localhost:8787",
);
// Set up event handlers
websocket.addEventListener("open", () => {
console.log("Connected to relay server");
// Announce this node to the network
sendP2PMessage({
message_type: "NodeAnnounce",
sender_id: nodeId,
recipient_id: "",
payload: { node_id: nodeId }
});
// Request the list of known nodes
setTimeout(() => {
sendP2PMessage({
message_type: "NodeListRequest",
sender_id: nodeId,
recipient_id: "",
payload: {}
});
}, 1000);
});
websocket.addEventListener("message", (event) => {
console.log("Message received from relay server");
try {
const message = JSON.parse(event.data);
handleP2PMessage(message);
} catch (error) {
console.error("Error parsing message:", error);
console.log("Raw message:", event.data);
}
});
websocket.addEventListener("close", () => {
console.log("Disconnected from relay server");
});
websocket.addEventListener("error", (error) => {
console.error("WebSocket error:", error);
});
// Function to send a p2p message
function sendP2PMessage(message: any) {
// Add a unique message ID if not provided
if (!message.message_id) {
message.message_id = uuidv4();
}
websocket.send(JSON.stringify(message));
console.log("Sent p2p message:", message);
}
// Function to handle incoming p2p messages
function handleP2PMessage(message: any) {
console.log("Handling p2p message:", message);
switch (message.message_type) {
case "NodeAnnounce":
console.log(`Node announced: ${message.payload.node_id}`);
break;
case "NodeListResponse":
console.log("Received node list:", message.payload.nodes);
break;
case "EntryAnnounce":
console.log("New ledger entry announced:", message.payload);
break;
case "EntryResponse":
console.log("Received ledger entry:", message.payload);
break;
case "LedgerSyncResponse":
console.log("Received ledger sync:", message.payload);
break;
default:
console.log("Unknown message type:", message.message_type);
}
}
// Example: Create and announce a ledger entry after 3 seconds
setTimeout(() => {
const entry = {
id: `${nodeId}-${Date.now()}`,
timestamp: new Date().toISOString(),
data: { message: "Hello from p2p node!", timestamp: Date.now() },
previous_hash: "0".repeat(64), // Genesis block has a hash of all zeros
hash: "", // Will be calculated by the node
creator_node_id: nodeId,
signatures: {}
};
sendP2PMessage({
message_type: "EntryAnnounce",
sender_id: nodeId,
recipient_id: "",
payload: entry
});
}, 3000);

View File

@@ -0,0 +1,68 @@
import { expect, test, describe, mock, spyOn } from "bun:test";
import { Manager as SocketIO } from "socket.io-client";
// Mock the socket.io-client
mock.module("socket.io-client", () => {
const mockEmit = mock(() => {});
const mockOn = mock((event, callback) => {
// Simulate the "connect" event
if (event === "connect") {
callback();
}
return mockSocket;
});
const mockSocket = {
emit: mockEmit,
on: mockOn,
connected: true,
id: "test-socket-id"
};
const mockSocketIO = {
socket: mock(() => mockSocket)
};
return {
Manager: mock(() => mockSocketIO)
};
});
describe("Socket.IO Client", () => {
test("should connect to the server", () => {
// Import the module that uses socket.io-client
const socketIO = new SocketIO("http://localhost:3000");
const socket = socketIO.socket("/");
// Verify the socket is connected
expect(socket.connected).toBe(true);
});
test("should emit events", () => {
const socketIO = new SocketIO("http://localhost:3000");
const socket = socketIO.socket("/");
// Spy on the emit method
const emitSpy = spyOn(socket, "emit");
// Emit an event
socket.emit("ping", "ping message");
// Verify the emit method was called with the correct arguments
expect(emitSpy).toHaveBeenCalledWith("ping", "ping message");
});
test("should handle events", () => {
const socketIO = new SocketIO("http://localhost:3000");
const socket = socketIO.socket("/");
// Create a mock callback
const callback = mock(() => {});
// Register the callback for an event
socket.on("message", callback);
// Verify the on method was called with the correct arguments
expect(socket.on).toHaveBeenCalledWith("message", callback);
});
});

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

20
packages/scripts/cleanup.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Clean up build artifacts and temporary directories
echo "Cleaning up build artifacts and temporary directories..."
# Remove persisted data
# Remove node_modules directories
find . -name "node_modules" -type d -prune -exec rm -rf {} \;
find . -name "target" -type d -prune -exec rm -rf {} \;
# Remove .wrangler directories
find . -name ".wrangler" -type d -prune -exec rm -rf {} \;
# Remove build directories
find . -name "dist" -type d -prune -exec rm -rf {} \;
find . -name "build" -type d -prune -exec rm -rf {} \;
echo "Cleanup complete!"