• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dangernoodle-io / TaipanMiner / 24010605428

05 Apr 2026 09:13PM UTC coverage: 92.802% (+0.4%) from 92.444%
24010605428

push

github

web-flow
refactor: unify mining behind miner_config_t + hash_backend_t (#40)

- fix: expand HW nonce range to full 32-bit (was halved to 0x7FFFFFFF)
- fix: detect new work via work_seq instead of strcmp(job_id)
- fix: remove share double-counting (count only on pool accept)
- fix: ASIC submits full rolled nVersion (BIP 320)
- remove dead SW mining task (SW SHA kept for host tests only)
- add miner_config_t dispatch — eliminates #ifdef ASIC_BM1370 from main/stratum
- add hash_backend_t abstraction for HW/SW SHA backends
- extract mine_nonce_range() with parameterized nonce loop
- add 12 mining loop tests (backend, early reject, nonce range, version rolling)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

134 of 174 branches covered (77.01%)

Branch coverage included in aggregate %.

78 of 79 new or added lines in 1 file covered. (98.73%)

588 of 604 relevant lines covered (97.35%)

688459.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.17
/components/mining/src/mining.c
1
#include "mining.h"
2
#include "sha256.h"
3
#include "work.h"
4
#include <string.h>
5
#include <stdio.h>
6
#include <inttypes.h>
7

8
#ifdef ESP_PLATFORM
9
#include "esp_log.h"
10
#include "esp_timer.h"
11
#include "sha256_hw.h"
12

13
static const char *TAG = "mining";
14

15
QueueHandle_t work_queue = NULL;
16
QueueHandle_t result_queue = NULL;
17

18
mining_stats_t mining_stats = {0};
19

20
void mining_stats_init(void)
21
{
22
    mining_stats.mutex = xSemaphoreCreateMutex();
23
}
24
#endif
25

26
// Store 32-bit big-endian value
27
static inline void store_be32(uint8_t *p, uint32_t v) {
24✔
28
    p[0] = (v >> 24) & 0xff;
24✔
29
    p[1] = (v >> 16) & 0xff;
24✔
30
    p[2] = (v >> 8) & 0xff;
24✔
31
    p[3] = v & 0xff;
24✔
32
}
24✔
33

34
// Build the 64-byte block2 from header tail + SHA padding
35
void build_block2(uint8_t block2[64], const uint8_t header[80])
8✔
36
{
37
    memset(block2, 0, 64);
8✔
38
    memcpy(block2, header + 64, 16);
8✔
39
    block2[16] = 0x80;
8✔
40
    block2[62] = 0x02;
8✔
41
    block2[63] = 0x80;
8✔
42
}
8✔
43

44
// Pack target bytes [28-31] into a single word for early reject comparison
45
uint32_t pack_target_word0(const uint8_t target[32])
7✔
46
{
47
    return ((uint32_t)target[28] << 24) |
7✔
48
           ((uint32_t)target[29] << 16) |
7✔
49
           ((uint32_t)target[30] <<  8) |
14✔
50
            (uint32_t)target[31];
7✔
51
}
52

53
// Fill a mining_result_t from work + nonce + version info
54
void package_result(mining_result_t *result,
6✔
55
                    const mining_work_t *work,
56
                    uint32_t nonce,
57
                    uint32_t base_version,
58
                    uint32_t ver_bits,
59
                    uint32_t version_mask)
60
{
61
    strncpy(result->job_id, work->job_id, sizeof(result->job_id) - 1);
6✔
62
    result->job_id[sizeof(result->job_id) - 1] = '\0';
6✔
63
    strncpy(result->extranonce2_hex, work->extranonce2_hex, sizeof(result->extranonce2_hex) - 1);
6✔
64
    result->extranonce2_hex[sizeof(result->extranonce2_hex) - 1] = '\0';
6✔
65
    sprintf(result->ntime_hex, "%08" PRIx32, work->ntime);
6✔
66
    sprintf(result->nonce_hex, "%08" PRIx32, nonce);
6✔
67
    if (version_mask != 0 && ver_bits != 0) {
6!
68
        uint32_t rolled = (base_version & ~version_mask) | (ver_bits & version_mask);
1✔
69
        sprintf(result->version_hex, "%08" PRIx32, rolled);
1✔
70
    } else {
71
        result->version_hex[0] = '\0';
5✔
72
    }
73
}
6✔
74

75
// --- Software SHA hash backend (native build + host tests) ---
76

77
void sw_prepare_job(hash_backend_t *b,
3✔
78
                    const mining_work_t *work,
79
                    const uint8_t block2[64])
80
{
81
    sw_backend_ctx_t *ctx = (sw_backend_ctx_t *)b->ctx;
3✔
82
    memcpy(ctx->midstate, sha256_H0, sizeof(sha256_H0));
3✔
83
    sha256_transform(ctx->midstate, work->header);
3✔
84
    ctx->target_word0 = pack_target_word0(work->target);
3✔
85
    memcpy(ctx->block2, block2, 64);
3✔
86
}
3✔
87

88
hash_result_t sw_hash_nonce(hash_backend_t *b,
3✔
89
                            uint32_t nonce,
90
                            uint8_t hash_out[32])
91
{
92
    sw_backend_ctx_t *ctx = (sw_backend_ctx_t *)b->ctx;
3✔
93

94
    // Set nonce in block2 (bytes 12-15, little-endian)
95
    ctx->block2[12] = (uint8_t)(nonce & 0xff);
3✔
96
    ctx->block2[13] = (uint8_t)((nonce >> 8) & 0xff);
3✔
97
    ctx->block2[14] = (uint8_t)((nonce >> 16) & 0xff);
3✔
98
    ctx->block2[15] = (uint8_t)((nonce >> 24) & 0xff);
3✔
99

100
    // First SHA-256: clone midstate + transform block2
101
    uint32_t state[8];
102
    memcpy(state, ctx->midstate, 32);
3✔
103
    sha256_transform(state, ctx->block2);
3✔
104

105
    // Write first hash into block3_words
106
    for (int i = 0; i < 8; i++) {
27✔
107
        ctx->block3_words[i] = state[i];
24✔
108
    }
109

110
    // Second SHA-256: H0 + transform block3_words
111
    memcpy(state, sha256_H0, 32);
3✔
112
    sha256_transform_words(state, ctx->block3_words);
3✔
113

114
    // Early reject: check MSB word
115
    if (state[7] <= ctx->target_word0) {
3!
116
        store_be32(hash_out,      state[0]);
3✔
117
        store_be32(hash_out + 4,  state[1]);
3✔
118
        store_be32(hash_out + 8,  state[2]);
3✔
119
        store_be32(hash_out + 12, state[3]);
3✔
120
        store_be32(hash_out + 16, state[4]);
3✔
121
        store_be32(hash_out + 20, state[5]);
3✔
122
        store_be32(hash_out + 24, state[6]);
3✔
123
        store_be32(hash_out + 28, state[7]);
3✔
124
        return HASH_CHECK;
3✔
125
    }
NEW
126
    return HASH_MISS;
×
127
}
128

129
void sw_backend_setup(hash_backend_t *b, sw_backend_ctx_t *ctx)
3✔
130
{
131
    memset(ctx->block3_words, 0, sizeof(ctx->block3_words));
3✔
132
    ctx->block3_words[8]  = 0x80000000U;
3✔
133
    ctx->block3_words[15] = 0x00000100U;
3✔
134

135
    b->init = NULL;
3✔
136
    b->prepare_job = sw_prepare_job;
3✔
137
    b->hash_nonce = sw_hash_nonce;
3✔
138
    b->ctx = ctx;
3✔
139
}
3✔
140

141
#ifdef ESP_PLATFORM
142
// --- Hardware SHA hash backend (ESP32-S3 SHA peripheral) ---
143

144
typedef struct {
145
    uint32_t midstate_hw[8];
146
    uint32_t *block2_words;
147
} hw_backend_ctx_t;
148

149
static void hw_backend_init(hash_backend_t *b)
150
{
151
    (void)b;
152
    sha256_hw_init();
153
}
154

155
static void hw_prepare_job(hash_backend_t *b,
156
                           const mining_work_t *work,
157
                           const uint8_t block2[64])
158
{
159
    hw_backend_ctx_t *ctx = (hw_backend_ctx_t *)b->ctx;
160
    sha256_hw_midstate(work->header, ctx->midstate_hw);
161
    ctx->block2_words = (uint32_t *)block2;
162
}
163

164
static hash_result_t hw_hash_nonce(hash_backend_t *b,
165
                                   uint32_t nonce,
166
                                   uint8_t hash_out[32])
167
{
168
    hw_backend_ctx_t *ctx = (hw_backend_ctx_t *)b->ctx;
169
    uint32_t digest_hw[8];
170
    uint32_t h7_raw = sha256_hw_mine_nonce(ctx->midstate_hw, ctx->block2_words,
171
                                           nonce, digest_hw);
172

173
    if ((h7_raw >> 16) == 0) {
174
        for (int i = 0; i < 8; i++) {
175
            uint32_t w = __builtin_bswap32(digest_hw[i]);
176
            store_be32(hash_out + i * 4, w);
177
        }
178
        return HASH_CHECK;
179
    }
180
    return HASH_MISS;
181
}
182

183
static void hw_backend_setup(hash_backend_t *b, hw_backend_ctx_t *ctx)
184
{
185
    b->init = hw_backend_init;
186
    b->prepare_job = hw_prepare_job;
187
    b->hash_nonce = hw_hash_nonce;
188
    b->ctx = ctx;
189
}
190
#endif // ESP_PLATFORM
191

192
bool mine_nonce_range(hash_backend_t *backend,
7✔
193
                      mining_work_t *work,
194
                      const mine_params_t *params,
195
                      mining_result_t *result_out,
196
                      bool *found_out)
197
{
198
    uint8_t block2[64];
199
    build_block2(block2, work->header);
7✔
200
    backend->prepare_job(backend, work, block2);
7✔
201

202
#ifdef ESP_PLATFORM
203
    int64_t start_us = esp_timer_get_time();
204
    uint32_t hashes = 0;
205
#endif
206

207
    for (uint32_t nonce = params->nonce_start; ; nonce++) {
25✔
208
        uint8_t hash[32];
209
        hash_result_t hr = backend->hash_nonce(backend, nonce, hash);
25✔
210

211
        if (hr == HASH_CHECK && meets_target(hash, work->target)) {
25!
212
            mining_result_t result;
213
            package_result(&result, work, nonce,
5✔
214
                           params->base_version, params->ver_bits,
5✔
215
                           params->version_mask);
5✔
216

217
#ifdef ESP_PLATFORM
218
            ESP_LOGI(TAG, "share found! (nonce=%08" PRIx32 ")", nonce);
219

220
            xQueueSend(result_queue, &result, 0);
221
#endif
222
            if (result_out) {
5!
223
                *result_out = result;
5✔
224
            }
225
            if (found_out) {
5!
226
                *found_out = true;
5✔
227
                return false;  // in tests, stop after first hit
5✔
228
            }
229
        }
230

231
#ifdef ESP_PLATFORM
232
        hashes++;
233

234
        // Periodic yield + job refresh
235
        if (((nonce + 1) & params->yield_mask) == 0) {
236
            if (((nonce + 1) & params->log_mask) == 0) {
237
                int64_t elapsed_us = esp_timer_get_time() - start_us;
238
                if (elapsed_us > 0) {
239
                    double hashrate = (double)hashes / ((double)elapsed_us / 1000000.0);
240
                    uint32_t shares = 0;
241
                    if (xSemaphoreTake(mining_stats.mutex, 0) == pdTRUE) {
242
                        mining_stats.hw_hashrate = hashrate;
243
                        shares = mining_stats.hw_shares;
244
                        xSemaphoreGive(mining_stats.mutex);
245
                    }
246
                    ESP_LOGI(TAG, "hw: %.1f kH/s | shares: %" PRIu32, hashrate / 1000.0, shares);
247
                }
248
            }
249

250
            // Check for new work
251
            mining_work_t new_work;
252
            if (xQueuePeek(work_queue, &new_work, 0) == pdTRUE &&
253
                new_work.work_seq != work->work_seq) {
254
                memcpy(work, &new_work, sizeof(*work));
255
                ESP_LOGI(TAG, "new job (%s)", work->job_id);
256
                build_block2(block2, work->header);
257
                backend->prepare_job(backend, work, block2);
258
                start_us = esp_timer_get_time();
259
                hashes = 0;
260
                // Reset nonce to start (will increment to nonce_start on next iteration)
261
                nonce = params->nonce_start - 1;
262
                continue;
263
            }
264

265
            vTaskDelay(pdMS_TO_TICKS(1));
266
        }
267
#endif
268

269
        if (nonce == params->nonce_end) break;
20✔
270
    }
271
    return false;
2✔
272
}
273

274
#ifdef ESP_PLATFORM
275
void mining_task(void *arg)
276
{
277
    (void)arg;
278

279
    // Set up hash backend
280
    hw_backend_ctx_t hw_ctx;
281
    hash_backend_t backend;
282
    hw_backend_setup(&backend, &hw_ctx);
283

284
    if (backend.init) {
285
        backend.init(&backend);
286
    }
287

288
    ESP_LOGI(TAG, "mining task started");
289

290
    for (;;) {
291
        mining_work_t work;
292
        if (xQueuePeek(work_queue, &work, portMAX_DELAY) != pdTRUE) {
293
            continue;
294
        }
295

296
        ESP_LOGI(TAG, "new job (%s)", work.job_id);
297

298
        uint32_t base_version = work.version;
299
        uint32_t ver_bits = 0;
300

301
        for (;;) {  // version rolling outer loop
302
            if (work.version_mask != 0 && ver_bits != 0) {
303
                uint32_t rolled = (base_version & ~work.version_mask) | (ver_bits & work.version_mask);
304
                work.header[0] = rolled & 0xFF;
305
                work.header[1] = (rolled >> 8) & 0xFF;
306
                work.header[2] = (rolled >> 16) & 0xFF;
307
                work.header[3] = (rolled >> 24) & 0xFF;
308
            }
309

310
            mine_params_t params = {
311
                .nonce_start = 0,
312
                .nonce_end = 0xFFFFFFFFU,
313
                .yield_mask = 0x3FFFF,
314
                .log_mask = 0xFFFFF,
315
                .ver_bits = ver_bits,
316
                .base_version = base_version,
317
                .version_mask = work.version_mask,
318
            };
319

320
            mine_nonce_range(&backend, &work, &params, NULL, NULL);
321

322
            if (work.version_mask == 0) break;
323
            ver_bits = next_version_roll(ver_bits, work.version_mask);
324
            if (ver_bits == 0) break;
325
            ESP_LOGI(TAG, "rolling version: mask=%08" PRIx32 " bits=%08" PRIx32, work.version_mask, ver_bits);
326
        }
327

328
        ESP_LOGW(TAG, "exhausted nonce range for job %s", work.job_id);
329
    }
330
}
331

332
#ifndef ASIC_BM1370
333
const miner_config_t g_miner_config = {
334
    .init = NULL,
335
    .task_fn = mining_task,
336
    .name = "mining_hw",
337
    .stack_size = 8192,
338
    .priority = 20,
339
    .core = 1,
340
    .extranonce2_roll = false,
341
    .roll_interval_ms = 0,
342
};
343
#endif
344
#endif // ESP_PLATFORM
345

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc