Skip to content

Releases: LeavesJ/ReserveGrid-OS

ReserveGrid OS v2.0.0-rc1

Pre-release

Choose a tag to compare

@LeavesJ LeavesJ released this 03 May 08:53

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...

Read more

ReserveGrid OS verify-only

Choose a tag to compare

@github-actions github-actions released this 28 Apr 09:29

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

Choose a tag to compare

@github-actions github-actions released this 13 Apr 03:12

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.

  1. HMAC body hash. Upstream services that verify gateway_signature_hex must 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.

  2. Tier rename. The observe_free tier string is replaced by shadow across the entire stack. External tooling matching on observe_free must 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

  1. Update upstream share verifiers to use the new HMAC body hash signature scheme before deploying the gateway (see deployment runbook)
  2. Update any external tooling that references the observe_free tier to use shadow
  3. Review new extended channel and vardiff config fields in gateway TOML
  4. Rebuild and deploy: docker compose build && docker compose up -d
  5. Verify health: curl -s http://localhost:8081/readyz | jq .

See CHANGELOG.md for the complete list of changes.

ReserveGrid OS v1.0.2

Choose a tag to compare

@github-actions github-actions released this 11 Apr 03:17

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

  1. No new environment variables required
  2. VELDRA_GATEWAY_MODE env var now works as documented (verify your docker-compose values match intended mode)
  3. Review policy.toml fee tier ordering: startup will now reject lo > mid or mid > hi configurations that were previously silently accepted
  4. 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

Choose a tag to compare

@LeavesJ LeavesJ released this 06 Apr 00:38

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

  1. Set VELDRA_LICENSE_SIGNING_KEY on rg-auth (Fly secret or env var)
  2. Set VELDRA_LICENSE_PUBKEY on rg-feed-server and as a GitHub Actions secret for desktop builds
  3. Review new configurable timeouts in gateway TOML if using non-default values
  4. 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

Choose a tag to compare

@LeavesJ LeavesJ released this 12 Mar 08:45

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 codes
  • reservegrid-common — reason code registry, config parsing, CSV/NDJSON/Prometheus exports
  • sv2-bridge — Stratum V2 protocol bridge with Noise NX handshake
  • reservegrid-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) exceeds safety.max_weight_ratio
  • template_stale — activates when current time minus created_at_unix_ms exceeds safety.max_template_age_ms
  • Each check gated by safety.enforce_weight_ratio and safety.enforce_template_age booleans in policy TOML, both default false
  • 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_sigops exceeds safety.warn_sigops_ratio of the 80,000 consensus limit
  • Coinbase sigops anomaly: warns when coinbase_sigops exceeds safety.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 file
  • VELDRA_TLS_KEY — path to PEM private key file
  • VELDRA_TLS_SELF_SIGNED — set to 1 to auto-generate a self-signed cert for dev
  • VELDRA_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_code labels 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_CERT and VELDRA_TLS_KEY environment 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, default 0.999
  • max_template_age_ms — maximum acceptable template age in milliseconds
  • enforce_weight_ratio — boolean, default false
  • enforce_template_age — boolean, default false
  • warn_sigops_ratio — float, default 0.95 (warn at 95% of consensus limit)
  • warn_coinbase_sigops_max — integer, default 400

Policy profiles included

  • policy-strict.toml — all enforcement enabled, strict thresholds
  • demo-open-policy.toml — permissive policy for demo/evaluation
  • demo-showcase-policy.toml — balanced policy for live demonstrations
  • demo-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...
Read more

ReserveGrid OS v0.3.0

Choose a tag to compare

@LeavesJ LeavesJ released this 23 Feb 03:42

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 file
  • VELDRA_TLS_KEY — path to PEM private key file
  • VELDRA_TLS_SELF_SIGNED — set to 1 to auto-generate a self-signed cert for dev
  • VELDRA_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-server 0.7 with tls-rustls feature (TLS termination)
  • rcgen 0.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:

  1. Set VELDRA_TLS_CERT and VELDRA_TLS_KEY for TLS
  2. Set VELDRA_API_KEY for endpoint authentication
  3. Monitor /ready for operational health

ReserveGrid OS v0.2.2

Choose a tag to compare

@LeavesJ LeavesJ released this 23 Feb 03:55

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 template
  • coinbase_sigops: Option<u32> signature operations attributed to the coinbase transaction
  • template_weight: Option<u64> total serialized weight reported by getblocktemplate
  • 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 exceeds safety.max_weight_ratio of consensus max
  • TemplateStale (template_stale) template age exceeds safety.max_template_age_ms
  • SigopsBudgetWarning (sigops_budget_warning) observe only, total sigops approaching consensus limit
  • CoinbaseSigopsAbnormal (coinbase_sigops_abnormal) observe only, coinbase sigops outside expected reservation range
  • VerdictReason::ALL and VerdictReason::ALL_CODES updated, length assertion bumped from 11 to 15

