mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-14 03:33:11 +00:00
feat: create @waku/run package for local dev env
This commit is contained in:
parent
ff9c43038e
commit
8b4e6d3542
51
package-lock.json
generated
51
package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"packages/rln",
|
||||
"packages/sdk",
|
||||
"packages/relay",
|
||||
"packages/run",
|
||||
"packages/tests",
|
||||
"packages/reliability-tests",
|
||||
"packages/browser-tests",
|
||||
@ -7643,6 +7644,10 @@
|
||||
"resolved": "packages/rln",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@waku/run": {
|
||||
"resolved": "packages/run",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@waku/sdk": {
|
||||
"resolved": "packages/sdk",
|
||||
"link": true
|
||||
@ -32565,6 +32570,26 @@
|
||||
"devOptional": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.6",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
|
||||
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
@ -35978,6 +36003,32 @@
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"packages/run": {
|
||||
"name": "@waku/run",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"waku-run": "dist/src/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@waku/core": "*",
|
||||
"@waku/interfaces": "*",
|
||||
"@waku/sdk": "*",
|
||||
"@waku/utils": "*",
|
||||
"chai": "^4.3.10",
|
||||
"cspell": "^8.6.1",
|
||||
"mocha": "^10.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@waku/sdk",
|
||||
"version": "0.0.35",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"packages/rln",
|
||||
"packages/sdk",
|
||||
"packages/relay",
|
||||
"packages/run",
|
||||
"packages/tests",
|
||||
"packages/reliability-tests",
|
||||
"packages/browser-tests",
|
||||
|
||||
20
packages/run/.env.example
Normal file
20
packages/run/.env.example
Normal file
@ -0,0 +1,20 @@
|
||||
# Waku Local Network Configuration
|
||||
|
||||
# Docker Image
|
||||
NWAKU_IMAGE=wakuorg/nwaku:v0.36.0
|
||||
|
||||
# Network Configuration
|
||||
CLUSTER_ID=0
|
||||
|
||||
# Node Ports (change if ports are in use)
|
||||
NODE1_WS_PORT=60000
|
||||
NODE1_REST_PORT=8646
|
||||
NODE2_WS_PORT=60001
|
||||
NODE2_REST_PORT=8647
|
||||
|
||||
# Postgres Configuration
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=test123
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
20
packages/run/.eslintrc.cjs
Normal file
20
packages/run/.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: "./tsconfig.dev.json"
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
},
|
||||
globals: {
|
||||
process: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js"],
|
||||
rules: {
|
||||
"no-console": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
11
packages/run/.gitignore
vendored
Normal file
11
packages/run/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Docker volumes and runtime data
|
||||
postgres-data/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
11
packages/run/.mocharc.cjs
Normal file
11
packages/run/.mocharc.cjs
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
extension: ['ts'],
|
||||
require: ['ts-node/register'],
|
||||
loader: 'ts-node/esm',
|
||||
'node-option': [
|
||||
'experimental-specifier-resolution=node',
|
||||
'loader=ts-node/esm'
|
||||
],
|
||||
timeout: 90000,
|
||||
exit: true
|
||||
};
|
||||
436
packages/run/README.md
Normal file
436
packages/run/README.md
Normal file
@ -0,0 +1,436 @@
|
||||
# @waku/run
|
||||
|
||||
> **Spin up a local Waku network for development without relying on external infrastructure**
|
||||
|
||||
Perfect for hackathons, offline development, or when you need a controlled testing environment for your js-waku application.
|
||||
|
||||
## What's Included
|
||||
|
||||
- **2 nwaku nodes** connected to each other with all protocols enabled:
|
||||
- ✅ Relay (gossipsub)
|
||||
- ✅ Filter (light client subscriptions)
|
||||
- ✅ LightPush (light client publishing)
|
||||
- ✅ Store (message history)
|
||||
- ✅ Peer Exchange (peer discovery)
|
||||
- **PostgreSQL database** for message persistence
|
||||
- **Isolated network** - nodes only connect to each other
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose plugin
|
||||
- Node.js 18+ (only for npx/npm usage)
|
||||
|
||||
## Installation
|
||||
|
||||
**Option 1: Use npx (no installation required)**
|
||||
```bash
|
||||
npx waku-run start
|
||||
```
|
||||
|
||||
**Option 2: Install globally**
|
||||
```bash
|
||||
npm install -g @waku/run
|
||||
waku-run start
|
||||
```
|
||||
|
||||
**Option 3: Clone and run locally**
|
||||
```bash
|
||||
git clone https://github.com/waku-org/js-waku.git
|
||||
cd js-waku/packages/run
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start the Network
|
||||
|
||||
**Option A: Using npx (recommended for quick setup)**
|
||||
```bash
|
||||
npx waku-run start
|
||||
```
|
||||
|
||||
**Option B: Local development**
|
||||
```bash
|
||||
cd packages/run
|
||||
npm run start
|
||||
```
|
||||
|
||||
This will:
|
||||
- Start 2 nwaku nodes and a PostgreSQL database
|
||||
- Run in the background (detached mode)
|
||||
- Display connection information you need for your app
|
||||
|
||||
**Example output:**
|
||||
```json
|
||||
{
|
||||
"bootstrapPeers": [
|
||||
"/ip4/127.0.0.1/tcp/60000/ws/p2p/16Uiu2HAm...",
|
||||
"/ip4/127.0.0.1/tcp/60001/ws/p2p/16Uiu2HAm..."
|
||||
],
|
||||
"networkConfig": {
|
||||
"clusterId": 1,
|
||||
"numShardsInCluster": 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Connect Your js-waku App
|
||||
|
||||
Copy the output from above and use it in your application:
|
||||
|
||||
```javascript
|
||||
import { createLightNode } from "@waku/sdk";
|
||||
|
||||
const waku = await createLightNode({
|
||||
defaultBootstrap: false,
|
||||
bootstrapPeers: [
|
||||
"/ip4/127.0.0.1/tcp/60000/ws/p2p/16Uiu2HAm...",
|
||||
"/ip4/127.0.0.1/tcp/60001/ws/p2p/16Uiu2HAm..."
|
||||
],
|
||||
networkConfig: {
|
||||
clusterId: 1,
|
||||
numShardsInCluster: 8
|
||||
}
|
||||
});
|
||||
|
||||
await waku.start();
|
||||
|
||||
// Your app is now connected to your local Waku network!
|
||||
```
|
||||
|
||||
### 3. Stop When Done
|
||||
|
||||
```bash
|
||||
npm run stop
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
### Using npx (published package)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npx waku-run start` | Start the network (detached) and show connection info |
|
||||
| `npx waku-run info` | Show connection info for running network |
|
||||
| `docker compose down` | Stop the network and clean up |
|
||||
|
||||
### Local development
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run start` | Start the network (detached) and show connection info |
|
||||
| `npm run stop` | Stop the network and clean up |
|
||||
| `npm run restart` | Restart the network |
|
||||
| `npm run logs` | View and follow logs from all nodes |
|
||||
| `npm run info` | Show connection info for running network |
|
||||
| `npm test` | Run integration tests |
|
||||
| `npm run build` | Build TypeScript to JavaScript |
|
||||
|
||||
### Direct Docker Compose Commands
|
||||
|
||||
You can also use standard Docker Compose commands:
|
||||
|
||||
```bash
|
||||
# Start and see all logs
|
||||
docker compose up
|
||||
|
||||
# Start in background
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
|
||||
# Stop and clean up
|
||||
docker compose down
|
||||
|
||||
# Stop and remove volumes (fresh start)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Port Configuration
|
||||
|
||||
If the default ports are in use, create a `.env` file:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` to change ports:
|
||||
|
||||
```bash
|
||||
NODE1_WS_PORT=60000
|
||||
NODE1_REST_PORT=8646
|
||||
NODE2_WS_PORT=60001
|
||||
NODE2_REST_PORT=8647
|
||||
```
|
||||
|
||||
### Cluster Configuration
|
||||
|
||||
The default configuration uses:
|
||||
- Cluster ID: 1
|
||||
- Number of shards: 8
|
||||
|
||||
To test with different network configurations, create a `.env` file:
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
CLUSTER_ID=16 # Change to a different cluster
|
||||
```
|
||||
|
||||
Your js-waku app will automatically use the correct configuration from `npm run info`:
|
||||
|
||||
```javascript
|
||||
const waku = await createLightNode({
|
||||
defaultBootstrap: false,
|
||||
bootstrapPeers: [...],
|
||||
networkConfig: {
|
||||
clusterId: 1, // Match your CLUSTER_ID
|
||||
numShardsInCluster: 8
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Changing nwaku Version
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
NWAKU_IMAGE=wakuorg/nwaku:v0.35.0
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### View Node Logs
|
||||
|
||||
```bash
|
||||
npm run logs
|
||||
|
||||
# Or for a specific node
|
||||
docker compose logs -f nwaku-1
|
||||
docker compose logs -f nwaku-2
|
||||
```
|
||||
|
||||
### Check Node Health
|
||||
|
||||
```bash
|
||||
# Node 1
|
||||
curl http://127.0.0.1:8646/health
|
||||
|
||||
# Node 2
|
||||
curl http://127.0.0.1:8647/health
|
||||
```
|
||||
|
||||
### Check Peer Connections
|
||||
|
||||
```bash
|
||||
# Node 1 debug info
|
||||
curl http://127.0.0.1:8646/debug/v1/info
|
||||
|
||||
# Node 2 debug info
|
||||
curl http://127.0.0.1:8647/debug/v1/info
|
||||
```
|
||||
|
||||
### View Database
|
||||
|
||||
Connect to PostgreSQL to inspect stored messages:
|
||||
|
||||
```bash
|
||||
docker compose exec postgres psql -U postgres
|
||||
|
||||
# In psql:
|
||||
\dt # List tables
|
||||
SELECT * FROM messages LIMIT 10;
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Nodes won't start
|
||||
|
||||
**Check if Docker is running:**
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
**Check logs for errors:**
|
||||
```bash
|
||||
docker compose logs
|
||||
```
|
||||
|
||||
**Try a fresh start:**
|
||||
```bash
|
||||
docker compose down -v
|
||||
npm run start
|
||||
```
|
||||
|
||||
### Port conflicts
|
||||
|
||||
If you see "port already in use":
|
||||
|
||||
1. Change ports in `.env`:
|
||||
```bash
|
||||
NODE1_WS_PORT=50000
|
||||
NODE2_WS_PORT=50001
|
||||
```
|
||||
|
||||
2. Or find and stop conflicting processes:
|
||||
```bash
|
||||
# macOS/Linux
|
||||
lsof -i :60000
|
||||
kill <PID>
|
||||
|
||||
# Or use different ports
|
||||
```
|
||||
|
||||
### Nodes won't discover each other
|
||||
|
||||
This is expected on first start. The nodes use peer exchange and discovery protocols to find each other, which can take 10-30 seconds.
|
||||
|
||||
**Check connection status:**
|
||||
```bash
|
||||
# Wait a moment after starting
|
||||
sleep 15
|
||||
npm run info
|
||||
```
|
||||
|
||||
### Can't connect from js-waku
|
||||
|
||||
**Verify nodes are running:**
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
**Check your firewall** - ensure localhost connections are allowed
|
||||
|
||||
**Verify ports match** - the ports in your js-waku config must match the `.env` configuration
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Customize Docker Compose
|
||||
|
||||
Need more nodes, different configurations, or additional services?
|
||||
|
||||
```bash
|
||||
# Copy and customize
|
||||
cp docker-compose.yml my-custom-compose.yml
|
||||
|
||||
# Edit my-custom-compose.yml to add:
|
||||
# - More nwaku nodes
|
||||
# - Custom protocol configurations
|
||||
# - Additional services (monitoring, etc.)
|
||||
|
||||
# Run with your custom config
|
||||
docker compose -f my-custom-compose.yml up
|
||||
```
|
||||
|
||||
### Add More Nodes
|
||||
|
||||
Add to `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
nwaku-3:
|
||||
<<: *nwaku-base
|
||||
container_name: waku-local-node-3
|
||||
ports:
|
||||
- "60002:60002/tcp"
|
||||
- "8648:8648/tcp"
|
||||
depends_on:
|
||||
- postgres
|
||||
- nwaku-1
|
||||
command:
|
||||
- --relay=true
|
||||
- --filter=true
|
||||
# ... same config as other nodes
|
||||
- --websocket-port=60002
|
||||
- --rest-port=8648
|
||||
```
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
Or directly in docker-compose:
|
||||
```yaml
|
||||
- --log-level=DEBUG
|
||||
- --log-format=json # Structured logs
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
- ✅ **Hackathon development** - Work without internet or unreliable connections
|
||||
- ✅ **Local testing** - Test your app against real nwaku nodes
|
||||
- ✅ **CI/CD integration tests** - Automated testing in isolated environments
|
||||
- ✅ **Protocol experimentation** - Try different configurations safely
|
||||
- ✅ **Offline demos** - Show your app working without external dependencies
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Your js-waku Application │
|
||||
│ │
|
||||
│ createLightNode({ │
|
||||
│ bootstrapPeers: [node1, node2] │
|
||||
│ }) │
|
||||
└──────────────┬──────────────────────────────┘
|
||||
│ WebSocket connections
|
||||
│ (127.0.0.1:60000, 60001)
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ │
|
||||
┌───▼────┐ ┌────▼───┐
|
||||
│ nwaku-1│◄─────────►│nwaku-2 │
|
||||
│ │ relay │ │
|
||||
│ :60000 │ gossip │ :60001 │
|
||||
└───┬────┘ └────┬───┘
|
||||
│ │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────▼─────┐
|
||||
│ PostgreSQL│
|
||||
│ :5432 │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
Both nodes:
|
||||
- Run all Waku protocols (relay, filter, lightpush, store)
|
||||
- Share a PostgreSQL database for message persistence
|
||||
- Connected to each other via relay protocol
|
||||
- Discover each other via peer exchange
|
||||
- Expose WebSocket for js-waku connections
|
||||
- Expose REST API for debugging
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Do I need to wait for nodes to connect before starting my app?**
|
||||
A: No, you can start your app immediately. js-waku will wait for peers to be available.
|
||||
|
||||
**Q: Can I use this for production?**
|
||||
A: No, this is for development only. For production, use The Waku Network or run your own fleet.
|
||||
|
||||
**Q: Why PostgreSQL?**
|
||||
A: The nwaku store protocol requires a database to persist messages. This allows your app to query message history.
|
||||
|
||||
**Q: Can I connect from a different machine?**
|
||||
A: Yes, but you'll need to change `127.0.0.1` to your machine's IP address in the multiaddrs and ensure your firewall allows the connections.
|
||||
|
||||
**Q: How much disk space does this use?**
|
||||
A: Minimal - the PostgreSQL database only stores messages from your local testing. Use `docker compose down -v` to remove all data.
|
||||
|
||||
## Resources
|
||||
|
||||
- [js-waku Documentation](https://docs.waku.org/guides/js-waku/)
|
||||
- [nwaku GitHub](https://github.com/waku-org/nwaku)
|
||||
- [Waku Protocol Specifications](https://rfc.vac.dev/)
|
||||
- [Example Applications](https://github.com/waku-org/js-waku-examples)
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
27
packages/run/cspell.json
Normal file
27
packages/run/cspell.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"language": "en",
|
||||
"words": [
|
||||
"hackathons",
|
||||
"hackathon",
|
||||
"waku",
|
||||
"Waku",
|
||||
"nwaku",
|
||||
"wakuorg",
|
||||
"dockerode",
|
||||
"multiaddr",
|
||||
"multiaddrs",
|
||||
"libp2p",
|
||||
"pubsub",
|
||||
"gossipsub",
|
||||
"lightpush",
|
||||
"Lightpush",
|
||||
"psql",
|
||||
"isready"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"*.log"
|
||||
]
|
||||
}
|
||||
126
packages/run/docker-compose.yml
Normal file
126
packages/run/docker-compose.yml
Normal file
@ -0,0 +1,126 @@
|
||||
# Environment variable definitions
|
||||
x-pg-pass: &pg_pass ${POSTGRES_PASSWORD:-test123}
|
||||
x-pg-user: &pg_user ${POSTGRES_USER:-postgres}
|
||||
|
||||
x-pg-environment: &pg_env
|
||||
POSTGRES_USER: *pg_user
|
||||
POSTGRES_PASSWORD: *pg_pass
|
||||
|
||||
# Shared nwaku configuration
|
||||
x-nwaku-base: &nwaku-base
|
||||
image: ${NWAKU_IMAGE:-wakuorg/nwaku:v0.36.0}
|
||||
restart: on-failure
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15.4-alpine3.18
|
||||
restart: on-failure
|
||||
environment:
|
||||
<<: *pg_env
|
||||
POSTGRES_DB: postgres
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
nwaku-1:
|
||||
<<: *nwaku-base
|
||||
container_name: waku-local-node-1
|
||||
ports:
|
||||
- "${NODE1_TCP_PORT:-30303}:30303/tcp"
|
||||
- "${NODE1_WS_PORT:-60000}:60000/tcp"
|
||||
- "${NODE1_REST_PORT:-8646}:8646/tcp"
|
||||
environment:
|
||||
<<: *pg_env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- --relay=true
|
||||
- --filter=true
|
||||
- --lightpush=true
|
||||
- --store=true
|
||||
- --peer-exchange=true
|
||||
- --discv5-discovery=true
|
||||
- --cluster-id=0
|
||||
- --shard=0
|
||||
- --shard=1
|
||||
- --shard=2
|
||||
- --shard=3
|
||||
- --shard=4
|
||||
- --shard=5
|
||||
- --shard=6
|
||||
- --shard=7
|
||||
- --listen-address=0.0.0.0
|
||||
- --tcp-port=30303
|
||||
- --websocket-support=true
|
||||
- --websocket-port=60000
|
||||
- --ext-multiaddr=/ip4/127.0.0.1/tcp/60000/ws
|
||||
- --ext-multiaddr-only=true
|
||||
- --rest=true
|
||||
- --rest-address=0.0.0.0
|
||||
- --rest-port=8646
|
||||
- --rest-admin=true
|
||||
- --store-message-db-url=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-test123}@postgres:5432/postgres
|
||||
- --log-level=${LOG_LEVEL:-INFO}
|
||||
- --max-connections=150
|
||||
|
||||
nwaku-2:
|
||||
<<: *nwaku-base
|
||||
container_name: waku-local-node-2
|
||||
ports:
|
||||
- "${NODE2_TCP_PORT:-30304}:30304/tcp"
|
||||
- "${NODE2_WS_PORT:-60001}:60001/tcp"
|
||||
- "${NODE2_REST_PORT:-8647}:8647/tcp"
|
||||
environment:
|
||||
<<: *pg_env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
nwaku-1:
|
||||
condition: service_started
|
||||
command:
|
||||
- --relay=true
|
||||
- --filter=true
|
||||
- --lightpush=true
|
||||
- --store=true
|
||||
- --peer-exchange=true
|
||||
- --discv5-discovery=true
|
||||
- --cluster-id=0
|
||||
- --shard=0
|
||||
- --shard=1
|
||||
- --shard=2
|
||||
- --shard=3
|
||||
- --shard=4
|
||||
- --shard=5
|
||||
- --shard=6
|
||||
- --shard=7
|
||||
- --listen-address=0.0.0.0
|
||||
- --tcp-port=30304
|
||||
- --websocket-support=true
|
||||
- --websocket-port=60001
|
||||
- --ext-multiaddr=/ip4/127.0.0.1/tcp/60001/ws
|
||||
- --ext-multiaddr-only=true
|
||||
- --rest=true
|
||||
- --rest-address=0.0.0.0
|
||||
- --rest-port=8647
|
||||
- --rest-admin=true
|
||||
- --store-message-db-url=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-test123}@postgres:5432/postgres
|
||||
- --log-level=${LOG_LEVEL:-INFO}
|
||||
- --max-connections=150
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: waku-local-network
|
||||
67
packages/run/package.json
Normal file
67
packages/run/package.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "@waku/run",
|
||||
"version": "0.0.1",
|
||||
"description": "Run a local Waku network for development and testing",
|
||||
"type": "module",
|
||||
"author": "Waku Team",
|
||||
"homepage": "https://github.com/waku-org/js-waku/tree/master/packages/run#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/waku-org/js-waku.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/waku-org/js-waku/issues"
|
||||
},
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"keywords": [
|
||||
"waku",
|
||||
"decentralized",
|
||||
"communication",
|
||||
"web3",
|
||||
"testing",
|
||||
"development"
|
||||
],
|
||||
"bin": {
|
||||
"waku-run": "./dist/src/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"docker-compose.yml",
|
||||
".env.example",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run build",
|
||||
"start": "tsx scripts/start.ts",
|
||||
"stop": "docker compose down",
|
||||
"restart": "npm run stop && npm run start",
|
||||
"logs": "docker compose logs -f",
|
||||
"info": "tsx scripts/info.ts",
|
||||
"test": "NODE_ENV=test node ./src/run-tests.js \"tests/**/*.spec.ts\"",
|
||||
"fix": "run-s fix:*",
|
||||
"fix:lint": "eslint src scripts tests --fix",
|
||||
"check": "run-s check:*",
|
||||
"check:tsc": "tsc -p tsconfig.dev.json",
|
||||
"check:lint": "eslint src scripts tests",
|
||||
"check:spelling": "cspell \"{README.md,src/**/*.ts,scripts/**/*.ts,tests/**/*.ts}\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@waku/core": "*",
|
||||
"@waku/interfaces": "*",
|
||||
"@waku/sdk": "*",
|
||||
"@waku/utils": "*",
|
||||
"chai": "^4.3.10",
|
||||
"cspell": "^8.6.1",
|
||||
"mocha": "^10.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
96
packages/run/scripts/info.ts
Executable file
96
packages/run/scripts/info.ts
Executable file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from "child_process";
|
||||
|
||||
interface Colors {
|
||||
reset: string;
|
||||
cyan: string;
|
||||
blue: string;
|
||||
gray: string;
|
||||
yellow: string;
|
||||
}
|
||||
|
||||
interface NodeInfo {
|
||||
listenAddresses: string[];
|
||||
}
|
||||
|
||||
// ANSI color codes
|
||||
const colors: Colors = {
|
||||
reset: "\x1b[0m",
|
||||
cyan: "\x1b[36m",
|
||||
blue: "\x1b[34m",
|
||||
gray: "\x1b[90m",
|
||||
yellow: "\x1b[33m"
|
||||
};
|
||||
|
||||
try {
|
||||
// Check if containers are running
|
||||
const output: string = execSync("docker compose ps --quiet", {
|
||||
encoding: "utf-8"
|
||||
}).trim();
|
||||
|
||||
if (!output) {
|
||||
process.stdout.write(
|
||||
`${colors.gray}No nodes running. Start with: ${colors.cyan}npm run start${colors.reset}\n`
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Get cluster config from env or defaults
|
||||
const clusterId: string = process.env.CLUSTER_ID || "0";
|
||||
const node1Port: string = process.env.NODE1_WS_PORT || "60000";
|
||||
const node2Port: string = process.env.NODE2_WS_PORT || "60001";
|
||||
|
||||
// Fetch node info
|
||||
const node1Info: NodeInfo = await fetch(
|
||||
"http://127.0.0.1:8646/debug/v1/info"
|
||||
).then((r) => r.json());
|
||||
const node2Info: NodeInfo = await fetch(
|
||||
"http://127.0.0.1:8647/debug/v1/info"
|
||||
).then((r) => r.json());
|
||||
|
||||
const peer1: string = node1Info.listenAddresses[0].split("/p2p/")[1];
|
||||
const peer2: string = node2Info.listenAddresses[0].split("/p2p/")[1];
|
||||
|
||||
// Print TypeScript-style config
|
||||
process.stdout.write(
|
||||
`${colors.blue}import${colors.reset} { createLightNode } ${colors.blue}from${colors.reset} ${colors.yellow}"@waku/sdk"${colors.reset};\n`
|
||||
);
|
||||
process.stdout.write(`\n`);
|
||||
process.stdout.write(
|
||||
`${colors.blue}const${colors.reset} waku = ${colors.blue}await${colors.reset} createLightNode({\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` defaultBootstrap: ${colors.cyan}false${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(` bootstrapPeers: [\n`);
|
||||
process.stdout.write(
|
||||
` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${peer1}"${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${peer2}"${colors.reset}\n`
|
||||
);
|
||||
process.stdout.write(` ],\n`);
|
||||
process.stdout.write(` networkConfig: {\n`);
|
||||
process.stdout.write(
|
||||
` clusterId: ${colors.cyan}${clusterId}${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` numShardsInCluster: ${colors.cyan}8${colors.reset}\n`
|
||||
);
|
||||
process.stdout.write(` }\n`);
|
||||
process.stdout.write(`});\n`);
|
||||
} catch (error: unknown) {
|
||||
const err = error as { cause?: { code?: string }; message?: string };
|
||||
if (err.cause?.code === "ECONNREFUSED") {
|
||||
process.stderr.write(
|
||||
`${colors.yellow}⚠${colors.reset} Nodes are still starting. Try again in a few seconds.\n`
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`${colors.yellow}✗${colors.reset} Error: ${err.message || String(error)}\n`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
141
packages/run/scripts/start.ts
Executable file
141
packages/run/scripts/start.ts
Executable file
@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from "child_process";
|
||||
|
||||
interface Colors {
|
||||
reset: string;
|
||||
cyan: string;
|
||||
green: string;
|
||||
blue: string;
|
||||
gray: string;
|
||||
yellow: string;
|
||||
}
|
||||
|
||||
interface NodeInfo {
|
||||
listenAddresses: string[];
|
||||
}
|
||||
|
||||
// ANSI color codes
|
||||
const colors: Colors = {
|
||||
reset: "\x1b[0m",
|
||||
cyan: "\x1b[36m",
|
||||
green: "\x1b[32m",
|
||||
blue: "\x1b[34m",
|
||||
gray: "\x1b[90m",
|
||||
yellow: "\x1b[33m"
|
||||
};
|
||||
|
||||
async function waitWithProgress(ms: number): Promise<void> {
|
||||
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
const startTime = Date.now();
|
||||
let frameIndex = 0;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
if (elapsed >= ms) {
|
||||
clearInterval(interval);
|
||||
process.stdout.write("\r" + " ".repeat(50) + "\r");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const frame = frames[frameIndex % frames.length];
|
||||
process.stdout.write(
|
||||
`\r${colors.cyan}${frame}${colors.reset} Waiting for nodes to start...`
|
||||
);
|
||||
frameIndex++;
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
process.stdout.write(
|
||||
`${colors.cyan}Starting local Waku development environment...${colors.reset}\n`
|
||||
);
|
||||
|
||||
try {
|
||||
// Start docker compose quietly
|
||||
execSync("docker compose up -d", {
|
||||
stdio: ["ignore", "ignore", "pipe"],
|
||||
encoding: "utf-8"
|
||||
});
|
||||
|
||||
// Wait for nodes to be ready
|
||||
await waitWithProgress(20000);
|
||||
|
||||
// Get cluster config from env or defaults
|
||||
const clusterId: string = process.env.CLUSTER_ID || "0";
|
||||
const node1Port: string = process.env.NODE1_WS_PORT || "60000";
|
||||
const node2Port: string = process.env.NODE2_WS_PORT || "60001";
|
||||
|
||||
// Fetch node info
|
||||
const node1Info: NodeInfo = await fetch(
|
||||
"http://127.0.0.1:8646/debug/v1/info"
|
||||
).then((r) => r.json());
|
||||
const node2Info: NodeInfo = await fetch(
|
||||
"http://127.0.0.1:8647/debug/v1/info"
|
||||
).then((r) => r.json());
|
||||
|
||||
const peer1: string = node1Info.listenAddresses[0].split("/p2p/")[1];
|
||||
const peer2: string = node2Info.listenAddresses[0].split("/p2p/")[1];
|
||||
|
||||
// Print TypeScript-style config
|
||||
process.stdout.write(
|
||||
`${colors.green}✓${colors.reset} Network started successfully!\n\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
`${colors.gray}Copy this into your application:${colors.reset}\n\n`
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
`${colors.blue}import${colors.reset} { createLightNode } ${colors.blue}from${colors.reset} ${colors.yellow}"@waku/sdk"${colors.reset};\n`
|
||||
);
|
||||
process.stdout.write(`\n`);
|
||||
process.stdout.write(
|
||||
`${colors.blue}const${colors.reset} waku = ${colors.blue}await${colors.reset} createLightNode({\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` defaultBootstrap: ${colors.cyan}false${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(` bootstrapPeers: [\n`);
|
||||
process.stdout.write(
|
||||
` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${peer1}"${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` ${colors.yellow}"/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${peer2}"${colors.reset}\n`
|
||||
);
|
||||
process.stdout.write(` ],\n`);
|
||||
process.stdout.write(` networkConfig: {\n`);
|
||||
process.stdout.write(
|
||||
` clusterId: ${colors.cyan}${clusterId}${colors.reset},\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` numShardsInCluster: ${colors.cyan}8${colors.reset}\n`
|
||||
);
|
||||
process.stdout.write(` }\n`);
|
||||
process.stdout.write(`});\n`);
|
||||
process.stdout.write(`\n`);
|
||||
process.stdout.write(`${colors.gray}Management:${colors.reset}\n`);
|
||||
process.stdout.write(
|
||||
` ${colors.cyan}npm run logs${colors.reset} - View logs\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` ${colors.cyan}npm run info${colors.reset} - Show config again\n`
|
||||
);
|
||||
process.stdout.write(
|
||||
` ${colors.cyan}npm run stop${colors.reset} - Stop network\n`
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
const err = error as { cause?: { code?: string }; message?: string };
|
||||
if (err.cause?.code === "ECONNREFUSED") {
|
||||
process.stderr.write(
|
||||
`${colors.yellow}⚠${colors.reset} Nodes are still starting up. Run ${colors.cyan}npm run info${colors.reset} in a few seconds.\n`
|
||||
);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`${colors.yellow}✗${colors.reset} Error: ${err.message || String(error)}\n`
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
34
packages/run/src/cli.ts
Normal file
34
packages/run/src/cli.ts
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
const scriptMap: Record<string, string> = {
|
||||
start: join(__dirname, "..", "scripts", "start.js"),
|
||||
info: join(__dirname, "..", "scripts", "info.js")
|
||||
};
|
||||
|
||||
if (!command || !scriptMap[command]) {
|
||||
process.stderr.write("Usage: @waku/run <command>\n");
|
||||
process.stderr.write("\n");
|
||||
process.stderr.write("Commands:\n");
|
||||
process.stderr.write(" start Start the local Waku network\n");
|
||||
process.stderr.write(" info Show connection info for running network\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const scriptPath = scriptMap[command];
|
||||
const child = spawn("node", [scriptPath], {
|
||||
stdio: "inherit",
|
||||
env: process.env
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
process.exit(code || 0);
|
||||
});
|
||||
30
packages/run/src/run-tests.js
Normal file
30
packages/run/src/run-tests.js
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "child_process";
|
||||
|
||||
const mochaArgs = [
|
||||
"mocha",
|
||||
"--require",
|
||||
"ts-node/register",
|
||||
"--project",
|
||||
"./tsconfig.json",
|
||||
...process.argv.slice(2)
|
||||
];
|
||||
|
||||
// Run mocha tests
|
||||
const mocha = spawn("npx", mochaArgs, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "test"
|
||||
}
|
||||
});
|
||||
|
||||
mocha.on("error", (error) => {
|
||||
console.log(`Error running mocha tests: ${error.message}`); // eslint-disable-line no-console
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
mocha.on("exit", (code) => {
|
||||
process.exit(code || 0);
|
||||
});
|
||||
110
packages/run/tests/basic.spec.ts
Normal file
110
packages/run/tests/basic.spec.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { execSync } from "child_process";
|
||||
|
||||
import { createEncoder } from "@waku/core";
|
||||
import type { LightNode } from "@waku/interfaces";
|
||||
import { createLightNode, Protocols, waitForRemotePeer } from "@waku/sdk";
|
||||
import { createRoutingInfo } from "@waku/utils";
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("Waku Run - Basic Test", function () {
|
||||
this.timeout(90000);
|
||||
|
||||
let waku: LightNode;
|
||||
|
||||
before(async function () {
|
||||
// Step 1: Start the nodes
|
||||
execSync("docker compose up -d", {
|
||||
stdio: "inherit"
|
||||
});
|
||||
|
||||
// Wait for nodes to be ready
|
||||
const maxRetries = 30;
|
||||
const retryDelay = 2000;
|
||||
let ready = false;
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
await fetch("http://127.0.0.1:8646/debug/v1/info");
|
||||
await fetch("http://127.0.0.1:8647/debug/v1/info");
|
||||
ready = true;
|
||||
break;
|
||||
} catch {
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
throw new Error("Nodes failed to start within expected time");
|
||||
}
|
||||
|
||||
// Connect the two nwaku nodes together
|
||||
const node1Info = await fetch("http://127.0.0.1:8646/debug/v1/info").then(
|
||||
(r) => r.json()
|
||||
);
|
||||
const peer1Multiaddr = node1Info.listenAddresses[0];
|
||||
|
||||
await fetch("http://127.0.0.1:8647/admin/v1/peers", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify([peer1Multiaddr])
|
||||
});
|
||||
|
||||
// Wait a bit for the connection to establish
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
// Step 4: Stop the nodes
|
||||
if (waku) {
|
||||
await waku.stop();
|
||||
}
|
||||
execSync("docker compose down", {
|
||||
stdio: "inherit"
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect to nodes and send lightpush message", async function () {
|
||||
// Step 2: Connect to nodes via js-waku
|
||||
const node1Port = process.env.NODE1_WS_PORT || "60000";
|
||||
const node2Port = process.env.NODE2_WS_PORT || "60001";
|
||||
|
||||
// Fetch node info to get peer IDs
|
||||
const node1Info = await fetch("http://127.0.0.1:8646/debug/v1/info").then(
|
||||
(r) => r.json()
|
||||
);
|
||||
const node2Info = await fetch("http://127.0.0.1:8647/debug/v1/info").then(
|
||||
(r) => r.json()
|
||||
);
|
||||
|
||||
const peer1 = node1Info.listenAddresses[0].split("/p2p/")[1];
|
||||
const peer2 = node2Info.listenAddresses[0].split("/p2p/")[1];
|
||||
|
||||
const networkConfig = {
|
||||
clusterId: 0,
|
||||
numShardsInCluster: 8
|
||||
};
|
||||
|
||||
waku = await createLightNode({
|
||||
defaultBootstrap: false,
|
||||
bootstrapPeers: [
|
||||
`/ip4/127.0.0.1/tcp/${node1Port}/ws/p2p/${peer1}`,
|
||||
`/ip4/127.0.0.1/tcp/${node2Port}/ws/p2p/${peer2}`
|
||||
],
|
||||
networkConfig
|
||||
});
|
||||
|
||||
await waku.start();
|
||||
await waku.waitForPeers([Protocols.LightPush]);
|
||||
|
||||
// Step 3: Send a lightpush message
|
||||
const contentTopic = "/test/1/basic/proto";
|
||||
const routingInfo = createRoutingInfo(networkConfig, { contentTopic });
|
||||
const encoder = createEncoder({ contentTopic, routingInfo });
|
||||
|
||||
const result = await waku.lightPush.send(encoder, {
|
||||
payload: new TextEncoder().encode("Hello Waku!")
|
||||
});
|
||||
|
||||
expect(result.successes.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
7
packages/run/tsconfig.dev.json
Normal file
7
packages/run/tsconfig.dev.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.dev",
|
||||
"compilerOptions": {
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src", "scripts", "tests"]
|
||||
}
|
||||
10
packages/run/tsconfig.json
Normal file
10
packages/run/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/",
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "dist/.tsbuildinfo"
|
||||
},
|
||||
"include": ["src", "scripts"],
|
||||
"exclude": ["tests", "dist", "node_modules"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user