Compare commits

...

10 Commits

Author SHA1 Message Date
7f217df04f feat(miner): scale displayed hashrate units dynamically 2026-03-30 11:47:36 +02:00
b6af554b0c perf(sha256): eliminate double bswap between SHA256d pass1 and pass2
Add sha256_transform_armv8_2way_pass2 which reads the pass1 output state
words directly into MSG0/MSG1 without byte serialization. Previously:
  sha256_state_to_digest() → native uint32 → BE bytes (8x write_u32_be)
  sha256_transform load   → BE bytes → vrev32q_u8 → native uint32 (4x)
These two conversions cancel out. The new path skips both, saving ~52
shift/store/load/vrev ops per 4-nonce group. Also eliminates the two
128-byte block2 stack buffers from sha256d80_hash_4way_armv8_2way.
2026-03-30 11:13:59 +02:00
8709072574 perf(miner): use CLOCK_MONOTONIC_COARSE and counter-based rate reporting
Replace time() syscall with clock_gettime(CLOCK_MONOTONIC_COARSE) and
gate timestamp/rate checks behind a batch counter to avoid clock overhead
on every iteration. Set reporting interval to 2s.
2026-03-30 10:42:40 +02:00
7d4096749a perf(sha256): add ARMv8 2-way interleaved transform and scan_4way_direct
Process two independent SHA256 chains simultaneously to hide the 2-cycle
latency of vsha256hq_u32 on Cortex-A76, approaching full throughput.
Also reduces memcpy from 512 to ~192 bytes per 4-nonce group by reusing
block buffers, and adds scan_4way_direct to bypass pthread_once (LDAR
barrier) on every inner-loop call.
2026-03-30 10:42:17 +02:00
b2f0090236 update gitignore 2026-03-30 09:07:48 +02:00
b700b7b25d bench: align bench_hash to backend 4-way scan path 2026-03-30 09:05:54 +02:00
6be9e3cafd test(sha256): add 100k nonce equivalence and hitmask checks 2026-03-30 09:05:42 +02:00
89dcee8951 build: wire ARM backend objects and keep bench/test/pgo targets 2026-03-30 09:05:10 +02:00
ef320a4397 perf(miner): route nonce scanning through sha256d80_scan_4way 2026-03-30 09:05:06 +02:00
5b4c11f6f0 feat(sha256): add sha256d80 backend API and ARM64 kernel entry 2026-03-30 09:04:57 +02:00
13 changed files with 1923 additions and 106 deletions

25
.gitignore vendored
View File

@@ -1,8 +1,31 @@
# Python/local environment (prototype legacy)
__pycache__/
**/__pycache__/
venv/
.venv/
.pytest_cache/
*.pyc
*.pyo
# Local-only config
miner.conf
config.py
# C/C++ build artifacts
*.o
*.a
*.so
# Binaries produced by Makefile
miner
launcher
miner.conf
bench_hash
test_sha256_backend
test_miner_regression
# Profiling / PGO artifacts
.pgo/
*.gcda
*.gcno
*.profraw
*.profdata

View File

