NerdNos-Firmware/src/mining.cpp

475 lines
15 KiB
C++
Raw Normal View History

#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
2023-09-07 22:01:50 +02:00
#include <nvs_flash.h>
#include <nvs.h>
2023-07-30 13:10:39 +02:00
#include "ShaTests/nerdSHA256.h"
2023-08-20 00:45:09 +02:00
//#include "ShaTests/nerdSHA256plus.h"
#include "stratum.h"
#include "mining.h"
#include "utils.h"
#include "monitor.h"
2023-09-15 07:36:46 +02:00
#include "drivers/displays/display.h"
2023-09-13 03:06:40 +02:00
#include "drivers/storage/storage.h"
2023-09-07 22:01:50 +02:00
nvs_handle_t stat_handle;
2023-05-08 01:31:05 +02:00
2023-09-07 22:01:50 +02:00
uint32_t templates = 0;
uint32_t hashes = 0;
uint32_t Mhashes = 0;
uint32_t totalKHashes = 0;
uint32_t elapsedKHs = 0;
uint64_t upTime = 0;
uint32_t shares; // increase if blockhash has 32 bits of zeroes
uint32_t valids; // increased if blockhash <= target
2023-07-24 21:33:15 +02:00
// Track best diff
double best_diff = 0.0;
// Variables to hold data from custom textboxes
2023-09-18 02:32:34 +02:00
//Track mining stats in non volatile memory
2023-09-12 09:26:28 +02:00
extern TSettings Settings;
IPAddress serverIP(1, 1, 1, 1); //Temporally save poolIPaddres
//Global work data
static WiFiClient client;
static miner_data mMiner; //Global miner data (Create a miner class TODO)
mining_subscribe mWorker;
mining_job mJob;
monitor_data mMonitor;
bool isMinerSuscribed = false;
unsigned long mLastTXtoPool = millis();
2023-09-07 22:01:50 +02:00
int saveIntervals[7] = {5 * 60, 15 * 60, 30 * 60, 1 * 360, 3 * 360, 6 * 360, 12 * 360};
int saveIntervalsSize = sizeof(saveIntervals)/sizeof(saveIntervals[0]);
int currentIntervalIndex = 0;
bool checkPoolConnection(void) {
if (client.connected()) {
return true;
}
isMinerSuscribed = false;
Serial.println("Client not connected, trying to connect...");
//Resolve first time pool DNS and save IP
if(serverIP == IPAddress(1,1,1,1)) {
2023-09-21 17:41:21 +02:00
WiFi.hostByName(Settings.PoolAddress.c_str(), serverIP);
Serial.printf("Resolved DNS and save ip (first time) got: %s\n", serverIP.toString());
}
//Try connecting pool IP
2023-09-12 09:26:28 +02:00
if (!client.connect(serverIP, Settings.PoolPort)) {
2023-09-21 17:41:21 +02:00
Serial.println("Imposible to connect to : " + Settings.PoolAddress);
WiFi.hostByName(Settings.PoolAddress.c_str(), serverIP);
Serial.printf("Resolved DNS got: %s\n", serverIP.toString());
vTaskDelay(1000 / portTICK_PERIOD_MS);
return false;
}
return true;
}
//Implements a socketKeepAlive function and
//checks if pool is not sending any data to reconnect again.
//Even connection could be alive, pool could stop sending new job NOTIFY
unsigned long mStart0Hashrate = 0;
bool checkPoolInactivity(unsigned int keepAliveTime, unsigned long inactivityTime){
unsigned long currentKHashes = (Mhashes*1000) + hashes/1000;
unsigned long elapsedKHs = currentKHashes - totalKHashes;
// If no shares sent to pool
// send something to pool to hold socket oppened
if(millis() - mLastTXtoPool > keepAliveTime){
mLastTXtoPool = millis();
Serial.println(" Sending : KeepAlive suggest_difficulty");
//if (client.print("{}\n") == 0) {
tx_suggest_difficulty(client, DEFAULT_DIFFICULTY);
/*if(tx_suggest_difficulty(client, DEFAULT_DIFFICULTY)){
Serial.println(" Sending keepAlive to pool -> Detected client disconnected");
return true;
}*/
}
if(elapsedKHs == 0){
//Check if hashrate is 0 during inactivityTIme
if(mStart0Hashrate == 0) mStart0Hashrate = millis();
if((millis()-mStart0Hashrate) > inactivityTime) { mStart0Hashrate=0; return true;}
return false;
}
mStart0Hashrate = 0;
return false;
}
void runStratumWorker(void *name) {
2023-05-07 13:21:00 +02:00
// TEST: https://bitcoin.stackexchange.com/questions/22929/full-example-data-for-scrypt-stratum-client
Serial.println("");
Serial.printf("\n[WORKER] Started. Running %s on core %d\n", (char *)name, xPortGetCoreID());
2023-05-07 13:21:00 +02:00
#ifdef DEBUG_MEMORY
2023-09-20 20:12:05 +02:00
Serial.printf("### [Total Heap / Free heap / Min free heap]: %d / %d / %d \n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap());
2023-05-07 13:21:00 +02:00
#endif
// connect to pool
double currentPoolDifficulty = DEFAULT_DIFFICULTY;
while(true) {
2023-05-07 13:21:00 +02:00
if(WiFi.status() != WL_CONNECTED){
// WiFi is disconnected, so reconnect now
2023-08-27 11:21:26 +02:00
mMonitor.NerdStatus = NM_Connecting;
WiFi.reconnect();
vTaskDelay(5000 / portTICK_PERIOD_MS);
2023-05-07 13:21:00 +02:00
continue;
}
//Test vars:
2023-05-29 22:37:14 +02:00
//************
//Nerdminerpool
// strcpy(poolString, "nerdminerPool");
// portNumber = 3002;
// strcpy(btcString,"test");
2023-05-29 22:37:14 +02:00
//Braiins
//strcpy(poolString, "eu.stratum.braiins.com");
//portNumber = 3333;
//strcpy(btcString,"Bitmaker.01");
//CKpool
//strcpy(poolString, "solo.ckpool.org");
//portNumber = 3333;
2023-05-29 22:37:14 +02:00
//strcpy(btcString,"test");
if(!checkPoolConnection())
//If server is not reachable add random delay for connection retries
srand(millis());
//Generate value between 1 and 15 secs
vTaskDelay(((1 + rand() % 15) * 1000) / portTICK_PERIOD_MS);
2023-05-07 13:21:00 +02:00
if(!isMinerSuscribed){
//Stop miner current jobs
mMiner.inRun = false;
mWorker = init_mining_subscribe();
// STEP 1: Pool server connection (SUBSCRIBE)
if(!tx_mining_subscribe(client, mWorker)) {
2023-05-07 13:21:00 +02:00
client.stop();
continue;
}
2023-05-27 23:54:16 +02:00
2023-09-12 09:26:28 +02:00
strcpy(mWorker.wName, Settings.BtcWallet);
2023-05-27 23:54:16 +02:00
strcpy(mWorker.wPass, "x");
// STEP 2: Pool authorize work (Block Info)
2023-05-27 23:54:16 +02:00
tx_mining_auth(client, mWorker.wName, mWorker.wPass); //Don't verifies authoritzation, TODO
//tx_mining_auth2(client, mWorker.wName, mWorker.wPass); //Don't verifies authoritzation, TODO
// STEP 3: Suggest pool difficulty
tx_suggest_difficulty(client, DEFAULT_DIFFICULTY);
2023-05-07 13:21:00 +02:00
isMinerSuscribed=true;
mLastTXtoPool = millis();
}
2023-05-07 13:21:00 +02:00
//Check if pool is down for almost 5minutes and then restart connection with pool (1min=600000ms)
if(checkPoolInactivity(KEEPALIVE_TIME_ms, POOLINACTIVITY_TIME_ms)){
//Restart connection
Serial.println(" Detected more than 2 min without data form stratum server. Closing socket and reopening...");
client.stop();
isMinerSuscribed=false;
continue;
}
//Read pending messages from pool
while(client.connected() && client.available()){
Serial.println(" Received message from pool");
String line = client.readStringUntil('\n');
stratum_method result = parse_mining_method(line);
switch (result)
{
case STRATUM_PARSE_ERROR: Serial.println(" Parsed JSON: error on JSON"); break;
case MINING_NOTIFY: if(parse_mining_notify(line, mJob)){
//Increse templates readed
templates++;
//Stop miner current jobs
mMiner.inRun = false;
//Prepare data for new jobs
mMiner=calculateMiningData(mWorker,mJob);
2023-05-28 22:58:23 +02:00
mMiner.poolDifficulty = currentPoolDifficulty;
mMiner.newJob = true;
mMiner.newJob2 = true;
//Give new job to miner
}
break;
2023-05-28 22:58:23 +02:00
case MINING_SET_DIFFICULTY: parse_mining_set_difficulty(line, currentPoolDifficulty);
mMiner.poolDifficulty = currentPoolDifficulty;
break;
2023-07-27 16:30:57 +02:00
case STRATUM_SUCCESS: Serial.println(" Parsed JSON: Success"); break;
default: Serial.println(" Parsed JSON: unknown"); break;
}
}
vTaskDelay(500 / portTICK_PERIOD_MS); //Small delay
2023-05-07 13:21:00 +02:00
}
}
//////////////////THREAD CALLS///////////////////
//This works only with one thread, TODO -> Class or miner_data for each thread
2023-08-20 00:45:09 +02:00
void runMiner(void * task_id) {
unsigned int miner_id = (uint32_t)task_id;
2023-08-11 13:37:25 +02:00
Serial.printf("[MINER] %d Started runMiner Task!\n", miner_id);
while(1){
//Wait new job
while(1){
if(mMiner.newJob==true || mMiner.newJob2==true) break;
vTaskDelay(100 / portTICK_PERIOD_MS); //Small delay
2023-05-07 13:21:00 +02:00
}
vTaskDelay(10 / portTICK_PERIOD_MS); //Small delay to join both mining threads
if(mMiner.newJob)
mMiner.newJob = false; //Clear newJob flag
else if(mMiner.newJob2)
mMiner.newJob2 = false; //Clear newJob flag
mMiner.inRun = true; //Set inRun flag
2023-08-27 11:21:26 +02:00
mMonitor.NerdStatus = NM_hashing;
//Prepare Premining data
2023-07-30 13:10:39 +02:00
nerd_sha256 nerdMidstate;
2023-08-20 00:45:09 +02:00
//nerdSHA256_context nerdMidstate; //NerdShaplus
uint8_t hash[32];
2023-08-20 00:45:09 +02:00
2023-08-20 00:45:09 +02:00
//Calcular midstate
2023-07-30 13:10:39 +02:00
nerd_midstate(&nerdMidstate, mMiner.bytearray_blockheader, 64);
2023-08-20 00:45:09 +02:00
//nerd_mids(&nerdMidstate, mMiner.bytearray_blockheader); //NerdShaplus
// search a valid nonce
unsigned long nonce = TARGET_NONCE - MAX_NONCE;
// split up odd/even nonces between miner tasks
nonce += miner_id;
uint32_t startT = micros();
unsigned char *header64;
// each miner thread needs to track its own blockheader template
2023-08-20 00:45:09 +02:00
uint8_t temp;
memcpy(mMiner.bytearray_blockheader2, &mMiner.bytearray_blockheader, 80);
if (miner_id == 0)
header64 = mMiner.bytearray_blockheader + 64;
else
header64 = mMiner.bytearray_blockheader2 + 64;
2023-08-20 00:45:09 +02:00
bool is16BitShare=true;
Serial.println(">>> STARTING TO HASH NONCES");
while(true) {
if (miner_id == 0)
memcpy(mMiner.bytearray_blockheader + 76, &nonce, 4);
else
memcpy(mMiner.bytearray_blockheader2 + 76, &nonce, 4);
2023-08-20 00:45:09 +02:00
nerd_double_sha2(&nerdMidstate, header64, hash);
//is16BitShare=nerd_sha256d(&nerdMidstate, header64, hash); //Boosted 80Khs sha
/*Serial.print("hash1: ");
for (size_t i = 0; i < 32; i++)
Serial.printf("%02x", hash[i]);
Serial.println("");
2023-08-20 00:45:09 +02:00
Serial.print("hash2: ");
for (size_t i = 0; i < 32; i++)
2023-08-20 00:45:09 +02:00
Serial.printf("%02x", hash2[i]);
Serial.println(""); */
2023-08-20 00:45:09 +02:00
hashes++;
if (nonce > TARGET_NONCE) break; //exit
if(!mMiner.inRun) { Serial.println ("MINER WORK ABORTED >> waiting new job"); break;}
// check if 16bit share
if(hash[31] !=0 || hash[30] !=0) {
2023-08-20 00:45:09 +02:00
//if(!is16BitShare){
// increment nonce
nonce += 2;
continue;
}
2023-07-24 21:33:15 +02:00
//Check target to submit
//Difficulty of 1 > 0x00000000FFFF0000000000000000000000000000000000000000000000000000
//NM2 pool diff 1e-9 > Target = diff_1 / diff_pool > 0x00003B9ACA00....00
//Swapping diff bytes little endian >>>>>>>>>>>>>>>> 0x0000DC59D300....00
//if((hash[29] <= 0xDC) && (hash[28] <= 0x59)) //0x00003B9ACA00 > diff value for 1e-9
double diff_hash = diff_from_target(hash);
2023-07-24 21:33:15 +02:00
// update best diff
if (diff_hash > best_diff)
best_diff = diff_hash;
2023-05-28 22:58:23 +02:00
if(diff_hash > mMiner.poolDifficulty)//(hash[29] <= 0x3B)//(diff_hash > 1e-9)
{
tx_mining_submit(client, mWorker, mJob, nonce);
2023-05-28 22:58:23 +02:00
Serial.print(" - Current diff share: "); Serial.println(diff_hash,12);
Serial.print(" - Current pool diff : "); Serial.println(mMiner.poolDifficulty,12);
Serial.print(" - TX SHARE: ");
for (size_t i = 0; i < 32; i++)
Serial.printf("%02x", hash[i]);
#ifdef DEBUG_MINING
Serial.println("");
Serial.print(" - Current nonce: "); Serial.println(nonce);
Serial.print(" - Current block header: ");
for (size_t i = 0; i < 80; i++) {
Serial.printf("%02x", mMiner.bytearray_blockheader[i]);
}
#endif
Serial.println("");
mLastTXtoPool = millis();
}
// check if 32bit share
if(hash[29] !=0 || hash[28] !=0) {
// increment nonce
nonce += 2;
continue;
}
shares++;
// check if valid header
if(checkValid(hash, mMiner.bytearray_target)){
Serial.printf("[WORKER] %d CONGRATULATIONS! Valid block found with nonce: %d | 0x%x\n", miner_id, nonce, nonce);
2023-05-07 13:21:00 +02:00
valids++;
Serial.printf("[WORKER] %d Submitted work valid!\n", miner_id);
// wait for new job
2023-05-07 13:21:00 +02:00
break;
}
// increment nonce
nonce += 2;
} // exit if found a valid result or nonce > MAX_NONCE
2023-07-30 13:19:01 +02:00
//wc_Sha256Free(&sha256);
//wc_Sha256Free(midstate);
mMiner.inRun = false;
Serial.print(">>> Finished job waiting new data from pool");
if(hashes>=MAX_NONCE_STEP) {
Mhashes=Mhashes+MAX_NONCE_STEP/1000000;
hashes=hashes-MAX_NONCE_STEP;
}
uint32_t duration = micros() - startT;
if (esp_task_wdt_reset() == ESP_OK)
Serial.print(">>> Resetting watchdog timer");
}
}
2023-08-31 17:44:58 +02:00
#define DELAY 100
#define REDRAW_EVERY 10
2023-09-07 22:01:50 +02:00
void restoreStat() {
2023-09-17 14:06:46 +02:00
if(!Settings.saveStats) return;
2023-09-07 22:01:50 +02:00
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
Serial.printf("[MONITOR] NVS partition is full or has invalid version, erasing...\n");
nvs_flash_init();
}
ret = nvs_open("state", NVS_READWRITE, &stat_handle);
size_t required_size = sizeof(double);
nvs_get_blob(stat_handle, "best_diff", &best_diff, &required_size);
nvs_get_u32(stat_handle, "Mhashes", &Mhashes);
nvs_get_u32(stat_handle, "shares", &shares);
nvs_get_u32(stat_handle, "valids", &valids);
nvs_get_u32(stat_handle, "templates", &templates);
nvs_get_u64(stat_handle, "upTime", &upTime);
}
void saveStat() {
2023-09-17 14:06:46 +02:00
if(!Settings.saveStats) return;
2023-09-07 22:01:50 +02:00
Serial.printf("[MONITOR] Saving stats\n");
nvs_set_blob(stat_handle, "best_diff", &best_diff, sizeof(double));
nvs_set_u32(stat_handle, "Mhashes", Mhashes);
nvs_set_u32(stat_handle, "shares", shares);
nvs_set_u32(stat_handle, "valids", valids);
nvs_set_u32(stat_handle, "templates", templates);
nvs_set_u64(stat_handle, "upTime", upTime + (esp_timer_get_time()/1000000));
}
2023-09-02 01:01:42 +02:00
void runMonitor(void *name)
{
2023-05-08 01:31:05 +02:00
Serial.println("[MONITOR] started");
2023-09-07 22:01:50 +02:00
restoreStat();
2023-09-02 01:01:42 +02:00
2023-05-08 01:31:05 +02:00
unsigned long mLastCheck = 0;
2023-09-02 01:01:42 +02:00
2023-08-28 22:54:13 +02:00
resetToFirstScreen();
2023-08-11 13:37:25 +02:00
2023-08-31 17:44:58 +02:00
unsigned long frame = 0;
2023-09-07 22:01:50 +02:00
uint32_t seconds_elapsed = 0;
totalKHashes = (Mhashes * 1000) + hashes / 1000;;
2023-09-02 01:01:42 +02:00
while (1)
{
if ((frame % REDRAW_EVERY) == 0)
{
unsigned long mElapsed = millis() - mLastCheck;
2023-08-31 17:44:58 +02:00
mLastCheck = millis();
2023-09-02 01:01:42 +02:00
unsigned long currentKHashes = (Mhashes * 1000) + hashes / 1000;
elapsedKHs = currentKHashes - totalKHashes;
2023-08-31 17:44:58 +02:00
totalKHashes = currentKHashes;
2023-09-02 01:01:42 +02:00
2023-08-31 17:44:58 +02:00
drawCurrentScreen(mElapsed);
2023-09-02 01:01:42 +02:00
// Monitor state when hashrate is 0.0
if (elapsedKHs == 0)
{
2023-08-31 17:44:58 +02:00
Serial.printf(">>> [i] Miner: newJob>%s / inRun>%s) - Client: connected>%s / subscribed>%s / wificonnected>%s\n",
2023-09-14 22:32:25 +02:00
mMiner.newJob ? "true" : "false", mMiner.inRun ? "true" : "false",
client.connected() ? "true" : "false", isMinerSuscribed ? "true" : "false", WiFi.status() == WL_CONNECTED ? "true" : "false");
2023-08-31 17:44:58 +02:00
}
#ifdef DEBUG_MEMORY
2023-09-20 20:12:05 +02:00
Serial.printf("### [Total Heap / Free heap / Min free heap]: %d / %d / %d \n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap());
Serial.printf("### Max stack usage: %d\n", uxTaskGetStackHighWaterMark(NULL));
#endif
2023-09-07 22:01:50 +02:00
seconds_elapsed++;
if(seconds_elapsed % (saveIntervals[currentIntervalIndex]) == 0){
saveStat();
seconds_elapsed = 0;
if(currentIntervalIndex < saveIntervalsSize - 1)
currentIntervalIndex++;
}
2023-08-31 17:44:58 +02:00
}
animateCurrentScreen(frame);
2023-09-02 01:01:42 +02:00
doLedStuff(frame);
2023-08-31 17:44:58 +02:00
vTaskDelay(DELAY / portTICK_PERIOD_MS);
frame++;
2023-05-08 01:31:05 +02:00
}
}