Skip to content

feat: perf & resilience (multi-pool failover, solo getblocktemplate, ctypes SHA-256)#7

Merged
devAsmodeus merged 5 commits intomainfrom
feat/perf-and-resilience
May 3, 2026
Merged

feat: perf & resilience (multi-pool failover, solo getblocktemplate, ctypes SHA-256)#7
devAsmodeus merged 5 commits intomainfrom
feat/perf-and-resilience

Conversation

@devAsmodeus
Copy link
Copy Markdown
Owner

Summary

PR B of the three-PR roadmap (docs/superpowers/specs/2026-05-02-three-pr-roadmap.md),
stacked on top of PR A (feat/ops-and-ux). Pure stdlib (ctypes +
urllib), no new pip dependencies. Previous handoff:
docs/handoff/pr-a-summary.md.

  • Multi-pool failover (pools.py, --pool repeatable). Round-robin
    rotation after N failures, exponential backoff once the full cycle
    has passed without a single success. StratumClient.set_endpoint()
    re-targets without recreating the client (preserves on_share_result,
    locks).
  • Solo mode (solo.py, --solo --rpc-url ... --rpc-cookie ...).
    SoloClient ducks StratumClient's surface so mine() works
    unchanged. getblocktemplate polling, BIP-34 height push, BIP-141
    witness commitment when default_witness_commitment is in the
    template, full block serialization, submitblock over JSON-RPC.
    Educational scope — coinbase scriptPubKey is OP_RETURN, real
    payout decode is left as a known TODO.
  • ctypes SHA-256 backend (sha_native.py, --sha-backend). Loads
    libcrypto-3.dll / libcrypto.so.3 / /opt/homebrew/lib/libcrypto.dylib
    via ctypes.CDLL, calls EVP API. No mid-state — sognately so, for
    honest benchmark comparison. Falls back to hashlib when libcrypto
    is missing.
  • --benchmark --backends runs all available backends back-to-back
    and prints [bench] result: ctypes X MH/s (Yx vs hashlib-midstate).

Hot path (_worker_hashlib_midstate) is byte-identical with v0.5.0 —
4-worker bench stays at ~3.18 MH/s. block.py endianness untouched.

Tests: 145 → 225 (+80).

Test plan

  • py -3.11 -m unittest discover -s tests -v — 225 OK
  • py -3.11 -m hope_hash --help — all new flags present
  • py -3.11 -m hope_hash --benchmark --bench-duration 1 --backends
    runs hashlib + ctypes, prints comparison
  • py -3.11 -m hope_hash --benchmark --bench-duration 3 --workers 4
    — hashrate matches v0.5.0 baseline
  • py -3.11 -m hope_hash --solo <addr> — fails fast with clear
    message about missing --rpc-url
  • No pip install performed; no third-party imports under src/hope_hash/

Files

  • New: src/hope_hash/{pools,sha_native,solo}.py,
    tests/test_{pools,sha_native,solo,bench_backends}.py,
    docs/handoff/pr-b-summary.md
  • Modified: src/hope_hash/{cli,miner,parallel,stratum,bench,tui,__init__}.py,
    CHANGELOG.md, README.md, ROADMAP.md

See docs/handoff/pr-b-summary.md for full file map, gotchas,
and open questions for PR C.

Base automatically changed from feat/ops-and-ux to main May 2, 2026 19:34
@devAsmodeus devAsmodeus marked this pull request as ready for review May 2, 2026 19:35
devAsmodeus and others added 5 commits May 2, 2026 22:35
Adds src/hope_hash/sha_native.py: loads libcrypto-3.dll /
libcrypto.so.3 / libcrypto.dylib via ctypes.CDLL, exposes
sha256() / sha256d() / is_available() / BACKEND_NAME via the
EVP API. Falls back to hashlib if libcrypto is not found.

parallel.worker now dispatches to _worker_hashlib_midstate()
(unchanged hot path) or _worker_ctypes() (sha256d per iteration,
no mid-state — honest baseline for benchmark comparison).
start_pool() takes sha_backend kwarg.

bench.run_benchmark() takes sha_backend and print_header.
New run_benchmark_all_backends() + available_backends() drive
the --backends comparison; final line "[bench] result: ctypes
X MH/s (Yx vs hashlib-midstate)" is grep-friendly.

Verified hot path unchanged: 4-worker hashlib-midstate stays at
~3.18 MH/s (same order as v0.5.0). ctypes is ~0.3x because no
mid-state — sognately so.

