Skip to content

stratum: Name the client that found a block - #7

Open
jasonsopko wants to merge 2 commits into
CONVOYMining:masterfrom
jasonsopko:convoy-log-block-finder
Open

jasonsopko wants to merge 2 commits into
CONVOYMining:masterfrom
jasonsopko:convoy-log-block-finder

Conversation

@jasonsopko

@jasonsopko jasonsopko commented Sep 4, 2026 •

Copy link
Copy Markdown

What

One more WARN line naming the client behind a found block: height, the username sent in mining.submit, client address, thread and slot, session id, user agent, and whether the share was on empty work. datum_log_block_finder writes it on both paths a block takes: after the BLOCK FOUND banner in client_mining_submit, and in datum_protocol_abw_reveal when the pool discloses the XOR key. The first commit adds T_DATUM_REFSTR, a reference-counted string, for the ABW path.

Why

The banner prints only the hash. I had to decode the session id out of the header's extranonce and work through thread assignment order to tell my own ASIC apart from rented hashpower. The session id in the new line is what the gateway writes into the extranonce, so an on-chain block matches a log line directly.

Under ABW the share is not recognizable as a block when it arrives, so each pending candidate keeps a reference to the client's description. A client builds its description again only when its username changes. It uses the username the miner sent, not the one a username modifier picks per share: the modified name says who gets credit, not which machine found the block. The username goes through strncpy_printable first; the user agent already went through strncpy_uachars at subscribe.

How I tested

Local builds on 69c61a9 (v0.5.0beta): gcc and clang with -Wall -Werror, ENABLE_API=OFF, DATUM_API_FOR_UMBREL, gcc ASan+UBSan and clang ASan+UBSan build clean and pass --help and --test. New tests cover the string type, the client's description and the reference counts through the ABW cache. Breaking any of the reference releases, or the hand-off in the reveal path, fails --test under ASan. The ABW reveal test now logs the line. TSan is clean with four threads taking and dropping a million references each on one string, and the count ends at one. Per ABW share, in a scratch bench on a Release build: 13 ns with an unchanged username, 232 ns when it changes every share, 225 ns at the previous head. The local path line only fires on a block, so it was not run.

Risk and rollback

Log output only, no behavior change. The pending table grows by 8 bytes per entry, 15.0 to 15.5 MiB (35.5 MiB at the previous head), and each client slot by 208 bytes. Worst case is about 22 MiB of descriptions, one for each of the 65536 candidates, and only if the username or connection changes before every share. Candidates on the full coinbase already hold a coinbase copy larger than that (540 to 1557 bytes for CONVOY's 215 blocks from 974108 to 977107), and the previous head spent 20.5 MiB on every pooled gateway. A smaller cap would cut long usernames or user agents short more often, so it stays at 320 bytes. The line puts the finder's username and address in the log at WARN. Revert the two commits.

Comment thread src/datum_stratum.c Outdated
// Copy a client-supplied string for logging, replacing anything outside
// printable ASCII so a miner cannot put line breaks or terminal escapes
// into the log through its username or user agent.
static void datum_log_safe_copy(char *dst, size_t dstsz, const char *src) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably belongs in utils?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to datum_utils as strncpy_printable, next to the other two strncpy_* filters, with a test. Dropped the copy of the user agent while I was there: strncpy_uachars already filters it at subscribe.

Comment thread src/datum_stratum.c Outdated
DLOG_WARN("************************************************************************************************");
DLOG_WARN("******** BLOCK FOUND - %s ********", new_notify_blockhash);
DLOG_WARN("************************************************************************************************");
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only one of two possible block-finding paths

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the ABW reveal. Each pending candidate now keeps the client description (one pointer and the height per entry, freed with the candidate), and datum_protocol_abw_reveal logs it before queuing the block. One helper in datum_stratum.c builds the line for both paths.

Comment thread src/datum_protocol.c Outdated
DATUM_BLAKE2B_BLOCK_HEADER_SIZE);
pending->coinbase = coinbase;
pending->coinbase_size = coinbase_size;
pending->finder = (finder && finder[0]) ? strdup(finder) : NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather not be doing memory allocation here

