diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7dc790c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/googletest"] + path = tests/googletest + url = https://github.com/google/googletest diff --git a/src/log.cpp b/src/log.cpp index 81c9fc3..5987608 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -234,7 +234,9 @@ private: std::ofstream m_logFile; }; +#ifndef P2POOL_LOG_DISABLE static Worker worker; +#endif NOINLINE Writer::Writer(Severity severity) : Stream(m_stackBuf) { @@ -252,7 +254,9 @@ NOINLINE Writer::~Writer() m_buf[1] = static_cast(size & 255); m_buf[2] = static_cast(size >> 8); m_buf[m_pos] = '\n'; +#ifndef P2POOL_LOG_DISABLE worker.write(m_buf, size); +#endif } void reopen() @@ -263,7 +267,9 @@ void reopen() void stop() { +#ifndef P2POOL_LOG_DISABLE worker.stop(); +#endif } NOINLINE void Stream::writeCurrentTime() diff --git a/src/log.h b/src/log.h index 5183a68..dac0860 100644 --- a/src/log.h +++ b/src/log.h @@ -384,6 +384,14 @@ namespace { #define CONCAT(a, b) CONCAT2(a, b) #define CONCAT2(a, b) a##b +#ifdef P2POOL_LOG_DISABLE + +#define LOGINFO(level, ...) +#define LOGWARN(level, ...) +#define LOGERR(level, ...) + +#else + #define LOG(level, severity, ...) \ do { \ if (level <= log::GLOBAL_LOG_LEVEL) { \ @@ -398,6 +406,8 @@ namespace { #define LOGWARN(level, ...) LOG(level, log::Severity::Warning, __VA_ARGS__) #define LOGERR(level, ...) LOG(level, log::Severity::Error, __VA_ARGS__) +#endif + void reopen(); void stop(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..15e637d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,108 @@ +cmake_minimum_required(VERSION 2.8.12) +project(p2pool_tests) + +add_subdirectory(googletest) +set(LIBS gtest) + +if (CMAKE_CXX_COMPILER_ID MATCHES GNU) + set(WARNING_FLAGS "") + set(OPTIMIZATION_FLAGS "-Ofast -s") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${WARNING_FLAGS} ${OPTIMIZATION_FLAGS}") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${WARNING_FLAGS} ${OPTIMIZATION_FLAGS}") + + if (WIN32) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + else() + if (STATIC_LINUX_BINARY) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") + else() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") + endif() + endif() +elseif (CMAKE_CXX_COMPILER_ID MATCHES MSVC) + set(WARNING_FLAGS "") + set(SECURITY_FLAGS "/GS /guard:cf") + set(OPTIMIZATION_FLAGS "/O2 /Oi /Ob2 /Ot /DNDEBUG /GL") + + set(CMAKE_C_FLAGS_DEBUG "${WARNING_FLAGS} ${SECURITY_FLAGS} /Od /Ob0 /Zi /MTd /fsanitize=address") + set(CMAKE_CXX_FLAGS_DEBUG "${WARNING_FLAGS} ${SECURITY_FLAGS} /Od /Ob0 /Zi /MTd /fsanitize=address") + + set(CMAKE_C_FLAGS_RELEASE "${WARNING_FLAGS} ${SECURITY_FLAGS} ${OPTIMIZATION_FLAGS} /MT") + set(CMAKE_CXX_FLAGS_RELEASE "${WARNING_FLAGS} ${SECURITY_FLAGS} ${OPTIMIZATION_FLAGS} /MT") + + set(CMAKE_C_FLAGS_RELWITHDEBINFO "${WARNING_FLAGS} ${SECURITY_FLAGS} /Ob1 /Ot /Zi /MT") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${WARNING_FLAGS} ${SECURITY_FLAGS} /Ob1 /Ot /Zi /MT") +elseif (CMAKE_CXX_COMPILER_ID MATCHES Clang) + set(WARNING_FLAGS "") + set(OPTIMIZATION_FLAGS "-Ofast -funroll-loops -fmerge-all-constants") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${WARNING_FLAGS} ${OPTIMIZATION_FLAGS}") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${WARNING_FLAGS} ${OPTIMIZATION_FLAGS}") +endif() + +set(HEADERS +) + +set(SOURCES + src/difficulty_type.cpp + src/tests.cpp + ../src/log.cpp + ../src/util.cpp +) + +include_directories(../src) +include_directories(../external/src) +include_directories(../external/src/cryptonote) +include_directories(../external/src/libuv/include) +include_directories(../external/src/cppzmq) +include_directories(../external/src/libzmq/include) +include_directories(../external/src/llhttp) +include_directories(../external/src/randomx/src) +include_directories(src) +include_directories(googletest/googletest/include) + +if (WIN32) + set(LIBS ${LIBS} ws2_32 iphlpapi userenv psapi) +elseif (NOT APPLE) + set(LIBS ${LIBS} pthread gss dl) +endif() + +if (CMAKE_CXX_COMPILER_ID MATCHES MSVC) + find_library(ZMQ_LIBRARY_DEBUG NAMES libzmq-v142-mt-sgd-4_3_5 PATHS "../external/lib/libzmq/Debug") + find_library(ZMQ_LIBRARY NAMES libzmq-v142-mt-s-4_3_5 PATHS "../external/lib/libzmq/Release") + find_library(UV_LIBRARY_DEBUG NAMES uv_a PATHS "../external/lib/libuv/Debug") + find_library(UV_LIBRARY NAMES uv_a PATHS "../external/lib/libuv/Release") +elseif (CMAKE_CXX_COMPILER_ID MATCHES GNU OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + find_library(ZMQ_LIBRARY_DEBUG NAMES zmq libzmq.a) + find_library(ZMQ_LIBRARY NAMES zmq libzmq.a) + find_library(UV_LIBRARY_DEBUG NAMES uv libuv.a) + find_library(UV_LIBRARY NAMES uv libuv.a) + find_library(SODIUM_LIBRARY sodium) +endif() + +find_library(PGM_LIBRARY pgm) +find_library(NORM_LIBRARY norm) + +if (PGM_LIBRARY) + set(LIBS ${LIBS} ${PGM_LIBRARY}) +endif() + +if (NORM_LIBRARY) + set(LIBS ${LIBS} ${NORM_LIBRARY}) +endif() + +if (SODIUM_LIBRARY) + set(LIBS ${LIBS} ${SODIUM_LIBRARY}) +endif() + +add_definitions(/DZMQ_STATIC /DP2POOL_LOG_DISABLE) + +add_executable(${CMAKE_PROJECT_NAME} ${HEADERS} ${SOURCES}) +target_link_libraries(${CMAKE_PROJECT_NAME} debug ${ZMQ_LIBRARY_DEBUG} debug ${UV_LIBRARY_DEBUG} optimized ${ZMQ_LIBRARY} optimized ${UV_LIBRARY} ${LIBS}) diff --git a/tests/googletest b/tests/googletest new file mode 160000 index 0000000..955c7f8 --- /dev/null +++ b/tests/googletest @@ -0,0 +1 @@ +Subproject commit 955c7f837efad184ec63e771c42542d37545eaef diff --git a/tests/src/difficulty_type.cpp b/tests/src/difficulty_type.cpp new file mode 100644 index 0000000..dbeb6bb --- /dev/null +++ b/tests/src/difficulty_type.cpp @@ -0,0 +1,248 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021 SChernykh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" +#include "gtest/gtest.h" +#include + +namespace p2pool { + +TEST(difficulty_type, target) +{ + // diff = 0 + { + difficulty_type d(0, 0); + ASSERT_EQ(d.target(), std::numeric_limits::max()); + } + + // diff = 1 + { + difficulty_type d(1, 0); + ASSERT_EQ(d.target(), std::numeric_limits::max()); + } + + // diff = 2^64 + { + difficulty_type d(0, 1); + ASSERT_EQ(d.target(), 1); + } + + // diff = 2^32 + { + difficulty_type d(1ull << 32, 0); + ASSERT_EQ(d.target(), 1ull << 32); + } + + // diff from block 2440918 + { + difficulty_type d(334654765825ull, 0); + ASSERT_EQ(d.target(), 55121714); + } +} + +TEST(difficulty_type, sum) +{ + // No carry + { + difficulty_type diff[4] = { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; + + for (int i = 0; i <= 3; ++i) { + for (int j = 0; j <= 3; ++j) { + difficulty_type a = diff[i]; + a += diff[j]; + ASSERT_EQ(a.lo, diff[i].lo + diff[j].lo); + ASSERT_EQ(a.hi, diff[i].hi + diff[j].hi); + } + } + } + + // Carry + { + difficulty_type a(11400714819323198485ull, 0); + difficulty_type b(15975348984942515101ull, 0); + a += b; + ASSERT_EQ(a.lo, 8929319730556161970ull); + ASSERT_EQ(a.hi, 1); + } + + // Carry (edge case) + { + difficulty_type a(std::numeric_limits::max(), 0); + difficulty_type b(1, 0); + a += b; + ASSERT_EQ(a.lo, 0); + ASSERT_EQ(a.hi, 1); + } +} + +TEST(difficulty_type, compare) +{ + const difficulty_type diff[4] = { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; + + for (int i = 0; i <= 3; ++i) { + for (int j = 0; j <= 3; ++j) { + ASSERT_EQ(diff[i] < diff[j], i < j); + ASSERT_EQ(diff[i] >= diff[j], i >= j); + ASSERT_EQ(diff[i] == diff[j], i == j); + ASSERT_EQ(diff[i] != diff[j], i != j); + } + } +} + +TEST(difficulty_type, check_pow) +{ + hash h; + + // Power of 2 close to the current Monero network difficulty + difficulty_type diff = { 1ull << 38, 0 }; + { + // 2^256 / 2^38 = 2^218 + // diff.check_pow() will get 2^256 as a multiplication result = lowest possible value that fails the test + uint64_t data[4] = { 0, 0, 0, 1ull << 26 }; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), false); + + // Now decrease the hash by 1. It should pass the test now + data[0] = data[1] = data[2] = std::numeric_limits::max(); + --data[3]; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), true); + } + + /* + * Factors of 2^256 - 1: + * P1 = 3 + * P1 = 5 + * P2 = 17 + * P3 = 257 + * P3 = 641 + * P5 = 65537 + * P6 = 274177 + * P7 = 6700417 + * P14 = 67280421310721 + * P17 = 59649589127497217 + * P22 = 5704689200685129054721 + */ + diff = { 67280421310721ull, 0 }; + { + // (2^256 - 1) / 67280421310721 = 1721036922503113971692907638171526209875755521904893141463060735 + // diff.check_pow() will get 2^256-1 as a multiplication result = highest possible value that still passes the test + uint64_t data[4] = { 0xfffffffffffbd0ffull, 0x0000000000042f00ull, 0xfffffffffffbd0ffull, 0x42f00ull }; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), true); + + // Now increase the hash by 1. It should not pass the test anymore + ++data[0]; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), false); + } + + // diff = 5704689200685129054721 + diff = { 4645281908877605377ull, 309ull }; + { + // (2^256 - 1) / 5704689200685129054721 = 20297703374166229616474325006177763232573806344580020735 + // diff.check_pow() will get 2^256-1 as a multiplication result = highest possible value that still passes the test + uint64_t data[4] = { 0xff2c1503c50eb9ffull, 0xffffffffffffffffull, 0xd3eafc3af14600ull, 0 }; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), true); + + // Now increase the hash by 1. It should not pass the test anymore + ++data[0]; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), false); + } + + /* + * Factors of 2^256 + 1: + * P16 = 1238926361552897 + * P62 = 93461639715357977769163558199606896584051237541638188580280321 + */ + diff = { 1238926361552897ull, 0 }; + { + // (2^256 + 1) / 1238926361552897 = 93461639715357977769163558199606896584051237541638188580280321 + // diff.check_pow() will get 2^256+1 as a multiplication result = lowest possible non-power of 2 that fails the test + uint64_t data[4] = { 0x49baa0ba2c911801ull, 0x6ee3637cab2586d0ull, 0x4c585a8f5c7073e3, 0x3a29ull }; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), false); + + // Now decrease the hash by 1. It should pass the test now + --data[0]; + memcpy(h.h, data, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), true); + } + + // Randomized tests with fixed seed + std::mt19937_64 r(0); + + for (int i = 0; i < 1000; ++i) { + // Random difficulty between 300G and 400G + difficulty_type diff{ 300000000000ull + (r() % 100000000000ull), 0 }; + hash h; + + // All zeros + memset(h.h, 0, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), true); + + // All ones + memset(h.h, -1, HASH_SIZE); + ASSERT_EQ(diff.check_pow(h), false); + + { + uint64_t data[4]; + uint64_t rem; + data[3] = udiv128(1, 0, diff.lo, &rem); + data[2] = udiv128(rem, 0, diff.lo, &rem); + data[1] = udiv128(rem, 0, diff.lo, &rem); + data[0] = udiv128(rem, 0, diff.lo, &rem); + + // Max hash value that passes this difficulty + memcpy(h.h, data, HASH_SIZE); + EXPECT_EQ(diff.check_pow(h), true); + + // Add 1 to data (256-bit number) + for (int j = 0; j <= 3; ++j) { + ++data[j]; + if (data[j]) { + // No carry, exit the loop + break; + } + } + + // Min hash value that fails this difficulty + memcpy(h.h, data, HASH_SIZE); + EXPECT_EQ(diff.check_pow(h), false); + } + + const uint64_t target = diff.target(); + + // Random values that pass + for (int j = 0; j < 10000; ++j) { + const uint64_t data[4] = { r(), r(), r(), r() % target }; + memcpy(h.h, data, HASH_SIZE); + EXPECT_EQ(diff.check_pow(h), true); + } + + // Random values that fail + for (int j = 0; j < 10000; ++j) { + const uint64_t data[4] = { r(), r(), r(), target + (r() % (std::numeric_limits::max() - target + 1)) }; + memcpy(h.h, data, HASH_SIZE); + EXPECT_EQ(diff.check_pow(h), false); + } + } +} + +} diff --git a/tests/src/tests.cpp b/tests/src/tests.cpp new file mode 100644 index 0000000..2822d0c --- /dev/null +++ b/tests/src/tests.cpp @@ -0,0 +1,24 @@ +/* + * This file is part of the Monero P2Pool + * Copyright (c) 2021 SChernykh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gtest/gtest.h" + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}