Skip to content

stratum: mining-hotel interim fix (FIFO+TTL eviction, shared work-template, notify debounce, RLIMIT, strict connection cap)#721

Merged
frstrtr merged 6 commits into
masterfrom
fix/stratum-hotel-interim-cap
Jul 16, 2026
Merged

stratum: mining-hotel interim fix (FIFO+TTL eviction, shared work-template, notify debounce, RLIMIT, strict connection cap)#721
frstrtr merged 6 commits into
masterfrom
fix/stratum-hotel-interim-cap

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 16, 2026

Copy link
Copy Markdown
Owner

DASH mining-hotel interim P0 — minimal c2pool stratum hardening for many concurrent low-diff hotel miners. Scope is deliberately small (no #57 v3 redesign); adds a strict connection cap as the backstop.

Changes (6 commits, 12 files):

  • stratum: FIFO + TTL job eviction, shared per-generation work payload (one template reused across miners), strict --max-stratum-connections cap (default 100).
  • web: trigger_work_refresh_debounced — leading-edge + 300ms trailing coalesce to collapse notify storms.
  • startup: raise RLIMIT_NOFILE to 65536; wire --max-stratum-connections config.
  • test: interim KATs — cap enforcement, stale-storm FIFO eviction, flat-RSS payload identity, DOA/stale-rate gate.
  • ci: test_stratum_hotel_interim added to both build.yml --target allowlists.

KAT targets: cap-enforcement, stale-storm/FIFO, flat-RSS, DOA/stale-rate gate (pre-fix ~92% -> post-fix single-digit), validated DASH byte-parity vs p2pool-dash (v16..36), not v36-uniform.

Note: this is the first full compile of the branch under CI. Any compile-error red will be iterated on this branch. Do not merge until the full CI rollup is green; merge is the integrator tap (no self-merge). Branch is 2 commits behind master; can rebase on request.

frstrtr added 6 commits July 16, 2026 20:13
… miner cap

Mining-hotel interim fixes #1/#2/#5 — memory/eviction/admission only,
zero wire-byte change for admitted sessions. Single-threaded; no new
threads, no NodeSnapshot.

Fix #1 — FIFO + 300s-TTL job eviction:
  active_jobs_ capacity eviction was erase(active_jobs_.begin()) on an
  unordered_map: an ARBITRARY entry — possibly the job the miner is
  currently hashing — was dropped, producing nondeterministic
  stale-rejects under notify storms. Add job_order_ (insertion-order
  deque) + a steady-clock created_at stamp per JobEntry: eviction is now
  genuinely-oldest-first (FIFO, MAX_ACTIVE_JOBS=256 kept) plus a 300 s
  TTL sweep on insert mirroring p2pool ExpiringDict semantics. The most
  recently issued job (deque back) is never evicted.

Fix #2 — one shared work template per generation:
  Fold the heavyweight per-job copies — coinb1/coinb2 (coinb2 carries
  the PPLNS outputs) + merkle branch vectors + frozen merged coinbase
  info — into one refcounted SharedJobPayload shared across all jobs of
  a work generation (tx_data was already shared). Reuse is gated on a
  full byte-compare (same generation AND identical bytes), so any real
  change allocates a fresh payload and the bytes a miner receives are
  identical to the pre-fix per-job copies. handle_submit reads via
  job.payload->…; the job copy is cheap and the payload stays alive
  across eviction mid-processing.

Fix #5 — STRICT per-node connection cap:
  handle_accept checks sessions_.size() under sessions_mutex_ against
  the new StratumConfig::max_stratum_connections (default 100, 0 =
  unlimited) BEFORE register_session. At cap: prune dead sessions,
  re-check, then cleanly shutdown+close the excess socket, WARN log,
  bump the refused_connections counter — and keep accepting.

Diagnostics for the KATs/dashboard: StratumSession::active_job_count /
distinct_job_payloads, StratumServer::get_refused_connections /
get_session_count / get_job_payload_stats.
…railing coalesce)

Mining-hotel interim fix #3. The debounced entry point was a no-op stub
that called refresh immediately, so share-arrival storms fanned out into
one full refresh_work() + notify_all() per share.

Implemented semantics (timing/coalescing only — the bytes of any notify
that IS sent are unchanged):
* Leading edge: a call outside the 300 ms window refreshes immediately
  (first share after quiet keeps p2pool-grade latency).
* Trailing coalesce: calls inside the window collapse into ONE deferred
  refresh at window end, event-gated on a REAL work change (sharechain
  tip differs from the last executed refresh), so redundant bursts cost
  nothing.
* New-block path untouched: trigger_work_refresh() stays immediate.

State (m_last_work_refresh / m_last_refresh_tip_hash / pending flag +
timer) is only touched from the main ioc_ thread — callers and the
timer handler run there. No new threads, no extra locking.
… config

Mining-hotel interim fix #4 + the config plumbing for fix #5.

* core::raise_nofile_limit(target=65536): shared helper in the
  dependency-free core_util leaf (log-free by design — callers log).
  Raises the RLIMIT_NOFILE soft limit clamped to the hard limit and
  returns the soft limit actually in effect (0 = unsupported platform).
  A capped node still holds one fd per stratum session + HTTP + RPC +
  P2P + LevelDB; distro-default 1024 starves the accept loop under
  miner churn.
* main_ltc: call it right after logger init and log the resulting soft
  limit (WARN when the hard limit keeps it below 65536).
* main_dash: call it first thing in main() and print the result.
* main_ltc config plumbing for the strict per-node miner cap:
  --max-stratum-connections N (CLI) / max_stratum_connections (YAML),
  default 100, 0 = unlimited; help text + startup config log line.
  Flows through the existing set_stratum_config() path for both the
  integrated and solo stratum servers.
…-RSS payload identity)

Loopback-only gtest harness (synthetic IWorkSource, real StratumServer on
an ephemeral test port — no daemon/RPC/sharechain):

* CapEnforcementThirdConnectionRefused: with max_stratum_connections=2
  the 3rd and 4th connections are cleanly refused (peer-close observed,
  refused_connections increments each time), existing miners keep
  receiving work, and a freed slot admits a new miner (accept loop
  survives the refusals).
* StaleStormFifoEviction: subscribe/authorize, then a 600-notify storm.
  The CURRENT job and a recent in-window job submit ACCEPTED — the
  current-job assertion FAILS on the pre-fix arbitrary
  erase(active_jobs_.begin()) eviction — while the genuinely-oldest
  pre-storm job is correctly stale (error 21; FIFO cap 256 still
  enforced, oldest-first). Note: with MAX_ACTIVE_JOBS=256 (p2pool
  parity) a job 600 generations back is genuinely oldest and correctly
  evicted; the bug being fixed was arbitrary eviction of RECENT jobs.
* FlatRssSharedPayloadIdentity: 3 sessions x 50-notify churn — total
  retained jobs stay bounded by the FIFO cap and the heavyweight
  template payload is POINTER-IDENTICAL across all jobs of one work
  generation (exactly 1 shared block per session, not one copy per job).

Byte-parity note (in-file comment too): the notify/coinbase/share
BYTE-parity KAT is out of scope here — for DASH it must run against the
frstrtr/p2pool-dash oracle (share version 16->36 transition line), NOT
the v36-uniform baseline.

CI: target registered in test/CMakeLists.txt (link set mirrors
test_stratum_extensions) and added to both build.yml --target
allowlists next to test_stratum_extensions.
The KAT target was added to test/CMakeLists.txt but the build.yml allowlist hunk was dropped from the original push (needs workflow scope). Without a --target entry in the Linux + ASAN build jobs, CTest treats it as a NOT_BUILT sentinel that silently passes, so the mining-hotel KATs never actually run in CI. Add it to both --target lists so the cap / FIFO-eviction / flat-RSS checks execute on every build.
Comment thread src/c2pool/main_ltc.cpp
cli_explicit.insert("max_coinbase_outputs");
}
else if (arg == "--max-stratum-connections" && i + 1 < argc) {
stratum_config.max_stratum_connections = static_cast<size_t>(std::stoul(argv[++i]));
@frstrtr
frstrtr merged commit f14e254 into master Jul 16, 2026
26 checks passed
@frstrtr
frstrtr deleted the fix/stratum-hotel-interim-cap branch July 16, 2026 19:10
frstrtr added a commit that referenced this pull request Jul 17, 2026
test_btc_underfill_guard + test_dgb_underfill_guard -> main-leg --target
lists (build_ci + build_asan); test_bch_underfill_guard -> BCH-leg lists
(build_bch build + run, COIN_BCH). Without these the new KATs register as
NOT_BUILT CTest sentinels and Not-Run on Linux/ASan. Same pattern as #721/#724.
frstrtr added a commit that referenced this pull request Jul 17, 2026
…728)

* btc/dgb/bch(underfill): port LTC/DOGE near-empty-template guard + KATs

Port the v36 template-underfill guard (near-empty template on a non-empty
fee-paying mempool, LOG-ONLY WARNING) from LTC/DOGE to the three coins that
were missing it, in the DASH free-predicate form (underfill_guard_trips +
50 kB UNDERFILL_MIN_FILL_BYTES / UNDERFILL_BACKLOG_SLACK pins):

- btc: guard inside TemplateBuilder::build_template (template_builder.hpp),
  SAFE-ADDITIVE trailing bool* underfill_tripped seam; GBT JSON untouched.
- bch: same, after the CTOR re-sort; ABLA budget / sizelimit untouched.
- dgb: build_work_template() is a pure shaper with no mempool access, so the
  guard evaluates in make_mempool_tx_source (embedded_tx_select.cpp) — the
  one mempool-visible point feeding BOTH template callers (stratum
  DGBWorkSource + EmbeddedCoinNode); predicate/thresholds pinned in
  coin/template_builder.hpp.

KATs per coin (test/test_{btc,dgb,bch}_underfill_guard.cpp, mirroring
test_dash_underfill_guard.cpp): threshold pins, boundary strictness,
empty/fee-unknown/drained no-trip, and real-build wiring via the seam
(stale-input-guard-emptied selection over a fee-paying backlog).
Registered in test/CMakeLists.txt (bch entry gated on COIN_BCH like
bch_coin). New targets still need the build.yml --target allowlist
(main leg: btc+dgb; BCH leg: bch) or CTest shows the NOT_BUILT sentinel.

* ci(build): allowlist btc/dgb/bch underfill-guard gtests (#728)

test_btc_underfill_guard + test_dgb_underfill_guard -> main-leg --target
lists (build_ci + build_asan); test_bch_underfill_guard -> BCH-leg lists
(build_bch build + run, COIN_BCH). Without these the new KATs register as
NOT_BUILT CTest sentinels and Not-Run on Linux/ASan. Same pattern as #721/#724.

---------

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
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.

2 participants