From 4a5d8f61d1d52cc78ba32ac669b63e3e491db3c8 Mon Sep 17 00:00:00 2001 From: Maveth-Grok Date: Fri, 9 Oct 2026 18:15:22 -0600 Subject: [PATCH] stratum: Replace the current job when the template or split arrives Job 1, the opening job, stays 0 transactions and 1 pool output. clean_jobs is already true there. That part is unchanged. Job 2 is the fee template on that same job. It now goes out with clean_jobs true, so the miner is told to leave job 1. Job 3 is the coinbase split that arrives after those two. It is a new job, and that notify also goes out with clean_jobs true. The miner is told to leave jobs 1 and 2 and take the split. The old job is not marked stale, so a share already submitted still counts. The later 40-second refresh stays clean_jobs false and sits alongside. --- src/datum_stratum.c | 16 +++++++++++++--- src/datum_stratum.h | 5 +++++ src/datum_stratum_tests.c | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/datum_stratum.c b/src/datum_stratum.c index a5e26a67..8057bfdd 100644 --- a/src/datum_stratum.c +++ b/src/datum_stratum.c @@ -519,7 +519,7 @@ void datum_stratum_v1_socket_thread_loop(T_DATUM_THREAD_DATA *my) { for(i=0;iapp->max_clients_thread;i++) { m = my->client_data[i].app_client_data; if (my->client_data[i].fd && m->subscribed) { - send_mining_notify(&my->client_data[i],false,false,false); + send_mining_notify(&my->client_data[i], datum_stratum_notify_replaces_job(job->job_state, true), false, false); } } @@ -558,7 +558,7 @@ void datum_stratum_v1_socket_thread_loop(T_DATUM_THREAD_DATA *my) { for(i=0;iapp->max_clients_thread;i++) { m = my->client_data[i].app_client_data; if (my->client_data[i].fd && m->subscribed) { - send_mining_notify(&my->client_data[i],false,false,false); + send_mining_notify(&my->client_data[i], datum_stratum_notify_replaces_job(job->job_state, true), false, false); } } @@ -584,7 +584,7 @@ void datum_stratum_v1_socket_thread_loop(T_DATUM_THREAD_DATA *my) { for(i=0;iapp->max_clients_thread;i++) { m = my->client_data[i].app_client_data; if (my->client_data[i].fd && m->subscribed) { - send_mining_notify(&my->client_data[i],false,false,false); + send_mining_notify(&my->client_data[i], datum_stratum_notify_replaces_job(job->job_state, sdata->last_was_empty), false, false); } } } else { @@ -1438,6 +1438,16 @@ unsigned int datum_stratum_coinbase_index( return miner->coinbase_selection; } +bool datum_stratum_notify_replaces_job(int job_state, bool opening_job_still_current) { + /* State 2's second send is the template. State 4 is the split. + * State 5 replaces only while miners are still on the opening job. + * The paced refresh passes opening_job_still_current false. */ + if (job_state == JOB_STATE_EMPTY_PLUS && opening_job_still_current) return true; + if (job_state == JOB_STATE_FULL_PRIORITY_WAIT_COINBASER) return true; + if (job_state == JOB_STATE_FULL_NORMAL_WAIT_COINBASER && opening_job_still_current) return true; + return false; +} + int send_mining_notify(T_DATUM_CLIENT_DATA *c, bool clean, bool quickdiff, bool new_block) { // send the current job to the miner diff --git a/src/datum_stratum.h b/src/datum_stratum.h index ef81a391..7feca15a 100644 --- a/src/datum_stratum.h +++ b/src/datum_stratum.h @@ -269,6 +269,11 @@ extern T_DATUM_STRATUM_JOB *global_cur_stratum_jobs[MAX_STRATUM_JOBS]; const char *datum_stratum_mod_username(const char *username_s, char *username_buf, size_t username_buf_sz, uint16_t share_rnd, const char *modname, size_t modname_len); int send_mining_notify(T_DATUM_CLIENT_DATA *c, bool clean, bool quickdiff, bool new_block); +/* True when this notify replaces the job miners are hashing. + * The opening job is 0 transactions and 1 pool output. The template reply + * and the split reply replace it. A later refresh does not. The replaced + * job is not marked stale, so shares already submitted still count. */ +bool datum_stratum_notify_replaces_job(int job_state, bool opening_job_still_current); void update_stratum_job(T_DATUM_TEMPLATE_DATA *block_template, bool new_block, int job_state); void datum_stratum_job_refresh_blake2b(T_DATUM_STRATUM_JOB *s); bool datum_stratum_job_blake2b_commitment_from_txn(const T_DATUM_STRATUM_JOB *s, const unsigned char *cb_txn, size_t cb_len, unsigned char target_pot, bool subsidy_only, unsigned char *commitment); diff --git a/src/datum_stratum_tests.c b/src/datum_stratum_tests.c index ebcf7af0..ebe65d4b 100644 --- a/src/datum_stratum_tests.c +++ b/src/datum_stratum_tests.c @@ -690,7 +690,21 @@ void datum_stratum_mod_username_tests() { datum_test(0 == strcmp(res, "def.ghi")); } +static void datum_stratum_notify_replaces_job_tests(void) { + /* Template reply and split reply replace the opening job. */ + datum_test(datum_stratum_notify_replaces_job(JOB_STATE_EMPTY_PLUS, true)); + datum_test(datum_stratum_notify_replaces_job(JOB_STATE_FULL_PRIORITY_WAIT_COINBASER, false)); + datum_test(datum_stratum_notify_replaces_job(JOB_STATE_FULL_PRIORITY_WAIT_COINBASER, true)); + /* The opening job itself is not this path, and a normal refresh stays alongside. */ + datum_test(!datum_stratum_notify_replaces_job(JOB_STATE_EMPTY_ONLY, true)); + datum_test(!datum_stratum_notify_replaces_job(JOB_STATE_EMPTY_PLUS, false)); + datum_test(!datum_stratum_notify_replaces_job(JOB_STATE_FULL_NORMAL_WAIT_COINBASER, false)); + datum_test(datum_stratum_notify_replaces_job(JOB_STATE_FULL_NORMAL_WAIT_COINBASER, true)); + datum_test(!datum_stratum_notify_replaces_job(JOB_STATE_FULL_PRIORITY, false)); +} + void datum_stratum_tests(void) { + datum_stratum_notify_replaces_job_tests(); datum_stratum_mod_username_tests(); datum_stratum_minimum_difficulty_configure_tests(); datum_stratum_string_request_id_tests();