diff --git a/monero-poc/verifier/CMakeLists.txt b/monero-poc/verifier/CMakeLists.txt index 325db6f..342546f 100644 --- a/monero-poc/verifier/CMakeLists.txt +++ b/monero-poc/verifier/CMakeLists.txt @@ -1,27 +1,18 @@ cmake_minimum_required(VERSION 3.1) project(OCVerifier CXX) -# Find GCC compiler -find_program(GCC_COMPILER NAMES gcc g++ - DOC "Path to the GNU C/C++ compiler") - -if(NOT GCC_COMPILER) - message(FATAL_ERROR "GCC/G++ compiler not found. Please install GCC or ensure it's in your PATH.") -endif() - -set (CMAKE_CXX_STANDARD 17) -if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_definitions(-D_CRT_SECURE_NO_WARNINGS) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message(STATUS "Detected GCC/G++ compiler") + set(CXX_OCVERIFIER_FLAGS "-Wunused-variable") else() - SET(CXX_OCVERIFIER_FLAGS "-Wunused-variable") + message(FATAL_ERROR "Must use GCC/G++ to compile") endif() message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") message(STATUS "Using C++ compiler: ${CMAKE_CXX_COMPILER}") -if(CMAKE_C_COMPILER MATCHES "gcc") - #ok -else() - message(FATAL_ERROR "Must use gcc/g++ to compile") +option(TESTNET_ENABLE "Building with testnest." OFF) +if (TESTNET_ENABLE) + add_definitions(-DTESTNET_ENABLE) endif() set(XMR_ROOT "/root/monero/" CACHE PATH "Monero root folder") @@ -36,19 +27,52 @@ message(WARNING "This project is using many precompiled libraries, it is recomme SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_OCVERIFIER_FLAGS}") SET(FILES ${CMAKE_SOURCE_DIR}/connection.cpp - ${CMAKE_SOURCE_DIR}/keyUtils.cpp + ${CMAKE_SOURCE_DIR}/keyUtils.cpp + ${CMAKE_SOURCE_DIR}/nodeVerifier.cpp ) link_directories(${CMAKE_SOURCE_DIR}/precompiledMoneroLibraries/) add_library(oc_verifier_lib STATIC verifierLib.cpp xmr.cpp) ADD_EXECUTABLE(oc_verifier main.cpp ${FILES}) +if (TESTNET_ENABLE) + add_executable(testnetDispatcher + testnetDispatcher.cpp + ${CMAKE_SOURCE_DIR}/connection.cpp + ${CMAKE_SOURCE_DIR}/keyUtils.cpp) +endif() + +if (BOOST_ROOT) + message(STATUS "Look for Boost at: " ${BOOST_ROOT}) +else() + message(STATUS "Look for Boost at default path") +endif() -target_link_libraries(oc_verifier pthread lmdb oc_verifier_lib cryptonote_basic cncrypto common ringct_basic device - cryptonote_format_utils_basic wallet-crypto epee easylogging version randomx boost_system boost_date_time - boost_chrono boost_filesystem boost_thread boost_regex boost_serialization boost_program_options sodium - unbound ssl crypto hidapi-libusb dl) -target_link_libraries(oc_verifier_lib cryptonote_basic cncrypto common ringct_basic device - cryptonote_format_utils_basic wallet-crypto epee easylogging version randomx boost_system boost_date_time - boost_chrono boost_filesystem boost_thread boost_regex boost_serialization boost_program_options sodium - unbound ssl crypto hidapi-libusb dl) +# This verion is required for precompiled libraries +find_package(Boost 1.71 EXACT REQUIRED COMPONENTS + filesystem system date_time chrono thread regex serialization program_options +) + +target_include_directories(oc_verifier_lib PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(oc_verifier_lib PRIVATE + cryptonote_basic cncrypto common ringct_basic device cryptonote_format_utils_basic + wallet-crypto epee easylogging version randomx ${Boost_LIBRARIES} sodium unbound + ssl crypto hidapi-libusb dl) target_compile_options(oc_verifier_lib PRIVATE -Ofast) + +target_link_libraries(oc_verifier PRIVATE pthread lmdb oc_verifier_lib) set_property(TARGET oc_verifier PROPERTY COMPILE_WARNING_AS_ERROR ON) + +# Install related libs and executable +# Install executables +install(TARGETS oc_verifier + RUNTIME DESTINATION bin) +if (TESTNET_ENABLE) + install(TARGETS testnetDispatcher + RUNTIME DESTINATION bin) +endif() + +# Install Boost libs +if(BOOST_ROOT) + install(DIRECTORY ${BOOST_ROOT}/lib/ + DESTINATION lib/boost + FILES_MATCHING PATTERN "*.so*") +endif() diff --git a/monero-poc/verifier/Dockerfile b/monero-poc/verifier/Dockerfile new file mode 100644 index 0000000..4f2872f --- /dev/null +++ b/monero-poc/verifier/Dockerfile @@ -0,0 +1,48 @@ +ARG base_image="ubuntu:22.04" +ARG image_type="release" +ARG package_location="" + +# Set environment variables to avoid user prompts during installation +ARG DEBIAN_FRONTEND=noninteractive + +#********************Setup an runtime environment for running. +FROM ${base_image} AS build_image_runtime +ARG DEBIAN_FRONTEND +ARG package_location + +# Install runtime environment +RUN apt-get update && \ + apt-get install -y libsodium23 libunbound8 libhidapi-hidraw0 liblmdb0 libssl3 libhidapi-libusb0 libicu70 && \ + apt-get clean + +#********************Setup an develop environment for building. +FROM ${base_image} AS build_image_develop +ARG DEBIAN_FRONTEND +ARG package_location + +# Install cmake, git and dev environments +RUN apt-get update && \ + apt-get install -y cmake build-essential libsodium-dev libunbound-dev libhidapi-dev liblmdb-dev libssl-dev git && \ + apt-get clean + +#********************Package runtime into a Docker image +FROM ${base_image} AS build_image_release +ARG DEBIAN_FRONTEND +ARG APP_DEPENDENCIES +ARG package_location + +# Create and set the working directory +WORKDIR /opt/qubic/oc_verifier + +# Copy the requirements.txt file +COPY ${package_location}/ /opt/qubic/oc_verifier + +# +ENV LD_LIBRARY_PATH="/opt/qubic/oc_verifier/lib/boost" + +# Place holder to trigger above build +FROM build_image_${image_type} +ARG DEBIAN_FRONTEND +ARG APP_DEPENDENCIES +ARG CMD_BUILD +ARG package_location \ No newline at end of file diff --git a/monero-poc/verifier/README.md b/monero-poc/verifier/README.md index cf135de..4ae5fdc 100644 --- a/monero-poc/verifier/README.md +++ b/monero-poc/verifier/README.md @@ -1,4 +1,15 @@ ### REQUIREMENTS + +The verifier will need monero repository set through `XMR_ROOT` variable when calling CMake, it also need some of libriaries that use by Monero + +``` +// Ubuntu +sudo apt-get install libsodium-dev ibunbound-dev libhidapi-dev liblmdb-dev libssl-dev +``` + +In case of using precompiledMoneroLibraries, you will need the `boost 1.71.0` otherwise you need to rebuild everything by your self. + + Run this command after `git clone` to fetch RandomX library ``` git submodule update --init --recursive @@ -7,14 +18,25 @@ git submodule update --init --recursive On Linux, make sure `cmake` and `make` commands are installed and then run: ``` -mkdir build; +mkdir build cd build; -cmake ../; -make; +cmake .. -DXMR_ROOT= -DBOOST_ROOT= +make ``` -On Windows, use the CMake GUI to create a Visual Studio project and then build the executable in Visual Studio. +## Testnet +Run belows command +``` +mkdir build +cd build; +cmake .. -DXMR_ROOT= -DBOOST_ROOT= -DTESTNET_ENABLE=1 +make +``` + + +## Docker +Docker image can be built using `build_docker.sh`. Please run with `-h` for detail information ### USAGE @@ -54,5 +76,16 @@ The verifier currently supports **three modes running in parallel**: # Fetch from both peers and node, and submit results ./oc_verifier --peers [nodeip0],[nodeip1],...,[nodeipN] --seed [OPERATOR seed] --nodeip [OPERATOR IP] ``` + +### Docker run +```bash +docker run -it --rm --init [docker image] bash -c "./bin/oc_verifier --peers [nodeip0],[nodeip1],...,[nodeipN]" + +docker run -it --rm --init [docker image] bash -c "./bin/oc_verifier --seed [OPERATOR seed] --nodeip [OPERATOR IP]" + +docker run -it --rm --init [docker image] bash -c "./bin/oc_verifier --peers [nodeip0],[nodeip1],...,[nodeipN] --seed [OPERATOR seed] --nodeip [OPERATOR IP]" + +``` + Screenshot: ![image](https://github.com/user-attachments/assets/c629abc8-afb9-4d05-97c5-487456946774) diff --git a/monero-poc/verifier/build_docker.sh b/monero-poc/verifier/build_docker.sh new file mode 100755 index 0000000..17ac3f8 --- /dev/null +++ b/monero-poc/verifier/build_docker.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Parse arguments +XMR_ROOT_LOCATION="" +BOOST_ROOT_LOCATION="" +BUILD_ALL=0 +VERSION=latest +TESTNET=0 + +# Parse args +while [[ $# -gt 0 ]]; do + case "$1" in + --xmr-root) + XMR_ROOT_LOCATION="$2" + shift 2 + ;; + --boost-root) + BOOST_ROOT_LOCATION="$2" + shift 2 + ;; + --build-all) + BUILD_ALL="$2" + shift 2 + ;; + --version) + VERSION="$2" + shift 2 + ;; + --testnet) + TESTNET_ENABLE="$2" + shift 2 + ;; + --help|-h) + echo "Usage: $0 [options]" + echo + echo "Options:" + echo " --xmr-root PATH Path to Monero root folder" + echo " --boost-root PATH Path to Boost root folder" + echo " --build-all 0|1 Rebuild both runtime and dev image (default: 0)" + echo " --version VERSION Specify build version" + echo " --testnet 0|1 Enable dummy test net mode" + echo " --help, -h Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Location to thirdparty +echo "XMR_ROOT_LOCATION=$XMR_ROOT_LOCATION" +echo "BOOST_ROOT_LOCATION=$BOOST_ROOT_LOCATION" + +# If this is enabled both runtime and develop image will be re-built +echo "BUILD_ALL=$BUILD_ALL" +echo "VERSION=$VERSION" + +echo "TESTNET_ENABLE=$TESTNET_ENABLE" + +# Build the environment Docker image +DEV_IMAGE="oc-verifier-dev" +RUNTIME_IMAGE="oc-verifier-rt" +RELEASE_IMAGE="oc-verifier" +DOCKER_SRC_DIR="/opt/qubic/oc_verifier_src" + +if [ "$BUILD_ALL" == 1 ]; then + # Trigger build the runtime image + echo "Building the runtime Docker image" + docker build -f Dockerfile --build-arg image_type=runtime -t ${RUNTIME_IMAGE} . + + # Build the dev image with tool for compilation + echo "Building the develop Docker image" + docker build -f Dockerfile --build-arg base_image=${RUNTIME_IMAGE} --build-arg image_type=develop -t ${DEV_IMAGE} . +fi + + +# Compile the code if neccessary +package_location=build_docker +build_cmd="rm -rf ${package_location} || true && \ +mkdir ${package_location} && \ +cd ${package_location} && \ +cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=./redist -DTESTNET_ENABLE=${TESTNET_ENABLE} -DXMR_ROOT=/xmr_root -DBOOST_ROOT=/boost_root && \ +make install" +install_cmd="echo " +echo $build_cmd +docker run --rm -v ./:${DOCKER_SRC_DIR} -v ${XMR_ROOT_LOCATION}:/xmr_root -v ${BOOST_ROOT_LOCATION}:/boost_root -u $(id -u) ${DEV_IMAGE} bash -c "cd $DOCKER_SRC_DIR && $build_cmd && $install_cmd" + +# Package into a new release image base on runtime time +echo "Packaging..." +docker build --build-arg base_image=${RUNTIME_IMAGE} --build-arg image_type=release --build-arg package_location=${package_location}/redist -t ${RELEASE_IMAGE}:${VERSION} . \ No newline at end of file diff --git a/monero-poc/verifier/connection.cpp b/monero-poc/verifier/connection.cpp index d717026..ac190be 100644 --- a/monero-poc/verifier/connection.cpp +++ b/monero-poc/verifier/connection.cpp @@ -137,6 +137,7 @@ void QubicConnection::getHandshakeData(std::vector& buffer) QubicConnection::~QubicConnection() { + shutdown(mSocket, SHUT_RDWR); close(mSocket); } diff --git a/monero-poc/verifier/main.cpp b/monero-poc/verifier/main.cpp index 59cc3f4..57d58c5 100644 --- a/monero-poc/verifier/main.cpp +++ b/monero-poc/verifier/main.cpp @@ -12,16 +12,16 @@ #include #include #include +#include #include #include "verifierLib.h" +#include "nodeVerifier.h" +#if TESTNET_ENABLE +#define DISPATCHER "DISPAPLNOYSWXCJMZEMFUNCCMMJANGQPYJDSEXZTTBFSUEPYPEKCICADBUCJ" +#else #define DISPATCHER "XPXYKFLGSWRHRGAUKWFWVXCDVEYAPCPCNUTMUDWFGDYQCWZNJMWFZEEGCFFO" -uint8_t dispatcherPubkey[32] = {0}; -uint8_t operatorSeed[56] = {0}; -uint8_t operatorPublicKey[32] = {0}; -uint8_t operatorSubSeed[32]= {0}; -uint8_t operatorPrivateKey[32]= {0}; -char operatorPublicIdentity[128] = {0}; +#endif // The LMDB C header #include @@ -195,160 +195,36 @@ MDB_dbi share_db; void setupDB() { printf("Initializing DB...\n"); - database = new Database("./database"); + const std::string dbPath = "./database"; + // Check if directory exists, if not, create it + if (!std::filesystem::exists(dbPath)) + { + if (!std::filesystem::create_directory(dbPath)) + { + printf("Failed to create database directory!\n"); + exit(1); + } + } + + database = new Database(dbPath.c_str()); share_db = database->open_db("shares"); } -#define DUMMY_TEST 0 -std::mutex gDummyLock; -unsigned long long gDummyCounter = 0; -uint16_t gComputorPartitionMap[NUMBER_OF_COMPUTORS]; - uint64_t compScore[676]; uint64_t prevTask = 0; std::mutex compScoreLock; -#if DUMMY_TEST +#if TESTNET_ENABLE #define OPERATOR_PORT 31841 #else #define OPERATOR_PORT 21841 #endif - #define PORT 21841 #define SLEEP(x) std::this_thread::sleep_for(std::chrono::milliseconds (x)); bool shouldExit = false; - -//struct XMRTask -//{ -// uint64_t taskIndex; // ever increasing number (unix timestamp in ms) -// uint16_t firstComputorIndex, lastComputorIndex; -// uint32_t padding; -// -// uint8_t m_blob[408]; // Job data from pool -// uint64_t m_size; // length of the blob -// uint64_t m_target; // Pool difficulty -// uint64_t m_height; // Block height -// uint8_t m_seed[32]; // Seed hash for XMR -// -// task convertToTask() -// { -// task tk; -// -// tk.taskIndex = taskIndex; -// tk.firstComputorIndex = firstComputorIndex; -// tk.lastComputorIndex = lastComputorIndex; -// memcpy(tk.m_blob, m_blob, 408); -// tk.m_size = m_size; -// tk.m_target = m_target; -// tk.m_height = m_height; -// memcpy(tk.m_seed, m_seed, 32); -// return tk; -// } -//}; - -//struct XMRSolution -//{ -// uint64_t taskIndex; -// uint16_t firstComputorIndex, lastComputorIndex; -// -// uint32_t nonce; // xmrig::JobResult.nonce -// uint8_t result[32]; // xmrig::JobResult.result -// -// solution convertToSol() -// { -// solution sol; -// sol._taskIndex = taskIndex; -// sol.firstComputorIndex = firstComputorIndex; -// sol.lastComputorIndex = lastComputorIndex; -// -// sol.nonce = nonce; -// memcpy(sol.result, result, 32); -// return sol; -// } -//}; - - -//struct RequestedCustomMiningData -//{ -// enum -// { -// type = 60, -// }; -// enum -// { -// taskType = 0, -// solutionType = 1, -// }; -// -// unsigned long long fromTaskIndex; -// unsigned long long toTaskIndex; -// -// // Determine which task partition -// unsigned short firstComputorIdx; -// unsigned short lastComputorIdx; -// unsigned int padding; -// -// long long dataType; -//}; -// -//struct RespondCustomMiningData -//{ -// enum -// { -// type = 61, -// }; -// enum -// { -// taskType = 0, -// solutionType = 1, -// }; -//}; - -//struct RequestedCustomMiningSolutionVerification -//{ -// enum -// { -// type = 62, -// }; -// unsigned long long taskIndex; -// unsigned short firstComputorIndex, lastComputorIndex; -// unsigned int nonce; // nonce of invalid solution -// unsigned long long isValid; // validity of the solution -//}; - -//struct RespondCustomMiningSolutionVerification -//{ -// enum -// { -// type = 63, -// }; -// enum -// { -// notExisted = 0, // solution not existed in cache -// valid = 1, // solution are set as valid -// invalid = 2, // solution are set as invalid -// customMiningStateEnded = 3, // not in custom mining state -// }; -// unsigned long long taskIndex; -// unsigned short firstComputorIndex, lastComputorIndex; -// unsigned int nonce; -// long long status; // Flag indicate the status of solution -//}; - - -//struct CustomMiningRespondDataHeader -//{ -// unsigned long long itemCount; // size of the data -// unsigned long long itemSize; // size of the data -// unsigned long long fromTimeStamp; // start of the ts -// unsigned long long toTimeStamp; // end of the ts -// unsigned long long respondType; // message type -//}; - - // simple poc design and queue, need better design to have higher precision std::mutex taskLock; task currentTask; @@ -361,135 +237,12 @@ std::atomic gInValid; std::atomic gValid; std::atomic nPeer; -//std::mutex gValidSolLock; -//std::vector gReportedSolutionsVec; -std::atomic gSubmittedSols(0); -//std::map nodeTasks; // Task that fetching from node -//std::map> nodeSolutions; // Solutions that fetching from node +uint8_t dispatcherPubkey[32] = {0}; #define XMR_NONCE_POS 39 #define XMR_VERIFY_THREAD 4 -void getKey() -{ - getSubseedFromSeed((unsigned char*)operatorSeed, operatorSubSeed); - getPrivateKeyFromSubSeed(operatorSubSeed, operatorPrivateKey); - getPublicKeyFromPrivateKey(operatorPrivateKey, operatorPublicKey); - getIdentityFromPublicKey(operatorPublicKey, operatorPublicIdentity, false); -} - -//bool reportVerifiedSol(QCPtr pConnection) -//{ -// bool haveSolsToReport = false; -// RequestedCustomMiningSolutionVerification verifiedSol; -// { -// { -// std::lock_guard validLock(gValidSolLock); -// if (!gReportedSolutionsVec.empty()) -// { -// verifiedSol = gReportedSolutionsVec.front(); -// haveSolsToReport = true; -// } -// } -// -// // Submit the validated solutions -// if (haveSolsToReport) -// { -// struct { -// RequestResponseHeader header; -// RequestedCustomMiningSolutionVerification verifiedSol; -// uint8_t signature[SIGNATURE_SIZE]; -// } packet; -// -// // Header -// packet.header.setSize(sizeof(packet)); -// packet.header.randomizeDejavu(); -// packet.header.setType(RequestedCustomMiningSolutionVerification::type); -// -// // Payload -// packet.verifiedSol = verifiedSol; -// -// // Sign the message -// uint8_t digest[32] = {0}; -// uint8_t signature[64] = {0}; -// KangarooTwelve( -// (unsigned char*)&packet.verifiedSol, -// sizeof(packet.verifiedSol), -// digest, -// 32); -// sign(operatorSubSeed, operatorPublicKey, digest, signature); -// memcpy(packet.signature, signature, 64); -// -// // Send data -// int dataSent = pConnection->sendData((uint8_t*)&packet, sizeof(packet)); -// -// // Send successfull, erase it from the invalid queue -// if (dataSent == sizeof(packet)) -// { -// struct { -// RequestResponseHeader header; -// RespondCustomMiningSolutionVerification verifiedSol; -// } respondPacket; -// -// int dataRev = pConnection->receiveData((uint8_t*)&respondPacket, sizeof(respondPacket)); -// if (dataRev > 0) -// { -// // TODO: process respond here. -// } -// -// // Repsond is good. Remove the submitted sol -// std::lock_guard validLock(gValidSolLock); -// if (!gReportedSolutionsVec.empty()) -// { -// gReportedSolutionsVec.erase(gReportedSolutionsVec.begin()); -// gSubmittedSols.fetch_add(1); -// } -// -// } -// } -// } -// return haveSolsToReport; -//} - -//void submitVerifiedSolution(const char* nodeIp) -//{ -// QCPtr qc; -// bool needReconnect = true; -// std::string log_header = "[" + std::string(nodeIp) + "]: "; -// while (!shouldExit) -// { -// try { -// if (needReconnect) -// { -// needReconnect = false; -// qc = make_qc(nodeIp, OPERATOR_PORT); -// qc->exchangePeer();// do the handshake stuff -// printf("%sConnected OPERATOR node %s\n", log_header.c_str(), nodeIp); -// } -// -// bool haveSolsToSubmited = reportVerifiedSol(qc); -// if (!haveSolsToSubmited) -// { -// SLEEP(1000); -// } -// } -// catch (std::logic_error &ex) { -// printf("%s\n", ex.what()); -// fflush(stdout); -// needReconnect = true; -// nPeer.fetch_add(-1);; -// SLEEP(1000); -// } -// catch (...) -// { -// printf("Unknown exception caught!\n"); -// fflush(stdout); -// needReconnect = true; -// nPeer.fetch_add(-1); -// SLEEP(1000); -// } -// } -//} +std::atomic gSubmittedSols(0); const unsigned hr_intervals[] = {120,600,1800,3600,86400,604800}; @@ -629,23 +382,23 @@ void verifyThread(int ignore) } if (haveSol) { - int computorId = (candidate._nonceu64 >> 32ULL) % 676; + int computorId = getComputorIDFromSol(&candidate); task matched_task; - if (candidate._taskIndex > local_task.taskIndex) + if (candidate.taskIndex > local_task.taskIndex) { printf("Do not expected: Missing task - check your peers\n"); continue; } - else if (candidate._taskIndex == local_task.taskIndex) + else if (candidate.taskIndex == local_task.taskIndex) { matched_task = local_task; } - else if (candidate._taskIndex == prevLocal_task.taskIndex) + else if (candidate.taskIndex == prevLocal_task.taskIndex) { matched_task = prevLocal_task; } - else if (candidate._taskIndex == prevPrevLocal_task.taskIndex) + else if (candidate.taskIndex == prevPrevLocal_task.taskIndex) { matched_task = prevPrevLocal_task; } else { @@ -654,12 +407,20 @@ void verifyThread(int ignore) continue; } + // Decypt the solution + solution decryptedSolution; + int decrypt_sts = decryptSolution((uint8_t*)&candidate, sizeof(candidate), nullptr, 0, &decryptedSolution); + if (decrypt_sts) + { + printf("Can not decrypt solution.\n"); + continue; + } + uint8_t out[32]; - bool isValid = verify(oc_ptr, &matched_task, &candidate, out); + bool isValid = verify(oc_ptr, &matched_task, &decryptedSolution, out); uint64_t v = ((uint64_t*)out)[3]; if (isValid) { -// verifedSol.isValid = 1; gValid.fetch_add(1); // printf("Valid Share from comp %d: %s\n", computorId, hex); { @@ -670,7 +431,7 @@ void verifyThread(int ignore) uint64_t share_value = 0xffffffffffffffffULL/v; std::string str_share_value = std::to_string(share_value); std::string job_hex = ""; - std::string nonce_str = std::to_string(candidate._nonceu64); + std::string nonce_str = std::to_string(decryptedSolution.combinedNonce); job_hex = "nonce" + nonce_str + "_comp" + std::to_string(computorId); addRecord(database->get_env(), share_db, job_hex, str_share_value); monitor_stats.diff_since.fetch_add(0xffffffffffffffff/matched_task.m_target); @@ -678,15 +439,11 @@ void verifyThread(int ignore) } else { - // Save the solution for sending to node this is an invalidate solutions -// { -// std::lock_guard validLock(gValidSolLock); -// verifedSol.isValid = 0; -// } gInValid.fetch_add(1); printf("Invalid Share from comp %d\n", computorId); } -// gReportedSolutionsVec.push_back(verifedSol); + // Add solution into submitted queue. This function will not run if no node verifiers are lauched + addVerifiedSolutions(&candidate, isValid); } else { @@ -730,6 +487,7 @@ void listenerThread(const char* nodeIp) if (buff.size() == sizeof(solution)) { solution* share = (solution*)buff.data(); + char iden[64] = {0}; getIdentityFromPublicKey(share->sourcePublicKey, iden, false); uint8_t sharedKeyAndGammingNonce[64]; @@ -746,7 +504,7 @@ void listenerThread(const char* nodeIp) continue; } - int computorNonceID = (share->_nonceu64 >> 32ULL) % 676; + int computorNonceID = getComputorIDFromSol(share); if (gammingKey[0] != 2) { printf("Wrong type from comps (%s) No.%d. want %d | have %d\n", iden, computorNonceID, 2, gammingKey[0]); @@ -755,7 +513,7 @@ void listenerThread(const char* nodeIp) { std::lock_guard slock(solLock); - auto p = std::make_pair(share->_taskIndex, share->_nonceu64); + auto p = std::make_pair(share->taskIndex, share->combinedNonce); if (mTaskNonce.find(p) == mTaskNonce.end()) { mTaskNonce[p] = true; @@ -823,6 +581,7 @@ void listenerThread(const char* nodeIp) debug_log += std::string(dbg); memset(dbg, 0, sizeof(dbg)); sprintf(dbg, " | height %llu\n", tk->m_height); debug_log += std::string(dbg); memset(dbg, 0, sizeof(dbg)); + printf("%s", debug_log.c_str()); } // TODO: help relaying the messages to connected peers @@ -847,12 +606,7 @@ void listenerThread(const char* nodeIp) } } -// Params controls if we should skip too late task compare to current time stamp -constexpr int64_t OFFSET_TIME_STAMP_IN_MS = 60000; -// The time windows allow we get more task from previous. -// This will allow us to get late arrival task -constexpr int64_t TIME_WINDOWS_IN_MS = 20000; template void printTaskInfo(T* tk, std::string logHeader) @@ -897,323 +651,6 @@ void printTaskInfo(T* tk, std::string logHeader) printf("%s", debug_log.c_str()); } -static uint64_t lastTaskTimeStamp = 0; - -//static randomx_flags gRandomXFlags; -//static randomx_cache *gRandomXCache; -//static randomx_vm * gRandomXVM = NULL; -//static uint8_t gRandomXCacheBuff[32]; - -//void initRandomX() -//{ -// gRandomXFlags = randomx_get_flags(); -// gRandomXCache = randomx_alloc_cache(gRandomXFlags); -// randomx_init_cache(gRandomXCache, gRandomXCacheBuff, 32); -// gRandomXVM = randomx_create_vm(gRandomXFlags, gRandomXCache, NULL); -//} -// -//void cleanRandomX() -//{ -// randomx_destroy_vm(gRandomXVM); -// randomx_release_cache(gRandomXCache); -//} - -//void verifySolutionFromNode(const XMRTask& rTask, std::vector& rSolutions) -//{ -// XMRTask local_task = rTask; -// -// int partId = getPartitionID(rTask.firstComputorIndex, rTask.lastComputorIndex); -// -// randomx_init_cache(gRandomXCache, local_task.m_seed, 32); -// randomx_vm_set_cache(gRandomXVM, gRandomXCache); -// -// for (auto& it : rSolutions) -// { -// uint8_t out[32]; -// std::vector blob; -// blob.resize(local_task.m_size, 0); -// memcpy(blob.data(), local_task.m_blob, local_task.m_size); -// uint32_t nonce = it.nonce; -// memcpy(blob.data() + XMR_NONCE_POS, &nonce, 4); -// randomx_calculate_hash(gRandomXVM, blob.data(), local_task.m_size, out); -// uint64_t v = ((uint64_t*)out)[3]; -// char hex[64]; -// byteToHex(out, hex, 32); -// solution candidate = it.convertToSol(); -// -// // Dummy test, for each 2 valid solution, the next one will be invalid -// #if DUMMY_TEST -// bool dummyInvalid = false; -// { -// std::lock_guard dummyLock(gDummyLock); -// gDummyCounter++; -// if (gDummyCounter % 4 == 0) -// { -// dummyInvalid = true; -// } -// } -// #endif -// -// RequestedCustomMiningSolutionVerification verifedSol; -// verifedSol.nonce = candidate.nonce; -// verifedSol.taskIndex = local_task.taskIndex; -// verifedSol.firstComputorIndex = candidate.firstComputorIndex; -// verifedSol.lastComputorIndex = candidate.lastComputorIndex; -// //verifedSol.padding = candidate.padding; -// -// int computorID = getComputorID(candidate.nonce, partId); -// #if DUMMY_TEST -// if (computorID <= local_task.lastComputorIndex && !dummyInvalid) -// #else -// if (computorID <= local_task.lastComputorIndex && v < local_task.m_target) -// #endif -// { -// verifedSol.isValid = 1; -// gValid.fetch_add(1); -//// printf("Valid Share for comp %d: %s\n", computorID, hex); -// { -// std::lock_guard g(compScoreLock); -// compScore[computorID]++; -// } -// } -// else -// { -// verifedSol.isValid = 0; -// gInValid.fetch_add(1); -//// printf("Invalid Share for comp %d: %s\n", computorID, hex); -// } -// // Save the solution for sending to node this is an invalidate solutions -// { -// std::lock_guard validLock(gValidSolLock); -// gReportedSolutionsVec.push_back(verifedSol); -// } -// } -//} -// -//void getCustomMiningSolutions(QCPtr pConnection, const char* logHeader, const std::map& nodeTask) -//{ -// nodeSolutions.clear(); -// -// struct { -// RequestResponseHeader header; -// RequestedCustomMiningData requestData; -// unsigned char signature[SIGNATURE_SIZE]; -// } packet; -// -// packet.header.setSize(sizeof(packet)); -// packet.header.randomizeDejavu(); -// packet.header.setType(RequestedCustomMiningData::type); -// -// for (const auto& it : nodeTask) -// { -// unsigned long long taskIndex = it.second.taskIndex; -// packet.requestData.dataType = RequestedCustomMiningData::solutionType; -// packet.requestData.fromTaskIndex = taskIndex; -// packet.requestData.firstComputorIdx = it.second.firstComputorIndex; -// packet.requestData.lastComputorIdx = it.second.lastComputorIndex; -// packet.requestData.toTaskIndex = 0; // Unused -// -// // Sign the message -// uint8_t digest[32] = {0}; -// uint8_t signature[64] = {0}; -// KangarooTwelve( -// (unsigned char*)&packet.requestData, -// sizeof(RequestedCustomMiningData), -// digest, -// 32); -// sign(operatorSubSeed, operatorPublicKey, digest, signature); -// memcpy(packet.signature, signature, 64); -// -// // Send request solution -// int dataSent = pConnection->sendData((uint8_t*)&packet, sizeof(packet)); -// if (dataSent == sizeof(packet)) -// { -// // Get the solution -// RequestResponseHeader respond_header = pConnection->receiveHeader(); -// // Verified the message -// if (respond_header.type() == RespondCustomMiningData::type) -// { -// if (respond_header.size() > sizeof(RequestResponseHeader)) -// { -// int dataSize = respond_header.size() - sizeof(RequestResponseHeader); -// std::vector dataBuffer(dataSize); -// unsigned char* pData = &dataBuffer[0]; -// int receivedSize = pConnection->receiveData(pData, dataSize); -// -// if (receivedSize != dataSize) -// { -// continue; -// } -// -// CustomMiningRespondDataHeader respondDataHeader = *(CustomMiningRespondDataHeader*)pData; -// if (respondDataHeader.itemCount > 0 && respondDataHeader.respondType == RespondCustomMiningData::solutionType) -// { -// std::cout << "Found " << respondDataHeader.itemCount -// << " shares for task index (" << taskIndex -// << ", " << it.second.firstComputorIndex << ", " << it.second.lastComputorIndex << ")" -// << std::endl << std::flush; -// -// // Push the solutions into queue -// XMRSolution* pSols = (XMRSolution*)(pData + sizeof(CustomMiningRespondDataHeader)); -// for (int k = 0; k < respondDataHeader.itemCount; k++) -// { -// nodeSolutions[taskIndex].push_back(pSols[k]); -// } -// -// // Verified solutions -// verifySolutionFromNode(it.second, nodeSolutions[taskIndex]); -// } -// } -// } -// } -// } -//} -// -//bool fetchCustomMiningData(QCPtr pConnection, const char* logHeader) -//{ -// bool haveCustomMiningData = false; -// struct { -// RequestResponseHeader header; -// RequestedCustomMiningData requestData; -// unsigned char signature[SIGNATURE_SIZE]; -// } packet; -// -// packet.header.setSize(sizeof(packet)); -// packet.header.randomizeDejavu(); -// packet.header.setType(RequestedCustomMiningData::type); -// -// uint64_t fromTaskIndex = lastTaskTimeStamp > TIME_WINDOWS_IN_MS ? lastTaskTimeStamp - TIME_WINDOWS_IN_MS : lastTaskTimeStamp; -// uint64_t toTaskIndex = 0; // Fetch all the task from the fromTaskIndex -// packet.requestData.dataType = RequestedCustomMiningData::taskType; -// packet.requestData.fromTaskIndex = fromTaskIndex; -// packet.requestData.toTaskIndex = toTaskIndex; -// -// // Sign the message -// uint8_t digest[32] = {0}; -// uint8_t signature[64] = {0}; -// KangarooTwelve( -// (unsigned char*)&packet.requestData, -// sizeof(RequestedCustomMiningData), -// digest, -// 32); -// sign(operatorSubSeed, operatorPublicKey, digest, signature); -// memcpy(packet.signature, signature, 64); -// -// // Send request all task -// int dataSent = pConnection->sendData((uint8_t*)&packet, sizeof(packet)); -// if (dataSent == sizeof(packet)) -// { -// // Get the task -// RequestResponseHeader respond_header = pConnection->receiveHeader(); -// // Verified the message -// if (respond_header.type() == RespondCustomMiningData::type) -// { -// if (respond_header.size() > sizeof(RequestResponseHeader)) -// { -// unsigned int dataSize = respond_header.size() - sizeof(RequestResponseHeader); -// std::vector dataBuffer(dataSize); -// unsigned char* pData = &dataBuffer[0]; -// int receivedSize = pConnection->receiveData(pData, dataSize); -// -// // Data is failed to rev -// if (receivedSize != dataSize) -// { -// return false; -// } -// -// unsigned long long lastReceivedTaskTs = 0; -// CustomMiningRespondDataHeader respondDataHeader = *(CustomMiningRespondDataHeader*)pData; -// if (respondDataHeader.itemCount > 0 && respondDataHeader.respondType == RespondCustomMiningData::taskType) -// { -// std::vector taskVec; -// XMRTask* pTask = (XMRTask*)(pData + sizeof(CustomMiningRespondDataHeader)); -// for (int i = 0; i < respondDataHeader.itemCount; i++) -// { -// XMRTask rawTask = pTask[i]; -// lastReceivedTaskTs = rawTask.taskIndex; -// nodeTasks[rawTask.taskIndex] = rawTask; -// } -// -// // Remove too late task. We can tune OFFSET_TIME_STAMP_IN_MS -// auto now = std::chrono::system_clock::now(); -// auto duration = now.time_since_epoch(); -// auto milliseconds = std::chrono::duration_cast(duration).count(); -// for (auto it = nodeTasks.begin(); it != nodeTasks.end(); ) -// { -// if (std::abs((int64_t)(it->first) - (int64_t)(milliseconds)) > OFFSET_TIME_STAMP_IN_MS) -// { -// it = nodeTasks.erase(it); -// } -// else -// { -// ++it; -// } -// } -// -// // From current active task. Try to fetch solutions/shares of the task -// getCustomMiningSolutions(pConnection, logHeader, nodeTasks); -// -// // Update the last time stamp by the last task received -// if (lastReceivedTaskTs > 0) -// { -// lastTaskTimeStamp = lastReceivedTaskTs; -// } -// } -// } -// } -// } -// -// return haveCustomMiningData; -//} -// -//void operatorFetcherThread(const char* nodeIp) -//{ -// QCPtr qc; -// bool needReconnect = true; -// std::string log_header = "[OP:" + std::string(nodeIp) + "]: "; -// while (!shouldExit) -// { -// try { -// if (needReconnect) -// { -// nPeer.fetch_add(1); -// needReconnect = false; -// qc = make_qc(nodeIp, OPERATOR_PORT); -// printf("%sConnected OPERATOR node %s\n", log_header.c_str(), nodeIp); -// } -// -// bool haveCustomMiningData = fetchCustomMiningData(qc, log_header.c_str()); -// if (!haveCustomMiningData) -// { -// // No custom mining sol. Sleep and update the time stamp -// auto now = std::chrono::system_clock::now(); -// auto duration = now.time_since_epoch(); -// auto milliseconds = std::chrono::duration_cast(duration).count(); -// lastTaskTimeStamp = milliseconds; -// SLEEP(1000); -// } -// else -// { -// SLEEP(10000); -// } -// } -// catch (std::logic_error &ex) { -// printf("%s\n", ex.what()); -// fflush(stdout); -// needReconnect = true; -// nPeer.fetch_add(-1); -// SLEEP(1000); -// } -// catch (...) -// { -// printf("Unknown exception caught!\n"); -// fflush(stdout); -// needReconnect = true; -// nPeer.fetch_add(-1); -// SLEEP(1000); -// } -// } -//} std::vector split(const std::string& s, char delimiter) { std::vector result; @@ -1297,14 +734,10 @@ int run(int argc, char *argv[]) { setupDB(); // Print parsed values - if (!seed.empty()) - { - memcpy(operatorSeed, seed.c_str(), 56); - std::cout << "Seed: " << operatorSeed << std::endl; - getKey(); - } - std::cout << "OperatorID: " << operatorPublicIdentity << "\n"; - std::cout << "Node IP: " << operatorIp << "\n"; +#if TESTNET_ENABLE + std::cout << "DUMMY TEST mode enabled! " << std::endl; +#endif + std::cout << "Peers: "; for (const auto& peer : peers) { @@ -1312,11 +745,6 @@ int run(int argc, char *argv[]) { } std::cout << std::endl; - auto now = std::chrono::system_clock::now(); - auto duration = now.time_since_epoch(); - auto milliseconds = std::chrono::duration_cast(duration).count(); - lastTaskTimeStamp = milliseconds; - getPublicKeyFromIdentity(DISPATCHER, dispatcherPubkey); loadScore(); std::vector thr; @@ -1331,12 +759,12 @@ int run(int argc, char *argv[]) { } // Fetch tasks from node ip -// initRandomX(); -// std::shared_ptr operator_thread; -// if (!seed.empty() && !operatorIp.empty()) -// { -// operator_thread = std::make_shared(operatorFetcherThread, operatorIp.c_str()); -// } + bool enableNodeVerifier = false; + if (!seed.empty() && !operatorIp.empty()) + { + int sts = launchNodeVerifier(operatorIp.c_str(), OPERATOR_PORT, seed.c_str()); + enableNodeVerifier = (sts == 0); + } std::thread verify_thr[XMR_VERIFY_THREAD]; for (int i = 0; i < XMR_VERIFY_THREAD; i++) @@ -1344,12 +772,6 @@ int run(int argc, char *argv[]) { verify_thr[i] = std::thread(verifyThread, i); } -// std::shared_ptr submit_thr; -// if (!seed.empty()) -// { -// submit_thr = std::make_shared(submitVerifiedSolution, operatorIp.c_str()); -// } - SLEEP(3000); int curEpoch = getCurrentEpoch(); memset(&monitor_stats, 0, sizeof(monitor_stats)); @@ -1357,6 +779,10 @@ int run(int argc, char *argv[]) { { hr_update(&monitor_stats); printf("Active peer: %d | Valid: %lu | Invalid: %lu | Stale: %lu | Submit: %lu | Expected HR: %.2f Mhs\n", nPeer.load(), gValid.load(), gInValid.load(), gStale.load(), gSubmittedSols.load(), monitor_stats.avg[0]/1e6); + if (enableNodeVerifier) + { + printNodeVerifierStats(); + } database->cleanup_db_if_needed("shares", 800ULL * 1024 * 1024, 10000); SLEEP(10000); { @@ -1370,7 +796,9 @@ int run(int argc, char *argv[]) { } } } -// cleanRandomX(); + + // Stop the node verifier + stopNodeVerifier(); return 0; } diff --git a/monero-poc/verifier/nodeVerifier.cpp b/monero-poc/verifier/nodeVerifier.cpp new file mode 100644 index 0000000..6568d5c --- /dev/null +++ b/monero-poc/verifier/nodeVerifier.cpp @@ -0,0 +1,695 @@ +#include "nodeVerifier.h" + +#include "verifierLib.h" +#include "connection.h" +#include "keyUtils.h" +#include "K12AndKeyUtil.h" + +#include +#include +#include +#include +#include +#include +#include + +// Message struture for request custom mining data +struct RequestedCustomMiningData +{ + enum + { + type = 60, + }; + enum + { + taskType = 0, + solutionType = 1, + }; + // Task index information: + // - For taskType: 'fromTaskIndex' is the lower bound and 'toTaskIndex' is the upper bound of the task range [fromTaskIndex, toTaskIndex]. + // - For solutionType: only 'fromTaskIndex' is used, since the solution response is tied to a single task. + unsigned long long fromTaskIndex; + unsigned long long toTaskIndex; + + // Type of the request: either task (taskType) or solution (solutionType). + long long dataType; +}; + +// Message struture for respond custom mining data +struct RespondCustomMiningData +{ + enum + { + type = 61, + }; + enum + { + taskType = 0, + solutionType = 1, + }; + // The 'payload' variable is defined externally and usually contains a byte array. + // Ussualy: [CustomMiningRespondDataHeader ... NumberOfItems * ItemSize]; +}; + +struct RequestedCustomMiningSolutionVerification +{ + enum + { + type = 62, + }; + unsigned long long taskIndex; + unsigned long long nonce; + unsigned long long encryptionLevel; + unsigned long long computorRandom; + unsigned long long reserve2; + unsigned long long isValid; // validity of the solution. 0: invalid, >0: valid +}; +struct RespondCustomMiningSolutionVerification +{ + enum + { + type = 63, + }; + enum + { + notExisted = 0, // solution not existed in cache + valid = 1, // solution are set as valid + invalid = 2, // solution are set as invalid + customMiningStateEnded = 3, // not in custom mining state + }; + unsigned long long taskIndex; + unsigned long long nonce; + unsigned long long encryptionLevel; + unsigned long long computorRandom; + unsigned long long reserve2; + long long status; // Flag indicate the status of solution +}; + +struct CustomMiningRespondDataHeader +{ + unsigned long long itemCount; // size of the data + unsigned long long itemSize; // size of the data + unsigned long long fromTimeStamp; // start of the ts + unsigned long long toTimeStamp; // end of the ts + unsigned long long respondType; // message type +}; + +constexpr uint64_t taskPayloadOffset = 3 * 32; // pubKey, zeros, gammingNonce and signature; +constexpr uint64_t taskPayloadSize = sizeof(task) - taskPayloadOffset - 64; // minus pubKey, zeros, gammingNonce and signature; +constexpr uint64_t solutionPayloadOffset = 3 * 32; // pubKey, zeros, gammingNonce and signature; +constexpr uint64_t solutionPayloadSize = sizeof(solution) - solutionPayloadOffset - 64; // minus pubKey, zeros, gammingNonce and signature; + +// Timeout of a connection. Increase it if the connection is robust +constexpr uint64_t NODE_CONNECTION_TIMEOUT = 10000; // 11000 is set in connection implementation, so 10000 is chosen +// This will allow us to get late arrival task +constexpr int64_t TIME_WINDOWS_OFFSET_IN_MS = 10000; +// Params controls avoid we stay in submitted loops for too long +constexpr int64_t REPORTED_TIMEOUT_IN_MS = 10000; + +class NodeVerifier +{ +public: + NodeVerifier(const char *nodeIp, int nodePort, const char *operatorSeed); + ~NodeVerifier(); + + // Lauch thread to get solutions from node and do verification + int launch(); + + // Stop verification thread + int stop(); + + // Add a solution into submitteed queue + int addVerifiedSolutions(solution *pSolution, bool isValid); + + void printStats(); + + RequestedCustomMiningSolutionVerification convertFromSolution(const solution &rSolution, bool isValid); + +private: + // Thread that continuously get task and solution from nodes + void getSolutionsThread(); + bool fetchCustomMiningData(QCPtr pConnection, const char *logHeader); + void getCustomMiningSolutions(QCPtr pConnection, const char *logHeader, const std::map &nodeTask); + void verifySolutionFromNode(const task &rTask, std::vector &rSolutions); + + // Thread continously submit verified solutions if there is any + void submitVerifiedSolutionsThread(); + bool reportVerifiedSol(QCPtr pConnection); + + void getKey(const char *operatorSeed); + std::vector waitForPackage( QCPtr pConnection, int packageType, int64_t timeOut); + + std::atomic mIsRunning; + std::unique_ptr mVerifierThread; + uint64_t mLastTaskTimeStamp; + std::map mNodeTasks; // Task that fetching from node + std::map> mNodeSolutions; // Solutions that fetching from node + + uint8_t *mOcVerifier; + + uint8_t mOperatorPublicKey[32]; + uint8_t mOperatorSubSeed[32]; + uint8_t operatorPrivateKey[32]; + char mOperatorPublicIdentity[128]; + + std::string mNodeIP; + int mNodePort; + + // Solutions are verified and wait for reported to node + std::vector mReportedSolutionsVec; + std::mutex mValidSolLock; + std::atomic mSubmittedSolutionsCount; + std::atomic mTotalSolutions; + std::atomic mValidSolutionsCount; + std::atomic mInvalidSolutionsCount; + +#if TESTNET_ENABLE + unsigned long long mDummyCounter; + std::mutex mDummyLock; +#endif +}; + +NodeVerifier::NodeVerifier(const char *nodeIp, int nodePort, const char *operatorSeed) +{ + mIsRunning = false; + mVerifierThread = nullptr; + mLastTaskTimeStamp = 0; + mNodeTasks.clear(); // Task that fetching from node + mNodeSolutions.clear(); // Solutions that fetching from node + mOcVerifier = nullptr; + memset(mOperatorPublicKey, 0, sizeof(mOperatorPublicKey)); + memset(mOperatorSubSeed, 0, sizeof(mOperatorSubSeed)); + memset(operatorPrivateKey, 0, sizeof(operatorPrivateKey)); + memset(mOperatorPublicIdentity, 0, sizeof(mOperatorPublicIdentity)); + mNodeIP = nodeIp; + mNodePort = nodePort; + mSubmittedSolutionsCount = 0; + mTotalSolutions = 0; + mValidSolutionsCount = 0; + mInvalidSolutionsCount = 0; + +#if TESTNET_ENABLE + mDummyCounter = 0; +#endif + + // Get require keys + getKey(operatorSeed); + std::cout << "OperatorAddress: " << nodeIp << ":" << nodePort << "\n"; + std::cout << "OperatorID: " << mOperatorPublicIdentity << "\n"; + + // Create verifier instance + mOcVerifier = (uint8_t *)createOCVerifier(); +} + +NodeVerifier::~NodeVerifier() +{ + // Stop all threads + stop(); + + // Clean oc verifier + if (mOcVerifier) + { + destroySolVerifier((void *)mOcVerifier); + mOcVerifier = nullptr; + } +} + +void NodeVerifier::getKey(const char *operatorSeed) +{ + getSubseedFromSeed((unsigned char *)operatorSeed, mOperatorSubSeed); + getPrivateKeyFromSubSeed(mOperatorSubSeed, operatorPrivateKey); + getPublicKeyFromPrivateKey(operatorPrivateKey, mOperatorPublicKey); + getIdentityFromPublicKey(mOperatorPublicKey, mOperatorPublicIdentity, false); +} + +RequestedCustomMiningSolutionVerification NodeVerifier::convertFromSolution(const solution &rSolution, bool isValid) +{ + RequestedCustomMiningSolutionVerification respond; + respond.taskIndex = rSolution.taskIndex; + respond.nonce = rSolution.combinedNonce; + respond.encryptionLevel = rSolution.encryptionLevel; + respond.computorRandom = rSolution.computorRandom; + respond.reserve2 = rSolution.reserve2; + respond.isValid = isValid ? 1 : 0; + return respond; +}; + +// Add a solution into submitteed queue +int NodeVerifier::addVerifiedSolutions(solution *pSolution, bool isValid) +{ + if (isValid) + { + mValidSolutionsCount.fetch_add(1); + } + else + { + mInvalidSolutionsCount.fetch_add(1); + } + mTotalSolutions.fetch_add(1); + + RequestedCustomMiningSolutionVerification verifedSol = convertFromSolution(*pSolution, isValid); + // Save the solution for sending to node + { + std::lock_guard validLock(mValidSolLock); + mReportedSolutionsVec.push_back(verifedSol); + } + return 0; +} + +void NodeVerifier::verifySolutionFromNode(const task &rTask, std::vector &rSolutions) +{ + task local_task = rTask; + for (auto &candidate : rSolutions) + { + //int computorID = getComputorIDFromSol(&candidate); + + // There is some case that solution need to be decrypt before do the verification + solution decryptedSolution; + int decrypt_sts = decryptSolution((uint8_t *)&candidate, sizeof(candidate), nullptr, 0, &decryptedSolution); + if (decrypt_sts) + { + printf("Can not decrypt solution.\n"); + continue; + } + uint8_t out[32]; + bool isValid = verify(mOcVerifier, &local_task, &decryptedSolution, out); + + // Dummy test, for each 2 valid solutions, the next one will be invalid +#if TESTNET_ENABLE + isValid = false; + { + std::lock_guard dummyLock(mDummyLock); + mDummyCounter++; + if (mDummyCounter % 4 == 0) + { + isValid = true; + } + } +#endif + // Save the solution for sending to node + addVerifiedSolutions(&candidate, isValid); + } +} + +void NodeVerifier::getCustomMiningSolutions(QCPtr pConnection, const char *logHeader, const std::map &nodeTask) +{ + mNodeSolutions.clear(); + + struct + { + RequestResponseHeader header; + RequestedCustomMiningData requestData; + unsigned char signature[SIGNATURE_SIZE]; + } packet; + + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestedCustomMiningData::type); + + for (const auto &it : nodeTask) + { + unsigned long long taskIndex = it.second.taskIndex; + packet.requestData.dataType = RequestedCustomMiningData::solutionType; + packet.requestData.fromTaskIndex = taskIndex; + packet.requestData.toTaskIndex = 0; // Unused + + // Sign the message + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + KangarooTwelve( + (unsigned char *)&packet.requestData, + sizeof(RequestedCustomMiningData), + digest, + 32); + sign(mOperatorSubSeed, mOperatorPublicKey, digest, signature); + memcpy(packet.signature, signature, 64); + + // Send request solution + int dataSent = pConnection->sendData((uint8_t *)&packet, sizeof(packet)); + if (dataSent == sizeof(packet)) + { + // Get the solution + RequestResponseHeader respond_header = pConnection->receiveHeader(); + // Verified the message + if (respond_header.type() == RespondCustomMiningData::type) + { + if (respond_header.size() > sizeof(RequestResponseHeader)) + { + int dataSize = respond_header.size() - sizeof(RequestResponseHeader); + std::vector dataBuffer(dataSize); + unsigned char *pData = &dataBuffer[0]; + int receivedSize = pConnection->receiveData(pData, dataSize); + + if (receivedSize != dataSize) + { + continue; + } + + CustomMiningRespondDataHeader respondDataHeader = *(CustomMiningRespondDataHeader *)pData; + if (respondDataHeader.itemCount > 0 && respondDataHeader.respondType == RespondCustomMiningData::solutionType) + { + std::cout << "Found " << respondDataHeader.itemCount + << " shares for task index " << taskIndex + << std::endl + << std::flush; + + solution respondSolution; + uint8_t *pSolutionData = pData + sizeof(CustomMiningRespondDataHeader); + for (int k = 0; k < respondDataHeader.itemCount; k++, pSolutionData += solutionPayloadSize) + { + memcpy((uint8_t *)&respondSolution + solutionPayloadOffset, pSolutionData, solutionPayloadSize); + mNodeSolutions[taskIndex].push_back(respondSolution); + } + + // Verified solutions + verifySolutionFromNode(it.second, mNodeSolutions[taskIndex]); + } + } + } + } + } +} + +std::vector NodeVerifier::waitForPackage( QCPtr pConnection, int packageType, int64_t timeOut) +{ + std::vector dataBuffer; + auto start = std::chrono::system_clock::now(); + const auto timeout = std::chrono::milliseconds(timeOut); + while (true) + { + // Check timeout + if (std::chrono::system_clock::now() - start > timeout) + { + std::cout << "Timeout: did not receive packet type " << packageType << std::endl << std::flush; + break; + } + + // Get the task + RequestResponseHeader respond_header = pConnection->receiveHeader(); + + // Empty or malfunction package just skip + if (respond_header.size() <= sizeof(RequestResponseHeader)) + { + continue; + } + + // Payload + unsigned int dataSize = respond_header.size() - sizeof(RequestResponseHeader); + dataBuffer.resize(dataSize); + unsigned char *pData = &dataBuffer[0]; + int receivedSize = pConnection->receiveData(pData, dataSize); + + // Expected header process and break + if ((int)respond_header.type() == packageType) + { + // Data is failed to rev + if (receivedSize != dataSize) + { + std::cout << "Respond size is mismatched!\n"; + dataBuffer.clear(); + } + return dataBuffer; + } + } + + return dataBuffer; +} + +bool NodeVerifier::fetchCustomMiningData(QCPtr pConnection, const char *logHeader) +{ + mNodeTasks.clear(); + struct + { + RequestResponseHeader header; + RequestedCustomMiningData requestData; + unsigned char signature[SIGNATURE_SIZE]; + } packet; + + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestedCustomMiningData::type); + + auto now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto milliseconds = std::chrono::duration_cast(duration).count(); + + uint64_t fromTaskIndex = (uint64_t)(milliseconds - TIME_WINDOWS_OFFSET_IN_MS); + uint64_t toTaskIndex = 0; // Fetch all the task from the fromTaskIndex + packet.requestData.dataType = RequestedCustomMiningData::taskType; + packet.requestData.fromTaskIndex = fromTaskIndex; + packet.requestData.toTaskIndex = toTaskIndex; + + // Sign the message + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + KangarooTwelve( + (unsigned char *)&packet.requestData, + sizeof(RequestedCustomMiningData), + digest, + 32); + sign(mOperatorSubSeed, mOperatorPublicKey, digest, signature); + memcpy(packet.signature, signature, 64); + + // Send request all task + int dataSent = pConnection->sendData((uint8_t *)&packet, sizeof(packet)); + if (dataSent == sizeof(packet)) + { + std::vector dataBuffer = waitForPackage( pConnection, (int)RespondCustomMiningData::type, 10000); + if (!dataBuffer.empty()) + { + uint8_t* pData = &dataBuffer[0]; + unsigned long long lastReceivedTaskTs = 0; + CustomMiningRespondDataHeader respondDataHeader = *(CustomMiningRespondDataHeader *)pData; + if (respondDataHeader.itemCount > 0 && respondDataHeader.respondType == RespondCustomMiningData::taskType) + { + std::vector taskVec; + task respondTask; + uint8_t *pTaskData = pData + sizeof(CustomMiningRespondDataHeader); + for (int i = 0; i < respondDataHeader.itemCount; i++, pTaskData += taskPayloadSize) + { + memcpy((uint8_t *)&respondTask + taskPayloadOffset, pTaskData, taskPayloadSize); + mNodeTasks[respondTask.taskIndex] = respondTask; + } + + for (auto it = mNodeTasks.begin(); it != mNodeTasks.end(); ++it) + { + lastReceivedTaskTs = std::max((unsigned long long)it->first, lastReceivedTaskTs); + } + + // From current active task. Try to fetch solutions/shares of the task + getCustomMiningSolutions(pConnection, logHeader, mNodeTasks); + } + } + } + + return !mNodeTasks.empty(); +} + +void NodeVerifier::getSolutionsThread() +{ + auto startTime = std::chrono::system_clock::now(); + + QCPtr qc; + bool needReconnect = true; + std::string log_header = "[OP:" + mNodeIP + "]: "; + mIsRunning = true; + while (mIsRunning) + { + try + { + if (needReconnect) + { + needReconnect = false; + qc = make_qc(mNodeIP.c_str(), mNodePort); + + startTime = std::chrono::system_clock::now(); + std::cout<< log_header << "Connected/Reconnected to OPERATOR node " << mNodeIP.c_str() << std::endl; + } + + // Fetch tasks and solution from node + fetchCustomMiningData(qc, log_header.c_str()); + + // Report verified solutions if there is any + reportVerifiedSol(qc); + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Check timeout + if (NODE_CONNECTION_TIMEOUT > 0 && + std::chrono::system_clock::now() - startTime > std::chrono::milliseconds(NODE_CONNECTION_TIMEOUT)) + { + needReconnect = true; + } + + } + catch (std::logic_error &ex) + { + std::cout<< ex.what() << std::endl; + fflush(stdout); + needReconnect = true; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + catch (...) + { + std::cout<< "Unknown exception caught!\n"; + fflush(stdout); + needReconnect = true; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } +} + +bool NodeVerifier::reportVerifiedSol(QCPtr pConnection) +{ + bool haveSolsToReport = false; + RequestedCustomMiningSolutionVerification verifiedSol; + bool isTimeOut = false; + auto start = std::chrono::system_clock::now(); + + while (mIsRunning && !mReportedSolutionsVec.empty() && !isTimeOut) + { + { + std::lock_guard validLock(mValidSolLock); + if (!mReportedSolutionsVec.empty()) + { + verifiedSol = mReportedSolutionsVec.front(); + haveSolsToReport = true; + } + } + + // Submit the validated solutions + if (haveSolsToReport) + { + struct + { + RequestResponseHeader header; + RequestedCustomMiningSolutionVerification verifiedSol; + uint8_t signature[SIGNATURE_SIZE]; + } packet; + + // Header + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestedCustomMiningSolutionVerification::type); + + // Payload + packet.verifiedSol = verifiedSol; + + // Sign the message + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + KangarooTwelve( + (unsigned char *)&packet.verifiedSol, + sizeof(packet.verifiedSol), + digest, + 32); + sign(mOperatorSubSeed, mOperatorPublicKey, digest, signature); + memcpy(packet.signature, signature, 64); + + // Send data + int dataSent = pConnection->sendData((uint8_t *)&packet, sizeof(packet)); + + // Send successfull, erase it from the invalid queue + if (dataSent == sizeof(packet)) + { + struct + { + RequestResponseHeader header; + RespondCustomMiningSolutionVerification verifiedSol; + } respondPacket; + + int dataRev = pConnection->receiveData((uint8_t *)&respondPacket, sizeof(respondPacket)); + if (dataRev > 0) + { + // TODO: process respond here. + } + + // Repsond is good. Remove the submitted sol + std::lock_guard validLock(mValidSolLock); + if (!mReportedSolutionsVec.empty()) + { + mReportedSolutionsVec.erase(mReportedSolutionsVec.begin()); + mSubmittedSolutionsCount.fetch_add(1); + } + } + } + + auto end = std::chrono::system_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start).count(); + isTimeOut = elapsed > REPORTED_TIMEOUT_IN_MS; + if (isTimeOut) + { + std::cout << "Submitted timeout " << elapsed << " ms\n"; + } + } + return haveSolsToReport; +} + +void NodeVerifier::printStats() +{ + std::stringstream statStr; + statStr << "Valid: " << mValidSolutionsCount.load() + << " | Invalid: " << mInvalidSolutionsCount.load() + << " | Total: " << mTotalSolutions.load() + << " | Submit: " << mSubmittedSolutionsCount.load(); + std::cout << "NodeVerifier - " << statStr.str() << std::endl; +} + +int NodeVerifier::launch() +{ + // Trigger thread + mVerifierThread = std::make_unique(&NodeVerifier::getSolutionsThread, this); + + return 0; +} + +int NodeVerifier::stop() +{ + mIsRunning = false; + + // Wait for thread join + if (mVerifierThread && mVerifierThread->joinable()) + { + mVerifierThread->join(); + mVerifierThread = nullptr; + } + return 0; +} + +std::unique_ptr pNodeVerifier = nullptr; +int launchNodeVerifier(const char *nodeIp, int nodePort, const char *operatorSeed) +{ + pNodeVerifier = nullptr; + pNodeVerifier.reset(new NodeVerifier(nodeIp, nodePort, operatorSeed)); + int sts = pNodeVerifier->launch(); + return sts; +} + +int stopNodeVerifier() +{ + std::cout << "Stop node verifier!\n"; + int sts = 0; + if (pNodeVerifier) + { + sts = pNodeVerifier->stop(); + } + return sts; +} + +int addVerifiedSolutions(void *pSolution, bool isValid) +{ + int sts = 0; + if (pNodeVerifier) + { + sts = pNodeVerifier->addVerifiedSolutions((solution *)pSolution, isValid); + } + return sts; +} + +void printNodeVerifierStats() +{ + if (pNodeVerifier) + { + pNodeVerifier->printStats(); + } +} \ No newline at end of file diff --git a/monero-poc/verifier/nodeVerifier.h b/monero-poc/verifier/nodeVerifier.h new file mode 100644 index 0000000..afeb622 --- /dev/null +++ b/monero-poc/verifier/nodeVerifier.h @@ -0,0 +1,12 @@ +#pragma once + +// Lauch a verifier that connect to node try to fetch solution and submit verified result back to node +int launchNodeVerifier(const char* nodeIp, int nodePort, const char* operatorSeed); + +// Stop the node verifier +int stopNodeVerifier(); + +// Add external verified solutions into submitted queue +int addVerifiedSolutions(void* pSolution, bool isValid); + +void printNodeVerifierStats(); \ No newline at end of file diff --git a/monero-poc/verifier/testnetDispatcher.cpp b/monero-poc/verifier/testnetDispatcher.cpp new file mode 100644 index 0000000..105d9e9 --- /dev/null +++ b/monero-poc/verifier/testnetDispatcher.cpp @@ -0,0 +1,361 @@ +#include +#include +#include "stdio.h" +#include "connection.h" +#include "structs.h" +#include "keyUtils.h" +#include "K12AndKeyUtil.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "verifierLib.h" + +int getComputorIDFromSol(const solution *_sol) +{ + int computorID = 0; + if (_sol->encryptionLevel == 0) + { + computorID = (_sol->combinedNonce >> 32ULL) % 676ULL; + } + else + { + computorID = _sol->computorRandom % 676ULL; + } + return computorID; +} + + +static char DISPATCHER_SEED[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +static char COMPUTOR_SEED[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + +static unsigned char DISPATCHER_SUBSEED[32]; +static unsigned char DISPATCHER_PUBLICKEY[32]; +static char DISPATCHER_ID[128]; + +static unsigned char COMPUTOR_SUBSEED[32]; +static unsigned char COMPUTOR_PUBLICKEY[32]; +static char COMPUTOR_ID[128]; + +void getKey(const char *seed, unsigned char *subSeed, unsigned char *pubKey, char *identity) +{ + unsigned char privateKey[32]; + getSubseedFromSeed((unsigned char *)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, pubKey); + getIdentityFromPublicKey(pubKey, identity, false); +} + +void printHelp() { + std::cout << "Usage: testnetDispatcher [legacyNonce] [dispatcherSeed] [computorSeeds]\n"; + std::cout << " nodeIP - IP of the node\n"; + std::cout << " nodePort - Port of the node\n"; + std::cout << " rateLimitPerMin - Requests per minute\n"; + std::cout << " sharePerTask - Number of shares per task\n"; + std::cout << " legacyNonce - (optional) 0 or 1, default = 1\n"; + std::cout << " dispatcherSeed - (optional) dispatcher seed string\n"; + std::cout << " computorSeeds - (optional) computor seeds string\n"; +} + +std::atomic shouldExit(false); + +#define BROADCAST_MESSAGE 1 +#define MESSAGE_TYPE_CUSTOM_MINING_TASK 1 +#define MESSAGE_TYPE_CUSTOM_MINING_SOLUTION 2 + +// Task packet +struct TaskPacket +{ + RequestResponseHeader header; + task payload; +}; + +// Share packet +struct SharePacket +{ + RequestResponseHeader header; + solution payload; +}; + +inline void fillRandom32(unsigned char *buf) +{ + // Thread-local fallback generator + thread_local std::mt19937_64 gen(std::random_device{}()); + for (int i = 0; i < 4; i++) + { + auto val = gen(); + std::memcpy(buf + i * 8, &val, 8); + } +} + +inline uint32_t randomU32() +{ + thread_local std::mt19937 gen(std::random_device{}()); + std::uniform_int_distribution dist(0, 0xFFFFFFFFu); + return dist(gen); +} + +TaskPacket createTask() +{ + // Craft a task + task taskMessage; + + memcpy(taskMessage.sourcePublicKey, DISPATCHER_PUBLICKEY, sizeof(taskMessage.sourcePublicKey)); + + // Zero destination is used for custom mining + memset(taskMessage.zero, 0, sizeof(taskMessage.zero)); + + // Payload of a dummy task + auto now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto milliseconds = std::chrono::duration_cast(duration).count(); + taskMessage.taskIndex = (uint64_t)milliseconds; + // Fill dummy data + taskMessage.m_height = 3511959; + taskMessage.m_size = 825; + taskMessage.m_extraNonceOffset = 174; + + // Gamming nonce + unsigned char sharedKeyAndGammingNonce[64]; + // Default behavior when provided seed is just a signing address + // first 32 bytes of sharedKeyAndGammingNonce is set as zeros + memset(sharedKeyAndGammingNonce, 0, 32); + + // Last 32 bytes of sharedKeyAndGammingNonce is randomly created so that gammingKey[0] = 0 (MESSAGE_TYPE_CUSTOM_MINING_TASK) + unsigned char gammingKey[32]; + do + { + fillRandom32(taskMessage.gammingNonce); + memcpy(&sharedKeyAndGammingNonce[32], taskMessage.gammingNonce, 32); + KangarooTwelve(sharedKeyAndGammingNonce, 64, gammingKey, 32); + } while (gammingKey[0] != MESSAGE_TYPE_CUSTOM_MINING_TASK); + + // Sign the message + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + KangarooTwelve( + (unsigned char *)&taskMessage, + sizeof(taskMessage) - SIGNATURE_SIZE, + digest, + 32); + sign(DISPATCHER_SUBSEED, DISPATCHER_PUBLICKEY, digest, signature); + memcpy(taskMessage.signature, signature, 64); + + TaskPacket packet; + packet.header.setSize(sizeof(packet)); + packet.header.zeroDejavu(); + packet.header.setType(BROADCAST_MESSAGE); + packet.payload = taskMessage; + + return packet; +} + +solution createShare(task &rTask, bool legacyNonce) +{ + solution share; + share.taskIndex = rTask.taskIndex; + memcpy(share.sourcePublicKey, COMPUTOR_PUBLICKEY, 32); + + // Zero destination is used for custom mining + memset(share.zero, 0, sizeof(share.zero)); + + // Fill dummy data + if (legacyNonce) + { + share.encryptionLevel = 0; + uint32_t extraNonce = randomU32(); + uint32_t nonce = randomU32(); + share.combinedNonce = ((uint64_t)extraNonce << 32) | (uint64_t)nonce; + } + else + { + share.encryptionLevel = 2; + share.computorRandom = randomU32(); + uint32_t extraNonce = randomU32(); + uint32_t nonce = randomU32(); + share.combinedNonce = ((uint64_t)extraNonce << 32) | (uint64_t)nonce; + } + + // Gamming nonce + unsigned char sharedKeyAndGammingNonce[64]; + // Default behavior when provided seed is just a signing address + // first 32 bytes of sharedKeyAndGammingNonce is set as zeros + memset(sharedKeyAndGammingNonce, 0, 32); + + // Last 32 bytes of sharedKeyAndGammingNonce is randomly created so that gammingKey[0] = 0 (MESSAGE_TYPE_CUSTOM_MINING_TASK) + unsigned char gammingKey[32]; + do + { + fillRandom32(share.gammingNonce); + memcpy(&sharedKeyAndGammingNonce[32], share.gammingNonce, 32); + KangarooTwelve(sharedKeyAndGammingNonce, 64, gammingKey, 32); + } while (gammingKey[0] != MESSAGE_TYPE_CUSTOM_MINING_SOLUTION); + + // Sign the message + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + KangarooTwelve( + (unsigned char *)&share, + sizeof(share) - SIGNATURE_SIZE, + digest, + 32); + sign(COMPUTOR_SUBSEED, COMPUTOR_PUBLICKEY, digest, signature); + memcpy(share.signature, signature, 64); + return share; +} + +std::vector createShares(task &rTask, int sharePerTask, bool legacyNonce) +{ + std::vector packets; + for (int i = 0; i < sharePerTask; i++) + { + solution share = createShare(rTask, legacyNonce); + + SharePacket packet; + packet.header.setSize(sizeof(packet)); + packet.header.zeroDejavu(); + packet.header.setType(BROADCAST_MESSAGE); + packet.payload = share; + + packets.push_back(packet); + } + return packets; +} + +void sendTaskAndShare(QCPtr pConnection, int sharePerTask, bool legacyNonce) +{ + + TaskPacket taskPacket = createTask(); + std::vector shares = createShares(taskPacket.payload, sharePerTask, legacyNonce); + + // Send + uint64_t dataSent = pConnection->sendData((uint8_t *)&taskPacket, sizeof(taskPacket)); + if (dataSent == sizeof(taskPacket)) + { + std::cout << "Sent a task " << taskPacket.payload.taskIndex << std::endl; + } + + for (int i = 0; i < shares.size(); i++) + { + dataSent = pConnection->sendData((uint8_t *)&shares[i], sizeof(SharePacket)); + if (dataSent == sizeof(SharePacket)) + { + std::cout << "Sent xmr share for compId " << getComputorIDFromSol(&(shares[i].payload)) + << "( " << shares[i].payload.combinedNonce << ")" << std::endl; + } + } +} + +void dispatchRun(const char *nodeIp, int nodePort, int rateLimitPerMin, int sharePerTask, bool legacyNonce) +{ + int64_t timeBetweenSend = 60000 / std::max(rateLimitPerMin, 1); + auto startTime = std::chrono::system_clock::now(); + QCPtr qc; + bool needReconnect = true; + while (!shouldExit.load()) + { + try + { + if (needReconnect) + { + needReconnect = false; + qc = make_qc(nodeIp, nodePort); + startTime = std::chrono::system_clock::now(); + } + + // Send task and sols + sendTaskAndShare(qc, sharePerTask, legacyNonce); + + std::this_thread::sleep_for(std::chrono::milliseconds(timeBetweenSend)); + // Check timeout + if (std::chrono::system_clock::now() - startTime > std::chrono::milliseconds(10000)) + { + needReconnect = true; + } + } + catch (std::logic_error &ex) + { + std::cout << ex.what() << std::endl; + fflush(stdout); + needReconnect = true; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + catch (...) + { + std::cout << "Unknown exception caught!\n"; + fflush(stdout); + needReconnect = true; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } +} + +int run(int argc, char *argv[]) +{ + if (argc < 5) + { + printHelp(); + return 1; + } + + std::string nodeIP = argv[1]; + int nodePort = std::stoi(argv[2]); + int rateLimitPerMin = std::stoi(argv[3]); + int sharePerTask = std::stoi(argv[4]); + + bool legacyNonce = true; + if (argc >= 6) + { + legacyNonce = std::stoi(argv[5]) > 0; + } + + if (argc >= 7) + { + std::strncpy(DISPATCHER_SEED, argv[6], sizeof(DISPATCHER_SEED) - 1); + DISPATCHER_SEED[sizeof(DISPATCHER_SEED) - 1] = '\0'; // ensure null termination + } + + if (argc >= 8) { + std::strncpy(COMPUTOR_SEED, argv[7], sizeof(COMPUTOR_SEED) - 1); + COMPUTOR_SEED[sizeof(COMPUTOR_SEED) - 1] = '\0'; + } + + + // Dispatcher + getKey(DISPATCHER_SEED, DISPATCHER_SUBSEED, DISPATCHER_PUBLICKEY, DISPATCHER_ID); + + // Owned computors + getKey(COMPUTOR_SEED, COMPUTOR_SUBSEED, COMPUTOR_PUBLICKEY, COMPUTOR_ID); + + std::cout << "dispatcher: " << DISPATCHER_ID << "\n"; + std::cout << "submit shares via: " << COMPUTOR_ID << "\n"; + + dispatchRun(nodeIP.c_str(), nodePort, rateLimitPerMin, sharePerTask, legacyNonce); + + return 0; +} + +int main(int argc, char *argv[]) +{ + +#ifndef _MSC_VER + // Ignore SIGPIPE globally + std::signal(SIGPIPE, SIG_IGN); +#endif + try + { + return run(argc, argv); + } + catch (std::exception &ex) + { + printf("%s\n", ex.what()); + return -1; + } +} diff --git a/monero-poc/verifier/verifierLib.cpp b/monero-poc/verifier/verifierLib.cpp index 00bde6b..9bf51d7 100644 --- a/monero-poc/verifier/verifierLib.cpp +++ b/monero-poc/verifier/verifierLib.cpp @@ -53,8 +53,8 @@ bool verify(void *ptr_, const task* _task, const solution * _sol, uint8_t* out) block_template.resize(_task->m_size, 0); memcpy(block_template.data(), _task->m_template, _task->m_size); - uint32_t extraNonce = _sol->_nonceu64 >> 32; - uint32_t nonceu32 = _sol->_nonceu64 & 0xFFFFFFFFU; + uint32_t extraNonce = _sol->combinedNonce >> 32; + uint32_t nonceu32 = _sol->combinedNonce & 0xFFFFFFFFU; memcpy(block_template.data() + _task->m_extraNonceOffset, &extraNonce, 4); uint8_t hashing_blob[256] = {0}; size_t hashing_blob_size = 0; @@ -74,4 +74,43 @@ bool verify(void *ptr_, const task* _task, const solution * _sol, uint8_t* out) { return false; } -} \ No newline at end of file +} + +int getComputorIDFromSol(const solution* _sol) +{ + int computorID = 0; + if (_sol->encryptionLevel == 0) + { + computorID = (_sol->combinedNonce >> 32ULL) % 676ULL; + } + else + { + computorID = _sol->computorRandom % 676ULL; + } + return computorID; +} + +int decryptSolution(const uint8_t * encryptedSol, const unsigned long long encryptedSolSizeInBytes, + const uint8_t * extraData, const unsigned long long extraDataSizeInbytes, + solution* out) +{ + if (((const solution*)encryptedSol)->encryptionLevel == 0) + { + // Simply copy the data + if (encryptedSolSizeInBytes == sizeof(solution)) + { + memcpy(out, encryptedSol, sizeof(solution)); + return 0; + } + } + else + { + // Empty decryption. Need to be filled + if (encryptedSolSizeInBytes == sizeof(solution)) + { + memcpy(out, encryptedSol, sizeof(solution)); + return 0; + } + } + return 1; +} diff --git a/monero-poc/verifier/verifierLib.h b/monero-poc/verifier/verifierLib.h index 1188ec2..b92f709 100644 --- a/monero-poc/verifier/verifierLib.h +++ b/monero-poc/verifier/verifierLib.h @@ -22,10 +22,10 @@ struct solution unsigned char zero[32]; // empty/zero 0 unsigned char gammingNonce[32]; - unsigned long long _taskIndex; - unsigned long long _nonceu64; // (extraNonce<<32) | nonce - unsigned long long reserve0; - unsigned long long reserve1; + unsigned long long taskIndex; + unsigned long long combinedNonce; // (extraNonce<<32) | nonce + unsigned long long encryptionLevel; // 0 = no encryption, 2 = EP173+ encryption + unsigned long long computorRandom; // random number which fullfils the condition computorRandom % 676 == ComputorIndex unsigned long long reserve2; unsigned char result[32]; // xmrig::JobResult.result @@ -51,6 +51,29 @@ void destroySolVerifier(void* ptr_); * Verifying pair (task,sol) if it's valid * */ bool verify(void *ptr_, const task* _task, const solution * _sol, unsigned char* out); + +/* + * Get the computor ID from a solution + **/ +int getComputorIDFromSol(const solution* _sol); + +/** + * @brief Decrypt the solutions. + * + * Currently, this function is implemented as a no-op (solutions are not actually encrypted). + * + * @param[in] encryptedSol Pointer to the encrypted solution data. + * @param[in] encryptedSolSizeInBytes Size of the encrypted solution data in bytes. + * @param[in] extraData Pointer to additional data required for decryption. + * @param[in] extraDataSizeInbytes Size of the extra data in bytes. + * @param[out] out Pointer to the decrypted solution output. + * + * @return 0 if successful. + */ +int decryptSolution(const unsigned char * encryptedSol, const unsigned long long encryptedSolSizeInBytes, + const unsigned char * extraData, const unsigned long long extraDataSizeInbytes, + solution* out); + #ifdef __cplusplus } #endif \ No newline at end of file