Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/stratum/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SRCS
"mining.c"
"mining_job.c"
"stratum_api.c"
"stratum_timing.c"
"stratum_socket.c"
"coinbase_decoder.c"
"segwit_addr.c"
Expand Down
7 changes: 0 additions & 7 deletions components/stratum/include/stratum_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ typedef struct sv1_conn {
int active_job_ids_count;
} sv1_conn_t;

typedef struct RequestTiming
{
int64_t timestamp_us;
bool tracking;
} RequestTiming;

esp_transport_handle_t STRATUM_V1_transport_init(tls_mode tls, const char * cert);

Expand Down Expand Up @@ -121,6 +116,4 @@ int STRATUM_V1_submit_share(esp_transport_handle_t transport, int send_uid, cons
const char *extranonce_2, const uint32_t ntime, const uint32_t nonce,
const uint32_t version_bits, uint64_t *out_sent_time_us);

float STRATUM_V1_get_response_time_ms(int request_id, int64_t receive_time_us);

#endif // STRATUM_API_H
39 changes: 39 additions & 0 deletions components/stratum/include/stratum_timing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef STRATUM_TIMING_H
#define STRATUM_TIMING_H

#include <stdint.h>
#include <stdbool.h>

#define STRATUM_TIMING_SLOTS 64
Comment thread
mutatrum marked this conversation as resolved.

typedef struct {
uint64_t submit_time_us[STRATUM_TIMING_SLOTS];
} stratum_timing_tracker_t;

/**
* @brief Record a submission timestamp for a given request / share ID.
*
* @param tracker Pointer to the timing tracker.
* @param id The request ID / share ID / sequence number.
* @param submit_time_us The submission timestamp in microseconds.
*/
void stratum_timing_record(stratum_timing_tracker_t *tracker, uint32_t id, uint64_t submit_time_us);

/**
* @brief Calculate round-trip response time in milliseconds for an ID, and clear the slot.
*
* @param tracker Pointer to the timing tracker.
* @param id The response ID / share ID / sequence number.
* @param now_us The current timestamp in microseconds.
* @return Latency in milliseconds, or -1.0f if the ID was not tracked or invalid.
*/
float stratum_timing_calculate_ms(stratum_timing_tracker_t *tracker, uint32_t id, uint64_t now_us);

/**
* @brief Reset/clear all tracking slots (e.g. on disconnect or pool switch).
*
* @param tracker Pointer to the timing tracker.
*/
void stratum_timing_reset(stratum_timing_tracker_t *tracker);

#endif /* STRATUM_TIMING_H */
61 changes: 1 addition & 60 deletions components/stratum/stratum_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,6 @@ static char * json_rpc_buffer = NULL;
static size_t json_rpc_buffer_size = 0;
static size_t json_rpc_buffer_len = 0;

static RequestTiming *request_timings = NULL;

static RequestTiming* get_request_timing(int request_id) {
if (request_id < 0) return NULL;
int index = request_id % MAX_REQUEST_IDS;
return &request_timings[index];
}

float STRATUM_V1_get_response_time_ms(int request_id, int64_t receive_time_us)
{
if (request_id < 0) return -1.0;

RequestTiming *timing = get_request_timing(request_id);
if (!timing || !timing->tracking) {
return -1.0;
}

float response_time = (receive_time_us - timing->timestamp_us) / 1000.0f;
timing->tracking = false;
return response_time;
}

esp_transport_handle_t STRATUM_V1_transport_init(tls_mode tls, const char * cert)
{
esp_transport_handle_t transport;
Expand Down Expand Up @@ -116,25 +94,6 @@ bool STRATUM_V1_initialize_buffer(void)
json_rpc_buffer_size = BUFFER_SIZE;
json_rpc_buffer[0] = '\0';

if (request_timings == NULL) {
request_timings = heap_caps_malloc(sizeof(RequestTiming) * MAX_REQUEST_IDS, MALLOC_CAP_SPIRAM);
if (request_timings == NULL) {
request_timings = malloc(sizeof(RequestTiming) * MAX_REQUEST_IDS);
}
if (request_timings == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for request_timings");
free(json_rpc_buffer);
json_rpc_buffer = NULL;
json_rpc_buffer_size = 0;
return false;
}
}

for (int i = 0; i < MAX_REQUEST_IDS; i++) {
request_timings[i].timestamp_us = 0;
request_timings[i].tracking = false;
}

return true;
}

Expand Down Expand Up @@ -809,19 +768,6 @@ bool STRATUM_V1_parse(StratumApiV1Message *message, const char *stratum_json, mi
return result;
}



static void stamp_tx(int request_id, uint64_t timestamp_us)
{
if (request_id >= 1) {
RequestTiming *timing = get_request_timing(request_id);
if (timing) {
timing->timestamp_us = timestamp_us;
timing->tracking = true;
}
}
}

