From 742d5fa22c60fccb0749fefcfd393a340bf7efb7 Mon Sep 17 00:00:00 2001 From: William Allen Date: Thu, 29 Jun 2023 18:28:19 -0500 Subject: [PATCH] Wallentx/bb harvest integration (#368) * Cleanup commits/rebase * Testing CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * CI * Changing to manylinux2014 * Update bladebit harvester sha256 * Remove bladebit building ref * Fixing more windows stuff * Change trigger CI branch * Some more build fixes for Windows * More cmake fixes * Updating CI for windows * CI * Fix packaging location of bladebit_harcester on linux * Reabling macos * Re-disabling macos * Remove this branch CI trigger * Re-enabling pre-release logic CI (#361) * Re-enabling pre-release logic CI * Lint fixes * Lint fixes2 --------- Co-authored-by: Harold Brenes * Fix missing decompressor queue when no compression * Update Catch2 to v3.3.2. (#346) * correct wheel matrix arch -> arm (#365) * Updating the ways bladebit harvester gets included * Remove packaging of bladebit harvester dlls into wheel. Opting for static lib instead. * Remove shutil * More logging on GRResult_OK not ok. (#366) * Copy .a instead of .so GreenReaper artifact * Fix no decompressor deallocation when it failues to pre-allocate decompression buffers. * Fix linking and building of bladebit harvester * Add action to fetch bladebit harvester * Fixing fetch_bladebit_harvester.sh on linux * Enabling all builds * Fixing macos multiline * Correct build opts for GR in win build extension * Re-add bladebit_harvester.dll copy into wheel code --------- Co-authored-by: Harold Brenes Co-authored-by: Amine Khaldi Co-authored-by: Kyle Altendorf Co-authored-by: Chris Marslender Co-authored-by: Florin Chirica --- .github/actions/fetch_bladebit_harvester.sh | 79 ++++ .github/workflows/build-wheels.yml | 87 +++- .gitignore | 5 +- CMakeLists.txt | 111 +++-- setup.py | 17 + src/prover_disk.hpp | 446 +++++++++++++------- 6 files changed, 548 insertions(+), 197 deletions(-) create mode 100755 .github/actions/fetch_bladebit_harvester.sh diff --git a/.github/actions/fetch_bladebit_harvester.sh b/.github/actions/fetch_bladebit_harvester.sh new file mode 100755 index 000000000..fe32db137 --- /dev/null +++ b/.github/actions/fetch_bladebit_harvester.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eo pipefail +_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +cd "$_dir/../.." + +## +# Usage: fetch_bladebit_harvester.sh +# +# Use gitbash or similar under Windows. +## +host_os=$1 +host_arch=$2 + +if [[ "${host_os}" != "linux" ]] && [[ "${host_os}" != "macos" ]] && [[ "${host_os}" != "windows" ]]; then + echo >&2 "Unkonwn OS '${host_os}'" + exit 1 +fi + +if [[ "${host_arch}" != "arm64" ]] && [[ "${host_arch}" != "x86-64" ]]; then + echo >&2 "Unkonwn Architecture '${host_arch}'" + exit 1 +fi + +# Update these when pointing to different releases +artifact_ver="v3.0.0-alpha4" +artifact_base_url="https://github.com/harold-b/bladebit-test/releases/download/v3-alpha4-fixes" + +linux_sha256= +macos_sha256= +windows_sha256= + +artifact_ext="tar.gz" +sha_bin="sha256sum" +expected_sha256= + +case "${host_os}" in +linux) + expected_sha256=$linux_sha256 + ;; +macos) + expected_sha256=$macos_sha256 + sha_bin="shasum -a 256" + ;; +windows) + expected_sha256=$windows_sha256 + artifact_ext="zip" + ;; +*) + echo >&2 "Unexpected OS '${host_os}'" + exit 1 + ;; +esac + +# Download artifact +artifact_name="green_reaper.${artifact_ext}" +curl -L "${artifact_base_url}/green_reaper-${artifact_ver}-${host_os}-${host_arch}.${artifact_ext}" >"${artifact_name}" + +# Validate sha256, if one was given +if [ -n "${expected_sha256}" ]; then + gr_sha256="$(${sha_bin} ${artifact_name})" + + if [[ "${gr_sha256}" != "${expected_sha256}" ]]; then + echo >&2 "GreenReaper SHA256 mismatch!" + echo >&2 " Got : '${gr_sha256}'" + echo >&2 " Expected: '${expected_sha256}'" + exit 1 + fi +fi + +# Unpack artifact +dst_dir="libs/green_reaper" +mkdir -p "${dst_dir}" +if [[ "${artifact_ext}" == "zip" ]]; then + unzip -d "${dst_dir}" "${artifact_name}" +else + pushd "${dst_dir}" + tar -xzvf "../../${artifact_name}" + popd +fi diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 8fbdfee8e..9eb0524f6 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -4,8 +4,8 @@ on: push: branches: - main - tags: - - '**' + release: + types: [published] pull_request: branches: - '**' @@ -19,6 +19,7 @@ jobs: build-wheels: name: Wheel - ${{ matrix.os.name }} ${{ matrix.python.major-dot-minor }} ${{ matrix.arch.name }} runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }} + continue-on-error: true strategy: fail-fast: false matrix: @@ -44,31 +45,31 @@ jobs: - major-dot-minor: '3.7' cibw-build: 'cp37-*' manylinux: - arch: manylinux2014 - intel: manylinux2010 + arm: manylinux2014 + intel: manylinux2014 matrix: '3.7' - major-dot-minor: '3.8' cibw-build: 'cp38-*' manylinux: - arch: manylinux2014 - intel: manylinux2010 + arm: manylinux2014 + intel: manylinux2014 matrix: '3.8' - major-dot-minor: '3.9' cibw-build: 'cp39-*' manylinux: - arch: manylinux2014 - intel: manylinux2010 + arm: manylinux2014 + intel: manylinux2014 matrix: '3.9' - major-dot-minor: '3.10' cibw-build: 'cp310-*' manylinux: - arch: manylinux2014 - intel: manylinux2010 + arm: manylinux2014 + intel: manylinux2014 matrix: '3.10' - major-dot-minor: '3.11' cibw-build: 'cp311-*' manylinux: - arch: manylinux2014 + arm: manylinux2014 intel: manylinux2014 matrix: '3.11' arch: @@ -112,6 +113,12 @@ jobs: with: fetch-depth: 0 + - name: Set Env + if: env.RUNNER_ARCH != 'ARM64' + uses: Chia-Network/actions/setjobenv@main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: Chia-Network/actions/setup-python@main with: python-version: ${{ matrix.python.major-dot-minor }} @@ -120,6 +127,16 @@ jobs: run: | pip install pipx + - name: Get Windows Bladebit Harvester Artifact + if: runner.os == 'Windows' + shell: bash + run: | + set -eo pipefail + set -x + + windows_sha256='8ad2710faad4a41f72de20707539f024f02db079968ea546699f4ded56fe9a57' + .github/actions/fetch_bladebit_harvester.sh windows x86-64 + - name: Build and test env: CIBW_PRERELEASE_PYTHONS: True @@ -130,22 +147,41 @@ jobs: CIBW_SKIP: '*-manylinux_i686 *-win32 *-musllinux_*' CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.python.manylinux['arm'] }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.python.manylinux['intel'] }} - CIBW_ENVIRONMENT_LINUX: "PATH=/project/cmake-3.17.3-Linux-`uname -m`/bin:$PATH" - CIBW_BEFORE_ALL_LINUX: > - curl -L https://github.com/Kitware/CMake/releases/download/v3.17.3/cmake-3.17.3-Linux-`uname -m`.sh > cmake.sh - && yes | sh cmake.sh | cat - && rm -f /usr/bin/cmake - && which cmake - && cmake --version - && uname -a + CIBW_ENVIRONMENT_WINDOWS: "CP_USE_GREEN_REAPER=1" + CIBW_ENVIRONMENT_LINUX: CP_USE_GREEN_REAPER="1" + CIBW_BEFORE_ALL_LINUX: | + set -eo pipefail + set -x + # Get bladebit harvester + set -eo pipefail + ARCH=$(uname -m) + if [[ $ARCH == x86_64 ]]; then + linux_sha256='3d0cc6e62a7936966789349c4bc573882866a3f1a9f5e365e4961e7bea80831b' + .github/actions/fetch_bladebit_harvester.sh linux x86-64 + else + linux_sha256='637c6b073c26d87be513d1e04a97f4e6cc6b27532dc716cc462ee7fbaae31470' + .github/actions/fetch_bladebit_harvester.sh linux arm64 + fi + CIBW_BEFORE_BUILD_LINUX: > python -m pip install --upgrade pip CIBW_ARCHS_MACOS: ${{ matrix.os.cibw-archs-macos[matrix.arch.matrix] }} - CIBW_BEFORE_ALL_MACOS: > + CIBW_BEFORE_ALL_MACOS: | brew install gmp boost cmake + # Get bladebit harvester + set -eo pipefail + ARCH=$(uname -m) + if [[ $ARCH == x86_64 ]]; then + macos_sha256='a76d7da43c6e045f3d0a57a51924211283ba60880558dbbb636d9601c62f6390' + .github/actions/fetch_bladebit_harvester.sh macos x86-64 + else + macos_sha256='6894b1b3ebae13a3233b9bc31d2b2a179078ba67595b8be0ee9a69a2a75bec23' + .github/actions/fetch_bladebit_harvester.sh macos arm64 + fi + CIBW_BEFORE_BUILD_MACOS: > python -m pip install --upgrade pip - CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.14" + CIBW_ENVIRONMENT_MACOS: "MACOSX_DEPLOYMENT_TARGET=10.14 CP_USE_GREEN_REAPER=1" CIBW_TEST_REQUIRES: pytest CIBW_TEST_COMMAND: py.test -v {project}/tests run: @@ -274,6 +310,11 @@ jobs: with: fetch-depth: 0 + - name: Set Env + uses: Chia-Network/actions/setjobenv@main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: Chia-Network/actions/setup-python@main with: python-version: ${{ matrix.python.major-dot-minor }} @@ -298,7 +339,7 @@ jobs: run: pip install twine - name: Publish distribution to PyPI - if: startsWith(github.event.ref, 'refs/tags') && steps.check_secrets.outputs.HAS_SECRET + if: env.RELEASE == 'true' && steps.check_secrets.outputs.HAS_SECRET env: TWINE_USERNAME: __token__ TWINE_NON_INTERACTIVE: 1 @@ -306,7 +347,7 @@ jobs: run: twine upload --non-interactive --skip-existing --verbose 'dist/*' - name: Publish distribution to Test PyPI - if: steps.check_secrets.outputs.HAS_SECRET + if: env.PRE_RELEASE == 'true' && steps.check_secrets.outputs.HAS_SECRET env: TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ TWINE_USERNAME: __token__ diff --git a/.gitignore b/.gitignore index fd9c2b875..698734681 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ build venv build-tsan build-* -cmake-build* \ No newline at end of file +cmake-build* +*.zip +*.tar.gz +libs/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a01abc8f0..989d34e6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,26 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cxxopts) + +option(CP_LINK_BLADEBIT_HARVESTER "Links libbladebit_harvester at build time instead of dynamically loading it." OFF) +option(CP_BUILD_BLADEBIT_HARVESTER "Pulls bladebit harvester target from git and builds it as a dependency.") + +if (${CP_BUILD_BLADEBIT_HARVESTER} AND NOT ${CP_LINK_BLADEBIT_HARVESTER}) + set(CP_LINK_BLADEBIT_HARVESTER ON) +endif() + +if (${CP_BUILD_BLADEBIT_HARVESTER}) + FetchContent_Declare( + bladebit + GIT_REPOSITORY https://github.com/Chia-Network/bladebit.git + GIT_TAG fix-cuda-compression-device-lost + ) + + set(BB_HARVESTER_ONLY ON) + set(BB_HARVESTER_STATIC ON) + FetchContent_MakeAvailable(bladebit) +endif() + FetchContent_Declare( gulrak GIT_REPOSITORY https://github.com/gulrak/filesystem.git @@ -57,7 +77,11 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../lib/FiniteStateEntropy/lib ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/test - ) +) + +IF (${CP_LINK_BLADEBIT_HARVESTER}) + message ("Bladebit Harvesting Enabled") +ENDIF () add_library(fse ${FSE_FILES}) @@ -151,7 +175,7 @@ ENDIF() FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.2.1 + GIT_TAG v3.3.2 ) FetchContent_MakeAvailable(Catch2) @@ -161,13 +185,6 @@ add_executable(RunTests ${BLAKE3_SRC} ) -target_link_libraries(RunTests - PRIVATE - fse - Threads::Threads - Catch2::Catch2 -) - find_package(Threads REQUIRED) add_library(uint128 STATIC uint128_t/uint128_t.cpp) @@ -175,26 +192,60 @@ target_include_directories(uint128 PUBLIC uint128_t) target_compile_features(fse PUBLIC cxx_std_17) target_compile_features(chiapos PUBLIC cxx_std_17) -target_compile_features(RunTests PUBLIC cxx_std_17) - -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - target_link_libraries(chiapos PRIVATE fse Threads::Threads) - target_link_libraries(ProofOfSpace fse Threads::Threads) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") - target_link_libraries(chiapos PRIVATE fse Threads::Threads) - target_link_libraries(ProofOfSpace fse Threads::Threads) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") - target_link_libraries(chiapos PRIVATE fse Threads::Threads) - target_link_libraries(ProofOfSpace fse Threads::Threads) -elseif (MSVC) - target_link_libraries(chiapos PRIVATE fse Threads::Threads uint128) - target_link_libraries(ProofOfSpace fse Threads::Threads uint128) - target_link_libraries(RunTests PRIVATE uint128) -else() - target_link_libraries(chiapos PRIVATE fse stdc++fs Threads::Threads) - target_link_libraries(ProofOfSpace fse stdc++fs Threads::Threads) - target_link_libraries(RunTests PRIVATE stdc++fs) +# target_compile_features(RunTests PUBLIC cxx_std_17) + +target_link_libraries(chiapos PRIVATE fse Threads::Threads + $<$:uint128> + $<$>:stdc++fs> +) +target_link_libraries(ProofOfSpace PRIVATE fse Threads::Threads + $<$:uint128> + $<$>:stdc++fs> +) +target_link_libraries(RunTests PRIVATE fse Threads::Threads Catch2::Catch2WithMain + $<$:uint128> + $<$>:stdc++fs> +) + +if (${CP_LINK_BLADEBIT_HARVESTER}) + + set(bb_defs + USE_GREEN_REAPER=1 + BLADEBIT_HARVESTER_LINKED=1 + $<$:BLADEBIT_IS_PROJECT_DEPENDENCY=1> + ) + set(bb_libs + bladebit_harvester + $<$>:dl> + ) + + include_directories( + ${INCLUDE_DIRECTORIES} + ${CMAKE_CURRENT_SOURCE_DIR}/libs/green_reaper/include + ) + + link_directories( + ${LINK_DIRECTORIES} + ${CMAKE_SOURCE_DIR}/libs/green_reaper/lib + ) + + target_compile_definitions(chiapos PUBLIC ${bb_defs}) + target_compile_definitions(ProofOfSpace PUBLIC ${bb_defs}) + target_compile_definitions(RunTests PUBLIC ${bb_defs}) + + target_link_libraries(chiapos PUBLIC ${bb_libs}) + target_link_libraries(ProofOfSpace PUBLIC ${bb_libs}) + target_link_libraries(RunTests PUBLIC ${bb_libs}) + + target_link_directories(chiapos PUBLIC ${CMAKE_SOURCE_DIR}/libs/green_reaper/lib) + target_link_directories(ProofOfSpace PUBLIC ${CMAKE_SOURCE_DIR}/libs/green_reaper/lib) + target_link_directories(RunTests PUBLIC ${CMAKE_SOURCE_DIR}/libs/green_reaper/lib) + + set_property(TARGET chiapos APPEND PROPERTY BUILD_RPATH "$ORIGIN") + set_property(TARGET ProofOfSpace APPEND PROPERTY BUILD_RPATH "$ORIGIN") + set_property(TARGET RunTests APPEND PROPERTY BUILD_RPATH "$ORIGIN") endif() -enable_testing() -add_test(NAME RunTests COMMAND RunTests) + +#enable_testing() +#add_test(NAME RunTests COMMAND RunTests) diff --git a/setup.py b/setup.py index 44d097dff..244e2ae56 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 import os import re +import shutil import sys import platform import subprocess @@ -44,6 +45,9 @@ def build_extension(self, ext): "-DPYTHON_EXECUTABLE=" + sys.executable, ] + if os.getenv("CP_USE_GREEN_REAPER") == "1": + cmake_args.append("-DCP_LINK_BLADEBIT_HARVESTER=ON") + cfg = "Debug" if self.debug else "Release" build_args = ["--config", cfg] @@ -177,11 +181,24 @@ def build_extensions(self): opts.append("-fvisibility=hidden") elif ct == "msvc": opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version()) + + # Link bladebit_harvester + if os.getenv("CP_USE_GREEN_REAPER") == "1": + opts.append("/DUSE_GREEN_REAPER=1") + opts.append("/DBLADEBIT_HARVESTER_LINKED=1") + opts.append("/Ilibs/green_reaper/include") + link_opts.append("libs/green_reaper/lib/bladebit_harvester.lib") + for ext in self.extensions: ext.extra_compile_args = opts ext.extra_link_args = link_opts build_ext.build_extensions(self) + # Copy bladebit_harvester.dll on windows to the target build directory + # in order to package it into the root directory of the wheel + if os.getenv("CP_USE_GREEN_REAPER") == "1" and sys.platform == "win32": + shutil.copy2("libs/green_reaper/lib/bladebit_harvester.dll", self.build_lib + "/bladebit_harvester.dll") + if platform.system() == "Windows": setup( diff --git a/src/prover_disk.hpp b/src/prover_disk.hpp index 6f8c14beb..9fb4c1d23 100644 --- a/src/prover_disk.hpp +++ b/src/prover_disk.hpp @@ -32,15 +32,16 @@ #include #include "../lib/include/picosha2.hpp" -#define uint32 uint32_t -#include "harvesting/GreenReaper.h" -#include "plotting/Compression.h" #include "calculate_bucket.hpp" #include "encoding.hpp" #include "entry_sizes.hpp" #include "serialize.hpp" #include "util.hpp" +#if USE_GREEN_REAPER + #include "GreenReaperPortable.h" +#endif + #define CHIA_PLOT_V2_MAGIC 0x544F4C50ul // "PLOT" #define CHIA_PLOT_VERSION_2_0_0 2 @@ -53,7 +54,9 @@ struct plot_header { uint8_t fmt_desc[50]; }; - +#if USE_GREEN_REAPER +static GRApi _grApi{}; +static bool _dcompressor_queue_initialized = false; class ContextQueue { public: ContextQueue() {} @@ -62,12 +65,55 @@ class ContextQueue { uint32_t context_count, uint32_t thread_count, bool no_cpu_affinity, - const uint32_t maxCompressionLevel, + const uint32_t max_compression_level, bool use_gpu_harvesting, uint32_t gpu_index, bool enforce_gpu_index ) { + assert(!_dcompressor_queue_initialized); + _dcompressor_queue_initialized = true; + + // Populate the API + #if _WIN32 + #define GR_LIB_PREFIX "" + #define GR_LIB_EXT ".dll" + #else + #define GR_LIB_PREFIX "lib" + + #if __APPLE__ + #define GR_LIB_EXT ".dylib" + #else + #define GR_LIB_EXT ".so" + #endif + #endif + + // void* lib = grLoadModule(GR_LIB_PREFIX "bladebit_harvester" GR_LIB_EXT); + + // if (lib == nullptr) { + // int code; + // #if _WIN32 + // code = (int)::GetLastError(); + // #else + // code = (int)dlerror(); + // #endif + + // std::stringstream err; err << "Failed to load bladebit_harvester with error: '" << code << "'"; + // throw std::runtime_error(err.str()); + // } + // #undef GR_LIB_PREFIX + // #undef GR_LIB_EXT + + // // Init GR API + // { + // const auto r = grPopulateApiFromModule(lib, &_grApi, sizeof(GRApi), GR_API_VERSION); + // if (r != GRResult_OK) { + // std::stringstream err; err << "Failed to initialize GR API with error " << r; + // throw std::runtime_error(err.str()); + // } + // } + GreenReaperConfig cfg = {}; + cfg.apiVersion = GR_API_VERSION; cfg.threadCount = thread_count; cfg.disableCpuAffinity = no_cpu_affinity; if (!use_gpu_harvesting) { @@ -82,24 +128,39 @@ class ContextQueue { cfg.gpuDeviceIndex = gpu_index; for (uint32_t i = 0; i < context_count; i++) { + cfg.cpuOffset = i * thread_count; - auto gr = grCreateContext(&cfg); + GreenReaperContext* gr = nullptr; + auto result = grCreateContext(&gr, &cfg, sizeof(cfg)); - if (gr == nullptr) { + std::string error_msg; + + if (result == GRResult_OK) { + assert(gr); + queue.push(gr); + + // Preallocate memory required fot the maximum compression level we are supporting initially + result = grPreallocateForCompressionLevel(gr, 32, max_compression_level); + if (result != GRResult_OK) { + std::stringstream err; err << "Failed to preallocate memory for contexts with result " << result; + error_msg = err.str(); + } + } + if (result != GRResult_OK) { // Destroy contexts that were already created while (!queue.empty()) { grDestroyContext( queue.front() ); queue.pop(); } - throw std::logic_error("Failed to create GRContext"); - } - auto result = grPreallocateForCompressionLevel(gr, 32, maxCompressionLevel); - if (result != GRResult_OK) { - throw std::logic_error("Failed to allocate enough memory for contexts"); + if (error_msg.length() < 1) { + std::stringstream err; err << "Failed to create GRContext with result " << result; + error_msg = err.str(); + } + throw std::runtime_error(error_msg); } - queue.push(gr); + if (i == 0 && use_gpu_harvesting) { - if (grHasGpuDecompressor(gr)) { + if (grHasGpuDecompressor(gr) == GR_TRUE) { return true; } else { // default to CPU @@ -136,15 +197,89 @@ class ContextQueue { std::mutex dequeue_lock; }; -ContextQueue decompresser_context_queue; +class ProofCache { + static constexpr uint32_t MAX_ENTRIES = 64; -struct CacheEntryProof { - // Input - uint8_t* challenge; - uint32_t index; - // Output - LargeBits full_proof; + struct Entry { + alignas(16) uint8_t challenge[32]; + uint32_t index; + }; + + uint32_t cache_entry_proof_position = 0; + std::vector challenges; + std::vector full_proofs; + mutable std::mutex lock; + +public: + inline ProofCache() = default; + inline ProofCache(ProofCache&& other) + : cache_entry_proof_position(other.cache_entry_proof_position) + , challenges(std::move(other.challenges)) + , full_proofs(std::move(other.full_proofs)) + { + other.cache_entry_proof_position = 0; + } + + inline ProofCache(ProofCache const& other) = delete; + + inline bool FoundCachedProof(const uint32_t index, const uint8_t* challenge, LargeBits& out_full_proof) { + std::lock_guard l(lock); + + Entry entry; + memcpy(entry.challenge, challenge, sizeof(entry.challenge)); + entry.index = index; + + for (uint32_t i = 0; i < challenges.size(); i++) { + if (memcmp(&challenges[i], &entry, sizeof(Entry)) == 0) { + out_full_proof = full_proofs[i]; + return true; + } + } + return false; + } + + inline void CacheProof(const uint32_t index, const uint8_t* challenge, const LargeBits& full_proof) { + std::lock_guard l(lock); + + Entry entry; + memcpy(entry.challenge, challenge, sizeof(entry.challenge)); + entry.index = index; + + if (challenges.size() < MAX_ENTRIES) { + challenges.emplace_back(entry); + full_proofs.emplace_back(full_proof); + } else { + challenges[cache_entry_proof_position] = entry; + full_proofs[cache_entry_proof_position] = full_proof; + } + cache_entry_proof_position = (cache_entry_proof_position + 1) % MAX_ENTRIES; + } + + static_assert(alignof(ProofCache::Entry) == 16); +}; +#else +// Dummy one for python +class ContextQueue { +public: + inline ContextQueue() {} + + inline bool init( + uint32_t context_count, + uint32_t thread_count, + bool no_cpu_affinity, + const uint32_t max_compression_level, + bool use_gpu_harvesting, + uint32_t gpu_index, + bool enforce_gpu_index + ) + { + return false; + } }; +#endif // USE_GREEN_REAPER + +ContextQueue decompresser_context_queue; + // The DiskProver, given a correctly formatted plot file, can efficiently generate valid proofs // of space, for a given challenge. @@ -157,7 +292,6 @@ class DiskProver { { struct plot_header header{}; this->compression_level = 0; - this->cache_entry_proof_position = 0; this->filename = filename; std::ifstream disk_file(filename, std::ios::in | std::ios::binary); @@ -236,9 +370,12 @@ class DiskProver { uint8_t compression_level; SafeRead(disk_file, &compression_level, sizeof(compression_level)); this->compression_level = compression_level; - std::cout << "Compression level: " << (int)compression_level << "\n"; } } + #if !defined( USE_GREEN_REAPER ) + if (this->compression_level > 0) + throw std::logic_error("Harvester does not support compressed plots."); + #endif this->table_begin_pointers = std::vector(11, 0); this->C2 = std::vector(); @@ -297,13 +434,20 @@ class DiskProver { deserializer >> compression_level; } else { compression_level = 0; - cache_entry_proof_position = 0; } + + #if !defined( USE_GREEN_REAPER ) + if (compression_level > 0) + throw std::runtime_error("Harvester does not support compressed plots."); + #endif } DiskProver(DiskProver const&) = delete; DiskProver(DiskProver&& other) noexcept + #if USE_GREEN_REAPER + : cached_proofs(std::move(other.cached_proofs)) + #endif { filename = std::move(other.filename); memo = std::move(other.memo); @@ -313,7 +457,6 @@ class DiskProver { table_begin_pointers = std::move(other.table_begin_pointers); C2 = std::move(other.C2); version = std::move(other.version); - cache_entry_proof_position = 0; } ~DiskProver() @@ -418,9 +561,11 @@ class DiskProver { uint8_t last_5_bits = challenge[31] & 0x1f; for (uint64_t position : p7_entries) { - if (compression_level == 9) { - continue; - } + #if USE_GREEN_REAPER + if (compression_level >= 9) { + break; + } + #endif // This inner loop goes from table 6 to table 1, getting the two backpointers, // and following one of them. uint64_t alt_position; @@ -440,33 +585,37 @@ class DiskProver { } uint128_t new_line_point = ReadLinePoint(disk_file, GetEndTable(), position); std::pair x1x2; - if (compression_level > 0) { - GRCompressedQualitiesRequest req; - req.compressionLevel = compression_level; - req.plotId = id.data(); - req.challenge = challenge; - req.xLinePoints[0].hi = (uint64_t)(new_line_point >> 64); - req.xLinePoints[0].lo = (uint64_t)new_line_point; - if (compression_level >= 6) { - uint128_t alt_line_point = ReadLinePoint(disk_file, GetEndTable(), alt_position); - req.xLinePoints[1].hi = (uint64_t)(alt_line_point >> 64); - req.xLinePoints[1].lo = (uint64_t)alt_line_point; - } - - GreenReaperContext* gr = decompresser_context_queue.pop(); - assert(gr); - - auto res = grGetFetchQualitiesXPair(gr, &req); - decompresser_context_queue.push(gr); - - if (res != GRResult_OK) { - // Expect this will result in failure in a later step. - x1x2.first = x1x2.second = 0; - } else { - x1x2.first = req.x1; - x1x2.second = req.x2; - } - } else { + + #if USE_GREEN_REAPER + if (compression_level > 0) { + GRCompressedQualitiesRequest req; + req.compressionLevel = compression_level; + req.plotId = id.data(); + req.challenge = challenge; + req.xLinePoints[0].hi = (uint64_t)(new_line_point >> 64); + req.xLinePoints[0].lo = (uint64_t)new_line_point; + if (compression_level >= 6) { + uint128_t alt_line_point = ReadLinePoint(disk_file, GetEndTable(), alt_position); + req.xLinePoints[1].hi = (uint64_t)(alt_line_point >> 64); + req.xLinePoints[1].lo = (uint64_t)alt_line_point; + } + + GreenReaperContext* gr = decompresser_context_queue.pop(); + assert(gr); + + auto res = grGetFetchQualitiesXPair(gr, &req); + decompresser_context_queue.push(gr); + + if (res != GRResult_OK) { + // Expect this will result in failure in a later step. + x1x2.first = x1x2.second = 0; + } else { + x1x2.first = req.x1; + x1x2.second = req.x2; + } + } else + #endif // #if USE_GREEN_REAPER + { x1x2 = Encoding::LinePointToSquare(new_line_point); } // The final two x values (which are stored in the same location) are hashed @@ -480,62 +629,38 @@ class DiskProver { } } // Scope for disk_file - if (compression_level == 9) { - uint8_t failure_bytes[32]; - for (int i = 0; i < 32; i++) { - failure_bytes[i] = 255; - } - for (uint32_t i = 0; i < p7_entries_size; i++) { - try { - auto proof = GetFullProof(challenge, i); - qualities.push_back(GetQualityStringFromProof(proof, challenge)); - } catch (const std::exception& error) { - qualities.emplace_back(failure_bytes, 32, 256); + #if USE_GREEN_REAPER + if (compression_level >= 9) { + uint8_t failure_bytes[32]; + for (int i = 0; i < 32; i++) { + failure_bytes[i] = 255; + } + for (uint32_t i = 0; i < p7_entries_size; i++) { + try { + auto proof = GetFullProof(challenge, i); + qualities.push_back(GetQualityStringFromProof(proof, challenge)); + } catch (const std::exception& error) { + qualities.emplace_back(failure_bytes, 32, 256); + } } } - } + #endif return qualities; } - bool FoundCachedProof(CacheEntryProof* proof_entry) { - std::lock_guard l(_cache_mtx); - for (uint32_t i = 0; i < cached_entry_proofs.size(); i++) { - if ( - memcmp(cached_entry_proofs[i].challenge, proof_entry->challenge, sizeof(proof_entry->challenge)) == 0 - && cached_entry_proofs[i].index == proof_entry->index - ) { - proof_entry->full_proof = cached_entry_proofs[i].full_proof; - return true; - } - } - return false; - } - - void AddCachedProof(CacheEntryProof proof_entry) { - std::lock_guard l(_cache_mtx); - if (cached_entry_proofs.size() < 1000) { - cached_entry_proofs.emplace_back(proof_entry); - } else { - cached_entry_proofs[cache_entry_proof_position] = proof_entry; - } - cache_entry_proof_position = (cache_entry_proof_position + 1) % 1000; - } - // Given a challenge, and an index, returns a proof of space. This assumes GetQualities was // called, and there are actually proofs present. The index represents which proof to fetch, // if there are multiple. LargeBits GetFullProof(const uint8_t* challenge, uint32_t index, bool parallel_read = true) { LargeBits full_proof; - - if (compression_level == 9) { - CacheEntryProof proof_entry; - memcpy(proof_entry.challenge, challenge, sizeof(challenge)); - proof_entry.index = index; - if (FoundCachedProof(&proof_entry)) { - return proof_entry.full_proof; + + #if USE_GREEN_REAPER + if (compression_level >= 9 && cached_proofs.FoundCachedProof(index, challenge, full_proof)) { + return full_proof; } - } + #endif + std::lock_guard l(_mtx); { std::ifstream disk_file(filename, std::ios::in | std::ios::binary); @@ -557,34 +682,49 @@ class DiskProver { xs = GetInputs(p7_entries[index], 6, &disk_file); // Passing in a disk_file disabled the parallel reads } - if (compression_level > 0) { - auto gr = decompresser_context_queue.pop(); + #if USE_GREEN_REAPER + if (compression_level > 0) { + auto gr = decompresser_context_queue.pop(); - uint32_t compressedProof[GR_POST_PROOF_CMP_X_COUNT] = {}; - uint8_t compressed_proof_size = (compression_level <= 8 ? GR_POST_PROOF_CMP_X_COUNT : (GR_POST_PROOF_CMP_X_COUNT / 2)); - for (int i = 0; i < compressed_proof_size; i++) { - compressedProof[i] = xs[i].GetValue(); - } - GRCompressedProofRequest req; - memcpy(req.compressedProof, compressedProof, sizeof(req.compressedProof)); - req.compressionLevel = compression_level; - req.plotId = id.data(); - - GRResult res = grFetchProofForChallenge(gr, &req); - decompresser_context_queue.push(gr); + GRCompressedProofRequest req{}; + req.compressionLevel = compression_level; + req.plotId = id.data(); - if (res != GRResult_OK) { - if (res == GRResult_NoProof) { - throw std::runtime_error("GRResult_NoProof received"); + uint8_t compressed_proof_size = (compression_level <= 8 ? GR_POST_PROOF_CMP_X_COUNT : (GR_POST_PROOF_CMP_X_COUNT / 2)); + for (int i = 0; i < compressed_proof_size; i++) { + req.compressedProof[i] = xs[i].GetValue(); } - throw std::runtime_error("GRResult is not GRResult_OK."); - } - std::vector uncompressed_xs; - for (int i = 0; i < GR_POST_PROOF_X_COUNT; i++) { - uncompressed_xs.push_back(Bits(req.fullProof[i], k)); + + GRResult res = grFetchProofForChallenge(gr, &req); + decompresser_context_queue.push(gr); + + if (res != GRResult_OK) { + if (res == GRResult_NoProof) { + throw std::runtime_error("GRResult_NoProof received"); + } + if (res == GRResult_Failed) { + throw std::runtime_error("GRResult is not GRResult_OK, received GRResult_Failed"); + } + if (res == GRResult_OutOfMemory) { + throw std::runtime_error("GRResult is not GRResult_OK, received GRResult_OutOfMemory"); + } + if (res == GRResult_WrongVersion) { + throw std::runtime_error("GRResult is not GRResult_OK, received GRResult_WrongVersion"); + } + if (res == GRResult_InvalidGPU) { + throw std::runtime_error("GRResult is not GRResult_OK, received GRResult_InvalidGPU"); + } + if (res == GRResult_InvalidArg) { + throw std::runtime_error("GRResult is not GRResult_OK, received GRResult_InvalidArg"); + } + } + std::vector uncompressed_xs; + for (int i = 0; i < GR_POST_PROOF_X_COUNT; i++) { + uncompressed_xs.push_back(Bits(req.fullProof[i], k)); + } + xs = uncompressed_xs; } - xs = uncompressed_xs; - } + #endif // Sorts them according to proof ordering, where // f1(x0) m= f1(x1), f2(x0, x1) m= f2(x2, x3), etc. On disk, they are not stored in @@ -595,13 +735,12 @@ class DiskProver { full_proof += x; } } // Scope for disk_file - if (compression_level == 9) { - CacheEntryProof proof_entry; - memcpy(proof_entry.challenge, challenge, sizeof(challenge)); - proof_entry.index = index; - proof_entry.full_proof = full_proof; - AddCachedProof(proof_entry); - } + + #if USE_GREEN_REAPER + if (compression_level >= 9) { + cached_proofs.CacheProof(index, challenge, full_proof); + } + #endif return full_proof; } @@ -618,16 +757,16 @@ class DiskProver { private: uint16_t version{VERSION}; mutable std::mutex _mtx; - mutable std::mutex _cache_mtx; std::string filename; std::vector memo; std::vector id; // Unique plot id - std::vector cached_entry_proofs; - uint32_t cache_entry_proof_position; uint8_t k; uint8_t compression_level; std::vector table_begin_pointers; std::vector C2; + #if USE_GREEN_REAPER + ProofCache cached_proofs; + #endif // Using this method instead of simply seeking will prevent segfaults that would arise when // continuing the process of looking up qualities. @@ -680,13 +819,34 @@ class DiskProver { uint8_t table_index, uint64_t position ) { - CompressionInfo info; - bool is_compressed = compression_level > 0 && table_index == GetEndTable(); - if (is_compressed) { - info = GetCompressionInfoForLevel(compression_level); - } + size_t compressed_park_size = 0; + uint32_t compressed_stub_size_bits = 0; + double compressed_ans_r_value = 0; + + const bool is_compressed = compression_level > 0 && table_index == GetEndTable(); + (void)is_compressed; + + #if USE_GREEN_REAPER + if (is_compressed) { + GRCompressionInfo info{}; + const auto r = grGetCompressionInfo(&info, sizeof(info), k, compression_level); + if (r != GRResult_OK) { + std::stringstream err; err << "Failed to obtain compression info with error " << r; + throw std::runtime_error(err.str()); + } + + compressed_park_size = info.tableParkSize;; + compressed_stub_size_bits = info.subtSizeBits; + compressed_ans_r_value = info.ansRValue; + } + #else + (void)compressed_stub_size_bits; + (void)compressed_ans_r_value; + (void)compressed_park_size; + #endif + uint64_t park_index = position / kEntriesPerPark; - uint32_t park_size_bits = (is_compressed ? info.tableParkSize : EntrySizes::CalculateParkSize(k, table_index)) * 8; + uint32_t park_size_bits = (is_compressed ? compressed_park_size : EntrySizes::CalculateParkSize(k, table_index)) * 8; SafeSeek(disk_file, table_begin_pointers[table_index] + (park_size_bits / 8) * park_index); @@ -697,12 +857,12 @@ class DiskProver { uint128_t line_point = Util::SliceInt128FromBytes(line_point_bin, 0, k * 2); // Reads EPP stubs - uint32_t stubs_size_bits = (is_compressed ? (Util::ByteAlign((kEntriesPerPark - 1) * info.subtSizeBits) / 8) : EntrySizes::CalculateStubsSize(k)) * 8; + uint32_t stubs_size_bits = (is_compressed ? (Util::ByteAlign((kEntriesPerPark - 1) * compressed_stub_size_bits) / 8) : EntrySizes::CalculateStubsSize(k)) * 8; auto* stubs_bin = new uint8_t[stubs_size_bits / 8 + 7]; SafeRead(disk_file, stubs_bin, stubs_size_bits / 8); // Reads EPP deltas - uint32_t max_deltas_size_bits = (is_compressed ? info.tableParkSize - (line_point_size + stubs_size_bits) : EntrySizes::CalculateMaxDeltasSize(k, table_index)) * 8; + uint32_t max_deltas_size_bits = (is_compressed ? compressed_park_size - (line_point_size + stubs_size_bits) : EntrySizes::CalculateMaxDeltasSize(k, table_index)) * 8; auto* deltas_bin = new uint8_t[max_deltas_size_bits / 8]; // Reads the size of the encoded deltas object @@ -725,13 +885,13 @@ class DiskProver { SafeRead(disk_file, deltas_bin, encoded_deltas_size); // Decodes the deltas - double R = (is_compressed ? info.ansRValue : kRValues[table_index - 1]); + double R = (is_compressed ? compressed_ans_r_value : kRValues[table_index - 1]); deltas = Encoding::ANSDecodeDeltas(deltas_bin, encoded_deltas_size, kEntriesPerPark - 1, R); } uint32_t start_bit = 0; - uint8_t stub_size = (is_compressed ? info.subtSizeBits : k - kStubMinusBits); + uint8_t stub_size = (uint8_t)(is_compressed ? compressed_stub_size_bits : k - kStubMinusBits); uint64_t sum_deltas = 0; uint64_t sum_stubs = 0; for (uint32_t i = 0;