Skip to content

logger: optional TTY collapse of repeating job and notification lines - #17

Closed
FlyTheElephant1 wants to merge 8 commits into
CONVOYMining:masterfrom
FlyTheElephant1:main
Closed

FlyTheElephant1 wants to merge 8 commits into
CONVOYMining:masterfrom
FlyTheElephant1:main

Conversation

@FlyTheElephant1

Copy link
Copy Markdown

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

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

FlyTheElephant1 and others added 8 commits September 9, 2026 16:30
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.
@FlyTheElephant1

Copy link
Copy Markdown
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.

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