static void debug_stratum_tx(const char * msg)
{
char *newline = strchr(msg, '\n');
Expand Down Expand Up @@ -923,14 +869,9 @@ int STRATUM_V1_submit_share(esp_transport_handle_t transport, int send_uid, cons

int ret = esp_transport_write(transport, submit_msg, strlen(submit_msg), TRANSPORT_TIMEOUT_MS);

uint64_t now = esp_timer_get_time();
if (out_sent_time_us) {
*out_sent_time_us = now;
}
if (out_sent_time_us) *out_sent_time_us = esp_timer_get_time();

debug_stratum_tx(submit_msg);

stamp_tx(send_uid, now);

return ret;
}
Expand Down
32 changes: 32 additions & 0 deletions components/stratum/stratum_timing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "stratum_timing.h"
#include <string.h>

void stratum_timing_record(stratum_timing_tracker_t *tracker, uint32_t id, uint64_t submit_time_us)
{
if (!tracker) {
return;
}
tracker->submit_time_us[id % STRATUM_TIMING_SLOTS] = submit_time_us;
}

float stratum_timing_calculate_ms(stratum_timing_tracker_t *tracker, uint32_t id, uint64_t now_us)
{
if (!tracker) {
return -1.0f;
}
uint32_t slot = id % STRATUM_TIMING_SLOTS;
uint64_t submit_time_us = tracker->submit_time_us[slot];
if (submit_time_us == 0 || now_us < submit_time_us) {
return -1.0f;
}
tracker->submit_time_us[slot] = 0;
return (float)(now_us - submit_time_us) / 1000.0f;
}

void stratum_timing_reset(stratum_timing_tracker_t *tracker)
{
if (!tracker) {
return;
}
memset(tracker->submit_time_us, 0, sizeof(tracker->submit_time_us));
}
77 changes: 77 additions & 0 deletions components/stratum/test/test_stratum_timing.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "unity.h"
#include "stratum_timing.h"
#include <string.h>

TEST_CASE("stratum_timing record and calculate latency", "[stratum_timing]")
{
stratum_timing_tracker_t tracker;
stratum_timing_reset(&tracker);

// Record request 1 at t = 1,000,000 us (1.0s)
stratum_timing_record(&tracker, 1, 1000000ULL);

// Calculate response at t = 1,025,000 us (25ms later)
float latency_ms = stratum_timing_calculate_ms(&tracker, 1, 1025000ULL);
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.0f, latency_ms);

// Second call should return -1.0f as the slot is cleared
latency_ms = stratum_timing_calculate_ms(&tracker, 1, 1030000ULL);
TEST_ASSERT_EQUAL_FLOAT(-1.0f, latency_ms);
}

TEST_CASE("stratum_timing wraparound and slot reuse", "[stratum_timing]")
{
stratum_timing_tracker_t tracker;
stratum_timing_reset(&tracker);

uint32_t id_first = 5;
uint32_t id_second = 5 + STRATUM_TIMING_SLOTS; // Wraps around to the exact same slot

// Record first ID
stratum_timing_record(&tracker, id_first, 2000000ULL);

// Second ID wraps around and overwrites the slot
stratum_timing_record(&tracker, id_second, 2100000ULL);

// Calculate for the second ID
float latency_ms = stratum_timing_calculate_ms(&tracker, id_second, 2115500ULL);
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.5f, latency_ms);

// Slot is now cleared, checking first or second ID yields -1.0f
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, id_first, 2120000ULL));
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, id_second, 2120000ULL));
}

TEST_CASE("stratum_timing reset", "[stratum_timing]")
{
stratum_timing_tracker_t tracker;
stratum_timing_reset(&tracker);

stratum_timing_record(&tracker, 10, 5000000ULL);
stratum_timing_record(&tracker, 11, 5000100ULL);
stratum_timing_record(&tracker, 12, 5000200ULL);

stratum_timing_reset(&tracker);

TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, 10, 5050000ULL));
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, 11, 5050000ULL));
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, 12, 5050000ULL));
}

TEST_CASE("stratum_timing invalid timestamps and null safety", "[stratum_timing]")
{
stratum_timing_tracker_t tracker;
stratum_timing_reset(&tracker);

// NULL tracker safety
stratum_timing_record(NULL, 1, 1000ULL);
stratum_timing_reset(NULL);
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(NULL, 1, 2000ULL));

// Backward clock jump (now_us < submit_time_us)
stratum_timing_record(&tracker, 7, 5000000ULL);
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, 7, 4999999ULL));

