14 KiB
Circles: Peer-Centric Application Layer Architecture
This document provides a comprehensive overview of the Circles project architecture - a distributed, peer-contextual application layer runtime where peers can send scripts to each other for execution within isolated contexts.
1. System Overview
1.1. Core Concept
Circles implements a peer-centric backend architecture where each user is treated as an autonomous execution context rather than a passive database entry. The system enables cross-peer script execution where:
- Peer A sends a script to Peer B
- The script executes within Peer B's context (filesystem namespace, data isolation)
- The script runs with awareness that Peer A initiated the execution
- Peer B controls what scripts it accepts and how they execute
1.2. Key Characteristics
- Peer-Centric Execution: Each peer is an isolated context, not a row in a global database
- Context-Based Routing: Execution flows are scoped by peer identity (public keys)
- Decentralizable: Circle nodes can be deployed independently and serve separate peer sets
- Script-Triggered Workflows: Cross-peer interactions happen via script execution
- Namespace Isolation: Workers enforce isolation through filesystem namespacing
2. Core Components
2.1. Peer Context
A peer context represents an isolated execution environment identified by a secp256k1 public key:
- Identity: Unique secp256k1 public key
- Filesystem Namespace: Isolated root directory (
DB_PATH/{peer_public_key}/
) - Execution Environment: Rhai script engine with peer-specific variables
- Policy Boundaries: Controls which peers can execute scripts and what operations are allowed
2.2. Circle (WebSocket Gateway)
The server
crate provides the WebSocket server that acts as a gateway for peer interactions:
- Peer Authentication: secp256k1 signature-based authentication via JSON-RPC
- Script Routing: Routes incoming script execution requests to Redis queues
- Session Management: Maintains authenticated WebSocket connections per peer
- Multi-Peer Support: Single server instance can handle multiple peer contexts
Key Features:
- Built on Actix Web with WebSocket actors (
CircleWs
) - JSON-RPC 2.0 API for authentication and script execution
- Redis integration for decoupled worker communication
2.3. Worker Engine
The worker
crate implements the script execution engine:
- Context-Aware Execution: Loads and executes scripts within specific peer contexts
- Namespace Isolation: Each peer gets isolated filesystem access
- Caller Awareness: Scripts execute with knowledge of both caller and target peer
- Multiplexing Support: Single worker can serve multiple peer contexts
Execution Environment Variables:
CALLER_PUBLIC_KEY // The peer who sent the script
CIRCLE_PUBLIC_KEY // The peer in whose context the script runs
DB_PATH // Namespaced database path for this peer
2.4. Client Library
The client_ws
crate provides cross-platform WebSocket client functionality:
- Cross-Platform: Native (tokio-tungstenite) and WASM (gloo-net) support
- Authentication: Automated secp256k1 signature-based authentication
- Script Execution: High-level API for sending scripts to other peers
- Builder Pattern: Flexible client construction with optional authentication
3. Communication Topology
3.1. Peer-to-Peer Script Execution Flow
sequenceDiagram
participant PeerA as Peer A (Client)
participant Circle as Circle Node (WebSocket)
participant Redis as Redis Queue
participant Worker as Worker Engine
participant FS as Peer B Filesystem
Note over PeerA,FS: Peer A sends script to execute in Peer B's context
PeerA->>Circle: WebSocket: JSON-RPC "play" request
Note right of PeerA: Script + Target Peer B's public key
Circle->>Circle: Validate authentication
Circle->>Redis: LPUSH to peer B's queue
Note right of Circle: Queue: rhailib:{peer_b_pubkey}
Worker->>Redis: BLPOP from peer B's queue
Worker->>Worker: Load Peer B's context
Note right of Worker: Set CALLER_PUBLIC_KEY=peer_a<br/>Set CIRCLE_PUBLIC_KEY=peer_b<br/>Set DB_PATH=peer_b_namespace/
Worker->>FS: Execute script in Peer B's namespace
FS-->>Worker: Script execution result
Worker->>Redis: LPUSH result to reply queue
Redis-->>Circle: Result notification
Circle-->>PeerA: WebSocket: JSON-RPC response
3.2. System Component Diagram
graph TB
subgraph "Peer A Environment"
ClientA[Client A<br/>client_ws]
KeyA[Private Key A]
ClientA -.-> KeyA
end
subgraph "Peer B Environment"
ClientB[Client B<br/>client_ws]
KeyB[Private Key B]
ClientB -.-> KeyB
end
subgraph "Circle Infrastructure"
Circle[Circle Node<br/>server]
Redis[(Redis<br/>Task Queues)]
Worker1[Worker Engine<br/>Peer A Context]
Worker2[Worker Engine<br/>Peer B Context]
Circle <--> Redis
Redis <--> Worker1
Redis <--> Worker2
end
subgraph "Isolated Storage"
FSA[Peer A Namespace<br/>DB_PATH/peer_a/]
FSB[Peer B Namespace<br/>DB_PATH/peer_b/]
Worker1 <--> FSA
Worker2 <--> FSB
end
ClientA <-->|WebSocket<br/>Authenticated| Circle
ClientB <-->|WebSocket<br/>Authenticated| Circle
style ClientA fill:#e1f5fe
style ClientB fill:#e8f5e8
style Circle fill:#fff3e0
style Worker1 fill:#e1f5fe
style Worker2 fill:#e8f5e8
3.3. Multi-Circle Deployment Architecture
graph TB
subgraph "Launcher Configuration"
Launcher[Launcher<br/>circles.json]
Config[["OurWorld: port 8090<br/>Threefold: port 8091<br/>Sikana: port 8092<br/>..."]]
Launcher --> Config
end
subgraph "Circle Nodes"
Circle1[Circle 1<br/>:8090]
Circle2[Circle 2<br/>:8091]
Circle3[Circle 3<br/>:8092]
end
subgraph "Worker Pool"
Worker1[Worker<br/>OurWorld Context]
Worker2[Worker<br/>Threefold Context]
Worker3[Worker<br/>Sikana Context]
end
subgraph "Shared Infrastructure"
Redis[(Redis<br/>Coordination)]
Storage[(Namespaced Storage<br/>peer_contexts/)]
end
Launcher --> Circle1
Launcher --> Circle2
Launcher --> Circle3
Circle1 <--> Redis
Circle2 <--> Redis
Circle3 <--> Redis
Redis <--> Worker1
Redis <--> Worker2
Redis <--> Worker3
Worker1 <--> Storage
Worker2 <--> Storage
Worker3 <--> Storage
4. Authentication and Security
4.1. Peer Authentication Flow
sequenceDiagram
participant Client as Peer Client
participant Circle as Circle Node
Note over Client: Has secp256k1 keypair
Client->>+Circle: JSON-RPC "fetch_nonce" (pubkey)
Circle->>Circle: generate_nonce()
Circle->>Circle: store_nonce(pubkey, nonce)
Circle-->>-Client: nonce + expiration
Client->>Client: sign(nonce, private_key)
Client->>+Circle: JSON-RPC "authenticate" (pubkey, signature)
Circle->>Circle: verify_signature(nonce, signature, pubkey)
alt Signature Valid
Circle->>Circle: Mark session as authenticated
Circle-->>Client: {"authenticated": true}
Note over Circle: Subsequent "play" requests include authenticated pubkey
else Signature Invalid
Circle-->>-Client: JSON-RPC Error: Invalid Credentials
Circle->>Circle: Close connection
end
4.2. Context Isolation Model
Each peer context is isolated through:
- Filesystem Namespacing: Each peer gets a dedicated root directory
- Environment Variables: Scripts execute with peer-specific context variables
- Redis Queue Isolation: Each peer has dedicated task queues
- Authentication Boundaries: Only authenticated peers can send scripts
worker_rhai_temp_db/
├── peer_a_public_key/ # Peer A's isolated namespace
│ ├── data/
│ ├── config/
│ └── logs/
├── peer_b_public_key/ # Peer B's isolated namespace
│ ├── data/
│ ├── config/
│ └── logs/
└── ...
5. Script Execution Model
5.1. Cross-Peer Script Execution
When Peer A sends a script to Peer B:
- Authentication: Peer A authenticates with their private key
- Script Routing: Circle routes the script to Peer B's Redis queue
- Context Loading: Worker loads Peer B's execution context
- Environment Setup: Worker sets context variables:
CALLER_PUBLIC_KEY = "peer_a_public_key" CIRCLE_PUBLIC_KEY = "peer_b_public_key" DB_PATH = "/path/to/peer_b_namespace"
- Script Execution: Rhai engine executes script in Peer B's context
- Result Return: Execution result is returned to Peer A via Redis/WebSocket
5.2. Example Script Execution
// Script sent by Peer A to Peer B
print("Script running in context of: " + CIRCLE_PUBLIC_KEY);
print("Initiated by: " + CALLER_PUBLIC_KEY);
// Access Peer B's data (isolated namespace)
let peer_b_data = load_data("user_preferences.json");
// Perform operations in Peer B's context
let result = process_data(peer_b_data);
// Return result to Peer A
result
6. API Reference
6.1. JSON-RPC Methods
The Circle WebSocket API provides three core methods:
fetch_nonce
Requests a cryptographic nonce for authentication.
Parameters:
pubkey
(string): Client's secp256k1 public key
Response:
{
"nonce": "base64_encoded_nonce",
"expires_at": 1234567890
}
authenticate
Authenticates the client using a signed nonce.
Parameters:
pubkey
(string): Client's public keysignature
(string): Nonce signed with client's private key
Response:
{
"authenticated": true
}
play
Executes a script in a target peer's context.
Parameters:
script
(string): Rhai script to execute
Response:
{
"output": "script_execution_result"
}
6.2. Client Library Usage
use circle_client_ws::{CircleWsClientBuilder, CircleWsClient};
// Create authenticated client
let mut client = CircleWsClientBuilder::new("ws://localhost:8090/peer_b_pubkey".to_string())
.with_keypair(private_key)
.build();
// Connect and authenticate
client.connect().await?;
client.authenticate().await?;
// Send script to execute in peer B's context
let result = client.play(r#"
print("Hello from " + CALLER_PUBLIC_KEY + " to " + CIRCLE_PUBLIC_KEY);
"Script executed successfully"
"#.to_string()).await?;
println!("Result: {}", result.output);
7. Deployment Models
7.1. Single-Peer Worker
Each peer runs in its own dedicated worker process:
# Start worker for specific peer
./worker --circle-public-key peer_a_public_key --redis-url redis://localhost:6379
7.2. Multi-Peer Worker
Single worker serves multiple peer contexts with namespace isolation:
# Worker handles multiple peers with context switching
./worker --redis-url redis://localhost:6379
7.3. Launcher-Managed Deployment
Use the launcher to manage multiple circles:
[
{
"name": "OurWorld",
"port": 8090,
"script_path": "scripts/ourworld.rhai"
},
{
"name": "Threefold",
"port": 8091,
"script_path": "scripts/threefold.rhai"
}
]
./launcher --config circles.json
8. Technical Implementation Details
8.1. Redis Protocol
Task Queue Pattern:
- Queue Key:
rhailib:{peer_public_key}
- Task Details:
rhailib:{task_id}
(hash) - Reply Queue:
rhailib:reply:{task_id}
Task Lifecycle:
- Client submits script → Redis queue
- Worker picks up task → Updates status to "processing"
- Worker executes script → Updates status to "completed"/"error"
- Worker publishes result → Reply queue
- Client receives result → Task cleanup (optional)
8.2. Worker Context Management
// Worker sets up peer context before script execution
let mut db_config = rhai::Map::new();
db_config.insert("DB_PATH".into(), db_path.clone().into());
db_config.insert("CALLER_PUBLIC_KEY".into(), caller_id.clone().into());
db_config.insert("CIRCLE_PUBLIC_KEY".into(), circle_public_key.clone().into());
engine.set_default_tag(Dynamic::from(db_config));
8.3. Filesystem Isolation
Each peer context gets an isolated filesystem namespace:
// Example namespace structure
let peer_namespace = format!("{}/{}", base_db_path, peer_public_key);
std::fs::create_dir_all(&peer_namespace)?;
// All file operations are relative to this namespace
let data_file = format!("{}/data/user_data.json", peer_namespace);
9. Comparison to Traditional Architectures
Feature | Traditional Backend | Circles (Peer-Centric) |
---|---|---|
User Representation | Row in database | Autonomous execution context |
Request Routing | Global endpoints | Peer-scoped script execution |
State Management | Shared application state | Isolated peer contexts |
Communication | HTTP/REST, JSON-RPC | WebSocket + Redis coordination |
Distribution | Centralized | Decentralized, per-peer scalable |
Execution Model | Threaded request handlers | Context-aware script processors |
Data Isolation | Application-level | Filesystem namespacing |
Cross-User Operations | Database transactions | Cross-peer script execution |
10. Summary
Circles implements a distributed, peer-contextual application layer runtime where:
- Each peer is an isolated execution context identified by secp256k1 public keys
- Peers can send scripts to other peers for execution within the target's context
- Workers enforce isolation through filesystem namespacing and environment variables
- The system supports both single-peer and multi-peer worker deployments
- Communication flows through WebSocket gateways coordinated via Redis queues
This architecture enables decentralized application logic where peers maintain autonomy over their execution environments while supporting secure cross-peer interactions through script-based workflows.