logger: optional TTY collapse of repeating job and notification lines - #17
Closed
FlyTheElephant1 wants to merge 8 commits into
Closed
FlyTheElephant1 wants to merge 8 commits into
FlyTheElephant1 wants to merge 8 commits into
Conversation
What this does -------------- When logger.log_shares is true, every stratum mining.submit result that passes the optional missingzeros gate is written at INFO as one compact line: SHARE 1x accepted bc1q... @192.168.4.115 ?ok diff=16384/16384/1141943014 missingzeros=6 SHARE rejected ? @192.168.4.115 ?high-hash diff=16384 Fields: Nx vardiff / job_diff when it divides evenly; omitted otherwise accepted|rejected username last-auth or submit username; "?" if unknown @host rem_host; "?" if empty ?reason ok | block | high-hash | stale-work | stale-prevblk | time-too-old | time-too-new | H-not-zero | duplicate | unknown-work | ... diff=job/vardiff/block block is nbits-derived uint64, 0 if unavailable missingzeros=N bits the hash is short of the *block* target (0 = block candidate). Omitted on early rejects that never hashed. A found block also emits a second line that matches the node-check format so log parsers can treat "we submitted a block" the same way: SHARE <64 hex> mode=block d=<jobdiff> => submitted missingzeros is counted against the full 256-bit block target, not leading hex zeros. That matters on BLAKE2b where the interesting bits are not always in the high bytes the way people read SHA256d explorers. Configuration (all optional) ---------------------------- logger.log_shares bool default false mining.share_node_check_missingzeros int default -1 -1 means "do not gate on closeness": if log_shares is on, log every result that hits the helpers (early rejects have missingzeros=-1 and still log). >= 0 means only emit SHARE lines when missingzeros was computed and is <= this value. Use 2 for "near-block only" (~4 per block in expectation at random hashing), 4 for ~16, 0 for block-candidates only. This is the same knob later used by node-check. Benefits -------- - Solo / lab operators can see *why* a share died without turning on DEBUG and drowning the console. - Compact form fits a 120-column TTY with log_calling_function on. - missingzeros is a cheap closeness metric for "is this miner actually approaching the block target or just sending job-diff dust." - Same reason strings the miner already got in the stratum error, so the log and the ASIC agree. Risks ----- 1. Log volume. At INFO, an unfiltered farm can write tens of thousands of lines per minute. Default is off. missingzeros >= 0 is the intended production filter. 2. Username / IP in the log. This is the same data already on the dashboard and in stratum. Do not ship logs off-box if that is a problem. 3. Wrong closeness math would silently mis-filter. The helper first compares hash vs block_target; if the share already meets target it returns 0. Otherwise it estimates the bit gap from leading-zero difference and checks by shifting. Off-by-one is possible on the gap; it will not invert accept/reject. 4. Early rejects (unknown-work before username is parsed) use last_auth_username or "?". That can attribute a malformed submit to the previous authorized name on that connection. Better than inventing a user, still not a proof of identity. Latency / main thread --------------------- All work is on the stratum worker that already handled mining.submit: - a few 32-byte copies and a 256-bit shift for missingzeros - one snprintf + DLOG_INFO DLOG_INFO is the existing lock-free-ish queue into the logger thread (rwlock + double buffer). This patch does not fopen, does not malloc on the accept path, and does not wait on the logger. Cost is microseconds, dominated by the hash the submit already computed. It does *not* talk to bitcoind. Possible improvements --------------------- - Pass job_diff into the reject helpers so early rejects show a real diff instead of 0 / a single number. - Sample by 1-of-N for logs independently of missingzeros, so a busy farm can keep a heartbeat without the closeness filter. - Rate-limit per connection if someone turns log_shares on at a site with 10k miners. - Move missingzeros into a shared util and unit-test the shift against known hash/target pairs. What to validate before merge ----------------------------- - Default config: zero new lines, accept/reject behavior unchanged. - log_shares=true, missingzeros=-1: one SHARE line per submit, compact form, no user=/host= keys. - Reject reasons still match the JSON error sent to the miner. - Found-block path still submits and still prints BLOCK FOUND; the extra SHARE mode=block line is additional, not a replacement. - missingzeros=0 logs only was_block shares. - Logger queue does not stall stratum under a share flood (watch LOGGER OVERRUN).
What this does -------------- When logger.console_collapse_job_updates is true and stdout/stderr is a TTY, repeating INFO lines of these four kinds are rewritten in place instead of scrolling: 1 "Updating standard stratum job for block ..." 2 "Updating priority stratum job for block ..." 3 "NEW NETWORK BLOCK NOTIFICATION RECEIVED" 4 "NEW NETWORK BLOCK: ..." Same kind on the next line becomes a counter after the timestamp: ... INFO: x12 Updating standard stratum job for block ... A notification burst followed by the actual NEW NETWORK BLOCK line is folded into: ... INFO: xN NOTIFICATION + NEW NETWORK BLOCK: <hash> (<height>) All four kinds use the same in-place path: first line of a run is printed with no newline; later lines are only CR + rewrite + CSI K. CSI 1A (cursor-up) is not used. File logs are untouched and always get every original full line. Non-TTY stdout/stderr (systemd, pipes, script) never collapse. Configuration ------------- logger.console_collapse_job_updates bool default false Why job updates and notifications used to diverge (tmux) -------------------------------------------------------- Notification first-lines are short and usually fit one tmux row, so in-place rewrite looked reliable. Job-update lines are ~180-200 characters (timestamp + 44-char function field + BTC + txn/byte/client counts). In an 80-140 column tmux pane that line wraps. A cursor-up then lands on the wrapped half, so previous full job lines stay on screen. A wide raw tty does not wrap, so the same code looked fine there. An extra eligibility gate (only collapse if linelen+16 < cols) made it worse: long job lines printed in full in tmux; short notification lines still collapsed. That was the "works on a real console, only sometimes in tmux" report. This commit uses one mechanism for every kind, clips the *on-screen* rewrite to cols-1 so tmux cannot wrap it, and drops the length gate. ioctl-failed columns fall back to 80. The file log is never clipped. Benefits -------- - Interactive consoles stop losing the last useful ERROR/WARN under a storm of job updates at low work_update_seconds. - Operators watching a gateway during a block race can still see that work *is* updating (the counter moves). - tmux panes behave like a wide raw tty for these four line kinds. Risks ----- 1. Broken terminals. A client that is a TTY but does not honor CSI will show leftover fragments or a missing newline. Mitigation: default off; collapse only if isatty(). Any non-matching log line flushes a pending collapse with a real newline. 2. Lost history on screen. That is the point. File log remains the record. Do not enable this if the console *is* the record. 3. On-screen truncation in a narrow pane. The live row may lose the tail of a job line (client count, etc.). File log still has it. 4. Kind matching is prefix/strcmp on the message body. If CONVOY changes those four strings, collapse silently stops matching and falls back to normal prints. No functional mining impact. 5. Logger thread only. A crash in ioctl/fileno would be on the logger thread, not stratum. Latency / main thread --------------------- Zero on stratum. Collapse runs when the logger thread drains its queue, which already writes every console line. Extra work per line: isatty, optional TIOCGWINSZ, a few strstr/snprintf, one fwrite. isatty/ioctl every line is the sloppy part (see improvements). Possible improvements --------------------- - Cache isatty + columns and refresh on SIGWINCH instead of ioctl per line. - Collapse from the file-log side too if someone wants a daily log that is not 90% job updates (separate knob). - Match kinds by a logger-side tag (DLOG_INFO_JOB) rather than string prefixes so a wording change cannot break it. - Also fold "Empty work send completed" into the priority-job row; it currently ends the collapse because it is a different message. What to validate before merge ----------------------------- - Default: console identical to CONVOY, including under script(1) / systemd (not a TTY). - Enabled on a raw TTY and in an 80-column tmux pane: repeated "Updating standard stratum job" stays on one row with x2, x3, ... - Notification then NEW NETWORK BLOCK folds; a lone NEW NETWORK BLOCK still prints. - "Server stats" / "Empty work send" end the collapse and start on the next row. - File log still has one full untruncated line per event. - log_calling_function on/off both still match (kind is on msg->msg).
…tblock)
What this does
--------------
When mining.validate_shares_on_node is true, a *sample* of accepted
non-block shares is assembled into the same BLAKE2b v2 block hex the
gateway would submit, then sent to the local node on a detached
thread:
share_node_check=proposal getblocktemplate mode=proposal
TestBlockValidity(..., check_pow=false)
share_node_check=submitblock submitblock
CheckProofOfWork first; sub-target
shares come back as high-hash
Results are logged as:
SHARE <64 hex> mode=proposal d=<jobdiff> => null (seems valid)
SHARE <64 hex> mode=proposal d=<jobdiff> => bad-txnmrklroot
SHARE <64 hex> mode=proposal d=<jobdiff> => transport/HTTP error (no JSON)
SHARE <64 hex> mode=submitblock d=<jobdiff> => high-hash
Real blocks are never sent through this path. They already go through
assembleBlockAndSubmit + datum_submitblock_trigger. was_block returns
immediately.
At most one node-check RPC is in flight. Further candidates are
skipped with a DEBUG line. That is the load shed.
Sampling:
mining.share_node_check_missingzeros < 0
take 1 of every mining.share_node_check_every accepted shares
(default every=16)
mining.share_node_check_missingzeros >= 0
ignore the 1-of-N sampler; only check shares with
missingzeros <= that value (same gate as log_shares)
Assembly uses CONVOY's own v2 header serialize
(datum_blake2b_serialize_block_header), witness-aware coinbase hex,
and the job's txn list. If proposal says the block is invalid, the
template/header/coinbase packing is wrong — not "the miner is weak."
Configuration
-------------
mining.validate_shares_on_node bool default false
mining.share_node_check string default "proposal"
mining.share_node_check_every int default 16
mining.share_node_check_missingzeros int default -1
(declared in 01-log-shares)
Benefits
--------
- Catches silent packing bugs (wrong witness, wrong v2 fields, stale
prevhdr) on shares that will never meet nBits, which submitblock
would hide behind high-hash.
- Gives lab operators a node-backed "this share is a real block
except PoW" signal without submitting junk to the network.
- Cheap enough to leave on at missingzeros=2 during a fork or gateway
upgrade.
Risks
-----
1. Extra bitcoind RPC. proposal is not free; a large block hex on
every share would hurt the node. Mitigations: default off, 1-of-N
or missingzeros gate, one-in-flight CAS, detached thread, never on
was_block.
2. submitblock mode on a *valid* near-block share that somehow meets
nBits would submit a real block through a second path. The was_block
early-out plus "we only get here after the share was already
classified" is the guard. Do not set share_node_check=submitblock
unless you understand high-hash will dominate.
3. proposal on an old tip can return stale-prevblk after a race.
That is useful, not a false "gateway is broken."
4. malloc of the block hex can fail; we drop the check and clear the
in-flight flag. No retry.
5. Detached thread leak if pthread_create succeeds but the worker
never runs — we only clear in-flight in the worker or on create
failure. Same pattern as other gateway detached work.
6. Hex assembly duplication vs assembleBlockAndSubmit. If one path
is updated and the other is not, node-check can lie. Reviewers
should diff datum_write_assembled_block_hex against the submit
builder when touching either.
Latency / main thread
---------------------
The stratum worker: filter arithmetic, one CAS, size estimate, two
mallocs, a full block hex sprintf, one more malloc for the JSON
wrapper, pthread_create. The hex walk is the costly part (copies
every txn hex). That is why sampling and one-in-flight exist.
The RPC itself (curl + bitcoind) runs on the detached worker. It does
not block mining.submit, vardiff, or job broadcast. Worst case for
the main worker is "we built a hex and spawned a thread."
Do not point this at a remote bitcoind over a high-latency link with
every=1.
Possible improvements
---------------------
- Build hex once and share it with assembleBlockAndSubmit on the
was_block path (one serializer).
- Reuse a thread-local curl handle instead of curl_easy_init per
check.
- Cap hex_guess / reject absurd templates so a malformed
txn_total_size cannot malloc hundreds of MB on the stratum thread.
- Bounded worker queue instead of skip-if-busy, if someone wants
"check the closest share that arrived during the last RPC."
- Unit test: known job + coinbase => proposal hex bit-identical to
submitblock hex.
What to validate before merge
-----------------------------
- Default: no extra RPC, no extra threads, submit path unchanged.
- proposal + missingzeros=2 against a real Knots node: near shares
log "null (seems valid)"; a deliberately broken coinbase logs a
reject reason, not a crash.
- every=1 with a CPU miner does not stall stratum accepts; in-flight
skips show at DEBUG.
- was_block still only uses assembleBlockAndSubmit; no SHARE
mode=proposal line for a found block.
- Pooled mode: node-check does not talk to Prime and does not change
DATUM pow_submit.
- Memory: watch RSS while a 4 MB template is being checked; should
return to baseline after the worker frees req+job.
Includes Insulince's share-check improvements from the innerhat / test/insulince tree.
Co-authored-by: Justin <justinreusnow@gmail.com>
What this does
--------------
CONVOY already writes the assembled submitblock JSON-RPC request to
disk when mining.save_submitblocks_dir is set. The payload is the
full block: v2 header hex + varint tx count + coinbase + every
template transaction, wrapped as {"method":"submitblock","params":["..."]}.
That is exactly what bitcoin-cli submitblock / a watchdog would replay.
Stock naming was only the hash:
<dir>/datum_submitblock_<hash>.json
This commit keeps the stock knob and the stock timing (write on the
submit thread after datum_submitblock_trigger, before the inline
bitcoind RPC) and changes the files to:
<dir>/datum_submitblock_<height>_<hash>.json
<dir>/datum_submitblock_last.json
The unique file is never overwritten. The last file is the stable
path for "what just happened." Both writes are fwrite of the exact
assembled length, fflush, fsync, then fclose. Success is an INFO
line with byte count and path. Failure is ERROR and does not change
the submitblock return value.
Startup logs the directory when the knob is set:
submitblock save dir: /tmp/datum_blocks (datum_submitblock_<height>_<hash>.json + datum_submitblock_last.json)
There is no second config key. mining.dump_submitblock_path is not
added. A detached 500 ms dump thread is not added.
datum_submitblock_trigger remains the async/redundant submit path;
delaying the backup copy would only risk losing it.
Configuration
-------------
mining.save_submitblocks_dir string default "" (disabled)
The directory must already exist. fopen does not mkdir.
Benefits
--------
- Height in the filename matches how operators look at logs
("block 123") without opening the JSON.
- last.json gives a single path for a lab watcher or scp.
- fsync makes the file durable if the process dies in the RPC that
follows.
- Same payload stock already wrote. No second serializer.
Risks
-----
1. Sync write of a multi-MB JSON on the submit thread. CONVOY
already accepted that cost; this adds fsync. Block-found is rare.
The dedicated submit thread is already running.
2. last.json is overwritten. Two blocks in the same second leave
only the latest last.json; the unique height_hash files remain.
3. No directory creation. A bad path is ERROR, submit still runs.
4. The file is a full block. Do not put the directory on a shared
or untrusted volume.
5. Height 0 if job is NULL. Should not happen on the real path.
Latency / main thread
---------------------
On assembleBlockAndSubmit only (already a block-found rarity):
two fopen/fwrite/fsync/fclose. Does not delay
datum_submitblock_trigger. The inline curl submitblock still
follows the writes, which is the stock order.
Possible improvements
---------------------
- mkdir -p for the configured directory.
- Write to .tmp and rename so a watcher never reads a truncated JSON.
- Also write a raw .blk (header+txs without the JSON wrapper).
What to validate before merge
-----------------------------
- Default "": no files, no extra INFO, submit unchanged.
- Dir="/tmp/datum_blocks": after a found block, both files appear
immediately, json_load_file succeeds, INFO lines show the paths.
- Height in the unique filename matches the job height in the log.
- fopen failure does not change submitblock return value.
- Startup prints the save-dir line only when the knob is non-empty.
What this does -------------- CONVOY already builds six coinbase sizes per job (MAX_COINBASE_TYPES). Type 2 is the ~755 byte Antminer-safe cap and the default after subscribe. Type 4 is the 16 KB window. Fingerprint-by-UA already picks among them. This adds a second bind so the *port* can force that choice instead of a second gateway process or a second template thread. stratum.listen_port default 23334 stratum.legacy_listen_port default 0 (disabled) When legacy_listen_port is set and not equal to listen_port: clients accepted on listen_port coinbase type 4 (16 KB) clients accepted on legacy_listen_port coinbase type 2 (~755 B) Same template thread, same job cache, same worker pool. The listener accepts on both fds, tags T_DATUM_CLIENT_DATA.accepted_listen_port, and subscribe overrides coinbase_selection after fingerprint runs. A subscribe INFO line reports ua, port, and selected type. legacy_listen_port 0 (default) leaves stock behavior: type 2 plus UA fingerprint, single listen socket. There is no second job #1, no second GBT, and no 17-output aggregator. Type 2 is already the small coinbase old boards tolerate. Configuration ------------- stratum.listen_addr string unchanged stratum.listen_port int default 23334 stratum.legacy_listen_port int default 0 Keys live under "stratum". Flat names like stratum_port / legacy_stratum_port are not parsed. Benefits -------- - One process can feed modern rigs a full coinbase window and old Antminer-class boards a size they will not reject, without two gateways and two template polls. - Port policy is explicit. Fingerprint still runs; the port override wins when the legacy port is enabled. - Default remains a single port and type-2 default, so stock configs do not change behavior. Risks ----- 1. Two listen fds. Bind failure on the legacy port aborts the listener thread the same way a primary bind failure does. 2. IPv4+IPv6 on each port. listen_socks grew from 2 to 8. Four sockets (two ports × two families) is the expected case. 3. accepted_listen_port is 0 until assign_to_thread. Subscribe before that cannot happen on this codebase. 4. Forcing type 4 on the primary port can still break a miner that connected to the "modern" port but cannot take 16 KB. That is the point of the split; put that miner on the legacy port. 5. Fingerprint-set difficulty (NiceHash high min diff) is kept; only coinbase_selection is overridden by port. Latency / main thread --------------------- Accept path: one extra int copy onto the client slot. Subscribe: two integer compares and one INFO log when the legacy port is enabled. No extra template work. Jobs already carry all six coinbases. Possible improvements --------------------- - Per-port vardiff / min-diff so NiceHash-class clients can sit on the small port without fingerprint. - Config to choose which type each port forces (not hard-coded 4/2). - Log listen addresses at startup for both ports. What to validate before merge ----------------------------- - Default legacy_listen_port=0: single listen, type 2 + fingerprint unchanged, no new INFO on subscribe. - listen_port=23334, legacy_listen_port=23335: both accept, subscribe logs type 4 vs type 2, miners on each port get the matching coinbase size in mining.notify. - Same job id on both ports (one template). - Fingerprint still adjusts NiceHash min diff on either port. - Binding legacy to the same number as listen_port is a no-op (second bind skipped).
…e2b, it does and it was the fix, but only worked if the legacy_port was configured
What this does -------------- On Blake the miner never receives the generation transaction. mining.notify is a 39-byte stub (000000 || H2 || 00000000) and an empty coinb2. Coinbase class only decides how many of the pool's payout outputs the gateway commits into the block it will submit. Classes were sized for SHA256d firmware that hashes coinb1/coinb2: 0 tiny / empty 1 NiceHash ~500 B 2 Antminer-safe ~755 B (old subscribe default) 3 Whatsminer ~6500 B 4 YUGE ~16 KB 5 S21 / ANTMAIN2 ~2250 B Default class 2 plus UA fingerprint meant unknown UAs, empty UAs, and most stock Antminer strings stayed on type 2. A TIDES split with more outputs than that window fit (~17 P2WPKH with default tags) truncated the list. Leftover value landed on pool_address and ops had to top up LISTED_ONLY tails by hand. datum_stratum_coinbase_index no longer returns miner->coinbase_selection. Once the job is at or above JOB_STATE_FULL_PRIORITY_WAIT_COINBASER and full_coinbase_ready is set, it returns COINBASE_TYPE_YUGE for every miner. New-block work and not-ready jobs still use empty / type 0. miner stays on the function signature so existing call sites are unchanged. Subscribe defaults coinbase_selection to YUGE so the API and logs match the class that will actually be mined. UA fingerprinting still runs and still raises NiceHash min-diff; class is forced back to YUGE afterward. Always serving class 4 is only safe if that coinbase cannot make an invalid block against the template the miner is already hashing. Two leftover SHA256d constants said "fits" when it did not: - fit_to_template weight used +340 for an 80-byte header. Blake header is 164 bytes. Count is now ((DATUM_BLAKE2B_BLOCK_HEADER_SIZE+5)<<2)+36. Size limit already used the 164-byte header; weight did not. - Static overhead was 119 bytes with the witness commitment counted as 46. Real serialized witness commitment is 47 bytes (8 value, 1 length, 38 script). Static is 124. Payout packing skips an output that would exceed the template sigop budget (sigoplimit - txn_total_sigops - pool output cost) in both the counting pass and the write pass so the varint matches the outputs written. P2PKH (script[0]==0x76) costs 4; other scripts 0. Remainder value stays on pool_address. That is a short list, not a rejected block. This replaces the dual-port approach in b88e146 (reverted separately). That patch only forced type 4 on listen_port when legacy_listen_port was set and non-zero. The example config and the key default were 0, so stock configs never left type 2. What this does not do --------------------- Does not add a second listen port. Does not change vardiff except the existing NiceHash min-diff. Does not change the DATUM POW layout. Prime sees coinbase_id=4 and the body the gateway uploads. Configuration ------------- No new keys. No JSON edit required. Behavior changes for every miner on the existing listen_port. Benefits -------- - Finds from this gateway commit the full Prime payout list up to what the template can actually hold. - Unknown-UA and "legacy" Blake boxes are fine: they never needed the big coinbase on the wire. - A packed Knots template cannot be given a coinbase that overflows weight or sigops. - Matches Luke 4d0a045 / CONVOY#8 policy and iohzrd CONVOY#10 accounting without dropping fingerprint or changing the coinbase_index prototype. Risks ----- 1. Type-4 bytes differ from the old 119-byte packer. A pool that rebuilds class 4 itself with the old recipe will disagree with this body. It must use the uploaded blob. 2. API "coinbase class" column is YUGE after subscribe, so it no longer reports firmware fingerprint class. Job id still encodes the class that was mined. 3. SHA256d firmware that hashes coinb1/coinb2 is not a supported path on this tree. Latency / main thread --------------------- Index path: one constant return. Subscribe: one assignment after fingerprint. Packing: one extra integer compare per candidate output. What to validate before merge ----------------------------- - Ready job: datum_stratum_coinbase_index returns 4 for miners whose coinbase_selection is 2, 3, or MAX. - New-block / !full_coinbase_ready: still 0 / empty. - Subscribe + fingerprint: NiceHash min-diff still applied, class is 4. - fit_to_template(1000,0) on a weight-bound template returns the Blake leftover, not the old 1000. - Sigop tests: output-count hex 05 / 04 / 03 / 03. - A packed Knots template does not produce a coinbase that pushes block weight or sigops over the GBT limits. - Share submit / notify job id uses class 4 once the coinbaser is ready. - No stratum.legacy_listen_port key, no second bind.
Author
|
This is not what I intended. I'll see if I can fix it. I only wanted to merge one commit with this PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
logger: optional TTY collapse of repeating job and notification lines
What this does
When logger.console_collapse_job_updates is true and stdout/stderr is a
TTY, repeating INFO lines of these four kinds are rewritten in place
instead of scrolling:
1 "Updating standard stratum job for block ..."
2 "Updating priority stratum job for block ..."
3 "NEW NETWORK BLOCK NOTIFICATION RECEIVED"
4 "NEW NETWORK BLOCK: ..."
Same kind on the next line becomes a counter after the timestamp:
... INFO: x12 Updating standard stratum job for block ...
A notification burst followed by the actual NEW NETWORK BLOCK line is
folded into:
... INFO: xN NOTIFICATION + NEW NETWORK BLOCK: ()
All four kinds use the same in-place path: first line of a run is
printed with no newline; later lines are only CR + rewrite + CSI K.
CSI 1A (cursor-up) is not used.
File logs are untouched and always get every original full line.
Non-TTY stdout/stderr (systemd, pipes, script) never collapse.
Configuration
logger.console_collapse_job_updates bool default false
Why job updates and notifications used to diverge (tmux)
Notification first-lines are short and usually fit one tmux row, so
in-place rewrite looked reliable.
Job-update lines are ~180-200 characters (timestamp + 44-char
function field + BTC + txn/byte/client counts). In an 80-140 column
tmux pane that line wraps. A cursor-up then lands on the wrapped
half, so previous full job lines stay on screen. A wide raw tty
does not wrap, so the same code looked fine there.
An extra eligibility gate (only collapse if linelen+16 < cols) made
it worse: long job lines printed in full in tmux; short notification
lines still collapsed. That was the "works on a real console, only
sometimes in tmux" report.
This commit uses one mechanism for every kind, clips the on-screen
rewrite to cols-1 so tmux cannot wrap it, and drops the length gate.
ioctl-failed columns fall back to 80. The file log is never clipped.
Benefits
storm of job updates at low work_update_seconds.
work is updating (the counter moves).
Risks
will show leftover fragments or a missing newline. Mitigation:
default off; collapse only if isatty(). Any non-matching log line
flushes a pending collapse with a real newline.
record. Do not enable this if the console is the record.
tail of a job line (client count, etc.). File log still has it.
changes those four strings, collapse silently stops matching and
falls back to normal prints. No functional mining impact.
thread, not stratum.
Latency / main thread
Zero on stratum. Collapse runs when the logger thread drains its
queue, which already writes every console line. Extra work per line:
isatty, optional TIOCGWINSZ, a few strstr/snprintf, one fwrite.
isatty/ioctl every line is the sloppy part (see improvements).
Possible improvements
line.
that is not 90% job updates (separate knob).
prefixes so a wording change cannot break it.
it currently ends the collapse because it is a different message.
What to validate before merge
systemd (not a TTY).
"Updating standard stratum job" stays on one row with x2, x3, ...
still prints.
the next row.