@jasonsopko
jasonsopko force-pushed the convoy-log-block-finder branch from 389628a to 583a8fb Compare September 6, 2026 21:50
@jasonsopko

Copy link
Copy Markdown
Author

Fair point. It's a fixed field on the pending entry now, and the reveal path copies it into a stack buffer. No allocation, no free.

@jasonsopko

Copy link
Copy Markdown
Author

Rebased on 69c61a9. The conflicts were all on master's side: the reveal path now submits the block before the ABW audit check, so the finder line sits in front of both and is logged either way; pow_submit latches ABW health when a candidate cannot be retained, and the finder argument goes along with that; the new difficulty helpers in datum_utils.h and their tests sit next to strncpy_printable; and the cache_candidate call master added to the tests gets a NULL finder. That NULL is the only line that differs from the previous head's diff. The commit message and the body still described the pointer version from before the 09-06 change; both now say fixed 320-byte field, and the body gives the real table growth, 328 bytes per entry and 20.5 MiB. gcc, clang, Umbrel, API off, gcc ASan+UBSan and clang ASan+UBSan build -Wall -Werror clean and pass --test on the rebased tree.

Comment thread src/datum_protocol.c Outdated
bool subsidy_only;
bool pool_handled;
char finder[DATUM_ABW_FINDER_LEN];
uint64_t height;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height is only 32-bit for now

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now uint32_t, cast the way the header build in datum_protocol_abw_cache_candidate already does. It sits in the padding after the two bools, so an entry is 248 bytes against 240 on master.

Comment thread src/datum_protocol.c Outdated
T_DATUM_ABW_TEMPLATE *block_template;
bool subsidy_only;
bool pool_handled;
char finder[DATUM_ABW_FINDER_LEN];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a new refcounted string type so we don't have to copy the username for every share.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added T_DATUM_REFSTR to datum_utils in its own commit. Each client builds its description once in datum_stratum_client_finder, again only if its username changes, and each candidate takes a reference instead of copying the description into a 320-byte field. datum_abw_pending drops from 35.5 MiB to 15.5 MiB (15.0 on master), and the per-share cost from 225 ns to 13 ns in a scratch bench. The line now uses the username the miner sent rather than the modifier's pick, so a rotating modifier doesn't rebuild it.

Comment thread src/datum_stratum.c Outdated
Comment on lines +1266 to +1267
datum_stratum_describe_block_finder(finder, sizeof(finder), c, username_s, empty_work);
DLOG_WARN("Block %s at height %llu found by %s", new_notify_blockhash, (unsigned long long)job->height, finder);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this should be a common helper outside of stratum

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datum_log_block_finder in datum_utils writes the line for both paths now. Building the description stays in datum_stratum.c, since it reads the client's address, session and agent.

With it, a string several owners read and nobody changes is handed on
as a reference instead of a copy. The count sits in front of the
characters in one allocation and is atomic, so owners on different
threads can drop theirs without a shared lock. datum_refstr_unref frees
the string with the last reference. All three functions accept NULL.
The BLOCK FOUND banner prints only the hash. Telling a local ASIC apart
from rented hashpower meant decoding the session id out of the header
extranonce and matching it against thread assignment order.

Log one more line with the height, the username sent in mining.submit,
the client address, thread and slot, the session id (the bytes written
into the header extranonce), the user agent, and whether the share was
on empty work. datum_log_block_finder in datum_utils writes it on both
paths a block takes: right after the banner in client_mining_submit,
and in datum_protocol_abw_reveal once the pool discloses the XOR key.

For the ABW path each pending candidate keeps a reference to the
client's description and a 32-bit height. A client builds the
description again only when its username changes, so a share costs a
string compare and a reference instead of a copy. It uses the username
the miner sent, not a username modifier's per-share pick, which says
who gets credit rather than which machine found the block. The
username is filtered through the new strncpy_printable first; the user
agent already went through strncpy_uachars at subscribe. No behavior
change.
@jasonsopko
jasonsopko force-pushed the convoy-log-block-finder branch from e26bec6 to 695c78a Compare October 14, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants