From e366ceb336550a72c76efea4c98a2a08cca27bce Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 May 2026 14:25:18 +0530 Subject: feat(mining): Bitcoin mining-for-bandwidth payment system New modules: - mining_payment.c/h: hashprice calc (nbits->difficulty->sat/GH/s/day), share validation, client stats, allotment conversion (ms + bytes) - stratum_client.c/h: SV1 upstream pool connection (subscribe/authorize/submit) - stratum_proxy.c/h: Local SV1 TCP server for downstream miners, job broadcast - sw_miner.c/h: Software SHA256d miner (ESP32 CPU fallback) - asic_miner.c/h: ASIC detection stub (BM1366/BM1368 SPI) Config: - config.h/c: mining_payout_mode_t enum (auto/pool/upstream/proxy_only), stratum pool settings, mining port, hashprice override, sandbox mint access - Defaults fill nostr_seed_relays (8/8) and nostr_relays (4/4) with fast relays Integration into existing modules: - session.h/c: payment_method_t enum (CASHU/MINING/BYTES) - firewall.h/c: firewall_set_mining_port(), firewall_set_sandbox_mint_access() - tollgate_api.c: GET /mining/job, POST /mining/share, GET /mining/stats - tollgate_client.h/c: TG_CLIENT_MINING state, mining discovery tag parsing - tollgate_main.c: mining init in start_services(), stratum_client_tick() in loop - captive_portal.c: tabbed Cashu/Mine UI with live hashrate polling Unit tests (69 new assertions across 4 suites): - test_mining_payment (23 tests): nbits->difficulty, hashprice, client stats, allotment - test_stratum_proxy (21 tests): job set/get, stats, type validation - test_session_payment_method (12 tests): PAYMENT_METHOD enum, bytes/cashu methods - test_tollgate_client_mining (20 tests): mining tag parsing, discovery struct - test_firewall_sandbox (16 tests): client grant/revoke, max clients, setters Enhanced test stubs: - BaseType_t/pdPASS in freertos/task.h - lwip: sockets.h, etharp.h, prot/ip.h, prot/ip4.h, prot/tcp.h, netif.h - dns_server.h, esp_wifi_ap_get_sta_list.h Build fixes: - cvm_server.c: replace esp_timer_get_time() with xTaskGetTickCount(), fix process_relay_message() 3-arg call to 2-arg, add WS keepalive ping - stratum_proxy.c: widen task_name buffer 16->20 - sw_miner.c: add missing #include esp_random.h - nucula_src: save_proofs() moved to public in wallet.hpp Nostr relay updates: - nostr_seed_relays: +relay.anzenkodo.workers.dev, +nostr.koning-degraaf.nl, +knostr.neutrine.com, +nostr.einundzwanzig.space (8/8 slots) - nostr_relays: +relay.anzenkodo.workers.dev, +nostr.koning-degraaf.nl (4/4 slots) Squash-merge of feature/mining-payment (5 commits: c75230e..9d98ba1) --- tests/unit/Makefile | 17 ++++- tests/unit/stubs/dns_server.h | 11 +++ tests/unit/stubs/esp_wifi_ap_get_sta_list.h | 38 ++++++++++ tests/unit/stubs/freertos/task.h | 7 +- tests/unit/stubs/lwip/etharp.h | 22 ++++++ tests/unit/stubs/lwip/ip4_addr.h | 3 + tests/unit/stubs/lwip/lwip_napt.h | 6 ++ tests/unit/stubs/lwip/napt.h | 6 -- tests/unit/stubs/lwip/netif.h | 16 +++++ tests/unit/stubs/lwip/prot/ip.h | 20 ++++++ tests/unit/stubs/lwip/prot/ip4.h | 6 ++ tests/unit/stubs/lwip/prot/tcp.h | 13 ++++ tests/unit/stubs/lwip/sockets.h | 6 ++ tests/unit/test_beacon_price | Bin 0 -> 34808 bytes tests/unit/test_cvm_server | Bin 0 -> 45720 bytes tests/unit/test_display | Bin 0 -> 24816 bytes tests/unit/test_firewall_sandbox | Bin 0 -> 30576 bytes tests/unit/test_firewall_sandbox.c | 94 +++++++++++++++++++++++++ tests/unit/test_lightning_payout | Bin 20552 -> 20552 bytes tests/unit/test_lnurl_pay | Bin 21304 -> 21312 bytes tests/unit/test_market | Bin 0 -> 48240 bytes tests/unit/test_mcp_handler | Bin 38736 -> 64152 bytes tests/unit/test_mining_payment | Bin 0 -> 28672 bytes tests/unit/test_mining_payment.c | 92 +++++++++++++++++++++++++ tests/unit/test_negentropy_adapter | Bin 0 -> 21216 bytes tests/unit/test_nip04 | Bin 298776 -> 298784 bytes tests/unit/test_session_payment_method | Bin 0 -> 54008 bytes tests/unit/test_session_payment_method.c | 74 ++++++++++++++++++++ tests/unit/test_stratum_proxy | Bin 0 -> 40752 bytes tests/unit/test_stratum_proxy.c | 95 +++++++++++++++++++++++++ tests/unit/test_tollgate_client | Bin 51992 -> 52968 bytes tests/unit/test_tollgate_client_mining | Bin 0 -> 47224 bytes tests/unit/test_tollgate_client_mining.c | 103 ++++++++++++++++++++++++++++ 33 files changed, 620 insertions(+), 9 deletions(-) create mode 100644 tests/unit/stubs/dns_server.h create mode 100644 tests/unit/stubs/esp_wifi_ap_get_sta_list.h create mode 100644 tests/unit/stubs/lwip/etharp.h create mode 100644 tests/unit/stubs/lwip/lwip_napt.h delete mode 100644 tests/unit/stubs/lwip/napt.h create mode 100644 tests/unit/stubs/lwip/prot/ip.h create mode 100644 tests/unit/stubs/lwip/prot/ip4.h create mode 100644 tests/unit/stubs/lwip/prot/tcp.h create mode 100755 tests/unit/test_beacon_price create mode 100755 tests/unit/test_cvm_server create mode 100755 tests/unit/test_display create mode 100755 tests/unit/test_firewall_sandbox create mode 100644 tests/unit/test_firewall_sandbox.c create mode 100755 tests/unit/test_market create mode 100755 tests/unit/test_mining_payment create mode 100644 tests/unit/test_mining_payment.c create mode 100755 tests/unit/test_negentropy_adapter create mode 100755 tests/unit/test_session_payment_method create mode 100644 tests/unit/test_session_payment_method.c create mode 100755 tests/unit/test_stratum_proxy create mode 100644 tests/unit/test_stratum_proxy.c create mode 100755 tests/unit/test_tollgate_client_mining create mode 100644 tests/unit/test_tollgate_client_mining.c (limited to 'tests/unit') diff --git a/tests/unit/Makefile b/tests/unit/Makefile index a06807f..edff73c 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -22,7 +22,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o -TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_display test_negentropy_adapter test_beacon_price test_market test_mint_health +TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_display test_negentropy_adapter test_beacon_price test_market test_mint_health test_mining_payment test_stratum_proxy test_session_payment_method test_tollgate_client_mining test_firewall_sandbox .PHONY: all test clean $(TESTS) @@ -90,5 +90,20 @@ test_beacon_price: test_beacon_price.c $(REPO_ROOT)/main/beacon_price.c test_market: test_market.c $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c $(CC) $(CFLAGS) -I $(REPO_ROOT)/main $< $(REPO_ROOT)/main/market.c $(REPO_ROOT)/main/beacon_price.c -o $@ $(LDFLAGS) +test_mining_payment: test_mining_payment.c $(REPO_ROOT)/main/mining_payment.c + $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/mining_payment.c -o $@ $(LDFLAGS) + +test_stratum_proxy: test_stratum_proxy.c $(REPO_ROOT)/main/stratum_proxy.c $(REPO_ROOT)/main/mining_payment.c + $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/stratum_proxy.c $(REPO_ROOT)/main/mining_payment.c -o $@ $(LDFLAGS) + +test_session_payment_method: test_session_payment_method.c $(REPO_ROOT)/main/session.c $(REPO_ROOT)/main/cashu.c + $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/session.c $(REPO_ROOT)/main/cashu.c -o $@ $(LDFLAGS) + +test_tollgate_client_mining: test_tollgate_client_mining.c + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +test_firewall_sandbox: test_firewall_sandbox.c $(REPO_ROOT)/main/firewall.c + $(CC) $(CFLAGS) -include stubs/dns_server.h $< $(REPO_ROOT)/main/firewall.c -o $@ $(LDFLAGS) + clean: rm -f $(TESTS) $(SECP256K1_OBJ) diff --git a/tests/unit/stubs/dns_server.h b/tests/unit/stubs/dns_server.h new file mode 100644 index 0000000..0a9450b --- /dev/null +++ b/tests/unit/stubs/dns_server.h @@ -0,0 +1,11 @@ +#ifndef STUBS_DNS_SERVER_H +#define STUBS_DNS_SERVER_H + +#include +#include + +static inline void dns_server_set_client_authenticated(uint32_t ip, bool auth) { + (void)ip; (void)auth; +} + +#endif diff --git a/tests/unit/stubs/esp_wifi_ap_get_sta_list.h b/tests/unit/stubs/esp_wifi_ap_get_sta_list.h new file mode 100644 index 0000000..3e98032 --- /dev/null +++ b/tests/unit/stubs/esp_wifi_ap_get_sta_list.h @@ -0,0 +1,38 @@ +#ifndef STUBS_ESP_WIFI_AP_GET_STA_LIST_H +#define STUBS_ESP_WIFI_AP_GET_STA_LIST_H + +#include +#include +#include "esp_err.h" + +#define ESP_WIFI_AP_MAX_STA 10 + +typedef struct { + uint8_t mac[6]; +} wifi_sta_info_t; + +typedef struct { + int num; + wifi_sta_info_t sta[ESP_WIFI_AP_MAX_STA]; +} wifi_sta_list_t; + +typedef struct { + int num; + struct { + uint8_t mac[6]; + esp_ip4_addr_t ip; + } sta[ESP_WIFI_AP_MAX_STA]; +} wifi_sta_mac_ip_list_t; + +static inline esp_err_t esp_wifi_ap_get_sta_list(wifi_sta_list_t *sta) { + memset(sta, 0, sizeof(*sta)); + return ESP_FAIL; +} + +static inline esp_err_t esp_wifi_ap_get_sta_list_with_ip(const wifi_sta_list_t *sta_in, wifi_sta_mac_ip_list_t *out) { + (void)sta_in; + memset(out, 0, sizeof(*out)); + return ESP_FAIL; +} + +#endif diff --git a/tests/unit/stubs/freertos/task.h b/tests/unit/stubs/freertos/task.h index 3855d41..ec96156 100644 --- a/tests/unit/stubs/freertos/task.h +++ b/tests/unit/stubs/freertos/task.h @@ -6,14 +6,17 @@ typedef void *TaskHandle_t; typedef void *SemaphoreHandle_t; +typedef int BaseType_t; + +#define pdPASS 1 static inline void vTaskDelete(TaskHandle_t t) { (void)t; } static inline SemaphoreHandle_t xSemaphoreCreateMutex(void) { return (SemaphoreHandle_t)malloc(1); } static inline void vSemaphoreDelete(SemaphoreHandle_t s) { free(s); } static inline int xSemaphoreTake(SemaphoreHandle_t s, uint32_t blk) { (void)s; (void)blk; return 1; } static inline int xSemaphoreGive(SemaphoreHandle_t s) { (void)s; return 1; } -static inline int xTaskCreate(void (*fn)(void*), const char *n, uint32_t st, void *p, uint32_t pri, TaskHandle_t *h) { - (void)fn; (void)n; (void)st; (void)p; (void)pri; (void)h; return 1; +static inline BaseType_t xTaskCreate(void (*fn)(void*), const char *n, uint32_t st, void *p, uint32_t pri, TaskHandle_t *h) { + (void)fn; (void)n; (void)st; (void)p; (void)pri; (void)h; return pdPASS; } #endif diff --git a/tests/unit/stubs/lwip/etharp.h b/tests/unit/stubs/lwip/etharp.h new file mode 100644 index 0000000..adc6b7b --- /dev/null +++ b/tests/unit/stubs/lwip/etharp.h @@ -0,0 +1,22 @@ +#ifndef STUBS_LWIP_ETHARP_H +#define STUBS_LWIP_ETHARP_H + +#include +#include +#include "lwip/ip4_addr.h" + +struct eth_addr { + uint8_t addr[6]; +}; + +struct netif; + +typedef int err_t; +#define ERR_OK 0 + +static inline err_t etharp_get_entry(ssize_t i, ip4_addr_t **ip, struct netif **netif, struct eth_addr **eth) { + (void)i; (void)ip; (void)netif; (void)eth; + return -1; +} + +#endif diff --git a/tests/unit/stubs/lwip/ip4_addr.h b/tests/unit/stubs/lwip/ip4_addr.h index 174211b..9d92ff0 100644 --- a/tests/unit/stubs/lwip/ip4_addr.h +++ b/tests/unit/stubs/lwip/ip4_addr.h @@ -3,6 +3,9 @@ #include +typedef uint32_t u32_t; +typedef uint16_t u16_t; + typedef struct { uint32_t addr; } ip4_addr_t; diff --git a/tests/unit/stubs/lwip/lwip_napt.h b/tests/unit/stubs/lwip/lwip_napt.h new file mode 100644 index 0000000..c6a5ca1 --- /dev/null +++ b/tests/unit/stubs/lwip/lwip_napt.h @@ -0,0 +1,6 @@ +#ifndef STUBS_LWIP_NAPT_H +#define STUBS_LWIP_NAPT_H + +static inline void ip_napt_enable(uint32_t num, int enable) { (void)num; (void)enable; } + +#endif diff --git a/tests/unit/stubs/lwip/napt.h b/tests/unit/stubs/lwip/napt.h deleted file mode 100644 index c6a5ca1..0000000 --- a/tests/unit/stubs/lwip/napt.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef STUBS_LWIP_NAPT_H -#define STUBS_LWIP_NAPT_H - -static inline void ip_napt_enable(uint32_t num, int enable) { (void)num; (void)enable; } - -#endif diff --git a/tests/unit/stubs/lwip/netif.h b/tests/unit/stubs/lwip/netif.h index 461a64e..9415539 100644 --- a/tests/unit/stubs/lwip/netif.h +++ b/tests/unit/stubs/lwip/netif.h @@ -1,4 +1,20 @@ #ifndef STUBS_LWIP_NETIF_H #define STUBS_LWIP_NETIF_H +#include +#include + +struct pbuf { + void *payload; + uint16_t len; +}; + +static inline uint32_t lwip_ntohl(uint32_t n) { + return ((n & 0xFF) << 24) | ((n & 0xFF00) << 8) | ((n >> 8) & 0xFF00) | ((n >> 24) & 0xFF); +} + +static inline uint16_t lwip_ntohs(uint16_t n) { + return ((n & 0xFF) << 8) | ((n >> 8) & 0xFF); +} + #endif diff --git a/tests/unit/stubs/lwip/prot/ip.h b/tests/unit/stubs/lwip/prot/ip.h new file mode 100644 index 0000000..0770760 --- /dev/null +++ b/tests/unit/stubs/lwip/prot/ip.h @@ -0,0 +1,20 @@ +#ifndef STUBS_LWIP_PROT_IP_H +#define STUBS_LWIP_PROT_IP_H + +#include + +#define IP_PROTO_TCP 6 +#define IP_PROTO_UDP 17 +#define IP_HLEN 20 + +struct ip_hdr { + uint8_t _proto; + union { + uint32_t addr; + } src; + union { + uint32_t addr; + } dest; +}; + +#endif diff --git a/tests/unit/stubs/lwip/prot/ip4.h b/tests/unit/stubs/lwip/prot/ip4.h new file mode 100644 index 0000000..0f70170 --- /dev/null +++ b/tests/unit/stubs/lwip/prot/ip4.h @@ -0,0 +1,6 @@ +#ifndef STUBS_LWIP_PROT_IP4_H +#define STUBS_LWIP_PROT_IP4_H + +#include "ip.h" + +#endif diff --git a/tests/unit/stubs/lwip/prot/tcp.h b/tests/unit/stubs/lwip/prot/tcp.h new file mode 100644 index 0000000..5841371 --- /dev/null +++ b/tests/unit/stubs/lwip/prot/tcp.h @@ -0,0 +1,13 @@ +#ifndef STUBS_LWIP_PROT_TCP_H +#define STUBS_LWIP_PROT_TCP_H + +#include + +#define TCP_HLEN 20 + +struct tcp_hdr { + uint16_t src; + uint16_t dest; +}; + +#endif diff --git a/tests/unit/stubs/lwip/sockets.h b/tests/unit/stubs/lwip/sockets.h index 44f03ac..91bf8b2 100644 --- a/tests/unit/stubs/lwip/sockets.h +++ b/tests/unit/stubs/lwip/sockets.h @@ -1,4 +1,10 @@ #ifndef STUBS_LWIP_SOCKETS_H #define STUBS_LWIP_SOCKETS_H +#include +#include +#include +#include +#include + #endif diff --git a/tests/unit/test_beacon_price b/tests/unit/test_beacon_price new file mode 100755 index 0000000..47efd2b Binary files /dev/null and b/tests/unit/test_beacon_price differ diff --git a/tests/unit/test_cvm_server b/tests/unit/test_cvm_server new file mode 100755 index 0000000..bd5e735 Binary files /dev/null and b/tests/unit/test_cvm_server differ diff --git a/tests/unit/test_display b/tests/unit/test_display new file mode 100755 index 0000000..9b8364e Binary files /dev/null and b/tests/unit/test_display differ diff --git a/tests/unit/test_firewall_sandbox b/tests/unit/test_firewall_sandbox new file mode 100755 index 0000000..4b85357 Binary files /dev/null and b/tests/unit/test_firewall_sandbox differ diff --git a/tests/unit/test_firewall_sandbox.c b/tests/unit/test_firewall_sandbox.c new file mode 100644 index 0000000..66a491b --- /dev/null +++ b/tests/unit/test_firewall_sandbox.c @@ -0,0 +1,94 @@ +#include "test_framework.h" +#include "../../main/firewall.h" +#include +#include + +int main(void) +{ + printf("=== test_firewall_sandbox ===\n"); + + printf("\n--- FW_MAX_MAC_LEN is 18 ---\n"); + { + ASSERT_EQ_INT(18, FW_MAX_MAC_LEN, "MAC length is 18 (17 chars + null)"); + } + + printf("\n--- esp_ip4_addr_t available ---\n"); + { + esp_ip4_addr_t ip; + ip.addr = 0x0102A8C0; + ASSERT(ip.addr == 0x0102A8C0, "ip4_addr stores value"); + } + + printf("\n--- firewall_set_mining_port / set_sandbox_mint_access compile ---\n"); + { + firewall_set_mining_port(3333); + firewall_set_mining_port(4033); + firewall_set_sandbox_mint_access(true); + firewall_set_sandbox_mint_access(false); + ASSERT(true, "setters compile and run without crash"); + } + + printf("\n--- firewall_init + client management ---\n"); + { + esp_ip4_addr_t ap_ip = { .addr = 0x012FA80A }; + esp_err_t ret = firewall_init(ap_ip); + ASSERT_EQ_INT(ESP_OK, (int)ret, "firewall_init succeeds"); + ASSERT_EQ_INT(0, firewall_client_count(), "no clients after init"); + + firewall_grant_access(0x0201A8C0); + ASSERT_EQ_INT(1, firewall_client_count(), "1 client after grant"); + ASSERT(firewall_is_client_allowed(0x0201A8C0), "client is allowed"); + + firewall_revoke_access(0x0201A8C0); + ASSERT_EQ_INT(0, firewall_client_count(), "0 clients after revoke"); + ASSERT(!firewall_is_client_allowed(0x0201A8C0), "client not allowed after revoke"); + } + + printf("\n--- grant same IP twice ---\n"); + { + esp_ip4_addr_t ap_ip = { .addr = 0x012FA80A }; + firewall_init(ap_ip); + + firewall_grant_access(0x0301A8C0); + firewall_grant_access(0x0301A8C0); + ASSERT_EQ_INT(1, firewall_client_count(), "duplicate grant does not double count"); + } + + printf("\n--- revoke non-existent ---\n"); + { + firewall_revoke_access(0x99999999); + ASSERT_EQ_INT(1, firewall_client_count(), "revoke non-existent no effect"); + } + + printf("\n--- revoke_all ---\n"); + { + firewall_grant_access(0x0401A8C0); + firewall_grant_access(0x0501A8C0); + ASSERT_EQ_INT(3, firewall_client_count(), "3 clients"); + firewall_revoke_all(); + ASSERT_EQ_INT(0, firewall_client_count(), "0 after revoke_all"); + } + + printf("\n--- max clients (10) ---\n"); + { + esp_ip4_addr_t ap_ip = { .addr = 0x012FA80A }; + firewall_init(ap_ip); + + for (int i = 0; i < 10; i++) { + firewall_grant_access(0x0A000000 + i); + } + ASSERT_EQ_INT(10, firewall_client_count(), "10 clients at max"); + + firewall_grant_access(0x0A000100); + ASSERT_EQ_INT(10, firewall_client_count(), "still 10 after exceeding max"); + } + + printf("\n--- is_mac_allowed (no MACs resolved in stub) ---\n"); + { + firewall_init((esp_ip4_addr_t){ .addr = 0x012FA80A }); + firewall_grant_access(0x0601A8C0); + ASSERT(!firewall_is_mac_allowed(""), "empty MAC not allowed"); + } + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_lightning_payout b/tests/unit/test_lightning_payout index b10888c..caa9626 100755 Binary files a/tests/unit/test_lightning_payout and b/tests/unit/test_lightning_payout differ diff --git a/tests/unit/test_lnurl_pay b/tests/unit/test_lnurl_pay index 1f16293..1345004 100755 Binary files a/tests/unit/test_lnurl_pay and b/tests/unit/test_lnurl_pay differ diff --git a/tests/unit/test_market b/tests/unit/test_market new file mode 100755 index 0000000..9823080 Binary files /dev/null and b/tests/unit/test_market differ diff --git a/tests/unit/test_mcp_handler b/tests/unit/test_mcp_handler index b5d6a85..be992f6 100755 Binary files a/tests/unit/test_mcp_handler and b/tests/unit/test_mcp_handler differ diff --git a/tests/unit/test_mining_payment b/tests/unit/test_mining_payment new file mode 100755 index 0000000..015deaf Binary files /dev/null and b/tests/unit/test_mining_payment differ diff --git a/tests/unit/test_mining_payment.c b/tests/unit/test_mining_payment.c new file mode 100644 index 0000000..c3834fd --- /dev/null +++ b/tests/unit/test_mining_payment.c @@ -0,0 +1,92 @@ +#include "test_framework.h" +#include "../../main/mining_payment.h" +#include +#include +#include + +int main(void) +{ + printf("=== test_mining_payment ===\n"); + + mining_payment_init(); + + printf("\n--- mining_nbits_to_difficulty ---\n"); + uint64_t d1 = mining_nbits_to_difficulty(0); + ASSERT(d1 == UINT64_MAX, "nbits=0 returns max"); + + uint64_t d2 = mining_nbits_to_difficulty(0x170309E2); + ASSERT(d2 > 0, "mainnet nbits gives positive difficulty"); + printf(" INFO: difficulty for 0x170309E2 = %llu\n", (unsigned long long)d2); + + uint64_t d3 = mining_nbits_to_difficulty(0x1d00ffff); + ASSERT(d3 == 1, "pseudotransaction nbits = difficulty 1"); + + printf("\n--- mining_calculate_hashprice ---\n"); + double hp1 = mining_calculate_hashprice(0); + ASSERT(hp1 == 0.0, "nbits=0 gives hashprice 0"); + + double hp2 = mining_calculate_hashprice(0x170309E2); + ASSERT(hp2 > 0.0, "mainnet nbits gives positive hashprice"); + printf(" INFO: hashprice for 0x170309E2 = %.6f sat/GH/s/day\n", hp2); + + printf("\n--- mining_calculate_hashprice_override ---\n"); + double hp3 = mining_calculate_hashprice_override(1000); + ASSERT(fabs(hp3 - 1000.0) < 0.001, "override returns exact value"); + + printf("\n--- mining_set_current_nbits ---\n"); + mining_set_current_nbits(0x170309E2); + double hp4 = mining_get_current_hashprice(); + ASSERT(fabs(hp4 - hp2) < 0.0001, "stored hashprice matches calculated"); + + printf("\n--- mining_get_or_create_client ---\n"); + mining_client_stats_t *c1 = mining_get_or_create_client(0x0A010203); + ASSERT(c1 != NULL, "created client 1"); + ASSERT(c1->ip == 0x0A010203, "client 1 IP matches"); + ASSERT(c1->shares_accepted == 0, "client 1 starts with 0 shares"); + + mining_client_stats_t *c2 = mining_get_or_create_client(0x0A010204); + ASSERT(c2 != NULL, "created client 2"); + ASSERT(c2->ip == 0x0A010204, "client 2 IP matches"); + + mining_client_stats_t *c1_again = mining_get_or_create_client(0x0A010203); + ASSERT(c1_again == c1, "same IP returns same client"); + + printf("\n--- mining_update_hashrate ---\n"); + mining_update_hashrate(0x0A010203, true); + mining_update_hashrate(0x0A010203, true); + mining_update_hashrate(0x0A010203, false); + + const mining_client_stats_t *c1_stats = mining_get_client_stats(0x0A010203); + ASSERT(c1_stats != NULL, "client 1 stats found"); + ASSERT(c1_stats->shares_accepted == 2, "client 1 has 2 accepted"); + ASSERT(c1_stats->shares_rejected == 1, "client 1 has 1 rejected"); + + printf("\n--- mining_get_client_stats ---\n"); + const mining_client_stats_t *notfound = mining_get_client_stats(0xFFFFFFFF); + ASSERT(notfound == NULL, "nonexistent client returns NULL"); + + printf("\n--- mining_shares_to_allotment_ms ---\n"); + uint64_t allot1 = mining_shares_to_allotment_ms(0.0, 100.0, 21, 60000); + ASSERT(allot1 == 0, "zero hashrate = zero allotment"); + + uint64_t allot2 = mining_shares_to_allotment_ms(1.0, 100.0, 21, 60000); + ASSERT(allot2 > 0, "positive hashrate gives positive allotment"); + printf(" INFO: 1 GH/s at 100 sat/GH/s/day, 21 sats/60s => %llu ms\n", (unsigned long long)allot2); + + uint64_t allot3 = mining_shares_to_allotment_ms(10.0, 50.0, 10, 30000); + ASSERT(allot3 > 0, "10 GH/s at 50 sat gives positive allotment"); + printf(" INFO: 10 GH/s at 50 sat/GH/s/day, 10 sats/30s => %llu ms\n", (unsigned long long)allot3); + + printf("\n--- mining_shares_to_allotment_bytes ---\n"); + uint64_t ab1 = mining_shares_to_allotment_bytes(0.0, 100.0, 21, 1048576); + ASSERT(ab1 == 0, "zero hashrate = zero bytes"); + + uint64_t ab2 = mining_shares_to_allotment_bytes(1.0, 100.0, 21, 1048576); + ASSERT(ab2 > 0, "positive hashrate gives positive bytes"); + + printf("\n--- mining_validate_share (stub) ---\n"); + esp_err_t vr = mining_validate_share(NULL, 0, NULL, 0); + ASSERT(vr == ESP_OK, "validate share stub returns OK"); + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_negentropy_adapter b/tests/unit/test_negentropy_adapter new file mode 100755 index 0000000..64b6053 Binary files /dev/null and b/tests/unit/test_negentropy_adapter differ diff --git a/tests/unit/test_nip04 b/tests/unit/test_nip04 index cb52040..daf5e16 100755 Binary files a/tests/unit/test_nip04 and b/tests/unit/test_nip04 differ diff --git a/tests/unit/test_session_payment_method b/tests/unit/test_session_payment_method new file mode 100755 index 0000000..94c7134 Binary files /dev/null and b/tests/unit/test_session_payment_method differ diff --git a/tests/unit/test_session_payment_method.c b/tests/unit/test_session_payment_method.c new file mode 100644 index 0000000..0239140 --- /dev/null +++ b/tests/unit/test_session_payment_method.c @@ -0,0 +1,74 @@ +#include "test_framework.h" +#include "../../main/session.h" +#include "../../main/firewall.h" +#include "../../main/config.h" +#include "../../main/cashu.h" +#include +#include + +static tollgate_config_t g_test_config; + +const tollgate_config_t *tollgate_config_get(void) { + return &g_test_config; +} + +esp_err_t firewall_get_mac_for_ip(uint32_t ip, char *mac_out, size_t size) { + (void)ip; + snprintf(mac_out, size, "AA:BB:CC:DD:EE:FF"); + return 0; +} + +static uint32_t g_granted_ips[32]; +static int g_granted_count = 0; + +void firewall_grant_access(uint32_t ip) { + if (g_granted_count < 32) g_granted_ips[g_granted_count++] = ip; +} + +void firewall_revoke_access(uint32_t ip) { + (void)ip; +} + +int main(void) +{ + printf("=== test_session_payment_method ===\n"); + memset(&g_test_config, 0, sizeof(g_test_config)); + strncpy(g_test_config.metric, "milliseconds", sizeof(g_test_config.metric) - 1); + g_granted_count = 0; + + printf("\n--- session_create sets PAYMENT_METHOD_CASHU ---\n"); + session_manager_init(); + session_t *s1 = session_create(0x0A010001, 60000); + ASSERT(s1 != NULL, "session created"); + ASSERT_EQ_INT(PAYMENT_METHOD_CASHU, (int)s1->payment_method, "cashu session has PAYMENT_METHOD_CASHU"); + + printf("\n--- session_create_bytes sets PAYMENT_METHOD_BYTES ---\n"); + session_manager_init(); + g_granted_count = 0; + session_t *s2 = session_create_bytes(0x0A010002, 1048576); + ASSERT(s2 != NULL, "bytes session created"); + ASSERT_EQ_INT(PAYMENT_METHOD_BYTES, (int)s2->payment_method, "bytes session has PAYMENT_METHOD_BYTES"); + ASSERT_EQ_UINT64(1048576, s2->allotment_bytes, "allotment_bytes set"); + ASSERT_EQ_UINT64(0, s2->bytes_consumed, "bytes_consumed starts at 0"); + + printf("\n--- payment_method_t enum values are distinct ---\n"); + ASSERT(PAYMENT_METHOD_CASHU != PAYMENT_METHOD_MINING, "CASHU != MINING"); + ASSERT(PAYMENT_METHOD_CASHU != PAYMENT_METHOD_BYTES, "CASHU != BYTES"); + ASSERT(PAYMENT_METHOD_MINING != PAYMENT_METHOD_BYTES, "MINING != BYTES"); + + printf("\n--- session extend preserves payment_method ---\n"); + session_manager_init(); + g_granted_count = 0; + session_t *s3 = session_create(0x0A010003, 60000); + ASSERT_EQ_INT(PAYMENT_METHOD_CASHU, (int)s3->payment_method, "initially CASHU"); + session_extend(s3, 30000); + ASSERT_EQ_INT(PAYMENT_METHOD_CASHU, (int)s3->payment_method, "still CASHU after extend"); + + printf("\n--- bytes session allotment_ms is INT64_MAX ---\n"); + session_manager_init(); + g_granted_count = 0; + session_t *s4 = session_create_bytes(0x0A010004, 2097152); + ASSERT(s4->allotment_ms == INT64_MAX, "bytes session has INT64_MAX allotment_ms"); + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_stratum_proxy b/tests/unit/test_stratum_proxy new file mode 100755 index 0000000..608835f Binary files /dev/null and b/tests/unit/test_stratum_proxy differ diff --git a/tests/unit/test_stratum_proxy.c b/tests/unit/test_stratum_proxy.c new file mode 100644 index 0000000..7788911 --- /dev/null +++ b/tests/unit/test_stratum_proxy.c @@ -0,0 +1,95 @@ +#include "test_framework.h" +#include "../../main/stratum_proxy.h" +#include "../../main/mining_payment.h" +#include +#include + +int main(void) +{ + printf("=== test_stratum_proxy ===\n"); + + mining_payment_init(); + + printf("\n--- stratum_proxy_set_job / get_current_job ---\n"); + { + stratum_job_t job = {0}; + job.job_id = 42; + job.nbits = 0x170309E2; + job.ntime = 0x6789ABCD; + job.version = 0x20000000; + job.valid = true; + memset(job.prevhash, 0xAA, 32); + memset(job.merkle_root, 0xBB, 32); + + stratum_proxy_set_job(&job); + + const stratum_job_t *cur = stratum_proxy_get_current_job(); + ASSERT(cur != NULL, "current job not NULL"); + ASSERT_EQ_INT(42, (int)cur->job_id, "job_id=42"); + ASSERT_EQ_INT(0x170309E2, (int)cur->nbits, "nbits preserved"); + ASSERT_EQ_INT(0x6789ABCD, (int)cur->ntime, "ntime preserved"); + ASSERT_EQ_INT(0x20000000, (int)cur->version, "version preserved"); + ASSERT(cur->valid, "job is valid"); + ASSERT_MEM_EQ(job.prevhash, cur->prevhash, 32, "prevhash preserved"); + } + + printf("\n--- stratum_proxy_set_job (NULL) ---\n"); + { + stratum_proxy_set_job(NULL); + const stratum_job_t *cur = stratum_proxy_get_current_job(); + ASSERT_EQ_INT(42, (int)cur->job_id, "job unchanged after NULL set"); + } + + printf("\n--- stratum_proxy_get_stats ---\n"); + { + mining_set_current_nbits(0x170309E2); + stratum_proxy_set_job(&(stratum_job_t){ + .job_id = 1, + .nbits = 0x170309E2, + .valid = true + }); + + stratum_proxy_stats_t stats; + memset(&stats, 0xFF, sizeof(stats)); + stratum_proxy_get_stats(&stats); + + ASSERT(stats.current_hashprice > 0.0, "hashprice populated from mining_payment"); + ASSERT_EQ_INT(0x170309E2, (int)stats.nbits, "nbits in stats"); + } + + printf("\n--- stratum_proxy_get_stats (NULL) ---\n"); + { + stratum_proxy_get_stats(NULL); + ASSERT(true, "get_stats with NULL does not crash"); + } + + printf("\n--- stratum_job_t initialization ---\n"); + { + stratum_job_t zero = {0}; + ASSERT(!zero.valid, "zero-initialized job is invalid"); + ASSERT_EQ_INT(0, (int)zero.job_id, "zero job_id"); + ASSERT_EQ_INT(0, (int)zero.nbits, "zero nbits"); + } + + printf("\n--- stratum_proxy_stats_t initialization ---\n"); + { + stratum_proxy_stats_t zero = {0}; + ASSERT(zero.hashrate_ghs == 0.0, "zero hashrate"); + ASSERT_EQ_INT(0, (int)zero.active_miners, "zero active miners"); + ASSERT_EQ_UINT64(0, zero.total_shares, "zero total shares"); + ASSERT_EQ_UINT64(0, zero.total_accepted, "zero total accepted"); + ASSERT_EQ_UINT64(0, zero.total_rejected, "zero total rejected"); + } + + printf("\n--- STRATUM_MAX_JOBS constant ---\n"); + { + ASSERT(STRATUM_MAX_JOBS >= 1, "STRATUM_MAX_JOBS >= 1"); + } + + printf("\n--- STRATUM_MAX_JOB_ID_LEN constant ---\n"); + { + ASSERT(STRATUM_MAX_JOB_ID_LEN >= 16, "STRATUM_MAX_JOB_ID_LEN >= 16"); + } + + TEST_SUMMARY(); +} diff --git a/tests/unit/test_tollgate_client b/tests/unit/test_tollgate_client index f9b0f7d..b56a6f0 100755 Binary files a/tests/unit/test_tollgate_client and b/tests/unit/test_tollgate_client differ diff --git a/tests/unit/test_tollgate_client_mining b/tests/unit/test_tollgate_client_mining new file mode 100755 index 0000000..64b99dd Binary files /dev/null and b/tests/unit/test_tollgate_client_mining differ diff --git a/tests/unit/test_tollgate_client_mining.c b/tests/unit/test_tollgate_client_mining.c new file mode 100644 index 0000000..e270864 --- /dev/null +++ b/tests/unit/test_tollgate_client_mining.c @@ -0,0 +1,103 @@ +#include "test_framework.h" +#include "../../main/config.h" +#include +#include +#include +#include + +static tollgate_config_t g_test_config; + +const tollgate_config_t *tollgate_config_get(void) { + return &g_test_config; +} + +uint64_t nucula_wallet_balance(void) { return 100; } +esp_err_t nucula_wallet_send(uint64_t a, char *b, size_t c) { (void)a; (void)b; (void)c; return ESP_OK; } + +#include "freertos/FreeRTOS.h" + +#include "../../main/tollgate_client.c" + +int main(void) +{ + printf("=== test_tollgate_client_mining ===\n"); + + memset(&g_test_config, 0, sizeof(g_test_config)); + g_test_config.client_enabled = true; + + printf("\n--- mining tag: mining_available=true, port=3333 ---\n"); + { + const char *json = "{\"kind\":10021,\"tags\":[" + "[\"metric\",\"milliseconds\"]," + "[\"step_size\",\"60000\"]," + "[\"price_per_step\",\"0\",\"mining\",\"3333\",\"sat\"]," + "[\"tips\",\"1\",\"2\",\"5\"]" + "]}"; + + tollgate_discovery_t disc; + bool ok = parse_discovery_response(json, &disc); + ASSERT(ok, "mining discovery parsed"); + ASSERT(disc.is_tollgate, "is_tollgate=true"); + ASSERT(disc.mining_available, "mining_available=true"); + ASSERT_EQ_INT(3333, (int)disc.mining_port, "mining_port=3333"); + } + + printf("\n--- mining tag: no mining tag ---\n"); + { + const char *json = "{\"kind\":10021,\"tags\":[" + "[\"metric\",\"milliseconds\"]," + "[\"step_size\",\"60000\"]," + "[\"price_per_step\",\"cashu\",\"21\",\"sat\",\"https://testnut.cashu.space\",\"1\"]" + "]}"; + + tollgate_discovery_t disc; + bool ok = parse_discovery_response(json, &disc); + ASSERT(ok, "cashu discovery parsed"); + ASSERT(disc.is_tollgate, "is_tollgate=true"); + ASSERT(!disc.mining_available, "mining_available=false"); + ASSERT_EQ_INT(0, (int)disc.mining_port, "mining_port=0 when no mining"); + ASSERT_EQ_INT(21, disc.price_per_step, "price_per_step=21 for cashu"); + } + + printf("\n--- mining tag: custom port 4033 ---\n"); + { + const char *json = "{\"kind\":10021,\"tags\":[" + "[\"metric\",\"milliseconds\"]," + "[\"step_size\",\"60000\"]," + "[\"price_per_step\",\"0\",\"mining\",\"4033\",\"sat\"]" + "]}"; + + tollgate_discovery_t disc; + bool ok = parse_discovery_response(json, &disc); + ASSERT(ok, "mining custom port parsed"); + ASSERT(disc.mining_available, "mining_available=true"); + ASSERT_EQ_INT(4033, (int)disc.mining_port, "mining_port=4033"); + } + + printf("\n--- tollgate_discovery_t zero-init ---\n"); + { + tollgate_discovery_t disc = {0}; + ASSERT(!disc.is_tollgate, "zero-init: is_tollgate=false"); + ASSERT(!disc.mining_available, "zero-init: mining_available=false"); + ASSERT_EQ_INT(0, (int)disc.mining_port, "zero-init: mining_port=0"); + ASSERT_EQ_INT(0, disc.price_per_step, "zero-init: price=0"); + } + + printf("\n--- TG_CLIENT_MINING state enum ---\n"); + { + ASSERT(TG_CLIENT_MINING > TG_CLIENT_PAID, "MINING > PAID in enum"); + ASSERT(TG_CLIENT_MINING < TG_CLIENT_ERROR, "MINING < ERROR in enum"); + } + + printf("\n--- discovery struct fields ---\n"); + { + tollgate_discovery_t disc; + memset(&disc, 0, sizeof(disc)); + disc.mining_available = true; + disc.mining_port = 9999; + ASSERT(disc.mining_available, "mining_available set"); + ASSERT_EQ_INT(9999, (int)disc.mining_port, "mining_port set"); + } + + TEST_SUMMARY(); +} -- cgit v1.2.3