21 new tests; total 145 → 166.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New module src/hope_hash/pools.py: PoolList holds endpoints,
tracks per-pool failure counts, rotates on threshold, knows
when a full cycle has passed without a single success
(full_cycle_failed). parse_pool_spec() handles host/host:port.
Dedup is case-insensitive on host.

stratum.StratumClient.set_endpoint(host, port) re-targets the
client without recreating it: on_share_result, stop_event,
suggest_diff, username and locks are preserved. This is what
makes failover seamless inside mine().

miner.supervisor_loop now accepts pools: Optional[PoolList] and
stats_provider: Optional[StatsProvider]. On a failed connect we
mark_failed(); after rotation we set_endpoint() and continue.
After full_cycle_failed we apply the existing exponential backoff
once and reset_round(). When connect succeeds we mark_success().

tui.StatsProvider.update_pool(url) lets the supervisor push the
active pool name through the canonical data bus so the TUI shows
the live target.

24 new tests for PoolList: rotation at threshold, wrap-around,
single-pool no-op, mark_success reset, full_cycle_failed,
no-deadlock smoke. Total 166 → 190.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New module src/hope_hash/solo.py implementing solo mining
against a local bitcoind:

- BitcoinRPC: minimal stdlib urllib JSON-RPC client. Auth via
  cookie file (default for local) or username/password. Cookie
  wins if both are provided.
- SoloClient: duck-types StratumClient's public surface
  (current_job, job_lock, extranonce1, extranonce2_size,
  difficulty, submit, on_share_result, connect,
  subscribe_and_authorize, reader_loop, close, host/port/sock)
  so mine() runs unchanged. reader_loop polls getblocktemplate
  every poll_interval_s and updates current_job on prevhash
  change.
- build_coinbase: serializes a coinbase tx with BIP-34 height
  push, optional extranonce push, and optional BIP-141 witness
  commitment output (OP_RETURN OP_PUSH36 0xaa21a9ed <hash>).
- compute_witness_commitment + parse_default_witness_commitment
  handle both directions: building from a witness root, or
  extracting the 32-byte hash from bitcoind's pre-built script.
- serialize_block + compute_merkle_root_from_txids round out
  the surface needed for submitblock.

scriptPubKey for the coinbase output is OP_RETURN — sognately
unspendable. Real solo would need bech32/base58 decode to a
P2WPKH/P2PKH script; that's noted in CLAUDE.md-style docstring
as a known simplification (chance of finding a block ≈ 0).

35 tests using FakeRPC, including SoloClient.connect/submit
success+reject paths, varint/push/height edge cases, witness
commitment parity vs double_sha256. Total 190 → 225.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds CLI flags for the new perf/resilience features:

- --pool HOST:PORT (repeatable) drives PoolList, --rotate-after-failures
  configures the threshold. When --pool is provided the legacy default
  CKPool is replaced.
- --sha-backend {auto,hashlib,ctypes} (default auto). auto picks
  ctypes when libcrypto loads, hashlib otherwise. The choice flows
  through mine() → start_pool() → worker.
- --backends turns --benchmark into a comparative run across all
  available backends.
- --solo + --rpc-url + (--rpc-cookie | --rpc-user/--rpc-pass) +
  --solo-poll-sec switch the supervisor to SoloClient with the
  same surface, so mine() works unchanged.

main() splits into three branches: bench, solo, and stratum-pool.
The supervisor thread is constructed with the right kwargs for each
case (pools=PoolList for stratum, plain for solo).

__init__.py bumps __version__ to 0.6.0 and re-exports the new
public symbols (PoolList, SoloClient, BitcoinRPC, build_coinbase,
sha_native module, run_benchmark_all_backends, etc.).

No behavior change for existing flags. All 225 tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the v0.6.0 section to CHANGELOG and README, ticks off
multi-pool failover, getblocktemplate solo mode, and the
ctypes SHA-256 wrapper in ROADMAP. Records what shipped, the
file map, new CLI flags, gotchas, and open questions for PR C
in docs/handoff/pr-b-summary.md.

README is intentionally only appended to (the v0.6.0 paragraph) —
the full bilingual rewrite is PR C scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@devAsmodeus devAsmodeus force-pushed the feat/perf-and-resilience branch from 0b8b85a to 660ab26 Compare May 2, 2026 19:36
@devAsmodeus devAsmodeus merged commit d755854 into main May 3, 2026
10 checks passed
@devAsmodeus devAsmodeus deleted the feat/perf-and-resilience branch May 3, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant