Phase 5 mesh networking: - E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes relay encrypted blobs transparently via Meshcore native routing - Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic looks like sensor data on the wire, 0xAA marker, configurable per-node - Pre-flight Bitcoin Core health check on relay node — specific error codes (bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails - mesh.relay-status RPC endpoint — frontend polls for relay result every 3s - On-Chain / Lightning tabs in Off-Grid Bitcoin panel - Archy Peers vs Mesh Broadcast relay mode selector - Mesh view fills viewport (no page scroll), internal panel scrolling - Version bump to 1.2.0-alpha Also includes: deploy hardening, container fixes, IndeedHub updates, boot screen, dashboard improvements, MASTER_PLAN task tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.0 KiB
Mesh Phase 4 Completion + Phase 5 Implementation
Context
Mesh Phases 1-3 are complete: serial driver, transport layer (Mesh>LAN>Tor), Double Ratchet encryption, typed messages, store-and-forward, chat UI. Phase 4 is 40% done — data structures, builders, and tests exist (bitcoin_relay.rs, alerts.rs, message_types.rs) but nothing is wired into the listener, MeshService, or RPC layer. Phase 5 (steganographic modes, adaptive routing, multi-hardware) is not started.
Phase 4: Wire Up Off-Grid Bitcoin Operations (Weeks 8-11)
Week 8: Typed Message Dispatch in Listener
The critical foundation — everything else depends on this.
mesh/listener.rs:
- Add
MeshCommand::SendRaw { dest_pubkey_prefix: [u8; 6], payload: Vec<u8> }andBroadcastChannel { channel: u8, payload: Vec<u8> }variants - In
handle_frame(): after extracting message bytes, check for0x02TypedEnvelope prefix - New
handle_typed_message()dispatches by type:BlockHeader→ validate Ed25519 sig, store inBlockHeaderCache, emit eventTxRelay→ spawn task: Bitcoin RPCsendrawtransaction, sendTxRelayResponsebackTxRelayResponse→ complete pending inRelayTracker, store as MeshMessageLightningRelay→ spawn task: LND RESTpayinvoice, send response backLightningRelayResponse→ complete pending, storeAlert→ verify sig, store, emitMeshEvent::AlertReceived
- Handle
SendRawandBroadcastChannelintokio::select!command dispatch
mesh/types.rs: New MeshEvent variants: BlockHeaderReceived, AlertReceived, TxRelayCompleted, LightningRelayCompleted
Key design: Spawn separate tokio tasks for Bitcoin/LND HTTP calls (don't block serial read loop). Response sent back via cmd_tx channel.
Week 9: MeshService Integration + Dead Man's Switch Task
mesh/mod.rs:
- Add fields:
block_header_cache: Arc<BlockHeaderCache>,relay_tracker: Arc<RelayTracker>,dead_man_switch: Arc<DeadManSwitch>,signing_key: ed25519_dalek::SigningKey - Init in
new(), pass cache + tracker into listener viaMeshState - Accessor methods for RPC layer
Dead Man background task (spawned in start()):
- Check every 60s: if triggered → build signed alert → broadcast on channel 0 + direct to emergency contacts
- Persist
last_check_in_timeas unix timestamp on disk (survives restarts)
Week 10: RPC Endpoints
api/rpc/mesh.rs — New handlers:
| Endpoint | Params | Description |
|---|---|---|
mesh.relay-tx |
{ tx_hex } |
Queue TX for relay via internet peer |
mesh.block-headers |
{ count? } |
Return cached block headers |
mesh.relay-lightning |
{ bolt11, amount_sats } |
Queue LN invoice for payment |
mesh.deadman-status |
— | Query switch state |
mesh.deadman-configure |
{ enabled, interval_secs, lat, lng, contacts, custom_message } |
Configure |
mesh.deadman-checkin |
— | Heartbeat reset |
Fix mesh.send-invoice: Replace placeholder bolt11 with real LND POST /v1/invoices call.
api/rpc/mod.rs: Register all new routes (~line 643).
Week 11: Block Header Announcer + Frontend
Backend: Optional background task: poll Bitcoin Core getblockchaininfo every 30s → on new block → signed announcement → broadcast channel 0. Config: announce_block_headers: bool.
Frontend stores/mesh.ts: New methods for all Phase 4 RPC calls.
Frontend views/Mesh.vue:
- "Off-Grid Bitcoin" panel: block height, headers, TX relay form, LN relay form
- "Dead Man's Switch" panel: enable/disable, interval, GPS, contacts, countdown, check-in
- Uses
.path-option-card,.glass-button,.info-card
Phase 5: Mesh Network Intelligence (Weeks 12-15)
Week 12: Steganographic Modes
New: mesh/steganography.rs
SteganographyModeenum:Normal,WeatherStation,SensorNetwork- Weather Station: Map payload bytes → plausible weather readings (temp, humidity, pressure, wind). Marker
0xAAreplaces0x02. - Sensor Network: Industrial sensor format (voltage, current, vibration)
to_wire_steganographic(mode)/from_wire_steganographic(data)on TypedEnvelope- Listener detects
0xAA→ decode stego → normal dispatch - Config:
steganography_modeinMeshConfig - Budget: ~80 bytes real data per 160-byte LoRa frame with stego overhead
Week 13: Adaptive Routing & Signal Intelligence
New: mesh/routing.rs
LinkQualityper peer: RSSI/SNR rolling 1h history, packet loss, hop countRoutingTable: link quality per peer + best route per destination DID- Score:
(rssi+120)*0.4 + (snr+20)*0.3 + (1-loss)*100*0.3 - Best relay selection for TX/LN relay (highest quality peer with internet)
- Multi-hop forwarding: if dest DID != ours and hops < 3, forward to best next-hop
- Extract RSSI from v3 frames (bytes 1-2, currently unused)
- RPC:
mesh.routing-table
Week 14: LoRa Radio Parameter Control
mesh/protocol.rs: Builders for SET_RADIO_PARAMS (0x0B), SET_TX_POWER (0x0C), SET_TUNING_PARAMS (0x15). Parse RESP_STATS (0x18).
RPC: mesh.set-radio-params, mesh.set-tx-power, mesh.get-radio-stats
Auto-adaptive SF: If link quality drops → increase spreading factor (longer range, slower). Config toggle.
Frontend: Radio tuning panel with SF/TX power sliders, stats, auto-adaptive toggle.
Week 15: Multi-Hardware + Topology UI
New: mesh/device_trait.rs
#[async_trait]
pub trait MeshDevice: Send + Sync {
async fn open(path: &str) -> Result<Self> where Self: Sized;
async fn initialize(&mut self) -> Result<DeviceInfo>;
async fn send_text(&mut self, dest: &[u8; 6], msg: &[u8]) -> Result<()>;
async fn try_recv_frame(&mut self) -> Result<Option<InboundFrame>>;
// ...
}
- Implement for
MeshcoreDevice, stub Meshtastic/WiFi/BLE listener.rsusesBox<dyn MeshDevice>- Topology UI: SVG graph (this node center, peers as satellites), edge thickness = quality, color = green/yellow/red, tooltips with RSSI/SNR/hops
- Stego mode selector, block relay status panel
Key Challenges
- TX hex > 160 bytes: Use Reed-Solomon chunking (already in
transport/chunking.rs) - Async in listener: Spawn tasks for Bitcoin/LND calls, don't block serial loop
- Dead man false triggers: Persist check-in time as unix timestamp on disk
- Stego overhead: ~80 bytes real data per 160-byte frame
Files Modified
Phase 4:
core/archipelago/src/mesh/listener.rs— typed dispatch, new MeshCommand variantscore/archipelago/src/mesh/mod.rs— new fields, init, background taskscore/archipelago/src/mesh/types.rs— new MeshEvent variantscore/archipelago/src/api/rpc/mesh.rs— 6+ new endpoints, fix send-invoicecore/archipelago/src/api/rpc/mod.rs— register routesneode-ui/src/stores/mesh.ts— new store methodsneode-ui/src/views/Mesh.vue— off-grid + dead man panels
Phase 5 new files:
core/archipelago/src/mesh/steganography.rscore/archipelago/src/mesh/routing.rscore/archipelago/src/mesh/device_trait.rs
Existing Code to Reuse
bitcoin_relay.rs:BlockHeaderCache,RelayTracker, allbuild_*functionsalerts.rs:DeadManSwitch,AlertConfig,load_config/save_configmessage_types.rs: All payload types,TypedEnvelope,encode_payload/decode_payloadapi/rpc/lnd.rs:128-141:lnd_client()pattern for LND REST callsapi/rpc/bitcoin.rs:74-107:bitcoin_rpc_call()for Bitcoin Core RPCtransport/chunking.rs: Reed-Solomon FEC for payloads > 160 bytes
Verification
# Unit tests on server
ssh archipelago@192.168.1.228 'cd ~/archy/core && source ~/.cargo/env && cargo test --all-features -- mesh'
# Type check frontend
cd neode-ui && npm run type-check
# Deploy to both
./scripts/deploy-to-target.sh --both
# E2E tests:
# 1. .228 (internet) relays TX from .198 (mesh-only)
# 2. .228 announces block headers, .198 receives them
# 3. Dead man's switch triggers after interval, broadcasts alert
# 4. Steganographic packet looks like weather data on wire