bm1397: add multiple midstates using version mask

Job packets can have more that 1 midstate. If the stratum supports
version-rolling, calculate additional midstates by incrementing
the version with the version_mask provided by the stratum server.
Currently does 4 midstates for each extranonce2.
This commit is contained in:
johnny9 2023-06-07 22:00:49 -04:00
parent 1e63f35888
commit c7de2c0795
12 changed files with 138 additions and 53 deletions

View File

@ -52,6 +52,9 @@ struct __attribute__((__packed__)) job_packet {
uint8_t ntime[4];
uint8_t merkle4[4];
uint8_t midstate[32];
uint8_t midstate1[32];
uint8_t midstate2[32];
uint8_t midstate3[32];
};
struct __attribute__((__packed__)) nonce_response {

View File

@ -21,17 +21,20 @@ TEST_CASE("Check known working midstate + job command", "[bm1397]")
}
uint8_t work1[50] = {
0xA3, // job id
0x01, // number of midstates
uint8_t work1[146] = {
0x18, // job id
0x04, // number of midstates
0x9B, 0x04, 0x4C, 0x0A, // starting nonce
0x3A, 0xAE, 0x05, 0x17, // nbits
0xA0, 0x84, 0x73, 0x64, // ntime
0x50, 0xE3, 0x71, 0x61, // merkle 4
0x7E, 0x02, 0x70, 0x35, 0xB1, 0xAC, 0xBA, 0xF2, 0x3E, 0xA0, 0x1A, 0x52, 0x73, 0x44, 0xFA, 0xF7, 0x6A, 0xB4, 0x76, 0xD3, 0x28, 0x21, 0x61, 0x18, 0xB7, 0x76, 0x0F, 0x7B, 0x1B, 0x22, 0xD2, 0x29
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7E, 0x02, 0x70, 0x35, 0xB1, 0xAC, 0xBA, 0xF2, 0x3E, 0xA0, 0x1A, 0x52, 0x73, 0x44, 0xFA, 0xF7, 0x6A, 0xB4, 0x76, 0xD3, 0x28, 0x21, 0x61, 0x18, 0xB7, 0x76, 0x0F, 0x7B, 0x1B, 0x22, 0xD2, 0x29,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
struct job_packet test_job;
memcpy((uint8_t *) &test_job, work1, 50);
memcpy((uint8_t *) &test_job, work1, 146);
uint8_t buf[1024];
memset(buf, 0, 1024);
@ -51,4 +54,6 @@ TEST_CASE("Check known working midstate + job command", "[bm1397]")
memcpy((void *) &nonce, buf + i, sizeof(struct nonce_response));
// expected nonce 9B 04 4C 0A
TEST_ASSERT_EQUAL_UINT32(0x0a4c049b, nonce.nonce);
TEST_ASSERT_EQUAL_UINT8(0x18, nonce.job_id & 0xfc);
TEST_ASSERT_EQUAL_UINT8(2, nonce.job_id & 0x03);
}

View File

@ -6,13 +6,18 @@
typedef struct {
uint32_t version;
uint32_t version_mask;
uint8_t prev_block_hash[32];
uint8_t merkle_root[32];
uint32_t ntime;
uint32_t target; // aka difficulty, aka nbits
uint32_t starting_nonce;
uint8_t num_midstates;
uint8_t midstate[32];
uint8_t midstate1[32];
uint8_t midstate2[32];
uint8_t midstate3[32];
uint32_t pool_diff;
char * jobid;
char * extranonce2;
@ -25,10 +30,12 @@ char * construct_coinbase_tx(const char * coinbase_1, const char * coinbase_2,
char * calculate_merkle_root_hash(const char * coinbase_tx, const uint8_t merkle_branches[][32], const int num_merkle_branches);
bm_job construct_bm_job(mining_notify * params, const char * merkle_root);
bm_job construct_bm_job(mining_notify * params, const char * merkle_root, const uint32_t version_mask);
double test_nonce_value(bm_job * job, uint32_t nonce);
double test_nonce_value(const bm_job * job, const uint32_t nonce, const uint8_t midstate_index);
char * extranonce_2_generate(uint32_t extranonce_2, uint32_t length);
uint32_t increment_bitmask(const uint32_t value, const uint32_t mask);
#endif // MINING_H

View File

@ -27,6 +27,7 @@ typedef struct {
uint8_t * merkle_branches;
size_t n_merkle_branches;
uint32_t version;
uint32_t version_mask;
uint32_t target;
uint32_t ntime;
uint32_t difficulty;
@ -69,7 +70,8 @@ void STRATUM_V1_configure_version_rolling(int socket);
int STRATUM_V1_suggest_difficulty(int socket, uint32_t difficulty);
void STRATUM_V1_submit_share(int socket, const char * username, const char * jobid,
const char * extranonce_2, const uint32_t ntime, const uint32_t nonce);
const char * extranonce_2, const uint32_t ntime, const uint32_t nonce,
const uint32_t version);
#endif // STRATUM_API_H

View File

@ -53,7 +53,7 @@ char * calculate_merkle_root_hash(const char * coinbase_tx, const uint8_t merkle
}
//take a mining_notify struct with ascii hex strings and convert it to a bm_job struct
bm_job construct_bm_job(mining_notify * params, const char * merkle_root) {
bm_job construct_bm_job(mining_notify * params, const char * merkle_root, const uint32_t version_mask) {
bm_job new_job;
new_job.version = params->version;
@ -76,6 +76,26 @@ bm_job construct_bm_job(mining_notify * params, const char * merkle_root) {
midstate_sha256_bin(midstate_data, 64, new_job.midstate); //make the midstate hash
reverse_bytes(new_job.midstate, 32); //reverse the midstate bytes for the BM job packet
if (version_mask != 0) {
uint32_t rolled_version = increment_bitmask(new_job.version, version_mask);
memcpy(midstate_data, &rolled_version, 4);
midstate_sha256_bin(midstate_data, 64, new_job.midstate1);
reverse_bytes(new_job.midstate1, 32);
rolled_version = increment_bitmask(rolled_version, version_mask);
memcpy(midstate_data, &rolled_version, 4);
midstate_sha256_bin(midstate_data, 64, new_job.midstate2);
reverse_bytes(new_job.midstate2, 32);
rolled_version = increment_bitmask(rolled_version, version_mask);
memcpy(midstate_data, &rolled_version, 4);
midstate_sha256_bin(midstate_data, 64, new_job.midstate3);
reverse_bytes(new_job.midstate3, 32);
new_job.num_midstates = 4;
} else {
new_job.num_midstates = 1;
}
return new_job;
}
@ -97,14 +117,18 @@ char * extranonce_2_generate(uint32_t extranonce_2, uint32_t length)
static const double truediffone = 26959535291011309493156476344723991336010898738574164086137773096960.0;
/* testing a nonce and return the diff - 0 means invalid */
double test_nonce_value(bm_job * job, uint32_t nonce) {
double test_nonce_value(const bm_job * job, const uint32_t nonce, const uint8_t midstate_index) {
double d64, s64, ds;
unsigned char header[80];
//TODO: use the midstate hash instead of hashing the whole header
// TODO: use the midstate hash instead of hashing the whole header
uint32_t rolled_version = job->version;
for (int i = 0; i < midstate_index; i++) {
rolled_version = increment_bitmask(rolled_version, job->version_mask);
}
//copy data from job to header
memcpy(header, &job->version, 4);
// copy data from job to header
memcpy(header, &rolled_version, 4);
memcpy(header + 4, job->prev_block_hash, 32);
memcpy(header + 36, job->merkle_root, 32);
memcpy(header + 68, &job->ntime, 4);
@ -124,3 +148,20 @@ double test_nonce_value(bm_job * job, uint32_t nonce) {
return ds;
}
uint32_t increment_bitmask(const uint32_t value, const uint32_t mask) {
// if mask is zero, just return the original value
if (mask == 0) return value;
uint32_t carry = (value & mask) + (mask & -mask); // increment the least significant bit of the mask
uint32_t overflow = carry & ~mask; // find overflowed bits that are not in the mask
uint32_t new_value = (value & ~mask) | (carry & mask); // set bits according to the mask
// Handle carry propagation
if (overflow > 0) {
uint32_t carry_mask = (overflow << 1); // shift left to get the mask where carry should be propagated
new_value = increment_bitmask(new_value, carry_mask); // recursively handle carry propagation
}
return new_value;
}

View File

@ -305,11 +305,12 @@ int STRATUM_V1_authenticate(int socket, const char * username)
/// @param extranonce_2 The hex-encoded value of extra nonce 2.
/// @param nonce The hex-encoded nonce value to use in the block header.
void STRATUM_V1_submit_share(int socket, const char * username, const char * jobid,
const char * extranonce_2, const uint32_t ntime, const uint32_t nonce)
const char * extranonce_2, const uint32_t ntime, const uint32_t nonce,
const uint32_t version)
{
char submit_msg[BUFFER_SIZE];
sprintf(submit_msg, "{\"id\": %d, \"method\": \"mining.submit\", \"params\": [\"%s\", \"%s\", \"%s\", \"%08x\", \"%08x\"]}\n",
send_uid++, username, jobid, extranonce_2, ntime, nonce);
sprintf(submit_msg, "{\"id\": %d, \"method\": \"mining.submit\", \"params\": [\"%s\", \"%s\", \"%s\", \"%08x\", \"%08x\", \"%08x\"]}\n",
send_uid++, username, jobid, extranonce_2, ntime, nonce, version);
debug_stratum_tx(submit_msg);
write(socket, submit_msg, strlen(submit_msg));

View File

@ -66,7 +66,7 @@ TEST_CASE("Validate bm job construction", "[mining]")
notify_message.target = 0x1705dd01;
notify_message.ntime = 0x64658bd8;
const char * merkle_root = "cd1be82132ef0d12053dcece1fa0247fcfdb61d4dbd3eb32ea9ef9b4c604a846";
bm_job job = construct_bm_job(&notify_message, merkle_root);
bm_job job = construct_bm_job(&notify_message, merkle_root, 0);
uint8_t expected_midstate_bin[32];
hex2bin("91DFEA528A9F73683D0D495DD6DD7415E1CA21CB411759E3E05D7D5FF285314D", expected_midstate_bin, 32);
@ -75,6 +75,21 @@ TEST_CASE("Validate bm job construction", "[mining]")
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_midstate_bin, job.midstate, 32);
}
TEST_CASE("Validate version mask incrementing", "[mining]")
{
uint32_t version = 0x20000004;
uint32_t version_mask = 0x00ffff00;
uint32_t rolled_version = increment_bitmask(version, version_mask);
TEST_ASSERT_EQUAL_UINT32(0x20000104, rolled_version);
rolled_version = increment_bitmask(rolled_version, version_mask);
TEST_ASSERT_EQUAL_UINT32(0x20000204, rolled_version);
rolled_version = increment_bitmask(rolled_version, version_mask);
TEST_ASSERT_EQUAL_UINT32(0x20000304, rolled_version);
rolled_version = increment_bitmask(rolled_version, version_mask);
TEST_ASSERT_EQUAL_UINT32(0x20000404, rolled_version);
}
// Values calculated from esp-miner/components/stratum/test/verifiers/bm1397.py
// TEST_CASE("Validate bm job construction 2", "[mining]")
// {
@ -131,10 +146,10 @@ TEST_CASE("Test nonce diff checking", "[mining test_nonce]")
notify_message.target = 0x1705ae3a;
notify_message.ntime = 0x646ff1a9;
const char * merkle_root = "6d0359c451434605c52a5a9ce074340be47c2c63840731f9edf1db3f26b1cdd9a9f16f64";
bm_job job = construct_bm_job(&notify_message, merkle_root);
bm_job job = construct_bm_job(&notify_message, merkle_root, 0);
uint32_t nonce = 0x276E8947;
double diff = test_nonce_value(&job, nonce);
double diff = test_nonce_value(&job, nonce, 0);
TEST_ASSERT_EQUAL_INT(18, (int) diff);
}
@ -167,9 +182,9 @@ TEST_CASE("Test nonce diff checking 2", "[mining test_nonce]")
char * merkle_root = calculate_merkle_root_hash(coinbase_tx, merkles, num_merkles);
TEST_ASSERT_EQUAL_STRING("5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", merkle_root);
bm_job job = construct_bm_job(&notify_message, merkle_root);
bm_job job = construct_bm_job(&notify_message, merkle_root, 0);
uint32_t nonce = 0x0a029ed1;
double diff = test_nonce_value(&job, nonce);
double diff = test_nonce_value(&job, nonce, 0);
TEST_ASSERT_EQUAL_INT(683, (int) diff);
}

View File

@ -27,12 +27,11 @@ typedef struct {
pthread_mutex_t valid_jobs_lock;
uint32_t stratum_difficulty;
uint32_t version_mask;
int sock;
} GlobalState;
#endif /* GLOBAL_STATE_H_ */

View File

@ -15,7 +15,8 @@
static GlobalState GLOBAL_STATE = {
.extranonce_str = NULL,
.extranonce_2_len = 0,
.abandon_work = 0
.abandon_work = 0,
.version_mask = 0
};
@ -40,7 +41,6 @@ void app_main(void)
queue_init(&GLOBAL_STATE.stratum_queue);
queue_init(&GLOBAL_STATE.ASIC_jobs_queue);
SERIAL_init();
BM1397_init();

View File

@ -7,7 +7,7 @@
static const char *TAG = "ASIC_task";
// static bm_job ** active_jobs; is required to keep track of the active jobs since the
// static bm_job ** active_jobs; is required to keep track of the active jobs since the
// ASIC may not return the nonce in the same order as the jobs were sent
// it also may return a previous nonce under some circumstances
// so we keep a list of jobs indexed by the job id
@ -16,8 +16,7 @@ static bm_job ** active_jobs;
void ASIC_task(void * pvParameters)
{
GlobalState *GLOBAL_STATE = (GlobalState*)pvParameters;
GlobalState *GLOBAL_STATE = (GlobalState*) pvParameters;
uint8_t buf[CHUNK_SIZE];
memset(buf, 0, 1024);
@ -50,23 +49,31 @@ void ASIC_task(void * pvParameters)
struct job_packet job;
// max job number is 128
// there is still some really weird logic with the job id bits for the asic to sort out
// there is still some really weird logic with the job id bits for the asic to sort out
// so we have it limited to 128 and it has to increment by 4
id = (id + 4) % 128;
job.job_id = id;
job.num_midstates = 1;
job.num_midstates = next_bm_job->num_midstates;
memcpy(&job.starting_nonce, &next_bm_job->starting_nonce, 4);
memcpy(&job.nbits, &next_bm_job->target, 4);
memcpy(&job.ntime, &next_bm_job->ntime, 4);
memcpy(&job.merkle4, next_bm_job->merkle_root + 28, 4);
memcpy(job.midstate, next_bm_job->midstate, 32);
if (job.num_midstates == 4)
{
memcpy(job.midstate1, next_bm_job->midstate1, 32);
memcpy(job.midstate2, next_bm_job->midstate2, 32);
memcpy(job.midstate3, next_bm_job->midstate3, 32);
}
if (active_jobs[job.job_id] != NULL) {
free_bm_job(active_jobs[job.job_id]);
}
active_jobs[job.job_id] = next_bm_job;
pthread_mutex_lock(&GLOBAL_STATE->valid_jobs_lock);
GLOBAL_STATE-> valid_jobs[job.job_id] = 1;
pthread_mutex_unlock(&GLOBAL_STATE->valid_jobs_lock);
@ -84,7 +91,7 @@ void ASIC_task(void * pvParameters)
// Didn't find a solution, restart and try again
continue;
}
if(received != 9 || buf[0] != 0xAA || buf[1] != 0x55){
ESP_LOGI(TAG, "Serial RX invalid %i", received);
ESP_LOG_BUFFER_HEX(TAG, buf, received);
@ -97,11 +104,13 @@ void ASIC_task(void * pvParameters)
struct nonce_response nonce;
memcpy((void *) &nonce, buf, sizeof(struct nonce_response));
if (GLOBAL_STATE->valid_jobs[nonce.job_id] == 0) {
ESP_LOGI(TAG, "Invalid job nonce found");
uint8_t rx_job_id = nonce.job_id & 0xfc;
uint8_t rx_midstate_index = nonce.job_id & 0x03;
if (GLOBAL_STATE->valid_jobs[rx_job_id] == 0) {
ESP_LOGI(TAG, "Invalid job nonce found, id=%d", nonce.job_id);
}
// ASIC may return the same nonce multiple times
// or one that was already found
// most of the time it behavies however
@ -120,24 +129,28 @@ void ASIC_task(void * pvParameters)
}
// check the nonce difficulty
double nonce_diff = test_nonce_value(active_jobs[nonce.job_id], nonce.nonce);
double nonce_diff = test_nonce_value(active_jobs[rx_job_id], nonce.nonce, rx_midstate_index);
ESP_LOGI(TAG, "Nonce difficulty %.2f of %d.", nonce_diff, active_jobs[nonce.job_id]->pool_diff);
if (nonce_diff > active_jobs[nonce.job_id]->pool_diff)
ESP_LOGI(TAG, "Nonce difficulty %.2f of %d.", nonce_diff, active_jobs[rx_job_id]->pool_diff);
if (nonce_diff > active_jobs[rx_job_id]->pool_diff)
{
SYSTEM_notify_found_nonce(&GLOBAL_STATE->SYSTEM_MODULE, active_jobs[nonce.job_id]->pool_diff, nonce_diff, next_bm_job->target);
SYSTEM_notify_found_nonce(&GLOBAL_STATE->SYSTEM_MODULE, active_jobs[rx_job_id]->pool_diff, nonce_diff, next_bm_job->target);
uint32_t rolled_version = active_jobs[rx_job_id]->version;
for (int i = 0; i < rx_midstate_index; i++) {
rolled_version = increment_bitmask(rolled_version, active_jobs[rx_job_id]->version_mask);
}
STRATUM_V1_submit_share(
GLOBAL_STATE->sock,
STRATUM_USER,
active_jobs[nonce.job_id]->jobid,
active_jobs[nonce.job_id]->extranonce2,
active_jobs[nonce.job_id]->ntime,
nonce.nonce
GLOBAL_STATE->sock,
STRATUM_USER,
active_jobs[rx_job_id]->jobid,
active_jobs[rx_job_id]->extranonce2,
active_jobs[rx_job_id]->ntime,
nonce.nonce,
rolled_version ^ active_jobs[rx_job_id]->version
);
}
}
}

View File

@ -23,7 +23,6 @@ void create_jobs_task(void * pvParameters)
uint32_t extranonce_2 = 0;
while (extranonce_2 < UINT_MAX && GLOBAL_STATE->abandon_work == 0)
{
struct timeval tv;
gettimeofday(&tv, NULL);
int job_time_sec = tv.tv_sec - mining_notification->ntime;
@ -38,16 +37,16 @@ void create_jobs_task(void * pvParameters)
char * extranonce_2_str = extranonce_2_generate(extranonce_2, GLOBAL_STATE->extranonce_2_len);
char *coinbase_tx = construct_coinbase_tx(mining_notification->coinbase_1, mining_notification->coinbase_2, GLOBAL_STATE->extranonce_str, extranonce_2_str);
char *merkle_root = calculate_merkle_root_hash(coinbase_tx, (uint8_t(*)[32])mining_notification->merkle_branches, mining_notification->n_merkle_branches);
bm_job next_job = construct_bm_job(mining_notification, merkle_root);
bm_job next_job = construct_bm_job(mining_notification, merkle_root, GLOBAL_STATE->version_mask);
bm_job * queued_next_job = malloc(sizeof(bm_job));
memcpy(queued_next_job, &next_job, sizeof(bm_job));
queued_next_job->extranonce2 = strdup(extranonce_2_str);
queued_next_job->jobid = strdup(mining_notification->job_id);
queued_next_job->version_mask = GLOBAL_STATE->version_mask;
queue_enqueue(&GLOBAL_STATE->ASIC_jobs_queue, queued_next_job);

View File

@ -113,7 +113,7 @@ void stratum_task(void * pvParameters)
}
pthread_mutex_unlock(&GLOBAL_STATE->valid_jobs_lock);
}
if ( GLOBAL_STATE->stratum_queue.count == QUEUE_SIZE) {
if (GLOBAL_STATE->stratum_queue.count == QUEUE_SIZE) {
mining_notify * next_notify_json_str = (mining_notify *) queue_dequeue(&GLOBAL_STATE->stratum_queue);
STRATUM_V1_free_mining_notify(next_notify_json_str);
}