Template manager GBT field extraction

Sigops and weight extraction

  • Parses sigops and weight from each transaction entry in the GBT response
  • Sums total sigops across all non-coinbase transactions
  • coinbase_sigops set to None in 0.2.2 because bitcoincore-rpc 0.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_weight from per-transaction weight field summation

Template timestamp

  • created_at_unix_ms now stamped on every TemplatePropose at creation time
  • Enables downstream staleness detection without clock synchronization assumptions

Pool verifier policy enforcement

Enforced checks (opt in, default observe only)

  • weight_ratio_exceeded activates when observed_weight or template_weight divided by max block weight (4,000,000 WU) exceeds safety.max_weight_ratio
  • template_stale activates when current time minus created_at_unix_ms exceeds safety.max_template_age_ms
  • Each check gated by safety.enforce_weight_ratio and safety.enforce_template_age booleans in policy TOML, both default false
  • 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_sigops exceeds safety.warn_sigops_ratio of the 80,000 consensus limit
  • Coinbase sigops anomaly: warns when coinbase_sigops exceeds safety.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_ratio float, default 0.999
  • max_template_age_ms maximum acceptable template age in milliseconds
  • enforce_weight_ratio boolean, default false
  • enforce_template_age boolean, default false
  • warn_sigops_ratio float, default 0.95 (warn at 95% of consensus limit)
  • warn_coinbase_sigops_max integer, default 400

Deterministic evaluation

  • evaluate_dynamic accepts explicit now_ms: u64 parameter for testability
  • evaluate() wrapper calls now_unix_ms() internally, preserving existing call sites
  • Safety checks run after existing 8 economic and structural checks in fixed order

CSV export completeness

  • /verdicts.csv header 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 /policy response
  • 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_tx and high_mempool_tx thresholds
  • 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.csv and /verdicts/log on the configured API endpoint
  • Hidden in demo mode since there is no backend to export from

Security hardening

  • ?noauth bypass restricted to localhost and 127.0.0.1 only; 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

  • TemplatePropose construction updated with total_sigops: None, coinbase_sigops: None, template_weight: None
  • Synthetic bridge has no real consensus data; fields set to None so 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_enforced
  • weight_ratio_exceeded_observe_only
  • weight_ratio_under_limit_no_warning
  • template_stale_enforced
  • template_stale_observe_only
  • sigops_warning_fires_above_threshold
  • sigops_warning_silent_below_threshold
  • coinbase_sigops_anomaly_detection
  • new_fields_backward_compatible_serde
  • all_codes_match_as_str (updated for new variants)
  • all_constant_covers_every_variant (length bumped to 15)

ReserveGrid OS v0.2.1

Choose a tag to compare

@LeavesJ LeavesJ released this 23 Feb 03:42

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 str method returning canonical snake_case codes.
  • Add VerdictReason::all_codes() -> &'static [&'static str] for test enumeration.
  • Delete wire_reason_code_str() from pool-verifier main.rs.

Tier naming canonicalized

  • Fix PolicyContext.fee_tier comment: "low" | "mid" | "high" (was incorrectly "lo" | "mid" | "hi").
  • No code change needed: FeeTier::as_str() already returns correct values.

Protocol compatibility documented

  • Add PROTOCOL.md or doc comment block in rg-protocol explaining 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 from main.rs.
  • Stats aggregation uses reason_code directly. No silent rewriting of legacy strings.
  • Legacy NDJSON lines without reason_code field 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 serde and as_str().
  • VerdictReason::ALL constant (11 variants) iterated exhaustively — adding a variant without updating ALL fails the test.
  • VerdictReason::ALL_CODES constant checked against as_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 in main.rs, outside the policy evaluator.
  • System errors emitted in main.rs handler code:
    • Policy load failure emits PolicyLoadError.
    • Mempool required but unavailable with no fallback emits MempoolBackendUnavailable.
    • Unexpected panic emits InternalError.
  • Degraded mode (unknown_mempool_as_high = true) proceeds with high tier assumption: not an error, deliberate feature.
  • Poisoned policy lock recovery extracts config synchronously (RwLockReadGuard dropped before any .await) and emits PolicyLoadError with continue — 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 from unknown_mempool_as_high flag.
  • No new reason_code emitted. Observability only.

Documentation alignment

beta-runbook.md

  • Section 6.6: Remove fallback language. Stats use reason_code directly.
  • 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 = true to block empty blocks.
    • This is policy choice, not a bug.

