475 lines
15 KiB
C++
475 lines
15 KiB
C++
#include <Arduino.h>
|
|
#include <ArduinoJson.h>
|
|
#include <WiFi.h>
|
|
#include <esp_task_wdt.h>
|
|
#include <nvs_flash.h>
|
|
#include <nvs.h>
|
|
#include "ShaTests/nerdSHA256.h"
|
|
//#include "ShaTests/nerdSHA256plus.h"
|
|
#include "stratum.h"
|
|
#include "mining.h"
|
|
#include "utils.h"
|
|
#include "monitor.h"
|
|
#include "drivers/displays/display.h"
|
|
#include "drivers/storage/storage.h"
|
|
|
|
nvs_handle_t stat_handle;
|
|
|
|
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
|
|
|
|
// Track best diff
|
|
double best_diff = 0.0;
|
|
|
|
// Variables to hold data from custom textboxes
|
|
//Track mining stats in non volatile memory
|
|
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();
|
|
|
|
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)) {
|
|
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
|
|
if (!client.connect(serverIP, Settings.PoolPort)) {
|
|
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) {
|
|
|
|
// 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());
|
|
|
|
#ifdef DEBUG_MEMORY
|
|
Serial.printf("### [Total Heap / Free heap / Min free heap]: %d / %d / %d \n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap());
|
|
#endif
|
|
|
|
// connect to pool
|
|
|
|
double currentPoolDifficulty = DEFAULT_DIFFICULTY;
|
|
|
|
while(true) {
|
|
|
|
if(WiFi.status() != WL_CONNECTED){
|
|
// WiFi is disconnected, so reconnect now
|
|
mMonitor.NerdStatus = NM_Connecting;
|
|
WiFi.reconnect();
|
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
continue;
|
|
}
|
|
|
|
//Test vars:
|
|
//************
|
|
//Nerdminerpool
|
|
// strcpy(poolString, "nerdminerPool");
|
|
// portNumber = 3002;
|
|
// strcpy(btcString,"test");
|
|
//Braiins
|
|
//strcpy(poolString, "eu.stratum.braiins.com");
|
|
//portNumber = 3333;
|
|
//strcpy(btcString,"Bitmaker.01");
|
|
//CKpool
|
|
//strcpy(poolString, "solo.ckpool.org");
|
|
//portNumber = 3333;
|
|
//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);
|
|
|
|
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)) {
|
|
client.stop();
|
|
continue;
|
|
}
|
|
|
|
strcpy(mWorker.wName, Settings.BtcWallet);
|
|
strcpy(mWorker.wPass, "x");
|
|
// STEP 2: Pool authorize work (Block Info)
|
|
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);
|
|
|
|
isMinerSuscribed=true;
|
|
mLastTXtoPool = millis();
|
|
}
|
|
|
|
//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);
|
|
mMiner.poolDifficulty = currentPoolDifficulty;
|
|
mMiner.newJob = true;
|
|
mMiner.newJob2 = true;
|
|
//Give new job to miner
|
|
|
|
}
|
|
break;
|
|
case MINING_SET_DIFFICULTY: parse_mining_set_difficulty(line, currentPoolDifficulty);
|
|
mMiner.poolDifficulty = currentPoolDifficulty;
|
|
break;
|
|
case STRATUM_SUCCESS: Serial.println(" Parsed JSON: Success"); break;
|
|
default: Serial.println(" Parsed JSON: unknown"); break;
|
|
|
|
}
|
|
}
|
|
|
|
vTaskDelay(500 / portTICK_PERIOD_MS); //Small delay
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//////////////////THREAD CALLS///////////////////
|
|
|
|
//This works only with one thread, TODO -> Class or miner_data for each thread
|
|
|
|
|
|
void runMiner(void * task_id) {
|
|
|
|
unsigned int miner_id = (uint32_t)task_id;
|
|
|
|
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
|
|
}
|
|
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
|
|
|
|
mMonitor.NerdStatus = NM_hashing;
|
|
|
|
//Prepare Premining data
|
|
nerd_sha256 nerdMidstate;
|
|
//nerdSHA256_context nerdMidstate; //NerdShaplus
|
|
uint8_t hash[32];
|
|
|
|
|
|
//Calcular midstate
|
|
nerd_midstate(&nerdMidstate, mMiner.bytearray_blockheader, 64);
|
|
//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
|
|
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;
|
|
|
|
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);
|
|
|
|
|
|
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("");
|
|
Serial.print("hash2: ");
|
|
for (size_t i = 0; i < 32; i++)
|
|
Serial.printf("%02x", hash2[i]);
|
|
Serial.println(""); */
|
|
|
|
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) {
|
|
//if(!is16BitShare){
|
|
// increment nonce
|
|
nonce += 2;
|
|
continue;
|
|
}
|
|
|
|
//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);
|
|
|
|
// update best diff
|
|
if (diff_hash > best_diff)
|
|
best_diff = diff_hash;
|
|
|
|
if(diff_hash > mMiner.poolDifficulty)//(hash[29] <= 0x3B)//(diff_hash > 1e-9)
|
|
{
|
|
tx_mining_submit(client, mWorker, mJob, nonce);
|
|
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);
|
|
valids++;
|
|
Serial.printf("[WORKER] %d Submitted work valid!\n", miner_id);
|
|
// wait for new job
|
|
break;
|
|
}
|
|
// increment nonce
|
|
nonce += 2;
|
|
} // exit if found a valid result or nonce > MAX_NONCE
|
|
|
|
//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");
|
|
}
|
|
}
|
|
|
|
#define DELAY 100
|
|
#define REDRAW_EVERY 10
|
|
|
|
void restoreStat() {
|
|
if(!Settings.saveStats) return;
|
|
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() {
|
|
if(!Settings.saveStats) return;
|
|
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));
|
|
}
|
|
|
|
void runMonitor(void *name)
|
|
{
|
|
|
|
Serial.println("[MONITOR] started");
|
|
restoreStat();
|
|
|
|
unsigned long mLastCheck = 0;
|
|
|
|
resetToFirstScreen();
|
|
|
|
unsigned long frame = 0;
|
|
|
|
uint32_t seconds_elapsed = 0;
|
|
|
|
totalKHashes = (Mhashes * 1000) + hashes / 1000;;
|
|
|
|
while (1)
|
|
{
|
|
if ((frame % REDRAW_EVERY) == 0)
|
|
{
|
|
unsigned long mElapsed = millis() - mLastCheck;
|
|
mLastCheck = millis();
|
|
unsigned long currentKHashes = (Mhashes * 1000) + hashes / 1000;
|
|
elapsedKHs = currentKHashes - totalKHashes;
|
|
totalKHashes = currentKHashes;
|
|
|
|
drawCurrentScreen(mElapsed);
|
|
|
|
// Monitor state when hashrate is 0.0
|
|
if (elapsedKHs == 0)
|
|
{
|
|
Serial.printf(">>> [i] Miner: newJob>%s / inRun>%s) - Client: connected>%s / subscribed>%s / wificonnected>%s\n",
|
|
mMiner.newJob ? "true" : "false", mMiner.inRun ? "true" : "false",
|
|
client.connected() ? "true" : "false", isMinerSuscribed ? "true" : "false", WiFi.status() == WL_CONNECTED ? "true" : "false");
|
|
}
|
|
|
|
#ifdef DEBUG_MEMORY
|
|
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
|
|
|
|
seconds_elapsed++;
|
|
|
|
if(seconds_elapsed % (saveIntervals[currentIntervalIndex]) == 0){
|
|
saveStat();
|
|
seconds_elapsed = 0;
|
|
if(currentIntervalIndex < saveIntervalsSize - 1)
|
|
currentIntervalIndex++;
|
|
}
|
|
}
|
|
animateCurrentScreen(frame);
|
|
doLedStuff(frame);
|
|
|
|
vTaskDelay(DELAY / portTICK_PERIOD_MS);
|
|
frame++;
|
|
}
|
|
}
|