// Non-existent ID
TEST_ASSERT_EQUAL_FLOAT(-1.0f, stratum_timing_calculate_ms(&tracker, 999, 6000000ULL));
}
1 change: 1 addition & 0 deletions main/global_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ typedef struct SystemModule
float hashrate_1h;
float error_percentage;
int64_t start_time_us;
uint64_t shares_submitted;
uint64_t shares_accepted;
uint64_t shares_rejected;
uint16_t shares_pending;
Expand Down
27 changes: 25 additions & 2 deletions main/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,10 @@ void SYSTEM_init_system(GlobalState * GLOBAL_STATE)
SystemModule * module = &GLOBAL_STATE->SYSTEM_MODULE;

module->screen_page = 0;
module->shares_submitted = 0;
module->shares_accepted = 0;
module->shares_rejected = 0;
module->shares_pending = 0;
module->best_nonce_diff = nvs_config_get_u64(NVS_CONFIG_BEST_DIFF);
module->best_session_nonce_diff = 0;
module->start_time_us = esp_timer_get_time();
Expand Down Expand Up @@ -424,11 +426,29 @@ static void clean_jobs_queue(GlobalState * GLOBAL_STATE)
hashrate_monitor_reset_measurements(GLOBAL_STATE);
}

void SYSTEM_notify_accepted_share(GlobalState * GLOBAL_STATE)
static inline void update_pending_shares(SystemModule *module)
{
uint64_t resolved = module->shares_accepted + module->shares_rejected;
uint64_t pending = (module->shares_submitted > resolved) ? (module->shares_submitted - resolved) : 0;
module->shares_pending = (uint16_t)(pending > UINT16_MAX ? UINT16_MAX : pending);
}

void SYSTEM_notify_submitted_share(GlobalState * GLOBAL_STATE)
{
if (!GLOBAL_STATE) return;

SystemModule * module = &GLOBAL_STATE->SYSTEM_MODULE;
module->shares_submitted++;
update_pending_shares(module);
}

void SYSTEM_notify_accepted_share(GlobalState * GLOBAL_STATE)
{
if (!GLOBAL_STATE) return;

SystemModule * module = &GLOBAL_STATE->SYSTEM_MODULE;
module->shares_accepted++;
update_pending_shares(module);
}

static int compare_rejected_reason_stats(const void *a, const void *b) {
Expand All @@ -439,9 +459,11 @@ static int compare_rejected_reason_stats(const void *a, const void *b) {

void SYSTEM_notify_rejected_share(GlobalState * GLOBAL_STATE, char * error_msg)
{
SystemModule * module = &GLOBAL_STATE->SYSTEM_MODULE;
if (!GLOBAL_STATE) return;

SystemModule * module = &GLOBAL_STATE->SYSTEM_MODULE;
module->shares_rejected++;
update_pending_shares(module);

for (int i = 0; i < module->rejected_reason_stats_count; i++) {
if (strncmp(module->rejected_reason_stats[i].message, error_msg, sizeof(module->rejected_reason_stats[i].message) - 1) == 0) {
Expand Down Expand Up @@ -514,6 +536,7 @@ void SYSTEM_reset_pool_session(GlobalState * GLOBAL_STATE)
module->rejected_reason_stats[i].message[0] = '\0';
}
module->rejected_reason_stats_count = 0;
module->shares_submitted = 0;
module->shares_accepted = 0;
module->shares_rejected = 0;
module->shares_pending = 0;
Expand Down
1 change: 1 addition & 0 deletions main/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void SYSTEM_init_versions(GlobalState * GLOBAL_STATE);
void SYSTEM_init_partitions(GlobalState * GLOBAL_STATE);
esp_err_t SYSTEM_init_peripherals(GlobalState * GLOBAL_STATE);

void SYSTEM_notify_submitted_share(GlobalState * GLOBAL_STATE);
void SYSTEM_notify_accepted_share(GlobalState * GLOBAL_STATE);
void SYSTEM_notify_rejected_share(GlobalState * GLOBAL_STATE, char * error_msg);
void SYSTEM_notify_found_nonce(GlobalState * GLOBAL_STATE, double diff, bool is_block);
Expand Down
4 changes: 3 additions & 1 deletion main/tasks/stratum_task.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ int stratum_submit_share(GlobalState *GLOBAL_STATE, const asic_job_t *active_job
ret = stratum_v1_submit_share(GLOBAL_STATE, active_job, nonce, rolled_version, sent_time_us);
}

if (ret < 0) {
if (ret >= 0) {
SYSTEM_notify_submitted_share(GLOBAL_STATE);
Comment on lines +272 to +273

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Record successful submissions before releasing the connection lock. A disconnect can reset the session before this counter update, counting an old submission in the new session. A controlled test leaves one phantom pending share; the base leaves zero. Add a regression test for this sequence.

} else {
ESP_LOGW(TAG, "Failed to submit share to socket (ret: %d, errno %d: %s)", ret, errno, strerror(errno));
// stratum_task recv loop will detect a broken connection on its next read and handle reconnection
}
Expand Down
Loading
Loading