@@ -1,10 +1,22 @@
CC := gcc
CFLAGS := -O3 -march=native -mtune=native -flto -fomit-frame-pointer -DNDEBUG -Wall -Wextra -std=c11 -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L
LDFLAGS := -pthread -lcrypto -lm -flto
PGO_DIR := .pgo
COMMON_OBJS := config.o utils.o json.o rpc.o types.o block_builder.o miner.o mining_loop.o
SHA256_BACKEND_OBJS := sha256/sha256_backend.o
HAS_ARM_CRYPTO := $(shell echo | $(CC) $(CFLAGS) -dM -E - 2>/dev/null | grep -c __ARM_FEATURE_CRYPTO)
ifeq ($(shell uname -m),aarch64)
ifneq ($(HAS_ARM_CRYPTO),0)
SHA256_BACKEND_OBJS += sha256/sha256d80_4way_aarch64.o
endif
endif
.PHONY: all clean
COMMON_OBJS := config.o utils.o json.o rpc.o types.o block_builder.o miner.o mining_loop.o $(SHA256_BACKEND_OBJS)
BENCH_BIN := bench_hash
TEST_SHA_BIN := test_sha256_backend
TEST_MINER_BIN := test_miner_regression
.PHONY: all clean bench bench-5x test pgo-gen pgo-use pgo-clean
all: miner launcher
@@ -18,4 +30,39 @@ launcher: launcher.o $(COMMON_OBJS)
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o miner launcher
rm -f *.o sha256/*.o bench/*.o tests/*.o miner launcher $(BENCH_BIN) $(TEST_SHA_BIN) $(TEST_MINER_BIN)
bench: $(BENCH_BIN)
$(BENCH_BIN): bench/bench_hash.o $(SHA256_BACKEND_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
bench-5x: $(BENCH_BIN)
@i=1; while [ $$i -le 5 ]; do \
echo "Run $$i/5"; \
./$(BENCH_BIN) 60; \
i=$$((i+1)); \
done
$(TEST_SHA_BIN): tests/test_sha256_backend.o $(SHA256_BACKEND_OBJS) utils.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
$(TEST_MINER_BIN): tests/test_miner_regression.o miner.o $(SHA256_BACKEND_OBJS) utils.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test: $(TEST_SHA_BIN) $(TEST_MINER_BIN)
./$(TEST_SHA_BIN)
./$(TEST_MINER_BIN)
pgo-gen: CFLAGS += -fprofile-generate=$(PGO_DIR)
pgo-gen: LDFLAGS += -fprofile-generate=$(PGO_DIR)
pgo-gen: clean all
@echo "Profilazione build pronta."
@echo "Esegui il tuo workload manualmente, poi lancia: make pgo-use"
pgo-use: CFLAGS += -fprofile-use=$(PGO_DIR) -fprofile-correction
pgo-use: LDFLAGS += -fprofile-use=$(PGO_DIR)
pgo-use: clean all
pgo-clean:
rm -rf $(PGO_DIR)

59
bench/bench_hash.c Normal file
View File

@@ -0,0 +1,59 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../sha256/sha256_backend.h"
static double now_seconds(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}
int main(int argc, char **argv) {
uint8_t header_76[76];
sha256d80_midstate_t mid;
sha256_state_t states[4];
uint32_t target_words[8];
uint32_t nonce = 0;
uint64_t hashes = 0;
volatile uint32_t sink = 0;
double seconds = 10.0;
double t0;
int i;
if (argc > 1) {
seconds = strtod(argv[1], NULL);
if (seconds <= 0.0) {
seconds = 10.0;
}
}
for (i = 0; i < 76; i++) {
header_76[i] = (uint8_t)(i * 13 + 7);
}
for (i = 0; i < 8; i++) {
target_words[i] = 0xFFFFFFFFU;
}
sha256d80_midstate_init(&mid, header_76);
t0 = now_seconds();
while ((now_seconds() - t0) < seconds) {
uint32_t mask = sha256d80_scan_4way(&mid, nonce, target_words, states);
sink ^= states[0].h[0] ^ states[1].h[0] ^ states[2].h[0] ^ states[3].h[0] ^ mask;
nonce += 4U;
hashes += 4U;
}
{
double elapsed = now_seconds() - t0;
double khs = (double)hashes / elapsed / 1000.0;
printf("bench_hash: %.2f kH/s | hashes=%llu | sink=%08x\n", khs, (unsigned long long)hashes, sink);
}
return 0;
}

View File

@@ -53,6 +53,7 @@ static void dashboard_print(DashboardState *st, int n) {
long long total_attempts = 0;
time_t now_t = time(NULL);
char ts[64];
char total_rate_buf[32];
if (st->lines_printed > 0) {
clear_lines(st->lines_printed);
@@ -64,13 +65,16 @@ static void dashboard_print(DashboardState *st, int n) {
}
strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", localtime(&now_t));
format_hashrate_khs(total_rate, total_rate_buf, sizeof(total_rate_buf));
printf("%s | MINING STATUS\n", ts);
printf("========================================\n");
printf("Total: %.2f kH/s | Attempts: %lld\n", total_rate, total_attempts);
printf("Total: %s | Attempts: %lld\n", total_rate_buf, total_attempts);
printf("----------------------------------------\n");
for (i = 0; i < n; i++) {
printf("Worker %-2d: %.2f kH/s | Attempts: %lld\n", i, st->rates[i], st->attempts[i]);
char worker_rate_buf[32];
format_hashrate_khs(st->rates[i], worker_rate_buf, sizeof(worker_rate_buf));
printf("Worker %-2d: %s | Attempts: %lld\n", i, worker_rate_buf, st->attempts[i]);
}
st->lines_printed = 4 + n;
@@ -228,6 +232,8 @@ static int aggregate_loop(WorkerProc *workers, int n) {
double elapsed = now_seconds() - st.start_t;
long long total_attempts = 0;
double avg_rate;
char winner_rate_buf[32];
char avg_rate_buf[32];
if (st.lines_printed > 0) {
clear_lines(st.lines_printed);
@@ -238,6 +244,7 @@ static int aggregate_loop(WorkerProc *workers, int n) {
}
avg_rate = (elapsed > 0.0) ? (double)total_attempts / elapsed / 1000.0 : 0.0;
format_hashrate_khs(avg_rate, avg_rate_buf, sizeof(avg_rate_buf));
printf("==============================================================================\n");
printf("[OK] BLOCK FOUND AND SUBMITTED\n");
@@ -246,9 +253,10 @@ static int aggregate_loop(WorkerProc *workers, int n) {
printf(" Worker: %d\n", st.winner_idx);
}
if (st.has_winner_rate) {
printf(" Worker hashrate: %.2f kH/s\n", st.winner_rate);
format_hashrate_khs(st.winner_rate, winner_rate_buf, sizeof(winner_rate_buf));
printf(" Worker hashrate: %s\n", winner_rate_buf);
}
printf(" Average total hashrate: %.2f kH/s\n", avg_rate);
printf(" Average total hashrate: %s\n", avg_rate_buf);
printf(" Total attempts: %lld\n", total_attempts);
printf("==============================================================================\n");

210
miner.c
View File

@@ -1,99 +1,106 @@
#include "miner.h"
#include <openssl/sha.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stddef.h>
#include "sha256/sha256_backend.h"
#include "utils.h"
static const double RATE_INTERVAL_SEC = 5.0;
static const double RATE_INTERVAL_SEC = 2.0;
static int hash_meets_target(const uint8_t digest[32], const uint8_t target_be[32]) {
int i;
for (i = 0; i < 32; i++) {
uint8_t hb = digest[31 - i];
if (hb < target_be[i]) {
return 1;
}
if (hb > target_be[i]) {
return 0;
}
}
return 1;
static inline uint32_t load_u32_be(const uint8_t *p) {
return ((uint32_t)p[0] << 24) |
((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) |
(uint32_t)p[3];
}
static void write_u32_be(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)((v >> 24) & 0xFF);
dst[1] = (uint8_t)((v >> 16) & 0xFF);
dst[2] = (uint8_t)((v >> 8) & 0xFF);
dst[3] = (uint8_t)(v & 0xFF);
}
static void sha256_ctx_to_digest(const SHA256_CTX *ctx, uint8_t out[32]) {
static void target_words_from_be(const uint8_t target_be[32], uint32_t out[8]) {
int i;
for (i = 0; i < 8; i++) {
write_u32_be(out + i * 4, ctx->h[i]);
out[i] = load_u32_be(target_be + i * 4);
}
}
static inline void write_u32_le(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFFU);
dst[1] = (uint8_t)((v >> 8) & 0xFFU);
dst[2] = (uint8_t)((v >> 16) & 0xFFU);
dst[3] = (uint8_t)((v >> 24) & 0xFFU);
}
static int compute_hash_batch(
const uint8_t header_76[76],
uint32_t start_nonce,
uint32_t batch_size,
const uint8_t target_be[32],
const uint32_t target_words[8],
uint32_t *found_nonce,
uint8_t found_digest[32]
) {
SHA256_CTX init_ctx;
SHA256_CTX first_chunk_ctx;
uint8_t block1[64];
uint8_t block2[64];
uint8_t digest1[32];
uint8_t digest2[32];
uint32_t i;
sha256d80_midstate_t mid;
uint32_t i = 0;
sha256d80_midstate_init(&mid, header_76);
/* Backend is now initialized; use direct scan to skip per-call pthread_once */
sha256_backend_ensure_init();
SHA256_Init(&init_ctx);
first_chunk_ctx = init_ctx;
SHA256_Transform(&first_chunk_ctx, header_76);
while ((batch_size - i) >= 4U) {
uint32_t n0 = start_nonce + i;
sha256_state_t st1[4];
uint32_t mask = sha256d80_scan_4way_direct(&mid, n0, target_words, st1);
memset(block1, 0, sizeof(block1));
memcpy(block1, header_76 + 64, 12);
block1[16] = 0x80;
block1[62] = 0x02;
block1[63] = 0x80;
if (mask & 1U) {
*found_nonce = n0;
sha256_state_to_digest(&st1[0], found_digest);
return 1;
}
if (mask & 2U) {
*found_nonce = n0 + 1U;
sha256_state_to_digest(&st1[1], found_digest);
return 1;
}
if (mask & 4U) {
*found_nonce = n0 + 2U;
sha256_state_to_digest(&st1[2], found_digest);
return 1;
}
if (mask & 8U) {
*found_nonce = n0 + 3U;
sha256_state_to_digest(&st1[3], found_digest);
return 1;
}
memset(block2, 0, sizeof(block2));
block2[32] = 0x80;
block2[62] = 0x01;
block2[63] = 0x00;
i += 4U;
}
for (i = 0; i < batch_size; i++) {
SHA256_CTX ctx1;
SHA256_CTX ctx2;
uint32_t n = start_nonce + i;
if (i < batch_size) {
uint32_t n0 = start_nonce + i;
uint32_t remain = batch_size - i;
uint32_t valid_mask = (1U << remain) - 1U;
sha256_state_t st1[4];
uint32_t mask = sha256d80_scan_4way(&mid, n0, target_words, st1) & valid_mask;
block1[12] = (uint8_t)(n & 0xFF);
block1[13] = (uint8_t)((n >> 8) & 0xFF);
block1[14] = (uint8_t)((n >> 16) & 0xFF);
block1[15] = (uint8_t)((n >> 24) & 0xFF);
ctx1 = first_chunk_ctx;
SHA256_Transform(&ctx1, block1);
sha256_ctx_to_digest(&ctx1, digest1);
memcpy(block2, digest1, 32);
ctx2 = init_ctx;
SHA256_Transform(&ctx2, block2);
sha256_ctx_to_digest(&ctx2, digest2);
if (hash_meets_target(digest2, target_be)) {
*found_nonce = n;
memcpy(found_digest, digest2, 32);
if (mask & 1U) {
*found_nonce = n0;
sha256_state_to_digest(&st1[0], found_digest);
return 1;
}
if (mask & 2U) {
*found_nonce = n0 + 1U;
sha256_state_to_digest(&st1[1], found_digest);
return 1;
}
if (mask & 4U) {
*found_nonce = n0 + 2U;
sha256_state_to_digest(&st1[2], found_digest);
return 1;
}
if (mask & 8U) {
*found_nonce = n0 + 3U;
sha256_state_to_digest(&st1[3], found_digest);
return 1;
}
}
@@ -101,13 +108,6 @@ static int compute_hash_batch(
return 0;
}
static void write_u32_le(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFF);
dst[1] = (uint8_t)((v >> 8) & 0xFF);
dst[2] = (uint8_t)((v >> 16) & 0xFF);
dst[3] = (uint8_t)((v >> 24) & 0xFF);
}
int mine_block(
uint8_t header_76[76],
const char *target_hex,
@@ -120,12 +120,17 @@ int mine_block(
MineResult *out
) {
uint8_t target_be[32];
uint32_t target_words[8];
uint32_t nonce;
long long attempts = 0;
time_t t_start;
struct timespec ts_start, ts_now;
double last_rate_t;
long long last_rate_n = 0;
double last_tsu;
uint32_t batch_count = 0;
/* Check timestamp update every 32 batches, rate every 32 batches (~1s) */
#define TSU_CHECK_MASK 31U
#define RATE_CHECK_MASK 31U
memset(out, 0, sizeof(*out));
@@ -133,6 +138,7 @@ int mine_block(
fprintf(stderr, "[miner] target hex non valido\n");
return 0;
}
target_words_from_be(target_be, target_words);
if (strcmp(nonce_mode, "incremental") == 0) {
nonce = 0;
@@ -140,30 +146,34 @@ int mine_block(
nonce = (uint32_t)rand();
}
t_start = time(NULL);
last_rate_t = (double)t_start;
last_tsu = (double)t_start;
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts_start);
last_rate_t = (double)ts_start.tv_sec + (double)ts_start.tv_nsec * 1e-9;
last_tsu = last_rate_t;
while (1) {
uint32_t found_nonce = 0;
uint8_t found_digest[32];
time_t now_t;
double now;
if (atomic_load(stop_flag) != 0) {
return 0;
}
now_t = time(NULL);
now = (double)now_t;
batch_count++;
if (timestamp_update_interval > 0 && (now - last_tsu) >= (double)timestamp_update_interval) {
write_u32_le(header_76 + 68, (uint32_t)now_t);
last_tsu = now;
/* Timestamp update: check every 64 batches to reduce clock calls */
if (timestamp_update_interval > 0 && (batch_count & TSU_CHECK_MASK) == 0) {
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts_now);
double now = (double)ts_now.tv_sec + (double)ts_now.tv_nsec * 1e-9;
if (now - last_tsu >= (double)timestamp_update_interval) {
write_u32_le(header_76 + 68, (uint32_t)ts_now.tv_sec);
last_tsu = now;
}
}
if (compute_hash_batch(header_76, nonce, batch_size, target_be, &found_nonce, found_digest)) {
double total = now - (double)t_start;
if (compute_hash_batch(header_76, nonce, batch_size, target_words, &found_nonce, found_digest)) {
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts_now);
double total = ((double)ts_now.tv_sec + (double)ts_now.tv_nsec * 1e-9)
- ((double)ts_start.tv_sec + (double)ts_start.tv_nsec * 1e-9);
if (total <= 0.0) {
total = 1e-6;
}
@@ -182,18 +192,24 @@ int mine_block(
attempts += batch_size;
nonce += batch_size;
now = (double)time(NULL);
if ((now - last_rate_t) >= RATE_INTERVAL_SEC) {
double dt = now - last_rate_t;
if (dt <= 0.0) {
dt = 1e-6;
/* Rate reporting: check every 128 batches */
if ((batch_count & RATE_CHECK_MASK) == 0) {
clock_gettime(CLOCK_MONOTONIC_COARSE, &ts_now);
double now = (double)ts_now.tv_sec + (double)ts_now.tv_nsec * 1e-9;
if (now - last_rate_t >= RATE_INTERVAL_SEC) {
double dt = now - last_rate_t;
if (dt <= 0.0) {
dt = 1e-6;
}
if (status_cb != NULL) {
double hr = (double)(attempts - last_rate_n) / dt;
status_cb(attempts, hr, status_ctx);
}
last_rate_t = now;
last_rate_n = attempts;
}
if (status_cb != NULL) {
double hr = (double)(attempts - last_rate_n) / dt;
status_cb(attempts, hr, status_ctx);
}
last_rate_t = now;
last_rate_n = attempts;
}
}
#undef TSU_CHECK_MASK
#undef RATE_CHECK_MASK
}

View File

@@ -53,6 +53,7 @@ static void emit_eventf(int fd, const char *fmt, ...) {
static void status_callback(long long attempts, double hashrate_hz, void *ctx_ptr) {
StatusCtx *ctx = (StatusCtx *)ctx_ptr;
char rate_buf[32];
if (ctx->event_fd >= 0) {
emit_eventf(ctx->event_fd, "status %d %.6f %lld\n", ctx->worker_idx, hashrate_hz / 1000.0, attempts);
@@ -60,7 +61,8 @@ static void status_callback(long long attempts, double hashrate_hz, void *ctx_pt
}
if (ctx->single_mode) {
printf("\r[main] hashrate: %.2f kH/s | attempts: %lld", hashrate_hz / 1000.0, attempts);
format_hashrate_hz(hashrate_hz, rate_buf, sizeof(rate_buf));
printf("\r[main] hashrate: %s | attempts: %lld", rate_buf, attempts);
fflush(stdout);
}
}

1046
sha256/sha256_backend.c Normal file

File diff suppressed because it is too large Load Diff

83
sha256/sha256_backend.h Normal file
View File

@@ -0,0 +1,83 @@
#ifndef SHA256_BACKEND_H
#define SHA256_BACKEND_H
#include <stdint.h>
/*
* Compact SHA256 state: only the 8 chaining words (32 bytes).
* Avoids copying the bloated OpenSSL SHA256_CTX (~112 bytes) in the hot loop.
*/
typedef struct {
uint32_t h[8];
} sha256_state_t;
typedef struct {
sha256_state_t init_state;
sha256_state_t first_chunk_state;
uint8_t block1_template[64];
uint8_t block2_template[64];
} sha256d80_midstate_t;
/* Set state to SHA256 initial values (IV). */
void sha256_state_init(sha256_state_t *state);
/* Serialize the 8 state words into a 32-byte big-endian digest. */
void sha256_state_to_digest(const sha256_state_t *state, uint8_t out[32]);
/* Single SHA256 block compression (64-byte block). */
void sha256_transform_fast(sha256_state_t *state, const uint8_t block[64]);
/*
* 2-way interleaved SHA256 block compression.
* Processes two independent (state, block) pairs so the CPU can overlap both
* instruction chains. On non-ARM builds falls back to two sequential calls.
*/
void sha256_transform_fast_2way(
sha256_state_t *stA, const uint8_t blkA[64],
sha256_state_t *stB, const uint8_t blkB[64]
);
/* Prepare SHA256d(80-byte header) midstate and constant blocks from header[0..75]. */
void sha256d80_midstate_init(sha256d80_midstate_t *mid, const uint8_t header_76[76]);
/*
* Hash 4 consecutive nonces with SHA256d(header80).
* start_nonce lane order: [n, n+1, n+2, n+3].
*/
void sha256d80_hash_4way(
const sha256d80_midstate_t *mid,
uint32_t start_nonce,
sha256_state_t out_states[4]
);
/*
* Hash 4 consecutive nonces and return hit mask against target words.
* target_words are big-endian words target[0..7].
* bit i set => lane i meets target.
*/
uint32_t sha256d80_scan_4way(
const sha256d80_midstate_t *mid,
uint32_t start_nonce,
const uint32_t target_words[8],
sha256_state_t out_states[4]
);
/*
* Ensure the SHA256 backend is initialized. Call once before using
* sha256d80_scan_4way_direct() to avoid per-call pthread_once overhead.
*/
void sha256_backend_ensure_init(void);
/*
* Like sha256d80_scan_4way() but skips the pthread_once check.
* Caller MUST have called sha256_backend_ensure_init() (or any other
* backend function) before calling this.
*/
uint32_t sha256d80_scan_4way_direct(
const sha256d80_midstate_t *mid,
uint32_t start_nonce,
const uint32_t target_words[8],
sha256_state_t out_states[4]
);
#endif

View File

@@ -0,0 +1,9 @@
.text
.align 2
.global sha256d80_4way_aarch64_kernel
.type sha256d80_4way_aarch64_kernel, %function
sha256d80_4way_aarch64_kernel:
b sha256d80_4way_aarch64_impl
.size sha256d80_4way_aarch64_kernel, .-sha256d80_4way_aarch64_kernel

View File

@@ -0,0 +1,214 @@
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../miner.h"
#include "../sha256/sha256_backend.h"
#include "../utils.h"
static inline void write_u32_le(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFFU);
dst[1] = (uint8_t)((v >> 8) & 0xFFU);
dst[2] = (uint8_t)((v >> 16) & 0xFFU);
dst[3] = (uint8_t)((v >> 24) & 0xFFU);
}
static void sha256d_from_header76_nonce(const uint8_t header76[76], uint32_t nonce, uint8_t out[32]) {
uint8_t header80[80];
uint8_t block1[64];
uint8_t block2[64];
sha256_state_t init_state;
sha256_state_t st1;
sha256_state_t st2;
memcpy(header80, header76, 76);
write_u32_le(header80 + 76, nonce);
sha256_state_init(&init_state);
st1 = init_state;
sha256_transform_fast(&st1, header80);
memset(block1, 0, sizeof(block1));
memcpy(block1, header80 + 64, 16);
block1[16] = 0x80;
block1[62] = 0x02;
block1[63] = 0x80;
sha256_transform_fast(&st1, block1);
memset(block2, 0, sizeof(block2));
sha256_state_to_digest(&st1, block2);
block2[32] = 0x80;
block2[62] = 0x01;
block2[63] = 0x00;
st2 = init_state;
sha256_transform_fast(&st2, block2);
sha256_state_to_digest(&st2, out);
}
static void digest_rev(const uint8_t digest[32], uint8_t rev[32]) {
int i;
for (i = 0; i < 32; i++) {
rev[i] = digest[31 - i];
}
}
static int find_record_nonce(
const uint8_t header76[76],
int parity,
uint32_t min_nonce,
uint32_t max_nonce,
uint32_t *out_nonce,
uint8_t out_target_be[32]
) {
uint8_t best[32];
uint32_t n;
memset(best, 0xFF, sizeof(best));
for (n = 0; n <= max_nonce; n++) {
uint8_t digest[32];
uint8_t rev[32];
sha256d_from_header76_nonce(header76, n, digest);
digest_rev(digest, rev);
if (memcmp(rev, best, 32) < 0) {
memcpy(best, rev, 32);
if (n >= min_nonce && ((int)(n & 1U) == parity)) {
*out_nonce = n;
memcpy(out_target_be, rev, 32);
return 1;
}
}
}
return 0;
}
static int run_case(uint32_t batch, int parity) {
uint8_t header76[76];
uint8_t target_be[32];
uint8_t expected_digest[32];
MineResult out;
atomic_int stop_flag;
uint32_t expected_nonce;
char *target_hex;
int rc;
int i;
for (i = 0; i < 76; i++) {
header76[i] = (uint8_t)((i * 29 + 11) & 0xFF);
}
if (!find_record_nonce(header76, parity, 4U, 150000U, &expected_nonce, target_be)) {
fprintf(stderr, "[test_miner_regression] no record nonce found for parity=%d\n", parity);
return 0;
}
target_hex = bytes_to_hex(target_be, 32);
if (target_hex == NULL) {
return 0;
}
atomic_init(&stop_flag, 0);
rc = mine_block(
header76,
target_hex,
"incremental",
batch,
0,
&stop_flag,
NULL,
NULL,
&out
);
free(target_hex);
if (rc == 0 || out.found == 0) {
fprintf(stderr, "[test_miner_regression] mine_block failed for batch=%u\n", batch);
return 0;
}
if (out.nonce != expected_nonce) {
fprintf(stderr, "[test_miner_regression] nonce mismatch batch=%u expected=%u got=%u\n",
batch, expected_nonce, out.nonce);
return 0;
}
sha256d_from_header76_nonce(header76, out.nonce, expected_digest);
if (memcmp(expected_digest, out.digest, 32) != 0) {
fprintf(stderr, "[test_miner_regression] digest mismatch for nonce=%u\n", out.nonce);
return 0;
}
return 1;
}
static int test_easy_target_finds_zero(void) {
uint8_t header76[76];
char *target_hex = NULL;
MineResult out;
atomic_int stop_flag;
int i;
for (i = 0; i < 76; i++) {
header76[i] = (uint8_t)((i * 7 + 3) & 0xFF);
}
target_hex = bytes_to_hex((const uint8_t[32]){
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
}, 32);
if (target_hex == NULL) {
return 0;
}
atomic_init(&stop_flag, 0);
if (!mine_block(header76, target_hex, "incremental", 1, 0, &stop_flag, NULL, NULL, &out)) {
free(target_hex);
return 0;
}
free(target_hex);
return out.found == 1 && out.nonce == 0U;
}
static int test_wrap_arithmetic(void) {
uint32_t start = 0xFFFFFFFEU;
uint32_t n0 = start + 0U;
uint32_t n1 = start + 1U;
uint32_t n2 = start + 2U;
uint32_t n3 = start + 3U;
return n0 == 0xFFFFFFFEU &&
n1 == 0xFFFFFFFFU &&
n2 == 0x00000000U &&
n3 == 0x00000001U;
}
int main(void) {
if (!test_easy_target_finds_zero()) {
fprintf(stderr, "test_easy_target_finds_zero: FAIL\n");
return 1;
}
if (!run_case(5U, 1)) {
fprintf(stderr, "run_case(batch=5, parity=1): FAIL\n");
return 1;
}
if (!run_case(6U, 0)) {
fprintf(stderr, "run_case(batch=6, parity=0): FAIL\n");
return 1;
}
if (!test_wrap_arithmetic()) {
fprintf(stderr, "test_wrap_arithmetic: FAIL\n");
return 1;
}
printf("test_miner_regression: OK\n");
return 0;
}

266
tests/test_sha256_backend.c Normal file
View File

@@ -0,0 +1,266 @@
#include <openssl/sha.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../sha256/sha256_backend.h"
#include "../utils.h"
static uint32_t xorshift32(uint32_t *s) {
uint32_t x = *s;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*s = x;
return x;
}
static inline void write_u32_le(uint8_t *dst, uint32_t v) {
dst[0] = (uint8_t)(v & 0xFFU);
dst[1] = (uint8_t)((v >> 8) & 0xFFU);
dst[2] = (uint8_t)((v >> 16) & 0xFFU);
dst[3] = (uint8_t)((v >> 24) & 0xFFU);
}
static void fill_random(uint8_t *dst, size_t n, uint32_t *seed) {
size_t i;
for (i = 0; i < n; i++) {
dst[i] = (uint8_t)(xorshift32(seed) & 0xFFU);
}
}
static void backend_double_sha256_80(const uint8_t header80[80], uint8_t out[32]) {
sha256_state_t init_state;
sha256_state_t st1;
sha256_state_t st2;
uint8_t block1[64];
uint8_t block2[64];
sha256_state_init(&init_state);
st1 = init_state;
sha256_transform_fast(&st1, header80);
memset(block1, 0, sizeof(block1));
memcpy(block1, header80 + 64, 16);
block1[16] = 0x80;
block1[62] = 0x02;
block1[63] = 0x80;
sha256_transform_fast(&st1, block1);
memset(block2, 0, sizeof(block2));
sha256_state_to_digest(&st1, block2);
block2[32] = 0x80;
block2[62] = 0x01;
block2[63] = 0x00;
st2 = init_state;
sha256_transform_fast(&st2, block2);
sha256_state_to_digest(&st2, out);
}
static int test_transform_equivalence_random(void) {
uint32_t seed = 0x12345678U;
int i;
for (i = 0; i < 10000; i++) {
uint8_t block[64];
SHA256_CTX ref;
sha256_state_t test;
fill_random(block, sizeof(block), &seed);
SHA256_Init(&ref);
sha256_state_init(&test);
SHA256_Transform(&ref, block);
sha256_transform_fast(&test, block);
if (memcmp(ref.h, test.h, sizeof(ref.h)) != 0) {
fprintf(stderr, "[test_sha256_backend] transform mismatch at iter=%d\n", i);
return 0;
}
}
return 1;
}
static int test_transform_2way_equivalence(void) {
uint32_t seed = 0xCAFEBABEU;
int i;
for (i = 0; i < 5000; i++) {
uint8_t block_a[64];
uint8_t block_b[64];
SHA256_CTX ref_a;
SHA256_CTX ref_b;
sha256_state_t test_a;
sha256_state_t test_b;
fill_random(block_a, sizeof(block_a), &seed);
fill_random(block_b, sizeof(block_b), &seed);
SHA256_Init(&ref_a);
SHA256_Init(&ref_b);
sha256_state_init(&test_a);
sha256_state_init(&test_b);
SHA256_Transform(&ref_a, block_a);
SHA256_Transform(&ref_b, block_b);
sha256_transform_fast_2way(&test_a, block_a, &test_b, block_b);
if (memcmp(ref_a.h, test_a.h, sizeof(ref_a.h)) != 0 ||
memcmp(ref_b.h, test_b.h, sizeof(ref_b.h)) != 0) {
fprintf(stderr, "[test_sha256_backend] 2way mismatch at iter=%d\n", i);
return 0;
}
}
return 1;
}
static int test_double_sha256_80_equivalence(void) {
uint32_t seed = 0xA55AA55AU;
int i;
for (i = 0; i < 2048; i++) {
uint8_t header80[80];
uint8_t ref[32];
uint8_t test[32];
fill_random(header80, sizeof(header80), &seed);
double_sha256(header80, sizeof(header80), ref);
backend_double_sha256_80(header80, test);
if (memcmp(ref, test, sizeof(ref)) != 0) {
fprintf(stderr, "[test_sha256_backend] sha256d80 mismatch at iter=%d\n", i);
return 0;
}
}
return 1;
}
static int test_sha256d80_4way_100k_nonces(void) {
uint8_t header76[76];
sha256d80_midstate_t mid;
uint32_t base = 0x12340000U;
int i;
for (i = 0; i < 76; i++) {
header76[i] = (uint8_t)((i * 19 + 5) & 0xFF);
}
sha256d80_midstate_init(&mid, header76);
for (i = 0; i < 25000; i++) {
uint32_t start_nonce = base + (uint32_t)(i * 4);
sha256_state_t states[4];
int lane;
sha256d80_hash_4way(&mid, start_nonce, states);
for (lane = 0; lane < 4; lane++) {
uint8_t header80[80];
uint8_t ref[32];
uint8_t got[32];
uint32_t nonce = start_nonce + (uint32_t)lane;
memcpy(header80, header76, 76);
write_u32_le(header80 + 76, nonce);
double_sha256(header80, sizeof(header80), ref);
sha256_state_to_digest(&states[lane], got);
if (memcmp(ref, got, 32) != 0) {
fprintf(stderr, "[test_sha256_backend] 4way nonce mismatch nonce=%u lane=%d\n", nonce, lane);
return 0;
}
}
}
return 1;
}
static int test_sha256d80_scan_4way_direct(void) {
uint8_t header76[76];
sha256d80_midstate_t mid;
sha256_state_t st_ref[4];
sha256_state_t st_direct[4];
uint32_t all_max[8];
uint32_t mask_ref;
uint32_t mask_direct;
int i;
for (i = 0; i < 76; i++) {
header76[i] = (uint8_t)((i * 13 + 7) & 0xFF);
}
for (i = 0; i < 8; i++) {
all_max[i] = 0xFFFFFFFFU;
}
sha256d80_midstate_init(&mid, header76);
sha256_backend_ensure_init();
mask_ref = sha256d80_scan_4way(&mid, 0x11223344U, all_max, st_ref);
mask_direct = sha256d80_scan_4way_direct(&mid, 0x11223344U, all_max, st_direct);
if (mask_ref != mask_direct) {
fprintf(stderr, "[test_sha256_backend] scan_4way_direct mask mismatch ref=%u direct=%u\n",
mask_ref, mask_direct);
return 0;
}
if (memcmp(st_ref, st_direct, sizeof(st_ref)) != 0) {
fprintf(stderr, "[test_sha256_backend] scan_4way_direct state mismatch\n");
return 0;
}
return 1;
}
static int test_sha256d80_scan_hitmask_basic(void) {
uint8_t header76[76];
sha256d80_midstate_t mid;
sha256_state_t states[4];
uint32_t all_max[8];
uint32_t all_zero[8];
uint32_t mask_max;
uint32_t mask_zero;
int i;
for (i = 0; i < 76; i++) {
header76[i] = (uint8_t)((i * 11 + 1) & 0xFF);
}
for (i = 0; i < 8; i++) {
all_max[i] = 0xFFFFFFFFU;
all_zero[i] = 0x00000000U;
}
sha256d80_midstate_init(&mid, header76);
mask_max = sha256d80_scan_4way(&mid, 0xABC00000U, all_max, states);
mask_zero = sha256d80_scan_4way(&mid, 0xABC00000U, all_zero, states);
return mask_max == 0xFU && mask_zero == 0U;
}
int main(void) {
if (!test_transform_equivalence_random()) {
return 1;
}
if (!test_transform_2way_equivalence()) {
return 1;
}
if (!test_double_sha256_80_equivalence()) {
return 1;
}
if (!test_sha256d80_4way_100k_nonces()) {
return 1;
}
if (!test_sha256d80_scan_4way_direct()) {
return 1;
}
if (!test_sha256d80_scan_hitmask_basic()) {
return 1;
}
printf("test_sha256_backend: OK\n");
return 0;
}

42
utils.c
View File

@@ -15,6 +15,48 @@ static char to_hex_digit(unsigned int v) {
return (char)('a' + (v - 10U));
}
static void format_hashrate_core(double rate_hs, char *out, size_t out_len) {
static const char *units[] = {"H/s", "kH/s", "MH/s", "GH/s", "TH/s", "PH/s", "EH/s"};
size_t unit_idx = 0;
const size_t unit_count = sizeof(units) / sizeof(units[0]);
double value;
if (out == NULL || out_len == 0) {
return;
}
if (!(rate_hs >= 0.0)) {
rate_hs = 0.0;
}
value = rate_hs;
while (value >= 999.95 && unit_idx + 1 < unit_count) {
value /= 1000.0;
unit_idx++;
}
while (value > 0.0 && value < 1.0 && unit_idx > 0) {
value *= 1000.0;
unit_idx--;
}
if (value >= 100.0) {
snprintf(out, out_len, "%.0f %s", value, units[unit_idx]);
} else if (value >= 10.0) {
snprintf(out, out_len, "%.1f %s", value, units[unit_idx]);
} else {
snprintf(out, out_len, "%.2f %s", value, units[unit_idx]);
}
}
void format_hashrate_hz(double rate_hz, char *out, size_t out_len) {
format_hashrate_core(rate_hz, out, out_len);
}
void format_hashrate_khs(double rate_khs, char *out, size_t out_len) {
format_hashrate_core(rate_khs * 1000.0, out, out_len);
}
void sb_init(StrBuf *sb) {
sb->data = NULL;
sb->len = 0;

View File

@@ -33,5 +33,7 @@ char *decode_nbits_hex(uint32_t nbits);
char *calculate_target_hex(const char *bits_hex, double difficulty_factor, const char *network);
char *hex_add_width(const char *base_hex, int add_value);
void format_hashrate_hz(double rate_hz, char *out, size_t out_len);
void format_hashrate_khs(double rate_khs, char *out, size_t out_len);
#endif