Releases: LeavesJ/ReserveGrid-OS
Release list
ReserveGrid OS v2.0.0-rc1
ReserveGrid OS v2.0.0-rc1 — Invariant
Release candidate for the Invariant chapter of ReserveGrid OS. Two architectural layers ship above the v1.1.0 policy plus gateway plus observability baseline: independent consensus re-derivation against raw block bytes, and mempool ground truth via direct bitcoind RPC. The verifier no longer trusts what the template declares about itself; it re-derives the consensus quantities and cross-references the tx set against the network mempool. 22 canonical v2_invariant_* reason codes formalize the new check classes. Final v2.0.0 ships once the production observation cycle completes cleanly.
Independent consensus re-derivation
Phase 1 of the Invariant chapter introduces rg-consensus, a new workspace crate that wraps rust-bitcoin behind a narrow facade. Five public re-derivation functions cover the consensus quantities every template carries: re_derive_coinbase_value, re_derive_template_weight, re_derive_merkle_root, re_derive_witness_commitment, count_sigops. Plus class accessors over ParsedBlock: template_txids, total_sigops, coinbase_sigops, bip34_height, parse_block. The facade boundary is intentionally narrow so neither the gateway nor the verifier imports rust-bitcoin directly; every call goes through rg-consensus.
The pool-verifier's invariant shield runs the re-derivation chain against every template that ships raw_block_hex. Three check classes execute in order: Class S (structural validity, single-deserialize against ParsedBlock), Class D (declared-versus-derived consistency, comparing the template's stated values against what re-derivation produces), and the Phase 2 Class M check described below. The shield short-circuits on the first violation and emits a canonical v2_invariant_* reason code with full policy context.
18 canonical reason codes ship at this layer. 10 critical and high-criticality variants are wired and tested today (v2_invariant_coinbase_value_mismatch, v2_invariant_coinbase_height_mismatch, v2_invariant_merkle_root_mismatch, v2_invariant_witness_commitment_missing, v2_invariant_witness_commitment_mismatch, v2_invariant_sigops_mismatch, v2_invariant_coinbase_sigops_mismatch, v2_invariant_template_weight_mismatch, v2_invariant_tx_count_mismatch, v2_invariant_coinbase_bip34_missing, plus v2_invariant_decode_failed as the catch-all). 7 belt-and-suspenders Tier 3 reason codes (v2_invariant_coinbase_script_length, v2_invariant_coinbase_output_count, v2_invariant_weight_exceeds_max, v2_invariant_sigops_exceed_max, v2_invariant_nontcb_null_prevout, v2_invariant_header_version_low, v2_invariant_duplicate_tx) are reserved at the facade layer and ship wired in Phase 1.5 after the production observation cycle.
The shield ships against a regtest segwit block fixture for tampering tests. Every wired reason code has a corresponding tamper test that mutates the block bytes at a known offset, re-derives the merkle root via the test helper, and asserts the shield emits the expected reason code on the tampered template.
Mempool ground truth
Phase 2 of the Invariant chapter adds Class M to the shield's check chain. The verifier holds its own bitcoind JSON-RPC client (reqwest over basic auth, configurable poll interval) and maintains a MempoolView populated by a tokio task that calls getrawmempool every [policy.mempool] poll_interval_secs seconds (default 10). Every template's non-coinbase txids are cross-referenced against the live view. When the unknown-tx ratio exceeds [policy.mempool] tolerance_pct (default 4.0, operator-tunable), the shield rejects with v2_invariant_mempool_tolerance_exceeded and an aggregate detail listing up to SAMPLE_UNKNOWN_CAP = 10 representative txids. Per-tx detail mode ([policy.mempool] per_tx_detail = true) expands the sample list to every unknown txid for forensics; wire format stays 1:1 (one TemplateVerdict per accepted TemplatePropose).
The view runs a fail-stale state machine. MempoolState::Fresh while the last refresh is within max_stale_secs (default 60). Stale between max_stale_secs and 2 * max_stale_secs. Degraded past 2 * max_stale_secs, where the Class M check is skipped and templates fall through to Phase 1 behavior. Every verdict served while Degraded increments verifier_phase2_degraded_total so dashboards can alert on extended bitcoind RPC outages. The view also reads Degraded when not yet primed (before the first successful poll); operators should expect a small bounded number of degraded events at boot until the polling task installs the first snapshot.
4 canonical reason codes ship at this layer: v2_invariant_mempool_disagreement, v2_invariant_mempool_tolerance_exceeded, v2_invariant_mempool_unavailable, v2_invariant_mempool_view_stale. Plus v2_invariant_mempool_tx_unknown for the per-tx detail mode taxonomy. Cross-crate string parity is enforced by the ALL_CODES length assertion (22 in rg-consensus::ConsensusViolation, 37 in rg-protocol::VerdictReason, 95 in reservegrid-common::ReasonCode after dedup of the shared internal_error).
Config fields at [policy.mempool]: enforce (default false), tolerance_pct, poll_interval_secs, max_stale_secs, per_tx_detail, rpc_url, rpc_user, rpc_pass. All optional with defaults so older policy.toml files continue to load unchanged. Prometheus metrics: verifier_phase2_checks_total{result} (counter vec, result in agreed/rejected/skipped/stale), verifier_phase2_degraded_total (counter), verifier_mempool_view_age_seconds (gauge), verifier_mempool_view_size (gauge).
Reason code taxonomy expansion
The canonical reason code surface grows from 91 to 95. The 22 new v2_invariant_* strings join the existing 73 non-shield codes (15 verdict reasons covering policy plus system plus advisory, and 59 gateway codes, with one internal_error shared between the verdict and gateway sides per the existing dedup convention). All 22 v2_invariant_* codes carry explicit #[serde(rename = "v2_invariant_...")] attributes per R-155 because the digit-to-uppercase boundary in V2InvariantXxx is exactly the case rename_all = "snake_case" does not reliably handle. The cross-crate string parity guard is the standard grep -rE "v2_invariant_[a-z0-9_]+" services/rg-protocol services/reservegrid-common services/rg-consensus plus the ALL_CODES.len() assertions in each crate's tests.
Configuration surface expansion
The operator-tunable surface grows from 61 to 69 TOML keys. The eight new keys all live at [policy.mempool] and are listed under the Mempool ground truth section above. The pool-verifier reads VELDRA_BITCOIND_RPC_PASS first and falls back to [policy.mempool] rpc_pass only if the env var is unset, so production deploys can keep the bitcoind RPC password out of policy.toml on disk. Pool-verifier and template-manager share the same VELDRA_BITCOIND_RPC_USER plus VELDRA_BITCOIND_RPC_PASS env var pair when they share a bitcoind backend.
Two-tier integration test layout
A new pair of integration test files in services/pool-verifier/tests/ covers the Phase 2 Class M code paths. phase2_eval.rs is the Tier 1 file: synthesizes MempoolSnapshot values directly via the new MempoolView::install_at test seam and exercises policy::evaluate_dynamic_phase2 end-to-end across happy path, fabrication path, below-threshold unknowns, view state machine cycling (Fresh / Stale / Degraded driven by install_at timestamps), Degraded view skip, Phase 2 disabled fall-through, and detail-format consistency. phase2_tcp.rs is the Tier 2 file: spawns the real pool-verifier binary via CARGO_BIN_EXE_pool-verifier, stands up an in-process axum mock that answers getrawmempool over JSON-RPC, and round-trips TemplatePropose plus TemplateVerdict envelopes through the listener. Three subprocess scenarios shipped: happy path, fabrication path, refresh-mid-flight, plus a kill-the-mock fail-stale scenario that flips an always_fail toggle on the mock and asserts verifier_phase2_degraded_total increments after the polling task crosses into Degraded.
The Tier 2 tests are #[ignore]d so the default cargo test --workspace stays fast for the pre-commit checklist. CI runs them via a dedicated step (cargo test -p pool-verifier --test phase2_tcp -- --ignored) so subprocess-spawned regressions are caught on every push and PR.
A static BOOT_MUTEX: tokio::sync::Mutex<()> serializes the racy port-discovery section across parallel Tier 2 tests because the kernel can hand the same 127.0.0.1:0 port to two parallel callers between drop-and-spawn-verifier windows; the mutex eliminates the resulting flakiness while keeping post-boot work parallel.
Synthetic getrawmempool in rg-feed-adapter
The docker-compose.shadow.yml dev stack uses rg-feed-adapter to impersonate bitcoind. The adapter previously supported getblocktemplate and getmempoolinfo only; the verifier's Phase 2 polling task calls getrawmempool, which the adapter answered with -32601 method not found. The mempool view never primed and Class M was skipped on every template, breaking the demo of the full v2.0 product to first-time evaluators using the shadow stack.
The adapter now answers getrawmempool synthetically by extracting every txid field from the latest blocktemplate.transactions array and returning them as an array of hex strings matching bitcoind's getrawmempool verbose=false wire shape. The synthetic mempool is a superset of (or equal to) the latest template's tx set by construction, so the verifier's Phase 2 Class M check always Agrees against the shadow stack. Real mainnet mempool divergence is the production-soak concern; this is wiring smoke for the shadow product demo. SUPPORTED_METHODS test constant grows from 2 to 3 entries; CL-20 updated from 7 to 9 tests covering the txid extraction happy path plus the missing-transactions-field def...
ReserveGrid OS verify-only
ReserveGrid OS verify-only
Downloads
| Platform | File |
|---|---|
| macOS (Apple Silicon) | .dmg |
| macOS (Intel) | .dmg |
| Linux (x86_64) | .AppImage / .deb |
Auto-update
Existing installations will update automatically via the built-in updater.
ReserveGrid OS v1.1.0
ReserveGrid OS v1.1.0 — Yield
Production readiness release. Extended mining channels and variable difficulty make the gateway compatible with real ASIC firmware for the first time. Automatic mode degradation keeps miners working when the verifier goes down. Eight security and operability improvements harden the authentication and share relay stack. Two breaking changes require coordinated upgrades.
Extended channels and variable difficulty
The SV2 gateway now accepts extended mining channels (OpenExtendedMiningChannel 0x13, SubmitSharesExtended 0x1b). Most production ASIC firmware and mining proxies (Braiins OS, firmware-side SRI) require extended channels to connect. Prior to this release those miners were rejected at handshake with ExtendedChannelUnsupported.
Extended channels give the miner a larger extranonce space and let it construct its own coinbase. The gateway negotiates extranonce_size at channel open, enforcing a minimum of 2 miner-controlled bytes. NewExtendedMiningJob delivers the full merkle path and coinbase prefix/suffix instead of a precomputed merkle root. The miner computes its own merkle root from these components.
Variable difficulty adjusts the channel target based on observed share submission rate. The default target is 6 shares per minute with a 120 second retarget interval. Each retarget is capped at 4x adjustment to prevent oscillation. Initial difficulty is seeded from the miner's nominal_hash_rate reported at channel open rather than starting at the config floor. Vardiff applies identically to standard and extended channels via the existing SetTarget message.
Config fields: extended_channels_enabled (default true), vardiff_enabled (default true), vardiff_target_shares_per_min, vardiff_retarget_interval_secs, vardiff_min_difficulty, vardiff_max_difficulty, vardiff_max_adjustment_factor. Prometheus metric: svtwo_vardiff_retargets_total{direction}.
Automatic inline-to-observe degradation
When the verifier heartbeat is lost, the gateway suspends verdict enforcement and flushes all pending templates to miners without blocking. Recovery requires a full HeartbeatAck round trip, not just TCP reconnect. The health probe returns "status":"degraded" during the window. A config validation warning fires at startup if auto_degrade_after_ms is set below the verifier heartbeat interval, which would cause permanent degradation.
Config fields: auto_degrade (default true), auto_degrade_after_ms (default 10000). Prometheus counter: svtwo_mode_transitions_total{direction}.
Mode transition NDJSON events
Every degradation entry and recovery emits a structured NDJSON event with timestamp, direction, and the count of jobs that flowed without enforcement during the degraded window.
Legacy auth fallback removal
The static key list in rg-feed-server and the DB-only validate_key path in rg-auth are removed. All key validation now requires Ed25519 signature verification. This eliminates the dual code path where one branch could bypass signature checks.
Multi-gateway deployment documentation
Active/standby gateway deployment guide covering TCP load balancer configuration, health check endpoints, connection draining behavior, and failover timing.
Tier rename
observe_free is renamed to shadow across the entire stack. SQLite migration v4 updates existing rows. All Rust constants, TypeScript types, and signed key payloads use the new string. External tooling that matches on the old tier name must be updated before deploying.
WebSocket auth at handshake
rg-feed-server rejects unauthenticated connections at the tungstenite handshake callback instead of after the first frame. This eliminates a class of resource exhaustion where unauthenticated clients hold open WebSocket connections indefinitely.
Rate limiter extraction
The per-IP rate limiter from rg-auth is extracted into reservegrid-common and wired into the sv2-gateway and rg-feed-server management endpoints. All three services now share one implementation with consistent configuration.
HMAC secret rotation
SIGHUP triggers a re-read of the share upstream HMAC secret from disk. The secret is held in an Arc<RwLock<Vec<u8>>> and swapped atomically. No restart required for secret rotation.
Management HTTP graceful drain
The health and management HTTP server now participates in the coordinated shutdown sequence via the existing shutdown_rx watch channel and axum::serve(...).with_graceful_shutdown().
License key generation
Production Ed25519 keypair generated and verified. VELDRA_LICENSE_SIGNING_KEY set on Fly.io for rg-auth. VELDRA_LICENSE_PUBKEY available for rg-feed-server and rg-desktop builds.
Devtools gating
Tauri devtools are gated behind cfg(feature = "devtools") which auto-enables only in debug builds. The build.rs reads the Cargo PROFILE env var rather than cfg!(debug_assertions) (which always evaluates as debug inside build scripts). Release builds strip devtools entirely.
HMAC body hash
The gateway_signature_hex field in ShareSubmission now covers HMAC-SHA256(secret, event_id || SHA256(canonical_body)) where canonical_body is the JSON serialization with gateway_signature_hex set to the empty string. Previously the signature covered only event_id. This prevents replay attacks with modified request bodies.
Infrastructure
Fly.io deployment scaffolding for rg-feed-server (fly.toml, app rg-feed-server-veldra). Actual deployment is gated on provisioning a bitcoind RPC endpoint that supports getblocktemplate, deferred until first observe customer confirms.
Breaking changes
Two changes require coordinated upgrades. See the deployment runbook for detailed procedures.
-
HMAC body hash. Upstream services that verify
gateway_signature_hexmust be updated to reconstruct the body hash before signature verification. Deploying sv2-gateway v1.1.0 against the old verification scheme causes all signature checks to fail. -
Tier rename. The
observe_freetier string is replaced byshadowacross the entire stack. External tooling matching onobserve_freemust be updated.
Known limitations
- rg-feed-server deployment pending a bitcoind RPC provider (deferred until first observe customer)
- Rate limiter state remains in-process only (shared state via Redis deferred to v1.2)
- Sustained multi-hour load testing and two-host network latency benchmarks have not been performed
Upgrade from v1.0.2
- Update upstream share verifiers to use the new HMAC body hash signature scheme before deploying the gateway (see deployment runbook)
- Update any external tooling that references the
observe_freetier to useshadow - Review new extended channel and vardiff config fields in gateway TOML
- Rebuild and deploy:
docker compose build && docker compose up -d - Verify health:
curl -s http://localhost:8081/readyz | jq .
See CHANGELOG.md for the complete list of changes.
ReserveGrid OS v1.0.2
ReserveGrid OS v1.0.2 — Stress
Resilience release following a deep stress-test audit and systemic frontend bug fix. 12 findings resolved across timing accuracy, error recovery, configuration correctness, and dashboard reliability. Website i18n coverage expanded from partial to complete across English, Spanish, and Mandarin.
Stress-test audit scope
Four parallel scans (2026-04-07) stress-tested the production pipeline for timing races, error recovery under outage, resource exhaustion, and config edge cases. 8 actionable findings fixed, 2 triaged as false positives. All fixes use safe Rust only (no unsafe blocks).
Template age clock fix
build_template_propose() previously stamped created_at_unix_ms with SystemTime::now() at the moment the gateway received the template from bitcoind. If the RPC call hangs for 15 seconds, the template appears fresh when it is actually stale, bypassing age enforcement entirely. Fixed: the timestamp now uses bitcoind's curtime field (the block creation time embedded in the template itself), converted to milliseconds. Template age enforcement is accurate regardless of RPC latency.
Gateway mode environment variable override
The docker-compose documentation has always shown VELDRA_GATEWAY_MODE=observe as the way to select gateway mode. The gateway only read mode from TOML, silently ignoring the env var. Operators following the docs ran in the wrong mode with no error. Fixed: VELDRA_GATEWAY_MODE now overlays the TOML value when set. Accepts inline, observe, or shadow. Invalid values fail startup with an explicit error.
Fee tier ordering validation
An operator could configure min_avg_fee_lo > min_avg_fee_mid > min_avg_fee_hi (inverted fee tiers) with no startup error. The verifier would run with contradictory thresholds, producing confusing accept/reject patterns. Fixed: PolicyConfig::validate() now enforces lo <= mid <= hi ordering and fails startup with a clear error message identifying which values are inverted.
Feed adapter backoff reduction
MAX_BACKOFF_SECS was 30, creating 40 to 50 second template gaps during sustained feed outages. Reduced to 10 seconds. Worst case gap between reconnection attempts is now approximately 15 seconds.
Verifier reconnect jitter
All gateways used a fixed-delay reconnect to the verifier. After a verifier restart, every gateway reconnected at the same instant (thundering herd). Fixed: hash-based jitter adds 0 to 50 percent of the base delay, spreading reconnection attempts across the window. The jitter seed is derived from the current instant, so each gateway instance picks a different offset.
Degraded policy mode logging
When policy.toml fails to parse, the verifier falls back to a built-in default policy that accepts all templates without fee enforcement. This critical operational state was logged at WARN. Upgraded to ERROR with an explicit impact description so operators and log alerting systems cannot miss it.
File descriptor limit check
sv2-gateway now reads /proc/self/limits at startup on Linux and warns when the soft FD limit is too low for the configured max_connections. At 10,000 miners the gateway needs 20,000+ file descriptors, but the default limit is 1024. The check uses safe filesystem reads only (no libc dependency, no unsafe blocks).
Dashboard poll interval sync
Policy state polled every 30 seconds while verdicts polled every 5 seconds. After a policy change, operators saw new rejections for up to 25 seconds before the policy display caught up. Policy poll interval reduced to 5 seconds to match verdict polling.
Stale-diff bug in dashboard save handlers
All four dashboard save/apply handlers (policy, verifier settings, gateway settings, template settings) shared a bug: they diffed local UI state against the most recently polled prop. If the user toggled a setting, saved, then toggled again before the prop refreshed, the diff compared against stale data and produced an empty or incorrect patch. The toggle appeared stuck. Fixed all four with a baselineRef pattern that tracks the last known server state independently of prop polling and advances after each successful save.
Website i18n completion
Added data-i18n attributes to 30+ previously untranslated elements across 14 site pages, including the Problem section (6 feature cards), SVG architecture diagram labels, Sign In buttons, Copy buttons, and the Architecture footer link. Added 32 new translation keys to es.json and zh.json. Renamed "Two paths to enforcement" to "Progressive deployment" across all three locales. Cache buster version bumped to v=6.
Unsafe code lint compliance
Workspace-level unsafe_code = "deny" blocked a libc::getrlimit call added for the FD limit check. Replaced with safe /proc/self/limits file read and removed the libc dependency entirely. This is the same class of error as the env::set_var incident in v1.0.2 config.rs (both resolved by finding safe alternatives to unsafe libc calls).
Documentation updates
README overhauled from the localhost-era format to reflect the shipping product: native desktop app as primary install path, rg-desktop in the architecture diagram, dual quickstart (desktop app vs Docker-only), signed updater and per-IP limit security notes. Deployment runbook, pitch prep, LinkedIn playbook, and three-mode architecture doc updated to v1.0.2.
False positives triaged
Two stress-test findings were triaged as already handled and required no changes:
- Pending templates HashMap unbounded: already bounded to 2 entries by FIFO eviction before each insert.
- Prometheus metric cardinality on reason_code: labels come from a bounded canonical enum (approximately 40 series maximum), not unbounded user input.
Known limitations
Unchanged from v1.0.1:
- Extended channels and vardiff remain deferred to v1.1 (PB-6)
- Rate limiter state is in-process only (shared state deferred to v1.1)
- Sustained load testing (hours) and two-host network latency benchmarks have not been performed
- Legacy DB-only key validation fallback in rg-auth will be removed in v1.1
- Automatic inline-to-observe degradation on verifier outage scoped for v1.1
Upgrade from v1.0.1
- No new environment variables required
VELDRA_GATEWAY_MODEenv var now works as documented (verify your docker-compose values match intended mode)- Review
policy.tomlfee tier ordering: startup will now rejectlo > midormid > hiconfigurations that were previously silently accepted - Desktop users: auto-update delivers v1.0.2 automatically if running v1.0.1+
See CHANGELOG.md for the complete list of changes.
ReserveGrid OS v1.0.1
ReserveGrid OS v1.0.1 — Temper
Hardening release following a full deep scan audit of the v1.0.0 codebase. 22 findings resolved across P0 (critical), P1 (important), and P2 (pre-pilot) priorities. Every finding has a compile verified fix and a DEVLOG entry. 453 tests pass, zero failures.
Audit scope
Two deep scan reports (2026-04-02) examined the full workspace for protocol safety gaps, async correctness issues, silent error drops, hardcoded configuration, race conditions, and missing test coverage. Findings were triaged into four priority tiers (P0 through P3). All P0, P1, and P2 items are resolved in this release. P3 items are scoped for v1.1.x.
License key signing
rg-auth now generates Ed25519 signed license keys in the veldra_lic_<base64url_payload>.<base64url_sig> format, replacing the legacy veldra_<hex> random keys. The payload embeds org, tier, expiry, and feature flags. Validation is a three step process: signature verification, expiry check, then DB revocation check. When the signing key is absent, rg-auth falls back to DB only validation for backward compatibility (removal planned for v1.1).
rg-feed-server validates keys offline using the Ed25519 public key, eliminating the runtime dependency on rg-auth for feed authentication. Tier gating enforces that only observe_paid and inline_licensed keys receive feed access.
rg-desktop embeds the public key at compile time via VELDRA_LICENSE_PUBKEY and persists the active license key to ~/.config/reservegrid/desktop.toml across restarts.
Protocol safety
SV2 frames carrying unsupported extension types are now rejected at all three dispatch points in the handler (SetupConnection, OpenMiningChannel, SubmitShares). Previously these were silently passed through, which could cause downstream parsing failures.
Codec decode failures now send a proper error frame to the peer before disconnecting. Three decode sites (SetupConnection, OpenStandardMiningChannel, SubmitSharesStandard) previously dropped the connection without notifying the client.
Six timing parameter cross-validation checks run at gateway startup to catch misconfigured timeout relationships (for example, heartbeat interval must be less than channel open timeout).
Async correctness
WAL writes and verdict log I/O are moved to spawn_blocking to avoid blocking the tokio executor on synchronous file operations. Nine silent try_send channel drops in the share handler now emit structured warnings with the channel name and drop count. Fifteen silent let _ = error drops across four crates (pool-verifier, sv2-gateway, rg-auth, verifier-stream) are replaced with triaged error handling: warnings for must-know failures, intentional ignores documented for benign cases.
Robustness
Health server bind failure is now non-fatal. If the metrics endpoint cannot bind (for example, port conflict), the gateway logs a warning and continues without the health endpoint rather than aborting.
The in-memory verdict log is now bounded at both push sites. Previously, only one of two push locations enforced the cap, allowing unbounded growth through the other path. The maximum is configurable via VELDRA_VERDICT_LOG_MAX_ENTRIES (default 1000).
TOCTOU races in WAL open and verdict log rotation are eliminated by replacing path.exists() guards with direct open and ENOENT handling.
SQLite integrity check (PRAGMA integrity_check) runs on rg-auth startup and aborts if the database is corrupt.
Configurability
Four previously hardcoded timeouts are now config fields in the gateway TOML:
| Field | Section | Default | Purpose |
|---|---|---|---|
reconnect_delay_ms |
[verifier] |
2000 | Delay between verifier reconnect attempts |
heartbeat_interval_ms |
[verifier] |
5000 | Verifier heartbeat send interval |
channel_open_timeout_ms |
[gateway] |
30000 | Maximum wait for mining channel open |
VELDRA_MEMPOOL_TIMEOUT_MS |
env var | 900 | Mempool HTTP client timeout |
Connection limits added to rg-feed-server: VELDRA_FEED_MAX_CONNECTIONS (default 256) and max_connections_per_ip default changed from 0 (unlimited) to 16.
Multi-gateway support
gateway_instance_id is included in the TemplatePropose protocol message, allowing the verifier to distinguish proposals from different gateway instances. The field is Option<String> with serde(default) for backward compatibility with older gateways. Verifier side filtering is available but not enforced by default (requires coordinated configuration).
Desktop auto-updater
The Tauri auto-updater is now fully configured. The updater signing keypair is generated, the public key is embedded in tauri.conf.json, and the release-desktop.yml workflow signs update bundles. Existing v1.0.0 installations must manually install v1.0.1 once (the v1.0.0 build shipped with an empty updater pubkey). After v1.0.1, all future releases auto-update.
Deployment
Fly.io deploy tooling simplified with scripts/fly-deploy.sh. The root Dockerfile is renamed to Dockerfile.gateway to prevent flyctl from auto-discovering it instead of the service specific Dockerfiles.
Production Ed25519 keypair generated for license signing. VELDRA_LICENSE_SIGNING_KEY is set on Fly (rg-auth), VELDRA_LICENSE_PUBKEY is set as a GitHub Actions secret and on the feed server.
Test results
453 tests across 12 crates, zero failures. 7 new tests added for the rg-auth email module covering SMTP config parsing, dev mode send, and all five email template body functions.
cargo test --workspace --exclude rg-desktop
Finished test profile
Running 453 tests
0 failures
Stale count correction
Reason code count corrected from "34" to "73" (15 VerdictReason + 58 GatewayReason) across 17 files including websites, i18n JSON, documentation, and the pitch deck. The count drifted when security hardening sessions added new GatewayReason variants without updating external materials.
New environment variables
| Variable | Service | Default | Purpose |
|---|---|---|---|
VELDRA_LICENSE_SIGNING_KEY |
rg-auth | (required) | Ed25519 seed for license key signing |
VELDRA_LICENSE_PUBKEY |
rg-feed-server, rg-desktop | (required) | Ed25519 pubkey for offline verification |
VELDRA_VERDICT_LOG_MAX_ENTRIES |
pool-verifier | 1000 | In-memory verdict log cap |
VELDRA_MEMPOOL_TIMEOUT_MS |
pool-verifier | 900 | Mempool HTTP client timeout |
VELDRA_FEED_MAX_CONNECTIONS |
rg-feed-server | 256 | Maximum concurrent feed connections |
Known limitations
Unchanged from v1.0.0:
- Extended channels and vardiff remain deferred to v1.1 (PB-6)
- Rate limiter state is in-process only (shared state deferred to v1.1)
- Sustained load testing (hours) and two-host network latency benchmarks have not been performed
- Legacy DB-only key validation fallback in rg-auth will be removed in v1.1
Upgrade from v1.0.0
- Set
VELDRA_LICENSE_SIGNING_KEYon rg-auth (Fly secret or env var) - Set
VELDRA_LICENSE_PUBKEYon rg-feed-server and as a GitHub Actions secret for desktop builds - Review new configurable timeouts in gateway TOML if using non-default values
- Desktop users: manually install v1.0.1 once to enable auto-update for future releases
See CHANGELOG.md for the complete list of changes.
ReserveGrid OS v1.0.0
ReserveGrid OS v1.0.0 — Forge
First stable release of ReserveGrid OS. Full template verification pipeline, three deployment modes, Stratum V2 gateway with Noise encryption, operator dashboard, and production security hardening across 14 workspace crates and 9 Docker services.
Scope
ReserveGrid OS sits between a mining pool's block template source (bitcoind or relay) and its Stratum V2 gateway. It verifies every candidate block template against a configurable policy before the template reaches miners, giving pool operators real time visibility into template quality and the ability to enforce standards on what their miners work on.
The system is designed for progressive deployment. Operators start in shadow mode (read only evaluation), move to observe mode (non enforcing with live pool data), and graduate to inline mode (full enforcement in the block construction pipeline).
Architecture
Core pipeline:
bitcoind → template-manager → pool-verifier → sv2-gateway → miners
Services
| Service | Role |
|---|---|
| pool-verifier | Template verification engine with configurable TOML policy |
| sv2-gateway | Stratum V2 gateway with Noise NX encryption and share lifecycle |
| template-manager | bitcoind RPC integration for block templates |
| rg-auth | Authentication, license keys, per endpoint rate limiting |
| rg-dashboard | Operator dashboard with embedded React frontend and API proxy |
| rg-demo-feed | Synthetic GBT generator for shadow mode evaluation |
| rg-feed-adapter | WebSocket to JSON RPC bridge |
| rg-feed-server | Authenticated template feed relay for observe mode |
| rg-load-test | HTTP load generator for benchmarking |
Libraries
rg-protocol— shared wire types,TemplatePropose/TemplateVerdict, and canonical reason codesreservegrid-common— reason code registry, config parsing, CSV/NDJSON/Prometheus exportssv2-bridge— Stratum V2 protocol bridge with Noise NX handshakereservegrid-gateway— legacy gateway (superseded by sv2-gateway in v1.0.0)
Deployment modes
| Mode | Risk | Data Source | Enforcement |
|---|---|---|---|
| Shadow | None | Synthetic (rg-demo-feed) | Read only |
| Observe | None | Live pool (rg-feed-server) | Logging only |
| Inline | Production | bitcoind (direct) | Full enforcement |
Shadow mode runs entirely offline against synthetic templates generated by rg-demo-feed. Observe mode connects to a live pool's template stream through rg-feed-server with HMAC authentication but never touches the block construction path. Inline mode sits directly in the pipeline between template-manager and sv2-gateway and can reject templates before they reach miners.
Stratum V2 gateway
Connection lifecycle
Miners connect over TCP with Noise NX encryption negotiated on the first handshake. Each connection opens a standard mining channel. The gateway validates share submissions against the current job, enforces per channel rate limits, and forwards accepted shares upstream.
Share verification
Every submitted share is checked for nonce uniqueness (HMAC replay protection), job freshness, difficulty target, and version mask compliance. Invalid shares receive a structured rejection with a canonical reason_code and human readable reason_detail.
Rate limiting
- Per channel token bucket for share submission rate
- Per IP TCP accept rate limiting at the listener level
- Both configurable via environment variables
Reason codes
19 gateway share and connection reason codes covering: share_accepted, share_rejected_low_difficulty, share_rejected_stale_job, share_rejected_duplicate_nonce, share_rejected_invalid_version_mask, connection_rejected_rate_limit, and others. All codes are canonical snake_case and stable across Prometheus labels and structured logs.
Template verification
Policy engine
The pool-verifier evaluates every TemplatePropose against a flat TOML policy. 15 verdict reason codes cover economic checks (fee delta, tx count, coinbase value), structural checks (version, prevhash, merkle root), and consensus safety checks (weight ratio, template staleness, sigops budget).
Evaluation order
Safety checks run after the 8 economic and structural checks in a fixed, deterministic order. evaluate_dynamic accepts an explicit now_ms: u64 parameter for testability. The production wrapper evaluate() calls now_unix_ms() internally, preserving existing call sites.
Enforced checks
weight_ratio_exceeded— activates when template weight divided by max block weight (4,000,000 WU) exceedssafety.max_weight_ratiotemplate_stale— activates when current time minuscreated_at_unix_msexceedssafety.max_template_age_ms- Each check gated by
safety.enforce_weight_ratioandsafety.enforce_template_agebooleans in policy TOML, both defaultfalse - When
enforce = false, structured warning log emitted with metric values but verdict stays accept
Observed checks (log and dashboard only)
- Sigops headroom: warns when
total_sigopsexceedssafety.warn_sigops_ratioof the 80,000 consensus limit - Coinbase sigops anomaly: warns when
coinbase_sigopsexceedssafety.warn_coinbase_sigops_max - Both thresholds configurable under
[policy.safety] - Warnings carry full policy context for traceability
New environment variables
VELDRA_TLS_CERT— path to PEM certificate fileVELDRA_TLS_KEY— path to PEM private key fileVELDRA_TLS_SELF_SIGNED— set to1to auto-generate a self-signed cert for devVELDRA_API_KEY— bearer token required for non-health endpoints (localhost bypasses)VELDRA_POLICY_FILE— path to policy TOML file
Verification and observability
15 template verdict reason codes and 19 gateway share/connection reason codes. All canonical snake_case, stable across protocol, verifier, Prometheus labels, NDJSON WAL, CSV exports, and dashboard queries.
Every rejection is 100% traceable: machine readable reason_code plus human readable reason_detail, with full policy context in structured logs.
Exports
- Prometheus metrics with
reason_codelabels on all verdict and share counters - NDJSON event stream (crash durable write ahead log)
- CSV verdict history via
/verdicts.csv - Embedded dashboard with 3 second live polling
Readiness probes
/ready returns 200 when the verifier is operationally healthy, 503 when degraded. Response is JSON:
{
"ready": true,
"policy_loaded": true,
"mempool_reachable": true,
"mempool_last_ok_age_secs": 2
}/health remains a simple liveness probe returning "ok" unconditionally. Both endpoints bypass API key authentication.
Dashboard
Built-in operator dashboard
rg-dashboard serves an embedded React frontend proxied behind the same auth layer as the API. Live verdict stream, reason code aggregates with collapsible drill-down, consensus safety panel (weight utilization gauge, sigops headroom bar, template age, coinbase sigops), and tier cards for mempool context.
Observe mode public dashboard
The public observe mode dashboard (veldra-site-v3) reaches full feature parity with the built-in dashboard. Enforcement mode badges, mempool context bar, CSV/NDJSON export links, and stale data overlay across all panels. Auth bypass restricted to localhost only.
Security
- Noise NX encryption on all miner connections (Stratum V2)
- Native TLS via
VELDRA_TLS_CERTandVELDRA_TLS_KEYenvironment variables (rustls) - Self-signed cert generation for dev via
VELDRA_TLS_SELF_SIGNED=1 - mTLS support for remote verifier channel
- HMAC nonce replay protection on share submissions
- Per channel share rate limiting (token bucket)
- Per IP TCP accept rate limiting
- Per endpoint auth rate limiting (sliding window, fail closed)
- API key authentication via
VELDRA_API_KEY(Bearer token, localhost bypasses) - Argon2id password hashing for rg-auth accounts
- Constant time API key comparison
- CORS wildcard hard error at startup
- SQL parameterization enforced (no string interpolation with user input)
unsafe_code = "deny"across entire workspace
Policy configuration
All verification thresholds configurable via a flat TOML [policy] table. 18 policy keys across [policy] and [policy.safety]. No code changes required to adjust thresholds. Switch policy files by setting the VELDRA_POLICY_FILE environment variable.
Policy TOML keys under [policy.safety]
max_weight_ratio— float, default0.999max_template_age_ms— maximum acceptable template age in millisecondsenforce_weight_ratio— boolean, defaultfalseenforce_template_age— boolean, defaultfalsewarn_sigops_ratio— float, default0.95(warn at 95% of consensus limit)warn_coinbase_sigops_max— integer, default400
Policy profiles included
policy-strict.toml— all enforcement enabled, strict thresholdsdemo-open-policy.toml— permissive policy for demo/evaluationdemo-showcase-policy.toml— balanced policy for live demonstrationsdemo-strict-policy.toml— aggressive policy for rejection showcase
Authentication (rg-auth)
Standalone auth service with Argon2id password hashing, JWT session tokens, license key validation, and per endpoint sliding window rate limiting that fails closed on threshold breach. Supports account creation, login, token refresh, and key activation flows.
CI and quality
Full GitHub Actions matrix: build, test, clippy pedantic, rustfmt, cargo audit, cargo deny, cargo vet, gitleaks secrets scan. Integration test suites for all three deployment modes.
| Suite | Tests |
|---|---|
| Inline mode smoke | 14 |
| Observe mode smoke | 9 |
| Auth lifecycle | 20 |
| Endpoint contracts | 22 |
| Shadow integration | CI job |
Performance
Release build benchmarks on single machine loopback (Docker Compose):
| Scenario | Connections | TPS | Avg | p99 | Max |
|---|---|---|---|---|---|
| Baseline valid | 10 | 100 | 5.34ms | 15... |
ReserveGrid OS v0.3.0
ReserveGrid OS v0.3.0 — Fortify
Production security boundary, legal framework, and operational readiness probes. This release closes the two remaining pool operator concerns (verifier HTTP auth/TLS and health check depth) and adds the legal pages required before pilot onboarding.
Scope
Three workstreams: HTTP security hardening for pool-verifier, readiness probes for operational monitoring, and legal/compliance pages on the public website. No changes to the policy evaluator, protocol, or template-manager.
pool-verifier HTTP security
TLS termination
Pool-verifier now supports native TLS via VELDRA_TLS_CERT and VELDRA_TLS_KEY environment variables pointing to PEM files. When both are set, the HTTP server binds with rustls and serves HTTPS directly.
For development and testing, set VELDRA_TLS_SELF_SIGNED=1 to auto-generate a self-signed certificate on startup. The self-signed path logs a warning at startup and is not intended for production.
When neither TLS env var is set, the server starts in plaintext HTTP mode (existing behavior).
API key authentication
Set VELDRA_API_KEY to require a Bearer token on all HTTP endpoints except /health and /ready. Requests from localhost (127.0.0.1, ::1) bypass authentication regardless of configuration, preserving dev ergonomics.
When VELDRA_API_KEY is not set, all endpoints remain open (existing behavior). Unauthorized requests receive 401 with missing or invalid api key.
New environment variables
VELDRA_TLS_CERT— path to PEM certificate fileVELDRA_TLS_KEY— path to PEM private key fileVELDRA_TLS_SELF_SIGNED— set to1to auto-generate a self-signed cert for devVELDRA_API_KEY— bearer token required for non-health endpoints (localhost bypasses)
Readiness probes
/ready endpoint
New /ready endpoint returns 200 when the verifier is operationally healthy, 503 when degraded. Response is JSON with four fields:
{
"ready": true,
"policy_loaded": true,
"mempool_reachable": true,
"mempool_last_ok_age_secs": 2
}Readiness requires both policy_loaded (policy parsed from file at startup, not degraded built-in default) and mempool_reachable (last successful mempool fetch within 30 seconds).
/health unchanged
/health remains a simple liveness probe returning "ok" unconditionally. Both /health and /ready bypass API key authentication.
Website legal pages
Privacy policy (/privacy/)
Covers rg-auth account data (name, email, org, Argon2id-hashed password), session tokens, planned analytics, and operational data boundaries. States that operator template data stays on operator infrastructure.
Terms of service (/terms/)
Governs observe mode access, acceptable use, account responsibilities, IP protection, limitation of liability, indemnification, and California governing law.
Disclaimer (/disclaimer/)
Software provided as-is, not financial advice, mining risk acknowledgment, no guarantee of correctness, consensus safety checks are informational only, operator responsibility for deployment security.
Site integration
- Legal footer section (Privacy, Terms, Disclaimer links) added to all 8 pages
- Sitemap updated with /observe/, /privacy/, /terms/, /disclaimer/
- GitHub source links removed from about, docs, and product pages
- Source-available section replaced with architecture overview and observe mode CTA
Dependencies
axum-server0.7 withtls-rustlsfeature (TLS termination)rcgen0.13 (self-signed certificate generation)
Test matrix
Existing tests unaffected. New functionality is infrastructure-level (TLS, middleware, readiness probe) tested via integration.
Upgrade notes
No breaking changes. All new environment variables are optional. Existing deployments continue to work with plaintext HTTP and open access. To enable security:
- Set
VELDRA_TLS_CERTandVELDRA_TLS_KEYfor TLS - Set
VELDRA_API_KEYfor endpoint authentication - Monitor
/readyfor operational health
ReserveGrid OS v0.2.2
ReserveGrid OS v0.2.2 — Foresight
Scope
v0.2.2 introduces consensus safety primitives into the policy layer, grounded in real world mining pool failure modes observed across the last two years. Weight ratio enforcement and template staleness checks activate using fields already present in the protocol. Sigops and coinbase accounting fields land as optional forward compatible additions with observe only logging, no enforcement until 0.2.3. All new checks default to observe only mode and require explicit operator opt in for rejection. The built-in dashboard ships a consensus safety panel and collapsible aggregate reason categories. The public observe mode dashboard reaches full feature parity with the built-in dashboard, adds enforcement mode badges, mempool context, CSV/NDJSON export links, and hardens the auth bypass to localhost only.
rg-protocol consensus safety fields
New optional fields on TemplatePropose
total_sigops: Option<u32>total signature operations across all transactions in the templatecoinbase_sigops: Option<u32>signature operations attributed to the coinbase transactiontemplate_weight: Option<u64>total serialized weight reported bygetblocktemplate- All three use
#[serde(default)]for backward compatibility with older senders - No protocol version bump required
New VerdictReason variants
WeightRatioExceeded(weight_ratio_exceeded) template weight exceedssafety.max_weight_ratioof consensus maxTemplateStale(template_stale) template age exceedssafety.max_template_age_msSigopsBudgetWarning(sigops_budget_warning) observe only, total sigops approaching consensus limitCoinbaseSigopsAbnormal(coinbase_sigops_abnormal) observe only, coinbase sigops outside expected reservation rangeVerdictReason::ALLandVerdictReason::ALL_CODESupdated, length assertion bumped from 11 to 15
Template manager GBT field extraction
Sigops and weight extraction
- Parses
sigopsandweightfrom each transaction entry in the GBT response - Sums total sigops across all non-coinbase transactions
coinbase_sigopsset toNonein 0.2.2 becausebitcoincore-rpc0.18.0 does not expose the coinbase transaction on the GBT result struct (tracked for 0.2.3 via raw JSON parsing or crate upgrade)- Populates
template_weightfrom per-transactionweightfield summation
Template timestamp
created_at_unix_msnow stamped on everyTemplateProposeat creation time- Enables downstream staleness detection without clock synchronization assumptions
Pool verifier policy enforcement
Enforced checks (opt in, default observe only)
weight_ratio_exceededactivates whenobserved_weightortemplate_weightdivided by max block weight (4,000,000 WU) exceedssafety.max_weight_ratiotemplate_staleactivates when current time minuscreated_at_unix_msexceedssafety.max_template_age_ms- Each check gated by
safety.enforce_weight_ratioandsafety.enforce_template_agebooleans in policy TOML, both defaultfalse - When
enforce = false, structured warning log emitted with metric values but verdict stays accept
Observed checks (log and dashboard only, no rejection path)
- Sigops headroom: warns when
total_sigopsexceedssafety.warn_sigops_ratioof the 80,000 consensus limit - Coinbase sigops anomaly: warns when
coinbase_sigopsexceedssafety.warn_coinbase_sigops_max - Both thresholds configurable under
[policy.safety] - Warnings carry full policy context for traceability
Policy TOML additions under [policy.safety]
max_weight_ratiofloat, default0.999max_template_age_msmaximum acceptable template age in millisecondsenforce_weight_ratioboolean, defaultfalseenforce_template_ageboolean, defaultfalsewarn_sigops_ratiofloat, default0.95(warn at 95% of consensus limit)warn_coinbase_sigops_maxinteger, default400
Deterministic evaluation
evaluate_dynamicaccepts explicitnow_ms: u64parameter for testabilityevaluate()wrapper callsnow_unix_ms()internally, preserving existing call sites- Safety checks run after existing 8 economic and structural checks in fixed order
CSV export completeness
/verdicts.csvheader and row formatting include all v0.2.2 fields:template_weight,total_sigops,coinbase_sigops,created_at_unix_ms,safety_warnings
Dashboard (built-in)
Consensus safety panel
- Weight utilization gauge showing template weight as percentage of 4,000,000 WU max
- Sigops headroom bar showing total sigops as percentage of 80,000 limit
- Template age display for most recent verdict
- Coinbase sigops reservation indicator
- Color thresholds: red at >99% weight, >95% sigops, >30s template age, >400 coinbase sigops
- All metrics live even in observe only mode
Aggregate reason categories rework
- Reason aggregates now display as collapsible categories sorted by count
- Clicking a category expands to show the last 15 matching verdicts with template id, block height, timestamp, and reason detail
- Overflow indicator shows count of remaining verdicts beyond the visible 15
- Accepted ("ok") category displayed as count only, not expandable
- Expand state persists across 3 second refresh cycles
- Chevron rotation animates via CSS transition
Observe mode (veldra-site-v3)
Full v0.2.2 feature parity with built-in dashboard
- Consensus safety panel with weight utilization gauge, sigops headroom gauge, template age, and coinbase sigops
- Collapsible aggregate reason categories with verdict drill-down (last 10 per reason code)
- Safety warning lines displayed on verdict rows
- Four new reason codes in the catalog table with Safety category badge (
weight_ratio_exceeded,template_stale,sigops_budget_warning,coinbase_sigops_abnormal) - Updated mock data and policy for demo mode with v0.2.2 fields
- "How It Works" flow updated to mention consensus safety checks
Enforcement mode badges
- Each safety gauge displays an "enforced" or "observe" badge pulled from the
/policyresponse - Weight ratio and template age reflect their
enforce_*flags - Sigops and coinbase always show "observe" in 0.2.2
Mempool context bar
- Displays live mempool tx count, memory usage percentage, and derived fee tier
- Tier derived from policy
low_mempool_txandhigh_mempool_txthresholds - Freshness indicator shows seconds since last mempool snapshot
- Hidden when mempool endpoint is unavailable (demo mode or disconnected)
CSV and NDJSON export links
- Export bar appears below the verdict stream when connected to a live backend
- Links point directly to
/verdicts.csvand/verdicts/logon the configured API endpoint - Hidden in demo mode since there is no backend to export from
Security hardening
?noauthbypass restricted tolocalhostand127.0.0.1only; ignored on production domains- Mixed content warning displayed when the page is served over https but the API URL uses http
Stale data overlay
- When data is older than 30 seconds, opacity dim now applies to all panels: stats grid, verdict stream, reason chart, safety panel, tier cards, and mempool bar
- Previously only the stats grid was affected
sv2-bridge compatibility
TemplateProposeconstruction updated withtotal_sigops: None,coinbase_sigops: None,template_weight: None- Synthetic bridge has no real consensus data; fields set to
Noneso the verifier knows values are unavailable
Repo policy files
- All four profiles updated with
[policy.safety]section:policy.toml,demo-showcase-policy.toml,demo-strict-policy.toml,demo-open-policy.toml - Strict profile enables enforcement:
enforce_weight_ratio = true,enforce_template_age = true, tighter thresholds (max_weight_ratio = 0.95,warn_sigops_ratio = 0.90) - Base and demo profiles default to observe only
Test matrix
weight_ratio_exceeded_enforcedweight_ratio_exceeded_observe_onlyweight_ratio_under_limit_no_warningtemplate_stale_enforcedtemplate_stale_observe_onlysigops_warning_fires_above_thresholdsigops_warning_silent_below_thresholdcoinbase_sigops_anomaly_detectionnew_fields_backward_compatible_serdeall_codes_match_as_str(updated for new variants)all_constant_covers_every_variant(length bumped to 15)
ReserveGrid OS v0.2.1
ReserveGrid OS v0.2.1 — Alignment
Scope
v0.2.1 is an alignment release ensuring reason codes, tier naming, and documentation are globally consistent across rg-protocol, pool-verifier, exports, dashboard, and website. No new features. Log rotation shipped in this release (see below). Auth/TLS and failover orchestration deferred to hardening release. Protocol version remains 2. Wire schema unchanged.
rg-protocol as single source of truth
Stringification moves to rg-protocol
- Add
VerdictReason::as_str() -> &'static strmethod returning canonical snake_case codes. - Add
VerdictReason::all_codes() -> &'static [&'static str]for test enumeration. - Delete
wire_reason_code_str()frompool-verifiermain.rs.
Tier naming canonicalized
- Fix
PolicyContext.fee_tiercomment:"low" | "mid" | "high"(was incorrectly"lo" | "mid" | "hi"). - No code change needed:
FeeTier::as_str()already returns correct values.
Protocol compatibility documented
- Add
PROTOCOL.mdor doc comment block inrg-protocolexplaining backward compatibility rules. - New fields use
#[serde(default)]and are backward-compatible. Old clients ignore unknown fields. - Protocol version bump only for breaking changes.
pool-verifier alignment
Remove normalization hack
- Delete
normalize_reason_key()function frommain.rs. - Stats aggregation uses
reason_codedirectly. No silent rewriting of legacy strings. - Legacy NDJSON lines without
reason_codefield aggregate under"unknown"bucket: explicit, not normalized. - Stats aggregation keys off reason_code directly from rg-protocol. Dashboard and CSV exports consume these codes without transformation.
Mapping hardened with tests
- Tests in rg-protocol verify every VerdictReason variant round-trips through
serdeandas_str(). VerdictReason::ALLconstant (11 variants) iterated exhaustively — adding a variant without updating ALL fails the test.VerdictReason::ALL_CODESconstant checked againstas_str()for each variant — no drift possible.
System errors at handler boundary
EvalResult(policy.rs) covers policy evaluation only. System errors are emitted at the TCP handler boundary inmain.rs, outside the policy evaluator.- System errors emitted in
main.rshandler code:- Policy load failure emits
PolicyLoadError. - Mempool required but unavailable with no fallback emits
MempoolBackendUnavailable. - Unexpected panic emits
InternalError.
- Policy load failure emits
- Degraded mode (
unknown_mempool_as_high = true) proceeds with high tier assumption: not an error, deliberate feature. - Poisoned policy lock recovery extracts config synchronously (
RwLockReadGuarddropped before any.await) and emitsPolicyLoadErrorwithcontinue— no double verdict.
Dashboard tier display
- Dashboard shows tier source indicator:
"mid (measured)"when tier derived from actual mempool tx count."high (fallback)"when tier derived fromunknown_mempool_as_highflag.
- No new
reason_codeemitted. Observability only.
Documentation alignment
beta-runbook.md
- Section 6.6: Remove fallback language. Stats use
reason_codedirectly. - Add section on empty mempool semantics:
- When mempool is empty, fee-based rules are vacuously satisfied (avg fee of zero txs is undefined, template passes).
- Use
reject_empty_templates = trueto block empty blocks. - This is policy choice, not a bug.
Website samples (veldra-site repo)
index.htmlsample verdict: Fix to match actualTemplateVerdictstruct.reason_code:"avg_fee_below_minimum"(was inventedMIN_AVG_FEE_TOO_LOW).policy_contextfields match actualPolicyContext.policy/index.htmlsample contract: Same fix.- Reason codes table: Add note that
policy_load_error,mempool_backend_unavailable,internal_errorare system-level codes (not policy evaluation results).
Hardening (implemented)
- Verdict log rotation: 50 MB cap per file, 5 rotations (
verdicts.log→verdicts.log.1 … .5). Checked before every append. - In-memory verdict buffer: 1000 entries max, oldest drained on overflow.
- CSV export: default 1000 rows, max 50,000. NDJSON tail: default 2000 lines, max 50,000.
- Mempool fetch timeout: 600ms hard timeout. HTTP client global timeout: 900ms.
Hardening backlog (documented, not implemented)
- The following operator feedback is acknowledged and tracked for a future hardening release.
Auth and TLS
- HTTP API authentication mechanism. TLS/HTTPS support for dashboard and exports.
- Credential hygiene: remove hardcoded regtest credentials from scripts, add production warning.
Service health linkage
- Health check coupling between template-manager, pool-verifier, and bitcoind.
- Configurable fail-open vs fail-closed behavior on upstream failure.
- Explicit error codes for upstream unavailability.
Test matrix
reason_mapping_exhaustive: EveryVerdictReasonhasas_str()coverage.eval_result_exhaustive: Every policy rejection path returns a validVerdictReasonvariant viaEvalResult.tier_naming_consistent:FeeTier::as_str()returns"low","mid","high"only.policy_context_tier_values:PolicyContext.fee_tieronly contains canonical tier names.stats_aggregation_no_normalization: Stats bucket keys match rawreason_codevalues.all_codes_match_as_str:VerdictReason::ALL_CODES[i] == VerdictReason::ALL[i].as_str()for alli.
Files changed
rg-protocol
src/lib.rs: AddVerdictReason::as_str(),VerdictReason::all_codes(), fix tier comment.
pool-verifier
src/policy.rs: Delete localVerdictReasonenum, addEvalResultstruct, add#[cfg(test)]mod tests (6 tests).src/main.rs: Deletewire_reason_code_str(), deletenormalize_reason_key(), addtier_sourcetoLoggedVerdict, add poisoned lock handler withPolicyLoadError, update CSV header.
docs
beta-runbook.md: Section 6.6 fix, empty mempool semantics section.
veldra-site (separate repo)
index.html: Fix sample verdict.policy/index.html: Fix sample contract, add system errors note.
ReserveGrid OS v0.2.0
ReserveGrid OS v0.2.0 — Credibility
Scope
v0.2.0 is a protocol-line release bundling pool-verifier hardening and policy correctness, rg-protocol alignment, and a completed template-manager. Protocol version is treated as an explicit compatibility boundary. No Arc<Mutex> fallback implementation in this release.
Pool verifier hardening and policy correctness
Runtime and filesystem
- Ensure
data/exists before any verdict log load or append (created inmain.rsprior to log operations).
HTTP export endpoints and UX
- Add bounded exports to prevent accidental huge responses:
/verdicts/log?tail=...hard capped at 50,000 lines/verdicts.csv?limit=...hard capped
- Document shell quoting requirements for query params in terminals such as
zsh. - CSV generation hygiene: avoid embedding
\ninside awriteln!format string to prevent double newlines.
Mempool client
- Reuse a single
reqwest::ClientviaOnceLockrather than creating a new client per request. - Centralize timeouts in the client builder and remove redundant per-request timeout code.
- Accept extra JSON fields (e.g.,
timestamp) via#[serde(default)]without breaking parsing. - Fix mempool tiering input: verifier now accepts
tx_countfrom template-manager/mempoolJSON (previously onlycount), preventing silent tier lock to low and restoring correct dynamic fee-tier behavior.
Policy engine correctness
- CoinbaseZero consistency: rejection is optional and relaxed:
- Reject
coinbase_value == 0only for non-empty templates and only whenreject_coinbase_zerois enabled.
- Reject
- Remove mismatch where
main.rsrejected coinbase zero unconditionally even when policy disabled it. - Ensure empty-template rejection runs before CoinbaseZero so the true reason is not masked.
Degraded mode for mempool dependency
- Remove the
unknown_mempool_tierTOML FeeTier idea (awkward deserialization, poor policy UX). - Add
unknown_mempool_as_high: bool(defaulttrue) as the degraded-mode knob. - Tier selection on mempool failure:
- Missing mempool selects High if
unknown_mempool_as_high = true, else Mid - No silent Low-tier fallback
- Missing mempool selects High if
Policy TOML format unification
- Standardize on canonical wrapper: a top-level
[policy]table with flat keys plus[policy.safety]. - Policy loader parses the wrapper consistently and rejects protocol mismatches (
policy.protocol_versionmust equalPROTOCOL_VERSION). - TOML loaders explicitly parse wrapped schemas (
[policy],[manager]) with clearer validation and protocol enforcement.
Init policy wizard
- “0 means unlimited” for
max_tx_count: convert0tou32::MAXso validation passes and semantics match the prompt. - Wizard writes canonical wrapper output (
[policy] ...) by serializing{ policy: cfg }. - Wizard writes to
config/policy.tomland ensuresconfig/exists. - Wizard includes toggles for
unknown_mempool_as_highandreject_coinbase_zero, and retainsreject_empty_templates.
State and logging hygiene
- Replace full
PolicyConfigdebug dumps with a safe concise summary. - Default policy text renders as real TOML, not debug output.
Dependency sanity
- Confirm
reqwestfeatures are correct (json,rustls-tls). - Track cleanup: avoid
tokiofull features and trim to only what is used.
100% traceable rejects and stats determinism
- Every reject yields a stable machine-readable
reason_codeplus humanreason_detail. LoggedVerdictcarriesreason_codeandreason_detail; legacyreasonis set to the stable code when available.- Stats bucketing prefers
reason_code, then falls back toreason, thenok, preventing splintered metrics. - TCP verdict path routes evaluation through a single policy evaluation function, mapping local reasons to rg-protocol wire reasons plus policy context.
Reason-code contract and docs alignment
- Canonical reason codes: standardize
TemplateVerdict.reason_codeto stablesnake_casealigned torg_protocol::VerdictReason; UI and exports preferreason_codeover legacyreason. - Policy UX: tiered floors (
min_avg_fee_lo/mid/hi) are the fee-floor inputs for dynamic tiers; examples/docs were updated accordingly.
rg-protocol alignment
- Protocol version updated for v0.2.0 and treated as an explicit compatibility boundary.
TemplateVerdictincludes stable reason coding and detail fields for operator-grade observability and downstream aggregation.- Evaluation mapping aligns verifier reasons to the wire representation with associated policy context.
Template manager (main.rs completed)
Async template sources
- Introduce async
TemplateSourceviaasync_trait. - Isolate bitcoind RPC calls with
spawn_blockingand use async backoff (tokio::time::sleep).
Stable template identity and dedupe
- Add
TemplateFingerprintbuilt from height, prevhash, tx count, total fees, and an order-independent txid hash. - Derive
template_idfrom the fingerprint via a stable hash, preventing duplicates across restarts and eliminating fake “new templates” from tx reordering.
Coinbase value correctness
- If
getblocktemplatereturnscoinbase_value == 0, template-manager computes a fallback as block subsidy + total fees and logs a warning to avoid downstream CoinbaseZero spam.
Hang prevention and bounded IO
- Add timeouts around verifier connect and the send/receive path.
- Bound writer write, newline write, and flush with timeouts.
- Bound verifier response read to prevent indefinite stalls.
Single instance lock
- Bind HTTP listener before starting the manager loop; if bind fails, exit immediately to prevent duplicate zombie managers.
HTTP observability
- Expose
/health,/templates,/mempool. - In-memory logs guarded by
RwLock, with template log capped at 500 entries.
Shared bitcoind RPC client
- Single shared
Arc<Client>built once and reused for template polling and mempool snapshots.
Stratum mode correctness and latency
- Stratum NDJSON parsing trims lines, skips empties, and logs the offending line on parse errors.
- Real async delivery uses
rx.recv().await. - Manager loop sleeps only on bitcoind backend; Stratum templates are not delayed by
poll_secs.
Dev UX and demo tooling
- Regtest reliability:
dev-regtest.shuses a dedicated datadir, kills only bitcoind instances bound to that datadir, waits for RPC readiness, fails-fast on port conflicts, and uses-namedRPC calls where applicable. - RPC single source of truth:
dev-bitcoin-cli.shis authoritative (includes demo datadir and RPC creds). - Deterministic demo loop:
dev-demo-phases.shadds health gating, balance checks, clearer phase labeling, and optional finite run control. - Traffic generator:
dev-traffic.shuses-named sendtoaddresswith explicitfee_rate, adds spendable balance checks, and exposes tuning knobs. - Stratum hygiene:
dev-stratum.shaligns with v0.2.0 policy env conventions, adds readiness gates, and fails-fast on port conflicts. - Documentation: update
docs/runbook to v0.2.0 (flat[policy]schema, env vars, regtest + Stratum workflows,reason_code/reason_detailsemantics). - Demo note: demo scripts space sends/mining to avoid ancestor-limit failures (
too many unconfirmed ancestors, error-6).
Repo policy files
- Consolidate policy TOMLs to the v0.2.0
[policy]schema and bumpprotocol_versionto match the compatibility boundary. - Refresh demo profiles (showcase/open/strict) to be schema-complete and aligned with updated demo scripts, including explicit
unknown_mempool_as_highandreject_coinbase_zerowhere applicable.