@serfab/cadre-core 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -0
- package/dist/arachnode-stub.d.ts +60 -0
- package/dist/arachnode-stub.js +120 -0
- package/dist/arachnode-stub.js.map +1 -0
- package/dist/cadre-node.d.ts +252 -0
- package/dist/cadre-node.js +753 -0
- package/dist/cadre-node.js.map +1 -0
- package/dist/control-database.d.ts +62 -0
- package/dist/control-database.js +295 -0
- package/dist/control-database.js.map +1 -0
- package/dist/enrollment.d.ts +76 -0
- package/dist/enrollment.js +126 -0
- package/dist/enrollment.js.map +1 -0
- package/dist/hibernation-manager.d.ts +68 -0
- package/dist/hibernation-manager.js +212 -0
- package/dist/hibernation-manager.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +164 -0
- package/dist/seed-bootstrap.js +554 -0
- package/dist/seed-bootstrap.js.map +1 -0
- package/dist/strand-database.d.ts +39 -0
- package/dist/strand-database.js +108 -0
- package/dist/strand-database.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +87 -0
- package/dist/strand-formation-manager.js +168 -0
- package/dist/strand-formation-manager.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +60 -0
- package/dist/strand-instance-manager.js +209 -0
- package/dist/strand-instance-manager.js.map +1 -0
- package/dist/strand-solicitation.d.ts +145 -0
- package/dist/strand-solicitation.js +180 -0
- package/dist/strand-solicitation.js.map +1 -0
- package/dist/strand-watcher.d.ts +70 -0
- package/dist/strand-watcher.js +136 -0
- package/dist/strand-watcher.js.map +1 -0
- package/dist/types.d.ts +484 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
- package/src/arachnode-stub.ts +160 -0
- package/src/cadre-node.ts +923 -0
- package/src/control-database.ts +376 -0
- package/src/enrollment.ts +199 -0
- package/src/hibernation-manager.ts +268 -0
- package/src/index.ts +71 -0
- package/src/seed-bootstrap.ts +753 -0
- package/src/strand-database.ts +168 -0
- package/src/strand-formation-manager.ts +277 -0
- package/src/strand-instance-manager.ts +281 -0
- package/src/strand-solicitation.ts +318 -0
- package/src/strand-watcher.ts +197 -0
- package/src/types.ts +553 -0
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# @serfab/cadre-core
|
|
2
|
+
|
|
3
|
+
Core library for Sereus cadre nodes—the infrastructure that enables parties to manage their personal cluster of nodes participating in distributed strand networks.
|
|
4
|
+
|
|
5
|
+
## Why Sereus Cadre Core?
|
|
6
|
+
|
|
7
|
+
In traditional distributed systems, users depend on centralized services to manage their data and identities. **Sereus** inverts this model: each user (or "party") controls their own **cadre**—a personal cluster of nodes ranging from always-on cloud servers to intermittently-connected mobile devices.
|
|
8
|
+
|
|
9
|
+
The cadre architecture provides:
|
|
10
|
+
|
|
11
|
+
- **Sovereignty**: Your data lives on your nodes, under your control
|
|
12
|
+
- **Resilience**: Multiple nodes means no single point of failure
|
|
13
|
+
- **Flexibility**: Mix cloud servers, home NAS, laptops, and phones
|
|
14
|
+
- **Privacy**: Cryptographic authorization without central servers
|
|
15
|
+
|
|
16
|
+
A cadre doesn't exist in isolation. Cadres participate in **strands**—shared data spaces where multiple parties collaborate. When you join a messaging app, your cadre joins that strand. When you share a document, you're creating a strand between your cadre and theirs.
|
|
17
|
+
|
|
18
|
+
## Architecture Overview
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ Your Cadre │
|
|
23
|
+
│ │
|
|
24
|
+
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
|
25
|
+
│ │ Control Network │ │
|
|
26
|
+
│ │ (Private Optimystic DB for cadre management) │ │
|
|
27
|
+
│ │ │ │
|
|
28
|
+
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
|
29
|
+
│ │ │ Phone │──│ Laptop │──│ Cloud │──│ NAS │ │ │
|
|
30
|
+
│ │ │ (edge) │ │ (edge) │ │ (core) │ │ (core) │ │ │
|
|
31
|
+
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
|
|
32
|
+
│ └────────────────────────────────────────────────────────────────────┘ │
|
|
33
|
+
│ │ │
|
|
34
|
+
│ ┌─────────────────────┼─────────────────────┐ │
|
|
35
|
+
│ ▼ ▼ ▼ │
|
|
36
|
+
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
|
37
|
+
│ │ Strand A │ │ Strand B │ │ Strand C │ │
|
|
38
|
+
│ │ (Chat App) │ │ (Shared Docs) │ │ (Photo Backup) │ │
|
|
39
|
+
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Each `CadreNode` instance:
|
|
44
|
+
1. **Connects to the control network** - A private Optimystic database shared only by your nodes
|
|
45
|
+
2. **Watches for strand changes** - Automatically detects when you join or leave strands
|
|
46
|
+
3. **Manages strand instances** - Spins up isolated libp2p networks for each strand
|
|
47
|
+
4. **Handles peer enrollment** - Cryptographically authorizes new devices to join your cadre
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @serfab/cadre-core
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { CadreNode } from '@serfab/cadre-core';
|
|
59
|
+
import { FileRawStorage } from '@optimystic/db-p2p-storage-fs';
|
|
60
|
+
|
|
61
|
+
const node = new CadreNode({
|
|
62
|
+
controlNetwork: {
|
|
63
|
+
partyId: 'your-unique-party-id',
|
|
64
|
+
bootstrapNodes: ['/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW...']
|
|
65
|
+
},
|
|
66
|
+
profile: 'storage', // 'storage' for servers, 'transaction' for mobile
|
|
67
|
+
storage: {
|
|
68
|
+
// Storage provider factory - called with strandId for strand-isolated storage
|
|
69
|
+
provider: (strandId) => new FileRawStorage(`/data/sereus/${strandId}`),
|
|
70
|
+
quotaBytes: 10 * 1024 * 1024 * 1024 // 10 GB
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Start the node
|
|
75
|
+
await node.start();
|
|
76
|
+
console.log('Node started with Peer ID:', node.peerId?.toString());
|
|
77
|
+
|
|
78
|
+
// Listen for strand events
|
|
79
|
+
node.on('strand:started', ({ strandId }) => {
|
|
80
|
+
console.log('Joined strand:', strandId);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Check active strands
|
|
84
|
+
for (const [id, strand] of node.getStrands()) {
|
|
85
|
+
console.log(`Strand ${id}: ${strand.status}, ${strand.connectedPeers} peers`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Graceful shutdown
|
|
89
|
+
await node.stop();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Node Profiles
|
|
93
|
+
|
|
94
|
+
| Profile | Storage Role | Use Case | Ring Participation |
|
|
95
|
+
|---------|--------------|----------|-------------------|
|
|
96
|
+
| **transaction** | Ring Zulu only | Mobile devices, intermittent connectivity | Transaction verification, caching |
|
|
97
|
+
| **storage** | Ring Zulu + Storage Rings | Servers, NAS, always-on nodes | Full archival storage |
|
|
98
|
+
|
|
99
|
+
Both profiles participate in transaction consensus. The distinction is long-term storage commitment.
|
|
100
|
+
|
|
101
|
+
## Strand Filtering
|
|
102
|
+
|
|
103
|
+
Mobile apps typically shouldn't participate in all strands. Use filters to control participation:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Only participate in strands for a specific app
|
|
107
|
+
strandFilter: { mode: 'appId', appId: 'com.example.chat' }
|
|
108
|
+
|
|
109
|
+
// Only participate in one specific strand
|
|
110
|
+
strandFilter: { mode: 'strandId', strandId: 'strand-abc123' }
|
|
111
|
+
|
|
112
|
+
// Control network only, no strand participation
|
|
113
|
+
strandFilter: { mode: 'none' }
|
|
114
|
+
|
|
115
|
+
// Participate in all strands (default for servers)
|
|
116
|
+
strandFilter: { mode: 'all' }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Enrolling New Devices
|
|
120
|
+
|
|
121
|
+
Adding a new device to your cadre uses the Seed Bootstrap API:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// On the new device: generate identity
|
|
125
|
+
const enrollment = new EnrollmentService();
|
|
126
|
+
const { peerId, privateKey } = await enrollment.createCadrePeer();
|
|
127
|
+
// Store privateKey securely, send peerId + multiaddrs to authority
|
|
128
|
+
|
|
129
|
+
// On authority device: authorize the new peer and create seed
|
|
130
|
+
await node.authorizePeer(newDevicePeerId, newDeviceMultiaddrs);
|
|
131
|
+
const seed = await node.createSeed();
|
|
132
|
+
|
|
133
|
+
// Deliver seed to new device (via protocol, API, or out-of-band)
|
|
134
|
+
await node.deliverSeed(newDeviceMultiaddr, seed);
|
|
135
|
+
// Or encode for QR/link: const encoded = node.encodeSeed(seed);
|
|
136
|
+
|
|
137
|
+
// On new device: apply seed to join cadre
|
|
138
|
+
const result = await newNode.applySeed(seed);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For provider-hosted drones, use the helper:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Get drone info from provider API
|
|
145
|
+
const droneInfo = await provider.createContainer(plan);
|
|
146
|
+
|
|
147
|
+
// One call: authorize + create seed
|
|
148
|
+
const { seed, encodedSeed } = await node.addDrone({
|
|
149
|
+
dronePeerId: droneInfo.peerId,
|
|
150
|
+
droneMultiaddrs: droneInfo.multiaddrs
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Send to provider for drone initialization
|
|
154
|
+
await provider.applySeed(droneInfo.containerId, encodedSeed);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## API Reference
|
|
158
|
+
|
|
159
|
+
### CadreNode
|
|
160
|
+
|
|
161
|
+
The main entry point for cadre participation.
|
|
162
|
+
|
|
163
|
+
| Method | Description |
|
|
164
|
+
|--------|-------------|
|
|
165
|
+
| `start()` | Connect to control network and begin strand participation |
|
|
166
|
+
| `stop()` | Gracefully disconnect from all networks |
|
|
167
|
+
| `getStrands()` | Get all active strand instances |
|
|
168
|
+
| `getStrand(id)` | Get a specific strand instance |
|
|
169
|
+
| `addStrand(row)` | Manually add a strand (testing/direct API) |
|
|
170
|
+
| `removeStrand(id)` | Manually remove a strand |
|
|
171
|
+
| `getEnrollmentService()` | Access peer enrollment API |
|
|
172
|
+
|
|
173
|
+
### Events
|
|
174
|
+
|
|
175
|
+
| Event | Payload | Description |
|
|
176
|
+
|-------|---------|-------------|
|
|
177
|
+
| `control:connected` | - | Connected to control network |
|
|
178
|
+
| `control:disconnected` | - | Disconnected from control network |
|
|
179
|
+
| `strand:started` | `{ strandId }` | Strand instance started |
|
|
180
|
+
| `strand:stopped` | `{ strandId }` | Strand instance stopped |
|
|
181
|
+
| `strand:error` | `{ strandId, error }` | Error in strand instance |
|
|
182
|
+
|
|
183
|
+
## Related Documentation
|
|
184
|
+
|
|
185
|
+
- **[Cadre Architecture](../../docs/cadre-architecture.md)** - Deep dive into the cadre system design
|
|
186
|
+
- **[Strand Management](../../docs/strands.md)** - How strands connect multiple cadres
|
|
187
|
+
- **[Schema Guide](../../docs/schema-guide.md)** - Optimystic schema definitions
|
|
188
|
+
|
|
189
|
+
## React Native Support
|
|
190
|
+
|
|
191
|
+
This package is compatible with React Native. For mobile apps, use the RN-specific storage provider:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { CadreNode } from '@serfab/cadre-core';
|
|
195
|
+
import { RNRawStorage } from '@optimystic/db-p2p-storage-rn';
|
|
196
|
+
import { webSockets } from '@libp2p/websockets';
|
|
197
|
+
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
|
|
198
|
+
|
|
199
|
+
const node = new CadreNode({
|
|
200
|
+
controlNetwork: {
|
|
201
|
+
partyId: 'your-unique-party-id',
|
|
202
|
+
bootstrapNodes: ['/dns4/relay.example.com/tcp/443/wss/p2p/12D3KooW...']
|
|
203
|
+
},
|
|
204
|
+
profile: 'transaction', // Mobile devices typically use transaction profile
|
|
205
|
+
strandFilter: { mode: 'sAppId', sAppId: 'com.example.myapp' },
|
|
206
|
+
storage: {
|
|
207
|
+
// RN storage provider creates strand-isolated AsyncStorage
|
|
208
|
+
provider: (strandId) => new RNRawStorage(strandId)
|
|
209
|
+
},
|
|
210
|
+
network: {
|
|
211
|
+
// TCP doesn't work in React Native — use WebSocket transport instead
|
|
212
|
+
transports: [webSockets(), circuitRelayTransport()],
|
|
213
|
+
listenAddrs: [] // RN nodes typically cannot listen
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Storage Provider Pattern
|
|
219
|
+
|
|
220
|
+
The `storage.provider` option accepts either:
|
|
221
|
+
- **An `IRawStorage` instance** - Shared storage for all strands
|
|
222
|
+
- **A factory function** `(strandId: string) => IRawStorage` - Creates isolated storage per strand (recommended)
|
|
223
|
+
|
|
224
|
+
Available storage implementations:
|
|
225
|
+
| Package | Environment | Description |
|
|
226
|
+
|---------|-------------|-------------|
|
|
227
|
+
| `@optimystic/db-p2p` | All | `MemoryRawStorage` - In-memory (testing only) |
|
|
228
|
+
| `@optimystic/db-p2p-storage-fs` | Node.js | `FileRawStorage` - File system storage |
|
|
229
|
+
| `@optimystic/db-p2p-storage-rn` | React Native | `RNRawStorage` - AsyncStorage-based |
|
|
230
|
+
|
|
231
|
+
### React Native Considerations
|
|
232
|
+
|
|
233
|
+
1. **Network Transport**: libp2p's TCP transport doesn't work in RN. Pass `transports: [webSockets(), circuitRelayTransport()]` in the `network` config (see example above).
|
|
234
|
+
|
|
235
|
+
2. **Schema Loading**: The control schema is embedded in the package. Do not set `schemaPath` in React Native.
|
|
236
|
+
|
|
237
|
+
3. **Profile**: Mobile devices should use `profile: 'transaction'` for battery/bandwidth efficiency.
|
|
238
|
+
|
|
239
|
+
4. **Strand Filtering**: Always filter strands on mobile to avoid participating in unnecessary networks.
|
|
240
|
+
|
|
241
|
+
## Related Packages
|
|
242
|
+
|
|
243
|
+
- **[@serfab/strand-proto](../strand-proto/)** - Strand initialization protocol
|
|
244
|
+
- **[@optimystic/db-core](https://github.com/gotchoices/optimystic)** - Distributed database core
|
|
245
|
+
- **[@optimystic/db-p2p](https://github.com/gotchoices/optimystic)** - libp2p integration for Optimystic
|
|
246
|
+
- **[@optimystic/db-p2p-storage-fs](https://github.com/gotchoices/optimystic)** - File system storage (Node.js)
|
|
247
|
+
- **[@optimystic/db-p2p-storage-rn](https://github.com/gotchoices/optimystic)** - AsyncStorage (React Native)
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
|
252
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { NodeProfile, ArachnodeConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ring configuration for a storage node
|
|
4
|
+
*/
|
|
5
|
+
export interface RingConfig {
|
|
6
|
+
/** Ring number (0 = full keyspace, higher = more partitions) */
|
|
7
|
+
ring: number;
|
|
8
|
+
/** Partition within the ring (depends on ring level) */
|
|
9
|
+
partition: number;
|
|
10
|
+
/** Keyspace range this node is responsible for */
|
|
11
|
+
keyspaceStart: Uint8Array;
|
|
12
|
+
keyspaceEnd: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Stub implementation of Arachnode ring participation.
|
|
16
|
+
*
|
|
17
|
+
* Arachnode uses a concentric ring system where:
|
|
18
|
+
* - Ring Zulu (transaction ring): All nodes participate for transaction verification
|
|
19
|
+
* - Storage rings (0, 1, 2, 3...): Nodes join based on their storage capacity
|
|
20
|
+
* - Ring 0: Full keyspace (requires most storage)
|
|
21
|
+
* - Ring 1: 2 partitions
|
|
22
|
+
* - Ring 2: 4 partitions
|
|
23
|
+
* - Ring 3: 8 partitions
|
|
24
|
+
* - etc.
|
|
25
|
+
*
|
|
26
|
+
* This is a stub that will be replaced when arachnode is fully implemented.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ArachnodeStub {
|
|
29
|
+
private readonly profile;
|
|
30
|
+
private readonly config;
|
|
31
|
+
private ringConfig?;
|
|
32
|
+
private running;
|
|
33
|
+
constructor(profile: NodeProfile, config: ArachnodeConfig);
|
|
34
|
+
/**
|
|
35
|
+
* Start participating in rings
|
|
36
|
+
*/
|
|
37
|
+
start(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Stop participating in rings
|
|
40
|
+
*/
|
|
41
|
+
stop(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Get current ring configuration
|
|
44
|
+
*/
|
|
45
|
+
getRingConfig(): RingConfig | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Check if participating in Ring Zulu
|
|
48
|
+
*/
|
|
49
|
+
isInRingZulu(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check if participating in a storage ring
|
|
52
|
+
*/
|
|
53
|
+
isInStorageRing(): boolean;
|
|
54
|
+
private calculateKeyspaceStart;
|
|
55
|
+
private calculateKeyspaceEnd;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create an arachnode instance for a strand
|
|
59
|
+
*/
|
|
60
|
+
export declare function createArachnodeStub(profile: NodeProfile, config?: Partial<ArachnodeConfig>): ArachnodeStub;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
const log = debug('sereus:cadre:arachnode');
|
|
3
|
+
/**
|
|
4
|
+
* Stub implementation of Arachnode ring participation.
|
|
5
|
+
*
|
|
6
|
+
* Arachnode uses a concentric ring system where:
|
|
7
|
+
* - Ring Zulu (transaction ring): All nodes participate for transaction verification
|
|
8
|
+
* - Storage rings (0, 1, 2, 3...): Nodes join based on their storage capacity
|
|
9
|
+
* - Ring 0: Full keyspace (requires most storage)
|
|
10
|
+
* - Ring 1: 2 partitions
|
|
11
|
+
* - Ring 2: 4 partitions
|
|
12
|
+
* - Ring 3: 8 partitions
|
|
13
|
+
* - etc.
|
|
14
|
+
*
|
|
15
|
+
* This is a stub that will be replaced when arachnode is fully implemented.
|
|
16
|
+
*/
|
|
17
|
+
export class ArachnodeStub {
|
|
18
|
+
constructor(profile, config) {
|
|
19
|
+
this.running = false;
|
|
20
|
+
this.profile = profile;
|
|
21
|
+
this.config = config;
|
|
22
|
+
log('ArachnodeStub created for profile=%s, config=%o', profile, config);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start participating in rings
|
|
26
|
+
*/
|
|
27
|
+
async start() {
|
|
28
|
+
if (this.running)
|
|
29
|
+
return;
|
|
30
|
+
this.running = true;
|
|
31
|
+
// All nodes participate in Ring Zulu
|
|
32
|
+
if (this.config.enableRingZulu) {
|
|
33
|
+
log('Joining Ring Zulu (transaction ring)');
|
|
34
|
+
// Stub: In real implementation, would register with transaction verification network
|
|
35
|
+
}
|
|
36
|
+
// Storage nodes join storage rings
|
|
37
|
+
if (this.profile === 'storage' && this.config.storageRing) {
|
|
38
|
+
const { ring, partition = 0 } = this.config.storageRing;
|
|
39
|
+
log('Joining storage ring %d, partition %d', ring, partition);
|
|
40
|
+
// Calculate keyspace range for this partition
|
|
41
|
+
// Stub: Using placeholder keyspace calculation
|
|
42
|
+
this.ringConfig = {
|
|
43
|
+
ring,
|
|
44
|
+
partition,
|
|
45
|
+
keyspaceStart: this.calculateKeyspaceStart(ring, partition),
|
|
46
|
+
keyspaceEnd: this.calculateKeyspaceEnd(ring, partition)
|
|
47
|
+
};
|
|
48
|
+
// Stub: In real implementation, would:
|
|
49
|
+
// 1. Register with the storage ring
|
|
50
|
+
// 2. Begin accepting block storage requests for our keyspace
|
|
51
|
+
// 3. Participate in replication with other ring members
|
|
52
|
+
}
|
|
53
|
+
log('ArachnodeStub started');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Stop participating in rings
|
|
57
|
+
*/
|
|
58
|
+
async stop() {
|
|
59
|
+
if (!this.running)
|
|
60
|
+
return;
|
|
61
|
+
this.running = false;
|
|
62
|
+
if (this.ringConfig) {
|
|
63
|
+
log('Leaving storage ring %d, partition %d', this.ringConfig.ring, this.ringConfig.partition);
|
|
64
|
+
// Stub: In real implementation, would gracefully leave the ring
|
|
65
|
+
this.ringConfig = undefined;
|
|
66
|
+
}
|
|
67
|
+
if (this.config.enableRingZulu) {
|
|
68
|
+
log('Leaving Ring Zulu');
|
|
69
|
+
// Stub: In real implementation, would leave transaction ring
|
|
70
|
+
}
|
|
71
|
+
log('ArachnodeStub stopped');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get current ring configuration
|
|
75
|
+
*/
|
|
76
|
+
getRingConfig() {
|
|
77
|
+
return this.ringConfig;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if participating in Ring Zulu
|
|
81
|
+
*/
|
|
82
|
+
isInRingZulu() {
|
|
83
|
+
return this.running && this.config.enableRingZulu;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if participating in a storage ring
|
|
87
|
+
*/
|
|
88
|
+
isInStorageRing() {
|
|
89
|
+
return this.running && this.ringConfig !== undefined;
|
|
90
|
+
}
|
|
91
|
+
// Stub keyspace calculations
|
|
92
|
+
calculateKeyspaceStart(ring, partition) {
|
|
93
|
+
// Stub: Real implementation would calculate based on ring partition scheme
|
|
94
|
+
const numPartitions = Math.pow(2, ring);
|
|
95
|
+
const partitionSize = 256 / numPartitions;
|
|
96
|
+
const start = new Uint8Array(32);
|
|
97
|
+
start[0] = Math.floor(partition * partitionSize);
|
|
98
|
+
return start;
|
|
99
|
+
}
|
|
100
|
+
calculateKeyspaceEnd(ring, partition) {
|
|
101
|
+
// Stub: Real implementation would calculate based on ring partition scheme
|
|
102
|
+
const numPartitions = Math.pow(2, ring);
|
|
103
|
+
const partitionSize = 256 / numPartitions;
|
|
104
|
+
const end = new Uint8Array(32);
|
|
105
|
+
end[0] = Math.floor((partition + 1) * partitionSize) - 1;
|
|
106
|
+
end.fill(0xFF, 1); // Fill rest with max values
|
|
107
|
+
return end;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create an arachnode instance for a strand
|
|
112
|
+
*/
|
|
113
|
+
export function createArachnodeStub(profile, config) {
|
|
114
|
+
const fullConfig = {
|
|
115
|
+
enableRingZulu: config?.enableRingZulu ?? true,
|
|
116
|
+
storageRing: profile === 'storage' ? (config?.storageRing ?? { ring: 0 }) : undefined
|
|
117
|
+
};
|
|
118
|
+
return new ArachnodeStub(profile, fullConfig);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=arachnode-stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arachnode-stub.js","sourceRoot":"","sources":["../src/arachnode-stub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { Libp2p, PeerId } from '@libp2p/interface';
|
|
2
|
+
import type { CadreNodeConfig, StrandInstance, StrandConfig, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
|
|
3
|
+
import { type SAppIdLookup } from './strand-watcher.js';
|
|
4
|
+
import { EnrollmentService } from './enrollment.js';
|
|
5
|
+
import { ControlDatabase } from './control-database.js';
|
|
6
|
+
import { SeedBootstrapService } from './seed-bootstrap.js';
|
|
7
|
+
import { StrandSolicitationService, type StrandSolicitationServiceOptions } from './strand-solicitation.js';
|
|
8
|
+
type EventHandler<T> = (data: T) => void;
|
|
9
|
+
/**
|
|
10
|
+
* CadreNode is the main entry point for a cadre member.
|
|
11
|
+
* It manages:
|
|
12
|
+
* - Connection to the control network
|
|
13
|
+
* - Watching for strand changes
|
|
14
|
+
* - Starting/stopping strand instances
|
|
15
|
+
* - Strand hibernation lifecycle
|
|
16
|
+
* - Peer enrollment
|
|
17
|
+
*/
|
|
18
|
+
export declare class CadreNode implements SAppIdLookup {
|
|
19
|
+
private readonly config;
|
|
20
|
+
private controlNode;
|
|
21
|
+
private controlDatabase;
|
|
22
|
+
private strandWatcher;
|
|
23
|
+
private strandManager;
|
|
24
|
+
private hibernationManager;
|
|
25
|
+
private enrollmentService;
|
|
26
|
+
private seedBootstrapService;
|
|
27
|
+
private strandSolicitationService;
|
|
28
|
+
private running;
|
|
29
|
+
private eventHandlers;
|
|
30
|
+
/** Map of strandId -> sAppConfig for sAppId filtering and management */
|
|
31
|
+
private sAppConfigs;
|
|
32
|
+
constructor(config: CadreNodeConfig);
|
|
33
|
+
/**
|
|
34
|
+
* SAppIdLookup implementation - get sAppId for a strand
|
|
35
|
+
*/
|
|
36
|
+
getSAppId(strandId: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Get the peer ID of this node (available after start)
|
|
39
|
+
*/
|
|
40
|
+
get peerId(): PeerId | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Get the multiaddrs of this node (available after start)
|
|
43
|
+
*/
|
|
44
|
+
getMultiaddrs(): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Check if the node is running
|
|
47
|
+
*/
|
|
48
|
+
get isRunning(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Get all strand instances
|
|
51
|
+
*/
|
|
52
|
+
getStrands(): Map<string, StrandInstance>;
|
|
53
|
+
/**
|
|
54
|
+
* Get a specific strand instance
|
|
55
|
+
*/
|
|
56
|
+
getStrand(strandId: string): StrandInstance | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Get the enrollment service for adding new peers
|
|
59
|
+
*/
|
|
60
|
+
getEnrollmentService(): EnrollmentService;
|
|
61
|
+
/**
|
|
62
|
+
* Start the cadre node
|
|
63
|
+
*/
|
|
64
|
+
start(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Stop the cadre node
|
|
67
|
+
*/
|
|
68
|
+
stop(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Subscribe to events
|
|
71
|
+
*/
|
|
72
|
+
on<K extends keyof CadreNodeEvents>(event: K, handler: EventHandler<CadreNodeEvents[K]>): void;
|
|
73
|
+
/**
|
|
74
|
+
* Unsubscribe from events
|
|
75
|
+
*/
|
|
76
|
+
off<K extends keyof CadreNodeEvents>(event: K, handler: EventHandler<CadreNodeEvents[K]>): void;
|
|
77
|
+
private emit;
|
|
78
|
+
private createControlNode;
|
|
79
|
+
private createStrandQueryable;
|
|
80
|
+
/**
|
|
81
|
+
* Schedule self-registration in the CadrePeer table.
|
|
82
|
+
* This runs as a background task after the node is started.
|
|
83
|
+
*/
|
|
84
|
+
private scheduleSelfRegistration;
|
|
85
|
+
/**
|
|
86
|
+
* Register this peer in the CadrePeer table.
|
|
87
|
+
* This allows other cadre members to discover this node.
|
|
88
|
+
*/
|
|
89
|
+
private registerSelf;
|
|
90
|
+
private handleStrandAdded;
|
|
91
|
+
private handleStrandRemoved;
|
|
92
|
+
private cleanup;
|
|
93
|
+
private handleStrandIdle;
|
|
94
|
+
private handleStrandHibernate;
|
|
95
|
+
private handleStrandWake;
|
|
96
|
+
/**
|
|
97
|
+
* Add a strand with its sApp configuration.
|
|
98
|
+
* The hosting application must provide the sApp schema when creating a strand.
|
|
99
|
+
*/
|
|
100
|
+
addStrand(config: StrandConfig): Promise<StrandInstance>;
|
|
101
|
+
/**
|
|
102
|
+
* Remove a strand
|
|
103
|
+
*/
|
|
104
|
+
removeStrand(strandId: string): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Record activity on a strand (resets hibernation timer)
|
|
107
|
+
*/
|
|
108
|
+
recordStrandActivity(strandId: string): void;
|
|
109
|
+
/**
|
|
110
|
+
* Force wake a hibernating strand
|
|
111
|
+
*/
|
|
112
|
+
wakeStrand(strandId: string): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Get the control network node (for advanced use)
|
|
115
|
+
*/
|
|
116
|
+
getControlNode(): Libp2p | null;
|
|
117
|
+
/**
|
|
118
|
+
* Get the control database (for advanced queries)
|
|
119
|
+
*/
|
|
120
|
+
getControlDatabase(): ControlDatabase | null;
|
|
121
|
+
/**
|
|
122
|
+
* Force a poll of the strand watcher (for testing)
|
|
123
|
+
*/
|
|
124
|
+
forceStrandPoll(): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Get the sApp configuration for a strand
|
|
127
|
+
*/
|
|
128
|
+
getSAppConfig(strandId: string): SAppConfig | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Initialize the seed bootstrap service with an authority key.
|
|
131
|
+
* Must be called before using seed-related methods that require signing.
|
|
132
|
+
*
|
|
133
|
+
* @param authorityPrivateKey - The authority's private key (base64url encoded)
|
|
134
|
+
*/
|
|
135
|
+
initializeSeedBootstrap(authorityPrivateKey: string): void;
|
|
136
|
+
/**
|
|
137
|
+
* Enable the seed listener for receiving seeds via the /sereus/seed/1.0.0 protocol.
|
|
138
|
+
* This is for drone nodes that need to receive seeds without being an authority.
|
|
139
|
+
* Does not require an authority key.
|
|
140
|
+
*/
|
|
141
|
+
enableSeedListener(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Get the seed bootstrap service (for advanced use)
|
|
144
|
+
*/
|
|
145
|
+
getSeedBootstrapService(): SeedBootstrapService | null;
|
|
146
|
+
/**
|
|
147
|
+
* Authorize a new peer to join the cadre.
|
|
148
|
+
* Signs the peer ID with the authority key and inserts into CadrePeer table.
|
|
149
|
+
*
|
|
150
|
+
* @param peerId - The peer ID to authorize
|
|
151
|
+
* @param multiaddrs - Optional multiaddrs for the peer
|
|
152
|
+
*/
|
|
153
|
+
authorizePeer(peerId: string, multiaddrs?: string[]): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* Create a seed from the current control network state.
|
|
156
|
+
* The seed contains peer information and is signed by an authority.
|
|
157
|
+
*/
|
|
158
|
+
createSeed(): Promise<ControlNetworkSeed>;
|
|
159
|
+
/**
|
|
160
|
+
* Apply a seed to populate the peer cache and enable connections.
|
|
161
|
+
* Validates the seed signature before applying.
|
|
162
|
+
*/
|
|
163
|
+
applySeed(seed: ControlNetworkSeed): Promise<ApplySeedResult>;
|
|
164
|
+
/**
|
|
165
|
+
* Deliver a seed directly to a peer via the /sereus/seed/1.0.0 protocol.
|
|
166
|
+
*/
|
|
167
|
+
deliverSeed(targetMultiaddr: string, seed: ControlNetworkSeed): Promise<{
|
|
168
|
+
accepted: boolean;
|
|
169
|
+
reason?: string;
|
|
170
|
+
}>;
|
|
171
|
+
/**
|
|
172
|
+
* Encode a seed for out-of-band delivery (e.g., QR code, copy/paste).
|
|
173
|
+
*/
|
|
174
|
+
encodeSeed(seed: ControlNetworkSeed): string;
|
|
175
|
+
/**
|
|
176
|
+
* Decode a seed from base64url encoding.
|
|
177
|
+
*/
|
|
178
|
+
decodeSeed(encoded: string): ControlNetworkSeed;
|
|
179
|
+
/**
|
|
180
|
+
* Get this node's circuit relay address for inclusion in seeds.
|
|
181
|
+
* Returns null if no relay address is available.
|
|
182
|
+
*/
|
|
183
|
+
getRelayAddress(): Promise<string | null>;
|
|
184
|
+
/**
|
|
185
|
+
* Add a drone to the cadre (for phone/server adding provider-hosted node).
|
|
186
|
+
* Creates authorization and seed for drone initialization.
|
|
187
|
+
*/
|
|
188
|
+
addDrone(options: AddDroneOptions): Promise<DroneInitResult>;
|
|
189
|
+
/**
|
|
190
|
+
* Create an invite for a phone to join the cadre.
|
|
191
|
+
* Use when a server wants to invite a NAT'd phone.
|
|
192
|
+
*/
|
|
193
|
+
createInvite(token?: string, expiresIn?: number): Promise<InviteResult>;
|
|
194
|
+
/**
|
|
195
|
+
* Accept a phone connection using an invite.
|
|
196
|
+
* Call this when a phone dials in with an invite token.
|
|
197
|
+
*/
|
|
198
|
+
acceptPhone(options: AddPhoneOptions, issuedInvite?: CadreInvite): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Add a phone to the cadre with relay support.
|
|
201
|
+
* Use when both nodes are NAT'd (phone-to-phone).
|
|
202
|
+
*/
|
|
203
|
+
addPhoneWithRelay(phonePeerId: string): Promise<DroneInitResult>;
|
|
204
|
+
/**
|
|
205
|
+
* Encode an invite for out-of-band delivery (QR, link, etc.).
|
|
206
|
+
*/
|
|
207
|
+
encodeInvite(invite: CadreInvite): string;
|
|
208
|
+
/**
|
|
209
|
+
* Decode an invite from base64url encoding.
|
|
210
|
+
*/
|
|
211
|
+
decodeInvite(encoded: string): CadreInvite;
|
|
212
|
+
/**
|
|
213
|
+
* Dial an authority from an invite (for phone joining via invite).
|
|
214
|
+
*/
|
|
215
|
+
dialInvite(invite: CadreInvite): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* Initialize the strand solicitation service.
|
|
218
|
+
* This enables forming strands with other parties via open invitations.
|
|
219
|
+
*
|
|
220
|
+
* @param options - Configuration for the solicitation service
|
|
221
|
+
*/
|
|
222
|
+
initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): void;
|
|
223
|
+
/**
|
|
224
|
+
* Get the strand solicitation service (for advanced use)
|
|
225
|
+
*/
|
|
226
|
+
getStrandSolicitationService(): StrandSolicitationService | null;
|
|
227
|
+
/**
|
|
228
|
+
* Create an open invitation for others to form strands with this party.
|
|
229
|
+
*
|
|
230
|
+
* @param sAppId - The sApp to use for formed strands
|
|
231
|
+
* @param expirationMs - How long the invitation is valid (ms from now)
|
|
232
|
+
* @returns The open invitation to share out-of-band
|
|
233
|
+
*/
|
|
234
|
+
createOpenInvitation(sAppId: string, expirationMs?: number): Promise<OpenInvitation>;
|
|
235
|
+
/**
|
|
236
|
+
* Form a strand with a responder via an open invitation.
|
|
237
|
+
*
|
|
238
|
+
* @param invitation - The open invitation received out-of-band
|
|
239
|
+
* @param disclosure - Identity/context information to share with the responder
|
|
240
|
+
* @returns The member key and strand info if successful
|
|
241
|
+
*/
|
|
242
|
+
formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
|
|
243
|
+
/**
|
|
244
|
+
* Encode an open invitation for out-of-band delivery (QR, link, etc.).
|
|
245
|
+
*/
|
|
246
|
+
encodeInvitation(invitation: OpenInvitation): string;
|
|
247
|
+
/**
|
|
248
|
+
* Decode an open invitation from base64url encoding.
|
|
249
|
+
*/
|
|
250
|
+
decodeInvitation(encoded: string): OpenInvitation;
|
|
251
|
+
}
|
|
252
|
+
export {};
|