Website samples (veldra-site repo)

  • index.html sample verdict: Fix to match actual TemplateVerdict struct.
  • reason_code: "avg_fee_below_minimum" (was invented MIN_AVG_FEE_TOO_LOW).
  • policy_context fields match actual PolicyContext.
  • policy/index.html sample contract: Same fix.
  • Reason codes table: Add note that policy_load_error, mempool_backend_unavailable, internal_error are system-level codes (not policy evaluation results).

Hardening (implemented)

  • Verdict log rotation: 50 MB cap per file, 5 rotations (verdicts.logverdicts.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: Every VerdictReason has as_str() coverage.
  • eval_result_exhaustive: Every policy rejection path returns a valid VerdictReason variant via EvalResult.
  • tier_naming_consistent: FeeTier::as_str() returns "low", "mid", "high" only.
  • policy_context_tier_values: PolicyContext.fee_tier only contains canonical tier names.
  • stats_aggregation_no_normalization: Stats bucket keys match raw reason_code values.
  • all_codes_match_as_str: VerdictReason::ALL_CODES[i] == VerdictReason::ALL[i].as_str() for all i.

Files changed

rg-protocol

  • src/lib.rs: Add VerdictReason::as_str(), VerdictReason::all_codes(), fix tier comment.

pool-verifier

  • src/policy.rs: Delete local VerdictReason enum, add EvalResult struct, add #[cfg(test)] mod tests (6 tests).
  • src/main.rs: Delete wire_reason_code_str(), delete normalize_reason_key(), add tier_source to LoggedVerdict, add poisoned lock handler with PolicyLoadError, 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

Choose a tag to compare

@LeavesJ LeavesJ released this 23 Feb 03:42

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 in main.rs prior 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 \n inside a writeln! format string to prevent double newlines.

Mempool client

  • Reuse a single reqwest::Client via OnceLock rather 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_count from template-manager /mempool JSON (previously only count), 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 == 0 only for non-empty templates and only when reject_coinbase_zero is enabled.
  • Remove mismatch where main.rs rejected 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_tier TOML FeeTier idea (awkward deserialization, poor policy UX).
  • Add unknown_mempool_as_high: bool (default true) 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

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_version must equal PROTOCOL_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: convert 0 to u32::MAX so validation passes and semantics match the prompt.
  • Wizard writes canonical wrapper output ([policy] ...) by serializing { policy: cfg }.
  • Wizard writes to config/policy.toml and ensures config/ exists.
  • Wizard includes toggles for unknown_mempool_as_high and reject_coinbase_zero, and retains reject_empty_templates.

State and logging hygiene

  • Replace full PolicyConfig debug dumps with a safe concise summary.
  • Default policy text renders as real TOML, not debug output.

Dependency sanity

  • Confirm reqwest features are correct (json, rustls-tls).
  • Track cleanup: avoid tokio full features and trim to only what is used.

100% traceable rejects and stats determinism

  • Every reject yields a stable machine-readable reason_code plus human reason_detail.
  • LoggedVerdict carries reason_code and reason_detail; legacy reason is set to the stable code when available.
  • Stats bucketing prefers reason_code, then falls back to reason, then ok, 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_code to stable snake_case aligned to rg_protocol::VerdictReason; UI and exports prefer reason_code over legacy reason.
  • 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.
  • TemplateVerdict includes 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 TemplateSource via async_trait.
  • Isolate bitcoind RPC calls with spawn_blocking and use async backoff (tokio::time::sleep).

Stable template identity and dedupe

  • Add TemplateFingerprint built from height, prevhash, tx count, total fees, and an order-independent txid hash.
  • Derive template_id from the fingerprint via a stable hash, preventing duplicates across restarts and eliminating fake “new templates” from tx reordering.

Coinbase value correctness

  • If getblocktemplate returns coinbase_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.sh uses a dedicated datadir, kills only bitcoind instances bound to that datadir, waits for RPC readiness, fails-fast on port conflicts, and uses -named RPC calls where applicable.
  • RPC single source of truth: dev-bitcoin-cli.sh is authoritative (includes demo datadir and RPC creds).
  • Deterministic demo loop: dev-demo-phases.sh adds health gating, balance checks, clearer phase labeling, and optional finite run control.
  • Traffic generator: dev-traffic.sh uses -named sendtoaddress with explicit fee_rate, adds spendable balance checks, and exposes tuning knobs.
  • Stratum hygiene: dev-stratum.sh aligns 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_detail semantics).
  • 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 bump protocol_version to 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_high and reject_coinbase_zero where applicable.