diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index db61d59d9e..c05b871b16 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,7 @@ labels: bug +* Discord https://discord.gg/UMWUnMjN4x --> diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 68151bb779..e32839fe8f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: Gridcoin Discord - url: https://discord.gg/jf9XX4a + url: https://discord.gg/UMWUnMjN4x about: Please go here if you have any general issues that are not bug reports. We can assist you much faster there. - name: Gridcoin Subreddit url: https://reddit.com/r/gridcoin diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a1b7ccd993..1e9ba30806 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,7 +8,7 @@ labels: enhancement +* Discord https://discord.gg/UMWUnMjN4x --> # Feature Request diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ed80872fe..9ddd104cac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,8 +46,8 @@ jobs: run: | ./ci/test_run_all.sh test-macos: - name: macOS 11 native [GOAL install] [GUI] [no depends] - runs-on: macos-11 + name: macOS 12 native [GOAL install] [GUI] [no depends] + runs-on: macos-12 env: DANGER_RUN_CI_ON_HOST: true CI_USE_APT_INSTALL: no diff --git a/.github/workflows/cmake-ci.yml b/.github/workflows/cmake-ci.yml new file mode 100644 index 0000000000..4105710601 --- /dev/null +++ b/.github/workflows/cmake-ci.yml @@ -0,0 +1,194 @@ +name: CMake CI +on: + - push + - pull_request + - workflow_dispatch + +jobs: + test-linux: + runs-on: ubuntu-latest + env: + CCACHE_DIR: ${{github.workspace}}/ccache + CCACHE_MAXSIZE: 400M + CCACHE_COMPILERCHECK: content + strategy: + matrix: + tag: + - minimal + - no-asm + - gui-full + - system-libs + include: + - tag: no-asm + deps: null + options: -DENABLE_PIE=ON -DUSE_ASM=OFF + - tag: gui-full + deps: >- + libminiupnpc-dev + libqrencode-dev + qtbase5-dev + qttools5-dev + options: >- + -DENABLE_GUI=ON + -DENABLE_QRENCODE=ON + -DENABLE_UPNP=ON + -DUSE_DBUS=ON + - tag: system-libs + deps: >- + libdb5.3++-dev + libleveldb-dev + libsnappy-dev + libsecp256k1-dev + libunivalue-dev + xxd + options: >- + -DSYSTEM_BDB=ON + -DSYSTEM_LEVELDB=ON + -DSYSTEM_UNIVALUE=ON + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: | + ${{matrix.deps}} + ccache + libcurl4-openssl-dev + libzip-dev + ninja-build + zipcmp + zipmerge + ziptool + version: ${{matrix.tag}} + - name: Install Boost dependencies + run: | + sudo apt-get install -y --no-install-recommends \ + libboost-dev \ + libboost-date-time-dev \ + libboost-exception-dev \ + libboost-filesystem-dev \ + libboost-iostreams-dev \ + libboost-serialization-dev \ + libboost-test-dev \ + libboost-thread-dev + - name: Configure + run: | + cmake -B ${{github.workspace}}/build -G Ninja \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + ${{matrix.options}} \ + -DENABLE_TESTS=ON + - name: Restore cache + uses: actions/cache/restore@v3 + if: always() + with: + path: ${{env.CCACHE_DIR}} + key: ccache-linux-${{matrix.tag}}-${{github.run_id}} + restore-keys: | + ccache-linux-${{matrix.tag}}- + - name: Build + run: | + cmake --build ${{github.workspace}}/build -v -j $(nproc) + - name: Save cache + uses: actions/cache/save@v3 + if: always() + with: + path: ${{env.CCACHE_DIR}} + key: ccache-linux-${{matrix.tag}}-${{github.run_id}} + - name: Run tests + run: | + ctest --test-dir ${{github.workspace}}/build -j $(nproc) + - name: Upload test logs + uses: actions/upload-artifact@v3 + if: always() + with: + name: testlog-linux-${{matrix.tag}} + path: ${{github.workspace}}/build/Testing/Temporary/LastTest.log + retention-days: 7 + + test-macos: + runs-on: macos-latest + env: + CCACHE_DIR: ${{github.workspace}}/ccache + CCACHE_MAXSIZE: 400M + CCACHE_COMPILERCHECK: content + strategy: + matrix: + tag: + - minimal + - no-asm + - gui-full + - system-libs + include: + - tag: no-asm + deps: null + options: -DENABLE_PIE=ON -DUSE_ASM=OFF + - tag: gui-full + deps: >- + miniupnpc + qrencode + qt@5 + options: >- + -DENABLE_GUI=ON + -DQt5_DIR=/usr/local/opt/qt@5/lib/cmake/Qt5 + -DENABLE_QRENCODE=ON + -DENABLE_UPNP=ON + - tag: system-libs + deps: >- + berkeley-db@5 + secp256k1 + vim + options: >- + -DSYSTEM_BDB=ON + -DBerkeleyDB_INCLUDE_DIR=/usr/local/opt/berkeley-db@5/include + -DBerkeleyDB_CXX_LIBRARY=/usr/local/opt/berkeley-db@5/lib/libdb_cxx.dylib + -DSYSTEM_SECP256K1=ON + -DSYSTEM_XXD=ON + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install dependencies + run: | + brew install boost ccache ninja ${{matrix.deps}} + - name: Configure + run: | + PKG_CONFIG_PATH="/usr/local/opt/openssl@3/lib/pkgconfig:${PKG_CONFIG_PATH}" + export PKG_CONFIG_PATH + + pushd src + ../contrib/nomacro.pl + popd + + cmake -B ${{github.workspace}}/build -G Ninja \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + ${{matrix.options}} \ + -DENABLE_TESTS=ON + - name: Restore cache + uses: actions/cache/restore@v3 + if: always() + with: + path: ${{env.CCACHE_DIR}} + key: ccache-macos-${{matrix.tag}}-${{github.run_id}} + restore-keys: | + ccache-macos-${{matrix.tag}}- + - name: Build + run: | + cmake --build ${{github.workspace}}/build -v -j $(sysctl -n hw.logicalcpu) + - name: Save cache + uses: actions/cache/save@v3 + if: always() + with: + path: ${{env.CCACHE_DIR}} + key: ccache-macos-${{matrix.tag}}-${{github.run_id}} + - name: Run tests + run: | + ctest --test-dir ${{github.workspace}}/build -j $(sysctl -n hw.logicalcpu) + - name: Upload test logs + uses: actions/upload-artifact@v3 + if: always() + with: + name: testlog-macos-${{matrix.tag}} + path: ${{github.workspace}}/build/Testing/Temporary/LastTest.log + retention-days: 7 diff --git a/.gitignore b/.gitignore index aed5378031..f2d2617bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ src/test/test_gridcoin.log src/test/test_gridcoin.trs src/build.h -src/obj +src/obj/build.h src/qt/forms/*.h src/qt/forms/voting/*.h src/qt/moc_*.cpp @@ -99,7 +99,6 @@ config.status configure libtool src/config/gridcoin-config.h -src/config/gridcoin-config.h.in src/config/stamp-h1 share/setup.nsi share/qt/Info.plist diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ab4f3ba9..945a0ecc15 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,67 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [5.4.6.0], 2024-03-02, leisure, "Miss Piggy" + +### Added + - contrib: add nix file for compilation environment #2660 (@div72) + - gui: Make main Gridcoin window geometry save unique to datadir location #2661 (@jamescowens) + - build: Initial CMake support #2676 (@CyberTailor) + - util: Add `-shutdownnotify` and `startupnotify` options from upstream #2688 (@barton2526) + - gui, staking: Implement facilities for mandatory sidestakes and sidestake GUI #2704 (@jamescowens) + - gui, voting: Implement poll result caching and poll stale indicator #2709 (@jamescowens) + - gui, projects: Implement greylist state for projects in GUI projects table #2715 (@jamescowens) + - gui, poll: Implement poll expiration reminders #2716 (@jamescowens) + - serialize: allow variants to be serialized #2729 (@div72) + - gui: Implement poll field length limiters in GUI forms #2742 (@jamescowens) + +### Changed + - consensus, contract, scraper, protocol, project, beacon, rpc: Replace remaining appcache sections with native structures #2639 (@jamescowens) + - build: update libsecp256k1 to v0.3.0 #2655 (@div72) + - build: Replace $(AT) with .SILENT #2674 (@barton2526) + - build: allow system bdb #2675 (@div72) + - Resize Header Column with Additional Text #2683 (@PrestackI) + - rpc: use RPCErrorCode everywhere #2687 (@Pythonix) + - wallet: SecureString to allow null characters #2690 (@barton2526) + - random: port some upstream changes #2696 (@div72) + - depends: Bump dependencies #2692 (@barton2526) + - doc: Update link to Discord server #2693 (@adriaanjoubert) + - rpc: Change capitalization, remove whitespace of rpc keys #2711 (@Pythonix) + - ci: bump MacOS version to 12 #2713 (@div72) + - depends: no-longer nuke libc++abi.so* in native_clang package #2719 (@div72) + - doc: update windows `-fstack-clash-protection` doc #2720 (@div72) + - Silence `-Wcast-function-type` warning #2721 (@div72) + - build: Use newest `config.{guess,sub}` available #2722 (@div72) + - refactor: use the try_lock result in TryEnter #2723 (@div72) + - Updates for file src/qt/locale/bitcoin_en.ts in pt_PT #2726 (@gridcoin-community) + - ci: do not silently fail #2727 (@div72) + - Properly include Boost Array header #2730 (@theMarix) + - build: Update depends zlib to 1.3.1 #2734 (@jamescowens) + - util: Enhance Fraction class overflow resistance #2735 (@jamescowens) + - refactor: Fix compilation warnings #2737 (@jamescowens) + - gui, util: Improve upgrade dialog #2738 (@jamescowens) + - util: Improve allocation class #2740 (@jamescowens) + - translations: translation updates for Miss Piggy release #2745 (@jamescowens) + +### Removed + - gui: Disable snapshot GUI action #2700 (@jamescowens) + - build, crypto, script: remove most of OpenSSL usage #2705 (@div72) + - util: remove WSL 1 workaround in fs #2717 (@div72) + +### Fixed + - diagnostics: fix missing arg in ETTS warning #2684 (@div72) + - misc: fix include guard in netaddress.h #2695 (@div72) + - gui: Fix expired pending beacon display #2698 (@jamescowens) + - consensus: Fix 20230904 testnet forking issue #2703 (@jamescowens) + - gui: Fix filter by type in Transaction View #2708 (@jamescowens) + - depends: make fontconfig build under clang-16 #2718 (@div72) + - diag: fix researcher mode check #2725 (@div72) + - gui: Add missing switch cases for ALREADY_IN_MEMPOOL #2728 (@jamescowens) + - beacon, registry: Fix beacon history stall #2731 (@jamescowens) + - build: Implement comp_double comparison function in certain tests #2741 (@jamescowens) + - ci: change Qt path in CMake CI #2743 (@div72) + - net: Correct -tor argument handling #2744 (@jamescowens) + ## [5.4.5.0] 2023-04-23, leisure ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..09cc834388 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,262 @@ +# CMake build system is intended to be used by maintainers to package Gridcoin +# for software repositories. For that reason, only external dependencies are +# supported. +# +# Use Autotools build system for all other needs. +# +# CMake support is experimental. Use with caution and report any bugs. + +cmake_minimum_required(VERSION 3.18) + +project("Gridcoin" + VERSION 5.4.6.0 + DESCRIPTION "POS-based cryptocurrency that rewards BOINC computation" + HOMEPAGE_URL "https://gridcoin.us" + LANGUAGES ASM C CXX +) + +set(CLIENT_VERSION_IS_RELEASE "true") +set(COPYRIGHT_YEAR "2024") +set(COPYRIGHT_HOLDERS_FINAL "The Gridcoin developers") + + +# Toolchain configuration +# ======================= + +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Remove '-DNDEBUG' from flags because we need asserts +string(REPLACE "NDEBUG" "_NDEBUG" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +string(REPLACE "NDEBUG" "_NDEBUG" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + + +# Load modules from the source tree +# ================================= + +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/build-aux/cmake") + +include(CheckCXXSymbolExists) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckPIESupported) +include(CheckSSE) +include(CheckStrerrorR) +include(CheckSymbolExists) +include(VersionFromGit) + +# Define options +# ============== + +# Build configuration +option(ENABLE_DAEMON "Enable daemon" ON) +option(ENABLE_GUI "Enable Qt-based GUI" OFF) +option(ENABLE_DOCS "Build Doxygen documentation" OFF) +option(ENABLE_TESTS "Build tests" OFF) +option(LUPDATE "Update translation files" OFF) + +# CPU-dependent options +option(ENABLE_SSE41 "Build code that uses SSE4.1 intrinsics" ${HAS_SSE41}) +option(ENABLE_AVX2 "Build code that uses AVX2 intrinsics" ${HAS_AVX2}) +option(ENABLE_X86_SHANI "Build code that uses x86 SHA-NI intrinsics" ${HAS_X86_SHANI}) +option(ENABLE_ARM_SHANI "Build code that uses ARM SHA-NI intrinsics" ${HAS_ARM_SHANI}) +option(USE_ASM "Enable assembly routines" ON) + +# Optional functionality +option(ENABLE_PIE "Build position-independent executables" OFF) +option(ENABLE_QRENCODE "Enable generation of QR Codes for receiving payments" OFF) +option(ENABLE_UPNP "Enable UPnP port mapping support" OFF) +option(DEFAULT_UPNP "Turn UPnP on startup" OFF) +option(USE_DBUS "Enable DBus support" OFF) +option(SYSTEM_BDB "Find system installation of Berkeley DB CXX 5.3" OFF) +option(SYSTEM_LEVELDB "Find system installation of leveldb" OFF) +option(SYSTEM_SECP256K1 "Find system installation of libsecp256k1 with pkg-config" OFF) +option(SYSTEM_UNIVALUE "Find system installation of Univalue with pkg-config" OFF) +option(SYSTEM_XXD "Find system xxd binary" OFF) + + +# Find dependencies +# ================= + +set(BOOST_MINIMUM_VERSION 1.63.0) +set(QT5_MINIMUM_VERSION 5.15.0) + +find_package(Atomics REQUIRED) +find_package(Boost ${BOOST_MINIMUM_VERSION} COMPONENTS filesystem iostreams thread REQUIRED) +find_package(CURL COMPONENTS HTTP HTTPS SSL REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(Threads REQUIRED) +find_package(libzip REQUIRED) + +if(SYSTEM_BDB) + find_package(BerkeleyDB 5.3...<5.4 COMPONENTS CXX REQUIRED) +else() + find_program(MAKE_EXE NAMES gmake nmake make) +endif() + +if(SYSTEM_LEVELDB) + find_package(leveldb REQUIRED) +endif() + +if(SYSTEM_SECP256K1) + find_package(PkgConfig) + pkg_check_modules(SECP256K1 REQUIRED IMPORTED_TARGET "libsecp256k1 >= 0.2.0") +endif() + +if(SYSTEM_UNIVALUE) + find_package(PkgConfig) + pkg_check_modules(UNIVALUE REQUIRED IMPORTED_TARGET libunivalue) +endif() + +if(ENABLE_GUI) + find_package(Qt5 ${QT5_MINIMUM_VERSION} REQUIRED COMPONENTS + Concurrent + Core + Gui + LinguistTools + Network + Widgets + ) + + if(USE_DBUS) + find_package(Qt5 ${QT5_MINIMUM_VERSION} COMPONENTS DBus REQUIRED) + endif() + + if(ENABLE_TESTS) + find_package(Qt5 ${QT5_MINIMUM_VERSION} COMPONENTS Test REQUIRED) + endif() + + if(ENABLE_QRENCODE) + pkg_check_modules(QRENCODE REQUIRED IMPORTED_TARGET libqrencode) + endif() +endif() + +if(ENABLE_UPNP) + pkg_check_modules(MINIUPNPC REQUIRED IMPORTED_TARGET miniupnpc>=1.9) +endif() + +if(ENABLE_TESTS) + find_package(Boost ${BOOST_MINIMUM_VERSION} COMPONENTS unit_test_framework REQUIRED) + enable_testing() + + if(SYSTEM_XXD) + find_program(XXD xxd REQUIRED) + endif() +endif() + +if(UNIX) + find_package(Rt REQUIRED) +endif() + +if(WIN32) + enable_language(RC) + find_program(MAKENSIS makensis) +elseif(APPLE) + enable_language(OBJCXX) +endif() + + +# Run probes +# ========== + +if(ENABLE_PIE) + check_pie_supported() + if(NOT CMAKE_CXX_LINK_PIE_SUPPORTED) + message(FATAL_ERROR "PIE is not supported by the current linker") + endif() +endif() + +set(CMAKE_POSITION_INDEPENDENT_CODE ${ENABLE_PIE}) + +# Set compiler flags +if (APPLE) + add_compile_options(-Wno-error=deprecated-declarations) + add_compile_options(-Wno-error=thread-safety-analysis) + add_compile_options(-Wno-error=thread-safety-reference) +endif() + +# Set endianness +if(CMAKE_CXX_BYTE_ORDER EQUAL BIG_ENDIAN) + set(WORDS_BIGENDIAN 1) +endif() + +# Check headers +check_include_file("byteswap.h" HAVE_BYTESWAP_H) +check_include_file("endian.h" HAVE_ENDIAN_H) +check_include_file("sys/endian.h" HAVE_SYS_ENDIAN_H) +check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) + +if(HAVE_ENDIAN_H) + set(ENDIAN_INCLUDES "endian.h") +else() + set(ENDIAN_INCLUDES "sys/endian.h") +endif() + +if(HAVE_BYTESWAP_H) + set(BYTESWAP_INCLUDES "byteswap.h") +endif() + +# Check symbols +check_symbol_exists(fork "unistd.h" HAVE_DECL_FORK) +check_symbol_exists(pipe2 "unistd.h" HAVE_DECL_PIPE2) +check_symbol_exists(setsid "unistd.h" HAVE_DECL_SETSID) + +check_symbol_exists(le16toh "${ENDIAN_INCLUDES}" HAVE_DECL_LE16TOH) +check_symbol_exists(le32toh "${ENDIAN_INCLUDES}" HAVE_DECL_LE32TOH) +check_symbol_exists(le64toh "${ENDIAN_INCLUDES}" HAVE_DECL_LE64TOH) + +check_symbol_exists(htole16 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOLE16) +check_symbol_exists(htole32 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOLE32) +check_symbol_exists(htole64 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOLE64) + +check_symbol_exists(be16toh "${ENDIAN_INCLUDES}" HAVE_DECL_BE16TOH) +check_symbol_exists(be32toh "${ENDIAN_INCLUDES}" HAVE_DECL_BE32TOH) +check_symbol_exists(be64toh "${ENDIAN_INCLUDES}" HAVE_DECL_BE64TOH) + +check_symbol_exists(htobe16 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOBE16) +check_symbol_exists(htobe32 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOBE32) +check_symbol_exists(htobe64 "${ENDIAN_INCLUDES}" HAVE_DECL_HTOBE64) + +check_symbol_exists(bswap_16 "${BYTESWAP_INCLUDES}" HAVE_DECL_BSWAP_16) +check_symbol_exists(bswap_32 "${BYTESWAP_INCLUDES}" HAVE_DECL_BSWAP_32) +check_symbol_exists(bswap_64 "${BYTESWAP_INCLUDES}" HAVE_DECL_BSWAP_64) + +check_function_exists(__builtin_clzl HAVE_BUILTIN_CLZL) +check_function_exists(__builtin_clzll HAVE_BUILTIN_CLZLL) + +check_symbol_exists(MSG_NOSIGNAL "sys/socket.h" HAVE_MSG_NOSIGNAL) +check_symbol_exists(MSG_DONTWAIT "sys/socket.h" HAVE_MSG_DONTWAIT) + +check_symbol_exists(malloc_info "malloc.h" HAVE_MALLOC_INFO) +check_symbol_exists(M_ARENA_MAX "malloc.h" HAVE_MALLOPT_ARENA_MAX) + +check_cxx_symbol_exists(std::system "cstdlib" HAVE_SYSTEM) +check_cxx_symbol_exists(gmtime_r "ctime" HAVE_GMTIME_R) + +if(NOT HAVE_GMTIME_R) + check_cxx_symbol_exists(gmtime_s "ctime" HAVE_GMTIME_S) + if(NOT HAVE_GMTIME_S) + message(FATAL_ERROR "Both gmtime_r and gmtime_s are unavailable") + endif() +endif() + +check_symbol_exists(SYS_getrandom "sys/syscall.h" HAVE_SYS_GETRANDOM) +check_symbol_exists(getentropy "unistd.h" HAVE_GETENTROPY) +check_symbol_exists(KERN_ARND "sys/sysctl.h" HAVE_SYSCTL_ARND) + +check_symbol_exists(O_CLOEXEC "fcntl.h" HAVE_O_CLOEXEC) +check_symbol_exists(getauxval "sys/auxv.h" HAVE_STRONG_GETAUXVAL) + +# Descend into subdirectories +# =========================== + +add_subdirectory(src) +if(ENABLE_DOCS) + add_subdirectory(doc) +endif() diff --git a/README.md b/README.md index 9e088ec989..66b481e5d0 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,28 @@ Building Gridcoin These dependencies are required: - Library | Purpose | Description - ------------|------------------|---------------------------------------------------------------- - pkg-config | Build | Learn library inter-dependencies - libssl | Crypto | Random Number Generation, Elliptic Curve Cryptography - libboost | Utility | Library for threading, data structures, etc - libevent | Networking | OS independent asynchronous networking - miniupnpc | UPnP Support | Firewall-jumping support - qt | GUI | GUI toolkit (only needed when GUI enabled) - libqrencode | QR codes in GUI | Optional for generating QR codes (only needed when GUI enabled) - -To build, run -```./autogen.sh && ./configure && make```. + Library | Purpose | Description + -------------|------------------|---------------------------------------------------------------- + cmake | Build | Build system (optional) + pkgconf | Build | Learn library inter-dependencies + openssl | Crypto | Random Number Generation, Elliptic Curve Cryptography + libboost | Utility | Library for threading, data structures, etc + libcurl | Utility | URL client library + libzip | Utility | Library for manipulating zip archives + miniupnpc | UPnP Support | Firewall-jumping support (optional) + qt5 | GUI | GUI toolkit (optional) + libqrencode | QR codes in GUI | Library for encoding data in a QR Code symbol (optional, depends on GUI) + +To build, run: + +* With CMake: + + `mkdir build && cmake build && cmake .. && cmake --build .` + +* With Autotools: + + `./autogen.sh && ./configure && make` + For more detailed and platform-specific instructions, see [the doc folder.](doc/) Development process @@ -71,7 +81,7 @@ master if the staging branch is busy. Community ========= -For general questions, please visit our Discord server at https://discord.gg/jf9XX4a, or Freenode IRC in #gridcoin-help. We also have a Slack channel at [teamgridcoin.slack.com](https://join.slack.com/t/teamgridcoin/shared_invite/zt-3s81akww-GHt~_KvtxfhxUgi3yW3~Bg). +For general questions, please visit our Discord server at https://discord.gg/UMWUnMjN4x, or Libera Chat in #gridcoin-help. We also have a Slack channel at [teamgridcoin.slack.com](https://join.slack.com/t/teamgridcoin/shared_invite/zt-3s81akww-GHt~_KvtxfhxUgi3yW3~Bg). License ------- diff --git a/autogen.sh b/autogen.sh index a0c323ce9e..b3cbb38389 100755 --- a/autogen.sh +++ b/autogen.sh @@ -14,3 +14,16 @@ fi command -v autoreconf >/dev/null || \ (echo "configuration failed, please install autoconf first" && exit 1) autoreconf --install --force --warnings=all + +if expr "'$(build-aux/config.guess --timestamp)" \< "'$(depends/config.guess --timestamp)" > /dev/null; then + chmod ug+w build-aux/config.guess + chmod ug+w src/secp256k1/build-aux/config.guess + cp depends/config.guess build-aux + cp depends/config.guess src/secp256k1/build-aux +fi +if expr "'$(build-aux/config.sub --timestamp)" \< "'$(depends/config.sub --timestamp)" > /dev/null; then + chmod ug+w build-aux/config.sub + chmod ug+w src/secp256k1/build-aux/config.sub + cp depends/config.sub build-aux + cp depends/config.sub src/secp256k1/build-aux +fi diff --git a/build-aux/cmake/CheckSSE.cmake b/build-aux/cmake/CheckSSE.cmake new file mode 100644 index 0000000000..56984403e3 --- /dev/null +++ b/build-aux/cmake/CheckSSE.cmake @@ -0,0 +1,50 @@ +include(CheckCSourceRuns) + +check_c_source_runs(" + #include + + int main() { + __m128i l = _mm_set1_epi32(0); + return _mm_extract_epi32(l, 3); + }" + HAS_SSE41 +) + +check_c_source_runs(" + #include + #include + + int main() { + __m256i l = _mm256_set1_epi32(0); + return _mm256_extract_epi32(l, 7); + }" + HAS_AVX2 +) + +check_c_source_runs(" + #include + #include + + int main() { + __m128i i = _mm_set1_epi32(0); + __m128i j = _mm_set1_epi32(1); + __m128i k = _mm_set1_epi32(2); + return _mm_extract_epi32(_mm_sha256rnds2_epu32(i, i, k), 0); + }" + HAS_X86_SHANI +) + +check_c_source_runs(" + #include + #include + + int main() { + uint32x4_t a, b, c; + vsha256h2q_u32(a, b, c); + vsha256hq_u32(a, b, c); + vsha256su0q_u32(a, b); + vsha256su1q_u32(a, b, c); + return 0; + }" + HAS_ARM_SHANI +) diff --git a/build-aux/cmake/CheckStrerrorR.cmake b/build-aux/cmake/CheckStrerrorR.cmake new file mode 100644 index 0000000000..e74840b7c8 --- /dev/null +++ b/build-aux/cmake/CheckStrerrorR.cmake @@ -0,0 +1,26 @@ +include(CheckCCompilerFlag) +include(CheckCSourceRuns) +include(CheckSymbolExists) + +check_symbol_exists(strerror_r "string.h" HAVE_STRERROR_R) + +if(HAVE_STRERROR_R) + check_c_compiler_flag(-Werror HAS_WERROR) + if(HAS_WERROR) + set(CMAKE_REQUIRED_FLAGS "-Werror") + endif() + check_c_source_runs(" + #include + #include + #include + + int main() { + char buf[280]; + char* s = strerror_r(ENOENT, buf, 280); + printf(\"%s\", s); + return 0; + }" + STRERROR_R_CHAR_P + ) + set(CMAKE_REQUIRED_FLAGS "") +endif() diff --git a/build-aux/cmake/ExternalLibraryHelper.cmake b/build-aux/cmake/ExternalLibraryHelper.cmake new file mode 100644 index 0000000000..2aeee7280e --- /dev/null +++ b/build-aux/cmake/ExternalLibraryHelper.cmake @@ -0,0 +1,62 @@ +# Copyright (c) 2017-2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +include(FindPackageMessage) + +# Find a library component, set the variables and create an imported target. +# Variable names are compliant with cmake standards. +function(find_component LIB COMPONENT) + cmake_parse_arguments(ARG + "" + "" + "HINTS;INCLUDE_DIRS;INTERFACE_LINK_LIBRARIES;NAMES;PATHS;PATH_SUFFIXES" + ${ARGN} + ) + + # If the component is not requested, skip the search. + if(${LIB}_FIND_COMPONENTS AND NOT ${COMPONENT} IN_LIST ${LIB}_FIND_COMPONENTS) + return() + endif() + + find_library(${LIB}_${COMPONENT}_LIBRARY + NAMES ${ARG_NAMES} + PATHS "" ${ARG_PATHS} + HINTS "" ${ARG_HINTS} + PATH_SUFFIXES "lib" ${ARG_PATH_SUFFIXES} + ) + mark_as_advanced(${LIB}_${COMPONENT}_LIBRARY) + + if(${LIB}_${COMPONENT}_LIBRARY) + # On success, set the standard FOUND variable... + set(${LIB}_${COMPONENT}_FOUND TRUE PARENT_SCOPE) + + # ... and append the library path to the LIBRARIES variable ... + list(APPEND ${LIB}_LIBRARIES + "${${LIB}_${COMPONENT}_LIBRARY}" + ${ARG_INTERFACE_LINK_LIBRARIES} + ) + list(REMOVE_DUPLICATES ${LIB}_LIBRARIES) + set(${LIB}_LIBRARIES ${${LIB}_LIBRARIES} PARENT_SCOPE) + + # ... and create an imported target for the component, if not already + # done. + if(NOT TARGET ${LIB}::${COMPONENT}) + add_library(${LIB}::${COMPONENT} UNKNOWN IMPORTED) + set_target_properties(${LIB}::${COMPONENT} PROPERTIES + IMPORTED_LOCATION "${${LIB}_${COMPONENT}_LIBRARY}" + ) + set_property(TARGET ${LIB}::${COMPONENT} PROPERTY + INTERFACE_INCLUDE_DIRECTORIES ${ARG_INCLUDE_DIRS} + ) + set_property(TARGET ${LIB}::${COMPONENT} PROPERTY + INTERFACE_LINK_LIBRARIES ${ARG_INTERFACE_LINK_LIBRARIES} + ) + endif() + + find_package_message("${LIB}_${COMPONENT}" + "Found ${LIB} component ${COMPONENT}: ${${LIB}_${COMPONENT}_LIBRARY}" + "[${${LIB}_${COMPONENT}_LIBRARY}][${ARG_INCLUDE_DIRS}]" + ) + endif() +endfunction() diff --git a/build-aux/cmake/FindAtomics.cmake b/build-aux/cmake/FindAtomics.cmake new file mode 100644 index 0000000000..5954308834 --- /dev/null +++ b/build-aux/cmake/FindAtomics.cmake @@ -0,0 +1,53 @@ +# Original issue: +# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733 +# +# For reference: +# * https://gcc.gnu.org/wiki/Atomic/GCCMM +# +# riscv64 specific: +# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html +# +# ATOMICS_FOUND - system has c++ atomics +# ATOMICS_LIBRARIES - libraries needed to use c++ atomics + +include(CheckCXXSourceCompiles) + +# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed +# to convert smaller atomics to those larger ones via masking and +# shifting like LLVM, but it’s a known bug that it does not. This means +# anything that wants to use atomics on 1-byte or 2-byte types needs +# -latomic, but not 4-byte or 8-byte (though it does no harm). +set(atomic_code " + #include + #include + + std::atomic n8 (0); // riscv64 + std::atomic n64 (0); // armel, mipsel, powerpc + + int main() { + ++n8; + ++n64; + return 0; + }" +) +check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS) + +if(ATOMICS_LOCK_FREE_INSTRUCTIONS) + set(ATOMICS_FOUND TRUE) + set(ATOMICS_LIBRARIES) +else() + set(CMAKE_REQUIRED_LIBRARIES "-latomic") + check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES) + if(ATOMICS_IN_LIBRARY) + set(ATOMICS_LIBRARY atomic) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY) + set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY}) + unset(ATOMICS_LIBRARY) + else() + if(Atomics_FIND_REQUIRED) + message(FATAL_ERROR "Neither lock free instructions nor -latomic found.") + endif() + endif() +endif() diff --git a/build-aux/cmake/FindBerkeleyDB.cmake b/build-aux/cmake/FindBerkeleyDB.cmake new file mode 100644 index 0000000000..f9b4405ca0 --- /dev/null +++ b/build-aux/cmake/FindBerkeleyDB.cmake @@ -0,0 +1,171 @@ +# Copyright (c) 2017-2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#.rst +# FindBerkeleyDB +# ------------- +# +# This is inspired by https://github.com/sum01/FindBerkeleyDB. +# +# Find the Berkeley database (versions >= 5.x) libraries The following +# components are available:: +# C +# CXX +# +# This will define the following variables:: +# +# BerkeleyDB_FOUND - system has Berkeley DB lib +# BerkeleyDB_INCLUDE_DIRS - the Berkeley DB include directories +# BerkeleyDB_LIBRARIES - Libraries needed to use Berkeley DB +# BerkeleyDB_VERSION - The library version MAJOR.MINOR.PATCH +# BerkeleyDB_VERSION_MAJOR - Major version number +# BerkeleyDB_VERSION_MINOR - Minor version number +# BerkeleyDB_VERSION_PATCH - Patch version number +# +# And the following imported target:: +# +# BerkeleyDB::C +# BerkeleyDB::CXX + +# Generate a list of all the possible versioned library name variants given a +# list of separators. +function(generate_versions_variants VARIANTS LIB MAJOR MINOR) + set(SEPARATORS + "" "." "-" "_" + ) + + set(${VARIANTS} "${LIB}") + foreach(_separator1 IN LISTS SEPARATORS) + list(APPEND ${VARIANTS} "${LIB}${_separator1}${MAJOR}") + foreach(_separator2 IN LISTS SEPARATORS) + list(APPEND ${VARIANTS} "${LIB}${_separator1}${MAJOR}${_separator2}${MINOR}") + endforeach() + endforeach() + + # We need to search from the most specific to the least specific to prevent + # mismatches, e.g. if the include dir is /usr/include/db5.3 we want to link + # /usr/lib/libdb5.3.so and not libdb.so, which could very well be another + # version. Note that this is not only theoretical and actually happened on + # Archlinux with both db5.3 and db6.2 installed. + list(REVERSE ${VARIANTS}) + + set(${VARIANTS} ${${VARIANTS}} PARENT_SCOPE) +endfunction() + +# If the include directory is user supplied, skip the search +if(NOT BerkeleyDB_INCLUDE_DIR) + # Berkeley DB 5 including latest minor release. + generate_versions_variants(_BerkeleyDB_PATH_SUFFIXES_5_3 db 5 3) + + set(_BerkeleyDB_PATH_SUFFIXES + include + ${_BerkeleyDB_PATH_SUFFIXES_5_3} + ) + list(REMOVE_DUPLICATES _BerkeleyDB_PATH_SUFFIXES) + + # Try to find the db.h header. + # If the header is not found the user can supply the correct path by passing + # the `BerkeleyDB_ROOT` variable to cmake. + find_path(BerkeleyDB_INCLUDE_DIR + NAMES db.h + PATH_SUFFIXES ${_BerkeleyDB_PATH_SUFFIXES} + ) +endif() + +# There is a single common include directory. +# Set the BerkeleyDB_INCLUDE_DIRS variable which is the expected standard output +# variable name for the include directories. +set(BerkeleyDB_INCLUDE_DIRS "${BerkeleyDB_INCLUDE_DIR}") +mark_as_advanced(BerkeleyDB_INCLUDE_DIR) + +if(BerkeleyDB_INCLUDE_DIR) + # Extract version information from the db.h header. + if(NOT DEFINED BerkeleyDB_VERSION) + set(db_version_test_program " + #include + #include \"${BerkeleyDB_INCLUDE_DIR}/db.h\" + + int main() { + printf(\"%d;%d;%d\", DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); + } + ") + + if(CMAKE_VERSION VERSION_LESS 3.25) + file(WRITE ${CMAKE_BINARY_DIR}/db-version.c "${db_version_test_program}") + try_run( + _run_result + _compile_result + ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/db-version.c + RUN_OUTPUT_STDOUT_VARIABLE BerkeleyDB_VERSION_LIST + ) + else() + try_run( + _run_result + _compile_result + SOURCE_FROM_CONTENT db-version.c "${db_version_test_program}" + RUN_OUTPUT_STDOUT_VARIABLE BerkeleyDB_VERSION_LIST + ) + endif() + + list(GET BerkeleyDB_VERSION_LIST 0 BerkeleyDB_VERSION_MAJOR) + list(GET BerkeleyDB_VERSION_LIST 1 BerkeleyDB_VERSION_MINOR) + list(GET BerkeleyDB_VERSION_LIST 2 BerkeleyDB_VERSION_PATCH) + + # Cache the result. + set(BerkeleyDB_VERSION_MAJOR ${BerkeleyDB_VERSION_MAJOR} + CACHE INTERNAL "BerkeleyDB major version number" + ) + set(BerkeleyDB_VERSION_MINOR ${BerkeleyDB_VERSION_MINOR} + CACHE INTERNAL "BerkeleyDB minor version number" + ) + set(BerkeleyDB_VERSION_PATCH ${BerkeleyDB_VERSION_PATCH} + CACHE INTERNAL "BerkeleyDB patch version number" + ) + # The actual returned/output version variable (the others can be used if + # needed). + set(BerkeleyDB_VERSION + "${BerkeleyDB_VERSION_MAJOR}.${BerkeleyDB_VERSION_MINOR}.${BerkeleyDB_VERSION_PATCH}" + CACHE INTERNAL "BerkeleyDB full version" + ) + endif() + + include(ExternalLibraryHelper) + + # Different systems sometimes have a version in the lib name... + # and some have a dash or underscore before the versions. + # Generate all combinations from the separators "" (none), ".", "-" and "_". + generate_versions_variants( + _db_variants + db + "${BerkeleyDB_VERSION_MAJOR}" + "${BerkeleyDB_VERSION_MINOR}" + ) + + find_component(BerkeleyDB C + NAMES ${_db_variants} + PATH_SUFFIXES ${_db_variants} + INCLUDE_DIRS ${BerkeleyDB_INCLUDE_DIRS} + ) + + generate_versions_variants( + _db_cxx_variants + db_cxx + "${BerkeleyDB_VERSION_MAJOR}" + "${BerkeleyDB_VERSION_MINOR}" + ) + + find_component(BerkeleyDB CXX + NAMES ${_db_cxx_variants} + PATH_SUFFIXES ${_db_variants} + INCLUDE_DIRS ${BerkeleyDB_INCLUDE_DIRS} + ) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BerkeleyDB + REQUIRED_VARS BerkeleyDB_INCLUDE_DIR + VERSION_VAR BerkeleyDB_VERSION + HANDLE_VERSION_RANGE + HANDLE_COMPONENTS +) diff --git a/build-aux/cmake/FindRt.cmake b/build-aux/cmake/FindRt.cmake new file mode 100644 index 0000000000..4e5b76c82a --- /dev/null +++ b/build-aux/cmake/FindRt.cmake @@ -0,0 +1,32 @@ +# RT_FOUND - system has shm_open +# RT_LIBRARIES - libraries needed to use shm_open + +include(CheckCSourceCompiles) + +set(rt_code " + #include + #include + + int main() { + shm_open(0, 0, 0); + return 0; + }" +) + +check_c_source_compiles("${rt_code}" RT_BUILT_IN) + +if(RT_BUILT_IN) + set(RT_FOUND TRUE) + set(RT_LIBRARIES) +else() + set(CMAKE_REQUIRED_LIBRARIES "-lrt") + check_c_source_compiles("${rt_code}" RT_IN_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES) + if(RT_IN_LIBRARY) + set(RT_LIBRARY rt) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Rt DEFAULT_MSG RT_LIBRARY) + set(RT_LIBRARIES ${RT_LIBRARY}) + unset(RT_LIBRARY) + endif() +endif() diff --git a/build-aux/cmake/VersionFromGit.cmake b/build-aux/cmake/VersionFromGit.cmake new file mode 100644 index 0000000000..7d0e500b6f --- /dev/null +++ b/build-aux/cmake/VersionFromGit.cmake @@ -0,0 +1,48 @@ +find_package(Git) + +set(BUILD_GIT_TAG "" CACHE STRING "Current Git tag") +set(BUILD_GIT_COMMIT "" CACHE STRING "Current Git commit") + +if(Git_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") + execute_process(COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE BUILD_GIT_IS_DIRTY + ) + + execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short=12 HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE BUILD_GIT_COMMIT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process(COMMAND "${GIT_EXECUTABLE}" describe --abbrev=0 + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE BUILD_GIT_LATEST_TAG + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + execute_process(COMMAND "${GIT_EXECUTABLE}" rev-list "${BUILD_GIT_LATEST_TAG}" -1 --abbrev-commit --abbrev=12 + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE BUILD_GIT_LATEST_TAG_COMMIT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(BUILD_GIT_IS_DIRTY EQUAL 0) # not dirty + if(BUILD_GIT_LATEST_TAG_COMMIT EQUAL BUILD_GIT_COMMIT) + set(BUILD_GIT_TAG "${BUILD_GIT_LATEST_TAG}") + endif() + else() + set(BUILD_GIT_COMMIT "${BUILD_GIT_COMMIT}-dirty") + endif() +endif() + +if(BUILD_GIT_TAG EQUAL "") + unset(BUILD_GIT_TAG) +endif() + +if(BUILD_GIT_COMMIT EQUAL "") + unset(BUILD_GIT_COMMIT) +endif() diff --git a/build-aux/cmake/json2header.cmake b/build-aux/cmake/json2header.cmake new file mode 100644 index 0000000000..da1c68711a --- /dev/null +++ b/build-aux/cmake/json2header.cmake @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.19) + +if(CMAKE_ARGC LESS 6) + message(FATAL_ERROR "Usage: cmake -P json2header.cmake xxd in_file out_file") +endif() + +set(XXD ${CMAKE_ARGV3}) +set(IN_FILE ${CMAKE_ARGV4}) +set(OUT_FILE ${CMAKE_ARGV5}) + +get_filename_component(var_name "${IN_FILE}" NAME_WE) +execute_process(COMMAND "${XXD}" -i -n ${var_name} "${IN_FILE}" + OUTPUT_VARIABLE command_out + COMMAND_ERROR_IS_FATAL ANY +) + +file(WRITE ${OUT_FILE} "namespace json_tests {\n") +file(APPEND "${OUT_FILE}" "${command_out}") +file(APPEND ${OUT_FILE} "};\n") diff --git a/build-aux/cmake/txt2header.cmake b/build-aux/cmake/txt2header.cmake new file mode 100644 index 0000000000..9d9da453c5 --- /dev/null +++ b/build-aux/cmake/txt2header.cmake @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.19) + +if(CMAKE_ARGC LESS 5) + message(FATAL_ERROR "Usage: cmake -P txt2header.cmake in_file out_file") +endif() + +set(IN_FILE ${CMAKE_ARGV3}) +set(OUT_FILE ${CMAKE_ARGV4}) + +get_filename_component(var_name "${IN_FILE}" NAME_WE) + +file(READ ${IN_FILE} text) +file(WRITE "${OUT_FILE}" " + static const std::string ${var_name}_text = R\"(${text})\"; +") diff --git a/ci/test_run_all.sh b/ci/test_run_all.sh index 618af9ff9f..b527771ba7 100755 --- a/ci/test_run_all.sh +++ b/ci/test_run_all.sh @@ -6,9 +6,14 @@ export LC_ALL=C.UTF-8 +# latest_stage.log will contain logs for a silenced stage if it +# fails. +trap "cat latest_stage.log" EXIT + set -o errexit; source ./ci/test/00_setup_env.sh set -o errexit; source ./ci/test/03_before_install.sh -set -o errexit; source ./ci/test/04_install.sh &> 04.log || (cat 04.log && exit 1) -set -o errexit; source ./ci/test/05_before_script.sh &> 05.log || (cat 05.log && exit 1) +set -o errexit; source ./ci/test/04_install.sh &> latest_stage.log +set -o errexit; source ./ci/test/05_before_script.sh &> latest_stage.log +echo -n > latest_stage.log set -o errexit; source ./ci/test/06_script_a.sh set -o errexit; source ./ci/test/06_script_b.sh diff --git a/configure.ac b/configure.ac index 4f0d63ae38..db4d895adc 100755 --- a/configure.ac +++ b/configure.ac @@ -2,10 +2,10 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 5) define(_CLIENT_VERSION_MINOR, 4) -define(_CLIENT_VERSION_REVISION, 5) +define(_CLIENT_VERSION_REVISION, 6) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) -define(_COPYRIGHT_YEAR, 2023) +define(_COPYRIGHT_YEAR, 2024) define(_COPYRIGHT_HOLDERS,[The %s developers]) define(_COPYRIGHT_HOLDERS_SUBSTITUTION,[[Gridcoin]]) AC_INIT([Gridcoin],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_REVISION],[https://github.com/gridcoin/Gridcoin-Research/issues],[gridcoin],[https://gridcoin.us/]) @@ -185,6 +185,12 @@ AC_ARG_ENABLE([asm], [use_asm=$enableval], [use_asm=yes]) +AC_ARG_ENABLE([embedded-bdb], + [AS_HELP_STRING([--disable-embedded-bdb], + [use embedded bdb instead of the system one (enabled by default)])], + [enable_embedded_bdb=$enableval], + [enable_embedded_bdb=yes]) + if test "$use_asm" = "yes"; then AC_DEFINE(USE_ASM, 1, [Define this symbol to build in assembly routines]) fi @@ -658,7 +664,8 @@ if test "$use_hardening" != "no"; then case $host in *mingw*) - dnl stack-clash-protection doesn't currently work, and likely should just be skipped for Windows. + dnl stack-clash-protection doesn't compile with GCC 10 and earlier. + dnl In any case, it is a no-op for Windows. dnl See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 for more details. ;; *) @@ -871,13 +878,6 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include [ AC_MSG_RESULT(no)] ) -AC_MSG_CHECKING(for getentropy) -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], - [[ getentropy(nullptr, 32) ]])], - [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GETENTROPY, 1,[Define this symbol if the BSD getentropy system call is available]) ], - [ AC_MSG_RESULT(no)] -) - AC_MSG_CHECKING(for sysctl KERN_ARND) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include ]], @@ -1212,6 +1212,30 @@ else AC_MSG_RESULT([no]) fi +AC_MSG_CHECKING([if embedded bdb should be used]) +if test "$enable_embedded_bdb" = "yes"; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) + AC_MSG_CHECKING([if bdb 5.3 links]) + TEMP_CXXFLAGS="$CXXFLAGS" + TEMP_LDFLAGS="$LDFLAGS" + CXXFLAGS="$CXXFLAGS $BDB_CFLAGS" + LDFLAGS="$LDFLAGS $BDB_LIBS" + AC_LANG_PUSH([C++]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include ]],[[ + #if !(DB_VERSION_MAJOR == 5 && DB_VERSION_MINOR == 3) + #error "unsupported bdb version" + #endif + Db* db = nullptr; + ]])],[AC_MSG_RESULT(yes)],[AC_MSG_ERROR([bdb 5.3 does not properly link])]) + AC_LANG_POP([C++]) + CXXFLAGS="$TEMP_CXXFLAGS" + LDFLAGS="$TEMP_LDFLAGS" +fi +AM_CONDITIONAL([EMBEDDED_BDB], [test "$enable_embedded_bdb" = "yes"]) + if test "$build_bitcoin_utils$build_bitcoin_libs$build_gridcoinresearchd$bitcoin_enable_qt$use_bench$use_tests" = "nononononono"; then AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-bench or --enable-tests]) fi @@ -1290,6 +1314,8 @@ AC_SUBST(EVENT_PTHREADS_LIBS) AC_SUBST(ZMQ_LIBS) AC_SUBST(PROTOBUF_LIBS) AC_SUBST(QR_LIBS) +AC_SUBST(BDB_CFLAGS) +AC_SUBST(BDB_LIBS) AC_SUBST(HAVE_FDATASYNC) AC_SUBST(HAVE_FULLFSYNC) AC_SUBST(HAVE_O_CLOEXEC) @@ -1324,7 +1350,7 @@ PKGCONFIG_LIBDIR_TEMP="$PKG_CONFIG_LIBDIR" unset PKG_CONFIG_LIBDIR PKG_CONFIG_LIBDIR="$PKGCONFIG_LIBDIR_TEMP" -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-module-ecdh" ADDITIONAL_BDB_FLAGS="" @@ -1336,7 +1362,9 @@ esac AC_CONFIG_SUBDIRS([src/univalue]) AC_CONFIG_SUBDIRS([src/secp256k1]) -AX_SUBDIRS_CONFIGURE([src/bdb53], [[CFLAGS="-Wno-error=implicit-function-declaration"], [CXXFLAGS="-Wno-error=implicit-function-declaration"], [--disable-java], [--disable-jdbc], [--disable-replication], [--enable-cxx], [$ADDITIONAL_BDB_FLAGS]], [], [], []) +if test "$enable_embedded_bdb" = "yes"; then + AX_SUBDIRS_CONFIGURE([src/bdb53], [[CFLAGS="-Wno-error=implicit-function-declaration"], [CXXFLAGS="-Wno-error=implicit-function-declaration"], [--disable-java], [--disable-jdbc], [--disable-replication], [--enable-cxx], [$ADDITIONAL_BDB_FLAGS]], [], [], []) +fi AC_OUTPUT diff --git a/contrib/default.nix b/contrib/default.nix new file mode 100644 index 0000000000..6efefa74b1 --- /dev/null +++ b/contrib/default.nix @@ -0,0 +1,13 @@ +let pkgs = import {}; +in +with pkgs.stdenv; +with pkgs.stdenv.lib; + +pkgs.mkShell { + nativeBuildInputs = with pkgs.buildPackages; [ autoreconfHook cmake libtool pkg-config qt5.wrapQtAppsHook ]; + buildInputs = with pkgs; [ boost openssl libevent curl qt5.qttools libzip qrencode ]; + + shellHook = '' + echo "Run configure with: --with-qt-bindir=${pkgs.qt5.qtbase.dev}/bin:${pkgs.qt5.qttools.dev}/bin --with-boost-libdir=${pkgs.boost.out}/lib" + ''; +} diff --git a/depends/Makefile b/depends/Makefile index c816a3a509..6710430c86 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -63,10 +63,6 @@ host_prefix=$($(host_arch)_$(host_os)_prefix) build_prefix=$(host_prefix)/native build_host=$(build) -AT_$(V):= -AT_:=@ -AT:=$(AT_$(V)) - all: install include hosts/$(host_os).mk @@ -109,12 +105,12 @@ include funcs.mk final_build_id_long+=$(shell $(build_SHA256SUM) config.site.in) final_build_id+=$(shell echo -n "$(final_build_id_long)" | $(build_SHA256SUM) | cut -c-$(HASH_LENGTH)) $(host_prefix)/.stamp_$(final_build_id): $(native_packages) $(packages) - $(AT)rm -rf $(@D) - $(AT)mkdir -p $(@D) - $(AT)echo copying packages: $^ - $(AT)echo to: $(@D) - $(AT)cd $(@D); $(foreach package,$^, tar xf $($(package)_cached); ) - $(AT)touch $@ + rm -rf $(@D) + mkdir -p $(@D) + echo copying packages: $^ + echo to: $(@D) + cd $(@D); $(foreach package,$^, tar xf $($(package)_cached); ) + touch $@ # $PATH is not preserved between ./configure and make by convention. Its # modification and overriding at ./configure time is (as I understand it) @@ -141,8 +137,8 @@ $(host_prefix)/.stamp_$(final_build_id): $(native_packages) $(packages) # we expect them to be available in $PATH at all times, more specificity does # not hurt. $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_build_id) - $(AT)@mkdir -p $(@D) - $(AT)sed -e 's|@HOST@|$(host)|' \ + @mkdir -p $(@D) + sed -e 's|@HOST@|$(host)|' \ -e 's|@CC@|$(host_CC)|' \ -e 's|@CXX@|$(host_CXX)|' \ -e 's|@AR@|$(host_AR)|' \ @@ -160,7 +156,7 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@no_upnp@|$(NO_UPNP)|' \ -e 's|@debug@|$(DEBUG)|' \ $< > $@ - $(AT)touch $@ + touch $@ define check_or_remove_cached @@ -200,3 +196,4 @@ download-win: download: download-osx download-linux download-win .PHONY: install cached download-one download-osx download-linux download-win download check-packages check-sources +$(V).SILENT: diff --git a/depends/funcs.mk b/depends/funcs.mk index 748c16c7fd..6866fe3f17 100644 --- a/depends/funcs.mk +++ b/depends/funcs.mk @@ -159,53 +159,53 @@ endef define int_add_cmds $($(1)_fetched): - $(AT)mkdir -p $$(@D) $(SOURCES_PATH) - $(AT)rm -f $$@ - $(AT)touch $$@ - $(AT)cd $$(@D); $(call $(1)_fetch_cmds,$(1)) - $(AT)cd $($(1)_source_dir); $(foreach source,$($(1)_all_sources),$(build_SHA256SUM) $(source) >> $$(@);) - $(AT)touch $$@ + mkdir -p $$(@D) $(SOURCES_PATH) + rm -f $$@ + touch $$@ + cd $$(@D); $(call $(1)_fetch_cmds,$(1)) + cd $($(1)_source_dir); $(foreach source,$($(1)_all_sources),$(build_SHA256SUM) $(source) >> $$(@);) + touch $$@ $($(1)_extracted): | $($(1)_fetched) - $(AT)echo Extracting $(1)... - $(AT)mkdir -p $$(@D) - $(AT)cd $$(@D); $(call $(1)_extract_cmds,$(1)) - $(AT)touch $$@ + echo Extracting $(1)... + mkdir -p $$(@D) + cd $$(@D); $(call $(1)_extract_cmds,$(1)) + touch $$@ $($(1)_preprocessed): | $($(1)_dependencies) $($(1)_extracted) - $(AT)echo Preprocessing $(1)... - $(AT)mkdir -p $$(@D) $($(1)_patch_dir) - $(AT)$(foreach patch,$($(1)_patches),cd $(PATCHES_PATH)/$(1); cp $(patch) $($(1)_patch_dir) ;) - $(AT)cd $$(@D); $(call $(1)_preprocess_cmds, $(1)) - $(AT)touch $$@ + echo Preprocessing $(1)... + mkdir -p $$(@D) $($(1)_patch_dir) + $(foreach patch,$($(1)_patches),cd $(PATCHES_PATH)/$(1); cp $(patch) $($(1)_patch_dir) ;) + cd $$(@D); $(call $(1)_preprocess_cmds, $(1)) + touch $$@ $($(1)_configured): | $($(1)_preprocessed) - $(AT)echo Configuring $(1)... - $(AT)rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), tar xf $($(package)_cached); ) - $(AT)mkdir -p $$(@D) - $(AT)+cd $$(@D); $($(1)_config_env) $(call $(1)_config_cmds, $(1)) - $(AT)touch $$@ + echo Configuring $(1)... + rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), tar xf $($(package)_cached); ) + mkdir -p $$(@D) + +cd $$(@D); $($(1)_config_env) $(call $(1)_config_cmds, $(1)) + touch $$@ $($(1)_built): | $($(1)_configured) - $(AT)echo Building $(1)... - $(AT)mkdir -p $$(@D) - $(AT)+cd $$(@D); $($(1)_build_env) $(call $(1)_build_cmds, $(1)) - $(AT)touch $$@ + echo Building $(1)... + mkdir -p $$(@D) + +cd $$(@D); $($(1)_build_env) $(call $(1)_build_cmds, $(1)) + touch $$@ $($(1)_staged): | $($(1)_built) - $(AT)echo Staging $(1)... - $(AT)mkdir -p $($(1)_staging_dir)/$(host_prefix) - $(AT)cd $($(1)_build_dir); $($(1)_stage_env) $(call $(1)_stage_cmds, $(1)) - $(AT)rm -rf $($(1)_extract_dir) - $(AT)touch $$@ + echo Staging $(1)... + mkdir -p $($(1)_staging_dir)/$(host_prefix) + cd $($(1)_build_dir); $($(1)_stage_env) $(call $(1)_stage_cmds, $(1)) + rm -rf $($(1)_extract_dir) + touch $$@ $($(1)_postprocessed): | $($(1)_staged) - $(AT)echo Postprocessing $(1)... - $(AT)cd $($(1)_staging_prefix_dir); $(call $(1)_postprocess_cmds) - $(AT)touch $$@ + echo Postprocessing $(1)... + cd $($(1)_staging_prefix_dir); $(call $(1)_postprocess_cmds) + touch $$@ $($(1)_cached): | $($(1)_dependencies) $($(1)_postprocessed) - $(AT)echo Caching $(1)... - $(AT)cd $$($(1)_staging_dir)/$(host_prefix); find . | sort | tar --no-recursion -czf $$($(1)_staging_dir)/$$(@F) -T - - $(AT)mkdir -p $$(@D) - $(AT)rm -rf $$(@D) && mkdir -p $$(@D) - $(AT)mv $$($(1)_staging_dir)/$$(@F) $$(@) - $(AT)rm -rf $($(1)_staging_dir) + echo Caching $(1)... + cd $$($(1)_staging_dir)/$(host_prefix); find . | sort | tar --no-recursion -czf $$($(1)_staging_dir)/$$(@F) -T - + mkdir -p $$(@D) + rm -rf $$(@D) && mkdir -p $$(@D) + mv $$($(1)_staging_dir)/$$(@F) $$(@) + rm -rf $($(1)_staging_dir) $($(1)_cached_checksum): $($(1)_cached) - $(AT)cd $$(@D); $(build_SHA256SUM) $$( $$(@) + cd $$(@D); $(build_SHA256SUM) $$( $$(@) .PHONY: $(1) $(1): | $($(1)_cached_checksum) diff --git a/depends/packages/curl.mk b/depends/packages/curl.mk index 48f0ec17ad..f492df5c03 100644 --- a/depends/packages/curl.mk +++ b/depends/packages/curl.mk @@ -1,9 +1,9 @@ package=curl GCCFLAGS?= -$(package)_version=7.78.0 +$(package)_version=7.88.1 $(package)_download_path=https://curl.haxx.se/download/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=ed936c0b02c06d42cf84b39dd12bb14b62d77c7c4e875ade022280df5dcc81d7 +$(package)_sha256_hash=cdb38b72e36bc5d33d5b8810f8018ece1baa29a8f215b4495e495ded82bbf3c7 $(package)_dependencies=openssl define $(package)_set_vars diff --git a/depends/packages/expat.mk b/depends/packages/expat.mk index 16e94b514a..eca30acdd2 100644 --- a/depends/packages/expat.mk +++ b/depends/packages/expat.mk @@ -1,9 +1,9 @@ package=expat GCCFLAGS?= -$(package)_version=2.4.1 +$(package)_version=2.4.8 $(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_$(subst .,_,$($(package)_version))/ $(package)_file_name=$(package)-$($(package)_version).tar.xz -$(package)_sha256_hash=cf032d0dba9b928636548e32b327a2d66b1aab63c4f4a13dd132c2d1d2f2fb6a +$(package)_sha256_hash=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 define $(package)_set_vars $(package)_config_opts=--disable-static --without-docbook --without-xmlwf diff --git a/depends/packages/fontconfig.mk b/depends/packages/fontconfig.mk index c8b2fc33d5..444acfe36d 100644 --- a/depends/packages/fontconfig.mk +++ b/depends/packages/fontconfig.mk @@ -9,6 +9,7 @@ $(package)_patches=gperf_header_regen.patch define $(package)_set_vars $(package)_config_opts=--disable-docs --disable-static --disable-libxml2 --disable-iconv $(package)_config_opts += --disable-dependency-tracking --enable-option-checking + $(package)_cflags += -Wno-implicit-function-declaration endef define $(package)_preprocess_cmds diff --git a/depends/packages/native_clang.mk b/depends/packages/native_clang.mk index 943ecbda87..a60a3299fc 100644 --- a/depends/packages/native_clang.mk +++ b/depends/packages/native_clang.mk @@ -5,10 +5,6 @@ $(package)_download_file=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubunt $(package)_file_name=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz $(package)_sha256_hash=48b83ef827ac2c213d5b64f5ad7ed082c8bcb712b46644e0dc5045c6f462c231 -define $(package)_preprocess_cmds - rm -f $($(package)_extract_dir)/lib/libc++abi.so* -endef - define $(package)_stage_cmds mkdir -p $($(package)_staging_prefix_dir)/lib/clang/$($(package)_version)/include && \ mkdir -p $($(package)_staging_prefix_dir)/bin && \ diff --git a/depends/packages/qrencode.mk b/depends/packages/qrencode.mk index 8fe419f2be..fe3e6e377d 100644 --- a/depends/packages/qrencode.mk +++ b/depends/packages/qrencode.mk @@ -1,9 +1,9 @@ package=qrencode GCCFLAGS?= -$(package)_version=3.4.4 +$(package)_version=4.1.1 $(package)_download_path=https://fukuchi.org/works/qrencode/ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=efe5188b1ddbcbf98763b819b146be6a90481aac30cfc8d858ab78a19cde1fa5 +$(package)_sha256_hash=e455d9732f8041cf5b9c388e345a641fd15707860f928e94507b1961256a6923 define $(package)_set_vars $(package)_config_opts=--disable-shared --without-tools --without-tests --disable-sdltest --libdir="$($($(package)_type)_prefix)/lib" diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 7e12066f22..3f721988a1 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,9 +1,9 @@ package=qt -$(package)_version=5.15.3 +$(package)_version=5.15.5 $(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules $(package)_suffix=everywhere-opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=26394ec9375d52c1592bd7b689b1619c6b8dbe9b6f91fdd5c355589787f3a0b6 +$(package)_sha256_hash=0c42c799aa7c89e479a07c451bf5a301e291266ba789e81afc18f95049524edc $(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm $(package)_qt_libs=corelib network widgets gui plugins testlib concurrent $(package)_patches = fix_qt_pkgconfig.patch @@ -14,20 +14,19 @@ $(package)_patches += fix_montery_include.patch $(package)_patches += fix_android_jni_static.patch $(package)_patches += dont_hardcode_pwd.patch $(package)_patches += qtbase-moc-ignore-gcc-macro.patch -$(package)_patches += fix_limits_header.patch $(package)_patches += no_qrhi.patch $(package)_patches += drop_lrelease_dependency.patch $(package)_patches += subdirs.pro $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=5d7869f670a135ad0986e266813b9dd5bbae2b09577338f9cdf8904d4af52db0 +$(package)_qttranslations_sha256_hash=c92af4171397a0ed272330b4fa0669790fcac8d050b07c8b8cc565ebeba6735e $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=463b2fe71a085e7ab4e39333ae360ab0ec857b966d7a08f752c427e5df55f90d +$(package)_qttools_sha256_hash=6d0778b71b2742cb527561791d1d3d255366163d54a10f78c683a398f09ffc6c # Gridcoin displays SVG images in the GUI: $(package)_qtsvg_file_name=qtsvg-$($(package)_suffix) -$(package)_qtsvg_sha256_hash=3adc41dfcc67bbe3b8ff553bdac30ee75e270745536a58e54cdb741fa0505d89 +$(package)_qtsvg_sha256_hash=c4cf9e640ad43f157c6b14ee7624047f5945288991ad5de83c9eec673bacb031 $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -57,6 +56,7 @@ $(package)_config_opts += -no-linuxfb $(package)_config_opts += -no-libjpeg $(package)_config_opts += -no-libproxy $(package)_config_opts += -no-libudev +$(package)_config_opts += -no-mimetype-database $(package)_config_opts += -no-mtdev $(package)_config_opts += -no-openssl $(package)_config_opts += -no-openvg @@ -262,7 +262,6 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_limits_header.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_montery_include.patch && \ patch -p1 -i $($(package)_patch_dir)/no_qrhi.patch && \ cp $($(package)_patch_dir)/subdirs.pro subdirs.pro && \ diff --git a/depends/packages/zlib.mk b/depends/packages/zlib.mk index acb02020a8..913888ed39 100644 --- a/depends/packages/zlib.mk +++ b/depends/packages/zlib.mk @@ -1,8 +1,8 @@ package=zlib -$(package)_version=1.2.11 +$(package)_version=1.3.1 $(package)_download_path=https://www.zlib.net $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 +$(package)_sha256_hash=9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23 define $(package)_set_vars $(package)_config_opts= CC="$($(package)_cc)" diff --git a/depends/patches/qt/dont_hardcode_x86_64.patch b/depends/patches/qt/dont_hardcode_x86_64.patch index 5c1e030fa4..a66426877a 100644 --- a/depends/patches/qt/dont_hardcode_x86_64.patch +++ b/depends/patches/qt/dont_hardcode_x86_64.patch @@ -73,7 +73,7 @@ diff --git a/mkspecs/features/mac/default_post.prf b/mkspecs/features/mac/defaul index 92a9112bca6..d888731ec8d 100644 --- old/qtbase/mkspecs/features/mac/default_post.prf +++ new/qtbase/mkspecs/features/mac/default_post.prf -@@ -90,6 +90,11 @@ app_extension_api_only { +@@ -95,6 +95,11 @@ app_extension_api_only { QMAKE_LFLAGS += $$QMAKE_CFLAGS_APPLICATION_EXTENSION } @@ -85,7 +85,7 @@ index 92a9112bca6..d888731ec8d 100644 macx-xcode { qmake_pkginfo_typeinfo.name = QMAKE_PKGINFO_TYPEINFO !isEmpty(QMAKE_PKGINFO_TYPEINFO): \ -@@ -145,9 +150,6 @@ macx-xcode { +@@ -150,9 +155,6 @@ macx-xcode { simulator: VALID_SIMULATOR_ARCHS = $$QMAKE_APPLE_SIMULATOR_ARCHS VALID_ARCHS = $$VALID_DEVICE_ARCHS $$VALID_SIMULATOR_ARCHS diff --git a/depends/patches/qt/fix_android_jni_static.patch b/depends/patches/qt/fix_android_jni_static.patch index 22a4d5ab0e..936b82e152 100644 --- a/depends/patches/qt/fix_android_jni_static.patch +++ b/depends/patches/qt/fix_android_jni_static.patch @@ -1,6 +1,6 @@ --- old/qtbase/src/plugins/platforms/android/androidjnimain.cpp +++ new/qtbase/src/plugins/platforms/android/androidjnimain.cpp -@@ -934,6 +934,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) +@@ -943,6 +943,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } diff --git a/depends/patches/qt/fix_limits_header.patch b/depends/patches/qt/fix_limits_header.patch deleted file mode 100644 index 32fac6911d..0000000000 --- a/depends/patches/qt/fix_limits_header.patch +++ /dev/null @@ -1,33 +0,0 @@ -Fix compiling with GCC 11 - -Upstream: - - bug report: https://bugreports.qt.io/browse/QTBUG-89977 - - fix in Qt 6.1: 813a928c7c3cf98670b6043149880ed5c955efb9 - ---- old/qtbase/src/corelib/text/qbytearraymatcher.h -+++ new/qtbase/src/corelib/text/qbytearraymatcher.h -@@ -42,6 +42,8 @@ - - #include - -+#include -+ - QT_BEGIN_NAMESPACE - - - -Upstream fix and backports: - - Qt 6.1: 3eab20ad382569cb2c9e6ccec2322c3d08c0f716 - - Qt 6.2: 380294a5971da85010a708dc23b0edec192cbf27 - - Qt 6.3: 2b2b3155d9f6ba1e4f859741468fbc47db09292b - ---- old/qtbase/src/corelib/tools/qoffsetstringarray_p.h -+++ new/qtbase/src/corelib/tools/qoffsetstringarray_p.h -@@ -55,6 +55,7 @@ - - #include - #include -+#include - - QT_BEGIN_NAMESPACE - \ No newline at end of file diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000000..d5ef97c1e9 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,25 @@ +find_package(Doxygen REQUIRED dot) + +set(DOXYGEN_PROJECT_BRIEF "P2P Digital Currency") +set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/share/icons/hicolor/48x48/apps/gridcoinresearch.png") +set(DOXYGEN_JAVADOC_AUTOBRIEF "YES") +set(DOXYGEN_TAB_SIZE 8) +set(DOXYGEN_EXTRACT_ALL "YES") +set(DOXYGEN_EXTRACT_PRIVATE "YES") +set(DOXYGEN_EXTRACT_STATIC "YES") +set(DOXYGEN_RECURSIVE "YES") +set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/README_doxygen.md") +set(DOXYGEN_SOURCE_BROWSER "YES") + +set(DOXYGEN_EXCLUDE + "${CMAKE_SOURCE_DIR}/src/bdb53" + "${CMAKE_SOURCE_DIR}/src/crc32c" + "${CMAKE_SOURCE_DIR}/src/leveldb" + "${CMAKE_SOURCE_DIR}/src/test" + "${CMAKE_SOURCE_DIR}/src/qt/test" +) + +doxygen_add_docs(docs + "${CMAKE_SOURCE_DIR}/src" + ALL +) diff --git a/doc/README.md b/doc/README.md index bff735ff6e..eaa6ad72bd 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,7 +2,7 @@ Gridcoin ============= Setup ---------------------- +----- Gridcoin is a free open source project derived from Bitcoin, with the goal of providing a long-term energy-efficient cryptocurrency, rewarding BOINC work. Built on the foundation of Bitcoin, PPCoin and NovaCoin, innovations such as proof-of-stake @@ -15,17 +15,19 @@ To download Gridcoin, visit [gridcoin.us](https://gridcoin.us). * See the documentation at the [Gridcoin Wiki](https://wiki.gridcoin.us/Main_Page) for help and more information. * A lot of features core features are based on Bitcoin and have been documented on the [Bitcoin Wiki](https://en.bitcoin.it/wiki/Main_Page) -* For general questions, please visit our Discord server at https://discord.gg/jf9XX4a -* Ask for help or discuss on [#gridcoin](https://webchat.freenode.net?channels=gridcoin) on Freenode -* You can also join us on [Slack](https://join.slack.com/t/teamgridcoin/shared_invite/enQtMjk2NTI4MzAwMzg0LTE4N2I3ZWZjYWJlZGM1Zjg3MTUyMDhiN2M5NmRmZTA2NDA0ZmY1ZTFmOGM3ZGU2YTBkOTdhNTk2ZjkzMGZkODY/)``` +* For general questions, please visit our Discord server at https://discord.gg/UMWUnMjN4x +* Ask for help or discuss on [#gridcoin](https://web.libera.chat/?channels=#gridcoin) on Libera Chat +* You can also join us on [Slack](https://join.slack.com/t/teamgridcoin/shared_invite/enQtMjk2NTI4MzAwMzg0LTE4N2I3ZWZjYWJlZGM1Zjg3MTUyMDhiN2M5NmRmZTA2NDA0ZmY1ZTFmOGM3ZGU2YTBkOTdhNTk2ZjkzMGZkODY/) Building ---------------------- +-------- The following are developer notes on how to build Gridcoin on your native platform. They are not complete guides, but include notes on the necessary libraries, compile flags, etc. +- [CMake Build Options](cmake-options.md) - [OS X Build Notes](build-macos.md) - [Unix Build Notes](build-unix.md) - [Windows Build Notes](build-windows.md) +- [FreeBSD Build Notes](build-freebsd.md) - [OpenBSD Build Notes](build-openbsd.md) Running @@ -35,15 +37,14 @@ To create a secure environment for running Gridcoin see: - [Running Gridcoin](running.md) Development ---------------------- +----------- The Gridcoin repo's [root README](/README.md) contains relevant information on the development process and automated testing. - [Developer Notes](coding.txt) - [Release Process](release-process.md) -- [Travis CI](travis-ci.md) License ---------------------- +------- Distributed under the [MIT software license](/COPYING). This product includes software developed by the OpenSSL Project for use in the [OpenSSL Toolkit](https://www.openssl.org/). This product includes cryptographic software written by Eric Young ([eay@cryptsoft.com](mailto:eay@cryptsoft.com)), and UPnP software written by Thomas Bernard. diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index d1fe8baae2..0d436cefb8 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -9,25 +9,41 @@ Preparing the Build -------------------- Install the required dependencies the usual way you [install software on FreeBSD](https://www.freebsd.org/doc/en/books/handbook/ports.html) - either with `pkg` or via the Ports collection. The example commands below use `pkg` which is usually run as `root` or via `sudo`. If you want to use `sudo`, and you haven't set it up: [use this guide](http://www.freebsdwiki.net/index.php/Sudo%2C_configuring) to setup `sudo` access on FreeBSD. + #### General Dependencies + ```bash -pkg install autoconf automake boost-libs git gmake libevent libtool pkgconf openssl libzip +pkg install cmake +# or +pkg install autoconf automake gmake libtool +pkg install boost-libs curl db5 leveldb libzip openssl pkgconf secp256k1 ``` + --- #### GUI Dependencies + ```bash pkg install qt5 libqrencode ``` --- #### Test Suite Dependencies + There is an included test suite that is useful for testing code changes when developing. -To run the test suite (recommended), you will need to have Python 3 installed: +To run the test suite (recommended), you will need to have the following packages installed: -```bash -pkg install python3 -``` +* With CMake: + + ```bash + pkg install vim + ``` + +* With Autotools: + + ```bash + pkg install python3 + ``` Clone the repository and cd into it: @@ -42,19 +58,52 @@ To Build ### 1. Configuration There are many ways to configure Gridcoin, here are a few common examples: + ##### Wallet Support, No GUI: -This explicitly enables wallet support and disables the GUI. -```bash -./autogen.sh -./configure --with-gui=no \ - MAKE=gmake -``` +This configuration does not enable the GUI. +* With CMake: + + ```bash + mkdir build && cd build + cmake .. + ``` + +* With Autotools: + + ```bash + ./autogen.sh + ./configure --with-gui=no \ + MAKE=gmake + ``` ### 2. Compile -**Important**: Use `gmake` (the non-GNU `make` will exit with an error). -```bash -gmake # use "-j N" for N parallel jobs -gmake check # Run tests if Python 3 is available -``` +* With CMake: + + ```bash + cmake --build . # use "-j N" for N parallel jobs + ctest . # Run tests + ``` + +* With Autotools: + + ```bash + gmake # use "-j N" for N parallel jobs + ``` + +### 3. Test + +* With CMake: + + ```bash + cmake .. -DENABLE_TESTS=ON + cmake --build . + ctest . + ``` + +* With Autotools: + + ```bash + gmake check + ``` diff --git a/doc/build-macos.md b/doc/build-macos.md index 51becf43a7..2b773e4348 100644 --- a/doc/build-macos.md +++ b/doc/build-macos.md @@ -14,6 +14,7 @@ macOS comes with a built-in Terminal located in: /Applications/Utilities/Terminal.app ``` ### 1. Xcode Command Line Tools + The Xcode Command Line Tools are a collection of build tools for macOS. These tools must be installed in order to build Gridcoin from source. @@ -27,6 +28,7 @@ Upon running the command, you should see a popup appear. Click on `Install` to continue the installation process. ### 2. Homebrew Package Manager + Homebrew is a package manager for macOS that allows one to install packages from the command line easily. While several package managers are available for macOS, this guide will focus on Homebrew as it is the most popular. Since the examples in this guide which walk through the installation of a package will use Homebrew, it is recommended that you install it to follow along. @@ -35,13 +37,20 @@ Otherwise, you can adapt the commands to your package manager of choice. To install the Homebrew package manager, see: https://brew.sh Note: If you run into issues while installing Homebrew or pulling packages, refer to [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting). + ### 3. Install Required Dependencies + The first step is to download the required dependencies. These dependencies represent the packages required to get a barebones installation up and running. To install, run the following from your terminal: ```shell -brew install automake libtool boost openssl pkg-config libzip berkeley-db@4 +brew install cmake +# or +brew install automake libtool + +brew install berkeley-db@5 boost curl leveldb libzip openssl pkgconf secp256k1 +export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:/usr/local/opt/openssl@3/lib/pkgconfig" ``` ### 4. Clone Gridcoin repository @@ -107,11 +116,19 @@ brew install miniupnpc #### Test Suite Dependencies There is an included test suite that is useful for testing code changes when developing. -To run the test suite (recommended), you will need to have Python 3 installed: +To run the test suite (recommended), you will need to have the following packages installed: -``` bash -brew install python -``` +* With CMake: + + ```bash + brew install vim + ``` + +* With Autotools: + + ```bash + brew install python + ``` --- @@ -132,32 +149,61 @@ pip3 install ds_store mac_alias ## Build Gridcoin -1. Build Gridcoin: - - Prepare the assembly code (requires Perl): - ```shell - cd src/ - ../contrib/nomacro.pl - cd .. - ``` - - Configure and build the headless Gridcoin binaries as well as the GUI (if Qt is found). - ```shell - ./autogen.sh - ./configure - make - ``` - You can disable the GUI build by passing `--without-gui` to configure. - -2. It is recommended to build and run the unit tests: - ```shell - make check - ``` - -3. You can also create a `.dmg` that contains the `.app` bundle (optional): - ```shell - make deploy - ``` +1. Prepare the assembly code (requires Perl): + + ```shell + pushd src + ../contrib/nomacro.pl + popd + ``` + +2. Configure and build the Gridcoin binaries: + + You can enable the GUI build by passing `-DENABLE_GUI=ON` to CMake or + `--with-gui=qt5` to the configure script. + + * With CMake: + + ```shell + mkdir build && cd build + + cmake .. + # or, to configure with GUI + cmake .. -DENABLE_GUI=ON -DQt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 + + cmake --build . + ``` + * With Autotools: + + ```shell + ./autogen.sh + ./configure + make + ``` + +3. It is recommended to build and run the unit tests: + + * With CMake: + + ```shell + cmake .. -DENABLE_TESTS=ON + cmake --build . + ctest . + ``` + + * With Autotools: + + ```shell + make check + ``` + +4. You can also create a `.dmg` that contains the `.app` bundle (optional): + + * With Autotools: + + ```shell + make deploy + ``` ## Running diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md index b841e05f36..b40f07b37a 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -1,24 +1,25 @@ OpenBSD build guide -====================== +=================== (updated for OpenBSD 7.0) This guide describes how to build gridcoinresearchd, command-line utilities, and GUI on OpenBSD. Preparation -------------- +----------- Run the following as root to install the base dependencies for building: ```bash -pkg_add git gmake libtool libevent boost libzip -pkg_add qt5 libqrencode # (optional for enabling the GUI) -pkg_add autoconf # (select highest version, e.g. 2.71) -pkg_add automake # (select highest version, e.g. 1.16) -pkg_add python # (select highest version, e.g. 3.10) +pkg_add cmake vim +# or +pkg_add autoconf automake gmake libtool python + +pkg_add boost curl libzip leveldb pkgconf +pkg_add qt5 libqrencode # optional for the GUI ``` Resource limits -------------------- +--------------- If the build runs into out-of-memory errors, the instructions in this section might help. @@ -41,34 +42,52 @@ make the change system-wide, change `datasize-cur` and `datasize-max` in **Important**: use `gmake`, not `make`. The non-GNU `make` will exit with a horrible error. -Preparation: -```bash +To configure with gridcoinresearchd: -# Replace this with the autoconf version that you installed. Include only -# the major and minor parts of the version: use "2.71" for "autoconf-2.71p1". -export AUTOCONF_VERSION=2.71 +* With CMake: -# Replace this with the automake version that you installed. Include only -# the major and minor parts of the version: use "1.16" for "automake-1.16.3". -export AUTOMAKE_VERSION=1.16 + ```bash + mkdir build && cd build + cmake .. + ``` -./autogen.sh -``` +* With Autotools: -To configure with gridcoinresearchd: -```bash -./configure --with-gui=no \ - MAKE=gmake -``` + ```bash + ./autogen.sh + ./configure --with-gui=no \ + MAKE=gmake + ``` To configure with GUI: -```bash -./configure --with-gui=yes \ - MAKE=gmake -``` + +* With CMake: + + ```bash + mkdir build && cd build + cmake -DENABLE_GUI=ON + ``` + +* With Autotools: + + ```bash + ./autogen.sh + ./configure --with-gui=yes \ + MAKE=gmake + ``` Build and run the tests: -```bash -gmake # use "-j N" here for N parallel jobs -gmake check -``` + +* With CMake: + + ```bash + cmake --build . # use "-j N" here for N parallel jobs + ctest . + ``` + +* With Autotools: + + ```bash + gmake # use "-j N" here for N parallel jobs + gmake check + ``` diff --git a/doc/build-unix.md b/doc/build-unix.md index da9c6f7faa..67ab662292 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -2,24 +2,15 @@ UNIX BUILD NOTES ==================== Some notes on how to build Gridcoin in Unix. -(For BSD specific instructions, see build-*bsd.md in this directory.) - -Note ---------------------- -Always use absolute paths to configure and compile Gridcoin and the dependencies, -for example, when specifying the path of the dependency: - - ../dist/configure --enable-cxx --disable-shared --with-pic --prefix=$BDB_PREFIX - -Here BDB_PREFIX must be an absolute path - it is defined using $(pwd) which ensures -the usage of the absolute path. +(For BSD specific instructions, see build-\*bsd.md in this directory.) Preparing the Build --------------------- +------------------- Install git: -Ubuntu & Debian: `sudo apt-get install git` -openSUSE: `sudo zypper install git` +* Ubuntu & Debian: `sudo apt install git` +* openSUSE: `sudo zypper install git` + Clone the repository and cd into it: ```bash @@ -27,111 +18,94 @@ git clone https://github.com/gridcoin-community/Gridcoin-Research cd Gridcoin-Research git checkout master ``` + Go to platform specific instructions for the required dependencies below. To Build ---------------------- +-------- -```bash -./autogen.sh -./configure -make # use "-j N" for N parallel jobs -make install # optional -``` +* With CMake: -Or, to keep the source directory clean: -```bash -./autogen.sh -mkdir build -cd build -../configure -make # use "-j N" for N parallel jobs -``` - -This will build gridcoinresearch (Qt client) as well if the dependencies are met. - -Dependencies ---------------------- + ```bash + mkdir build && cd build + # minimal configuration + cmake .. + # full GUI build with ccache + cmake .. -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DENABLE_GUI=ON -DENABLE_QRENCODE=ON -DENABLE_UPNP=ON -DUSE_DBUS=ON + ``` -These dependencies are required: + ```bash + cmake --build . # use "-j N" for N parallel jobs + cmake --install . # optional + ``` - Library | Purpose | Description - ------------|------------------|---------------------- - libssl | Crypto | Random Number Generation, Elliptic Curve Cryptography - libboost | Utility | Library for threading, data structures, etc - libevent | Networking | OS independent asynchronous networking - miniupnpc | UPnP Support | Firewall-jumping support - qt | GUI | GUI toolkit (only needed when GUI enabled) - libqrencode | QR codes in GUI | Optional for generating QR codes (only needed when GUI enabled) - libzip | Zip Compression | For Zip Compression and Decompression for snapshot and scraper related functions +* With Autotools: -For the versions used in the release, see [release-process.md](release-process.md) under *Fetch and build inputs*. + ```bash + ./autogen.sh + ./configure + make # use "-j N" for N parallel jobs + make install # optional + ``` Memory Requirements --------------------- +------------------- C++ compilers are memory-hungry. It is recommended to have at least 1.5 GB of memory available when compiling Gridcoin. On systems with less, gcc can be tuned to conserve memory with additional CXXFLAGS: + export CXXFLAGS="--param ggc-min-expand=1 --param ggc-min-heapsize=32768" - ./configure CXXFLAGS="--param ggc-min-expand=1 --param ggc-min-heapsize=32768" - -Alternatively, clang (often less resource hungry) can be used instead of gcc, which is used by default: - - ./configure CXX=clang++ CC=clang +Clang (often less resource hungry) can be used instead of gcc, which is used by default: + export CXX=clang++ + export CC=clang Dependency Build Instructions: Ubuntu & Debian ---------------------------------------------- -Build requirements: - sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils libzip-dev libfreetype-dev - -**For Ubuntu 18.04 gcc8 is also required** - - sudo apt-get install gcc-8 g++-8 - -Now, you can either build from self-compiled [depends](/depends/README.md) or install the required dependencies: +Build requirements: - sudo apt-get install libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libboost-iostreams-dev libcurl4-gnutls-dev + sudo apt install build-essential cmake libboost-all-dev libcurl4-gnutls-dev libdb5.3++-dev libleveldb-dev libsecp256k1-dev libssl-dev libzip-dev pkgconf -Optional (see --with-miniupnpc and --enable-upnp-default): +Optional (see `ENABLE_UPNP / DEFAULT_UPNP` options for CMake and +`--with-miniupnpc / --enable-upnp-default` for Autotools): - sudo apt-get install libminiupnpc-dev + sudo apt install libminiupnpc-dev Dependencies for the GUI: Ubuntu & Debian ----------------------------------------- -If you want to build Gridcoin with UI, make sure that the required packages for Qt development -are installed. Qt 5 is necessary to build the GUI. -To build without GUI pass `--without-gui` to configure. +If you want to build Gridcoin with UI, make sure that the required packages for +Qt development are installed. Qt 5 is necessary to build the GUI. -To build with Qt 5 you need the following: +To build with GUI pass `-DENABLE_GUI=ON` to CMake or `--with-gui=qt5` to the +configure script. - sudo apt-get install libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libprotobuf-dev protobuf-compiler +To build with Qt 5 you need the following: -libqrencode (enabled by default, switch off by passing `--without-qrencode` to configure) can be installed with: + sudo apt install qtbase5-dev qttools5-dev - sudo apt-get install libqrencode-dev +libqrencode (switch on by passing `-DENABLE_QRENCODE=ON` to CMake or +`--with-qrencode` to the configure script) can be installed with: -Once these are installed, they will be found by configure and a gridcoinresearch executable will be -built by default. + sudo apt install libqrencode-dev Dependency Build Instructions: OpenSUSE ----------------------------------------------- +--------------------------------------- Build requirements: sudo zypper install -t pattern devel_basis - sudo zypper install libtool automake autoconf pkg-config libopenssl-devel libevent-devel + sudo zypper install cmake libtool automake autoconf pkg-config libopenssl-devel libevent-devel Tumbleweed: - sudo zypper install libboost_system1_*_0-devel libboost_filesystem1_*_0-devel libboost_test1_*_0-devel libboost_thread1_*_0-devel + sudo zypper install libboost_system1_*_0-devel libboost_filesystem1_*_0-devel libboost_test1_*_0-devel libboost_thread1_*_0-devel Leap: - sudo zypper install libboost_system1_61_0-devel libboost_filesystem1_61_0-devel libboost_test1_61_0-devel libboost_thread1_61_0-devel + sudo zypper install libboost_system1_61_0-devel libboost_filesystem1_61_0-devel libboost_test1_61_0-devel libboost_thread1_61_0-devel If that doesn't work, you can install all boost development packages with: @@ -139,58 +113,61 @@ Leap: sudo zypper install boost_1_61-devel -Optional (see --with-miniupnpc and --enable-upnp-default): +Optional (see `ENABLE_UPNP / DEFAULT_UPNP` options for CMake and +`--with-miniupnpc / --enable-upnp-default` for Autotools): sudo zypper install libminiupnpc-devel Dependencies for the GUI: openSUSE ------------------------------------------ +---------------------------------- + +If you want to build Gridcoin with UI, make sure that the required packages for +Qt development are installed. Qt 5 is necessary to build the GUI. -If you want to build gridcoinresearch, make sure that the required packages for Qt development -are installed. Qt 5 is necessary to build the GUI. -To build without GUI pass `--without-gui` to configure. +To build with GUI pass `-DENABLE_GUI=ON` to CMake or `--with-gui=qt5` to the +configure script. To build with Qt 5 you need the following: sudo zypper install libQt5Gui5 libQt5Core5 libQt5DBus5 libQt5Network-devel libqt5-qttools-devel libqt5-qttools -libqrencode (enabled by default, switch off by passing `--without-qrencode` to configure) can be installed with: +libqrencode (switch on by passing `-DENABLE_QRENCODE=ON` to CMake or +`--with-qrencode` to the configure script) can be installed with: sudo zypper install qrencode-devel -Once these are installed, they will be found by configure and a gridcoinresearch executable will be -built by default. - - Dependency Build Instructions: Alpine Linux ----------------------------------------------- +------------------------------------------- Build requirements: - apk add autoconf automake boost-dev build-base curl-dev libtool libzip-dev miniupnpc-dev openssl-dev pkgconfig + apk add autoconf automake boost-dev build-base cmake curl-dev db-dev leveldb-dev libtool libsecp256k1-dev libzip-dev miniupnpc-dev openssl-dev pkgconf Dependencies for the GUI: Alpine Linux ------------------------------------------ +-------------------------------------- To build the Qt GUI on Alpine Linux, we need these dependencies: - apk add libqrencode-dev protobuf-dev qt5-qtbase-dev qt5-qtsvg-dev qt5-qttools-dev + apk add libqrencode-dev qt5-qtbase-dev qt5-qttools-dev Setup and Build Example: Arch Linux ----------------------------------- This example lists the steps necessary to setup and build a command line only of the latest changes on Arch Linux: - pacman -S git base-devel boost libevent python + pacman -S git base-devel boost cmake db5.3 leveldb curl libsecp256k1 + git clone https://github.com/gridcoin/Gridcoin-Research.git git checkout master cd Gridcoin-Research/ - ./autogen.sh - ./configure --without-gui --without-miniupnpc - make check + + mkdir build && cd build + cmake .. + cmake --build . ARM Cross-compilation -------------------- +--------------------- + These steps can be performed on, for example, an Ubuntu VM. The depends system will also work on other Linux distributions, however the commands for installing the toolchain will be different. diff --git a/doc/build-windows.md b/doc/build-windows.md index fa1d14f59d..5f58bf1603 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -1,6 +1,8 @@ WINDOWS BUILD NOTES ==================== +> This document is outdated. + Below are some notes on how to build Gridcoin for Windows. The options known to work for building Gridcoin on Windows are: @@ -13,7 +15,7 @@ Other options which may work, but which have not been extensively tested are (pl * On Windows, using a POSIX compatibility layer application such as [cygwin](https://www.cygwin.com/) or [msys2](https://www.msys2.org/). Installing Windows Subsystem for Linux ---------------------------------------- +-------------------------------------- Follow the upstream installation instructions, available [here](https://docs.microsoft.com/windows/wsl/install-win10). @@ -115,7 +117,7 @@ Then build using: For further documentation on the depends system see [README.md](../depends/README.md) in the depends directory. Installation -------------- +------------ After building using the Windows subsystem it can be useful to copy the compiled executables to a directory on the Windows drive in the same directory structure diff --git a/doc/cmake-options.md b/doc/cmake-options.md new file mode 100644 index 0000000000..95cd8c4163 --- /dev/null +++ b/doc/cmake-options.md @@ -0,0 +1,32 @@ +## CMake Build Options + +You can use GUI (`cmake-gui`) or TUI (`ccmake`) to browse and toggle all +available options: + +```bash +mkdir build && cd build +cmake .. +ccmake . +``` + +### Common configurations + +* Build with GUI, QR code support and DBus support: + + `cmake .. -DENABLE_GUI=ON -DENABLE_QRENCODE=ON -DUSE_DBUS=ON` + +* Build with UPnP: + + `cmake .. -DENABLE_UPNP=ON -DDEFAULT_UPNP=ON` + +* Enable PIE and disable assembler routines: + + `cmake .. -DENABLE_PIE=ON -DUSE_ASM=OFF` + +* Build tests and docs, run `lupdate`: + + `cmake .. -DENABLE_DOCS=ON -DENABLE_TESTS=ON -DLUPDATE=ON` + +* Build with system libraries: + + `cmake .. -DSYSTEM_BDB=ON -DSYSTEM_LEVELDB=ON -DSYSTEM_SECP256K1=ON -DSYSTEM_UNIVALUE=ON -DSYSTEM_XXD=ON` diff --git a/doc/readme-qt.rst b/doc/readme-qt.rst deleted file mode 100644 index d59ccd6a88..0000000000 --- a/doc/readme-qt.rst +++ /dev/null @@ -1,141 +0,0 @@ -Gridcoin-qt: Qt5 GUI for Gridcoin -================================= - -Build instructions -================== - -Debian ------- - -First, make sure that the required packages for Qt5 development of your -distribution are installed, for Debian and Ubuntu these are: - -:: - - apt-get install qt5-default qt5-qmake qtbase5-dev-tools qttools5-dev-tools \ - build-essential libboost-dev libboost-system-dev \ - libboost-filesystem-dev libboost-thread-dev \ - libssl-dev libminiupnpc-dev libzip-dev - -then execute the following: - -:: - - qmake - make - -Alternatively, install Qt Creator and open the `gridcoin-qt.pro` file. - -An executable named `gridcoinresearch` will be built. - - -Windows -------- - -Windows build instructions: - -- Download the `QT Windows SDK`_ and install it. You don't need the Symbian stuff, just the desktop Qt. - -- Compile openssl and boost. - -- Open the .pro file in QT creator and build as normal (ctrl-B) - -.. _`QT Windows SDK`: https://qt-project.org/downloads - - -MacOS ------ - -- Download and install the `Qt Mac OS X SDK`_. It is recommended to also install Apple's Xcode with UNIX tools. - -- Execute the following commands in a terminal to get the dependencies: - -:: - - sudo port selfupdate - sudo port install boost miniupnpc - -- Open the .pro file in Qt Creator and build as normal (cmd-B) - -.. _`Qt Mac OS X SDK`: https://qt-project.org/downloads - -Alternatively -------------- - -- Install Qt Creator and open the makefile.am file for Auto-Tools to build for your OS. - -- An executable named gridcoinresearch will be built in the /src/Qt directory. - - -Build configuration options -=========================== - -UPNnP port forwarding ---------------------- - -To use UPnP for port forwarding behind a NAT router (recommended, as more connections overall allow for a faster and more stable Gridcoin experience), pass the following argument to qmake: - -:: - - qmake "USE_UPNP=1" - -(in **Qt Creator**, you can find the setting for additional qmake arguments under "Projects" -> "Build Settings" -> "Build Steps", then click "Details" next to **qmake**) - -This requires miniupnpc for UPnP port mapping. It can be downloaded from -http://miniupnp.tuxfamily.org/files/. UPnP support is not compiled in by default. - -Set USE_UPNP to a different value to control this: - -+------------+--------------------------------------------------------------------------+ -| USE_UPNP=- | no UPnP support, miniupnpc not required; | -+------------+--------------------------------------------------------------------------+ -| USE_UPNP=0 | (the default) built with UPnP, support turned off by default at runtime; | -+------------+--------------------------------------------------------------------------+ -| USE_UPNP=1 | build with UPnP support turned on by default at runtime. | -+------------+--------------------------------------------------------------------------+ - -Notification support for recent (k)ubuntu versions --------------------------------------------------- - -To see desktop notifications on (k)ubuntu versions starting from 10.04, enable usage of the -FreeDesktop notification interface through DBUS using the following qmake option: - -:: - - qmake "USE_DBUS=1" - -Generation of QR codes ----------------------- - -libqrencode may be used to generate QRCode images for payment requests. -It can be downloaded from https://fukuchi.org/works/qrencode/index.html.en, or installed via your package manager. Pass the USE_QRCODE -flag to qmake to control this: - -+--------------+--------------------------------------------------------------------------+ -| USE_QRCODE=0 | (the default) No QRCode support - libarcode not required | -+--------------+--------------------------------------------------------------------------+ -| USE_QRCODE=1 | QRCode support enabled | -+--------------+--------------------------------------------------------------------------+ - -Ubuntu 11.10 warning -==================== - -Ubuntu 11.10 has a package called 'qt-at-spi' installed by default. At the time of writing, having that package -installed causes gridcoin-qt to crash intermittently. The issue has been reported as `launchpad bug 857790`_, but -isn't yet fixed. - -Until the bug is fixed, you can remove the qt-at-spi package to work around the problem, though this will presumably -disable screen reader functionality for Qt apps: - -:: - - sudo apt-get remove qt-at-spi - -.. _`launchpad bug 857790`: https://bugs.launchpad.net/ubuntu/+source/qt-at-spi/+bug/857790 - -Disabling the built-in upgrader -=============================== - -If the Gridcoin binary is managed via a distributions package management the built-in upgrader will do more harm than good. Disable it through the following qmake option:: - - qmake NO_UPGRADE=1 diff --git a/doc/release-process.md b/doc/release-process.md old mode 100755 new mode 100644 index d91775513e..4fec277bf3 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -5,7 +5,7 @@ - git pull - git merge testnet - Update changelog and commit - - Change version in configure.ac to [version] and set CLIENT_VERSION_IS_RELEASE to 'true' and commit + - Change version in configure.ac and CMakeLists.txt to [version] and set CLIENT_VERSION_IS_RELEASE to 'true' and commit - git push ### Merge to master diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..479d210bee --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,291 @@ +configure_file( + config/gridcoin-config.h.cmake.in + config/gridcoin-config.h +) +configure_file( + obj/build.h.in + obj/build.h +) + +add_compile_definitions( + BOOST_SPIRIT_THREADSAFE + __STDC_FORMAT_MACROS +) + +if(WIN32) + add_compile_definitions(_MT _WINDOWS _WIN32_WINNT=0x0601) + add_compile_definitions(WIN32 BOOST_THREAD_USE_LIB UNICODE) +elseif(UNIX AND NOT APPLE) + add_compile_definitions(_FILE_OFFSET_BITS=64) +endif() + + +# Dependencies +# ============ + +if(SYSTEM_BDB) + set(LIBBDB_CXX BerkeleyDB::CXX) +else() + add_subdirectory(bdb53) + set(LIBBDB_CXX libdb_cxx) +endif() + +if(SYSTEM_LEVELDB) + set(LIBLEVELDB leveldb::leveldb) +else() + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + + set(LEVELDB_BUILD_TESTS ${ENABLE_TESTS}) + set(LEVELDB_BUILD_BENCHMARKS OFF) + set(LEVELDB_INSTALL OFF) + + set(HAVE_CRC32C OFF) + set(HAVE_SNAPPY OFF) + set(HAVE_TCMALLOC OFF) + set(HAVE_CLANG_THREAD_SAFETY OFF) + + set(BUILD_SHARED_LIBS_ORIG ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS OFF) + + add_subdirectory(leveldb) + set(LIBLEVELDB leveldb) + + set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_ORIG}) +endif() + +if(SYSTEM_SECP256K1) + set(LIBSECP256K1 PkgConfig::SECP256K1) +else() + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + + set(SECP256K1_BUILD_SHARED OFF) + set(SECP256K1_BUILD_STATIC ON) + set(SECP256K1_ENABLE_MODULE_ECDH OFF) + set(SECP256K1_ENABLE_MODULE_RECOVERY ON) + set(SECP256K1_ENABLE_MODULE_EXTRAKEYS OFF) + set(SECP256K1_ENABLE_MODULE_SCHNORRSIG OFF) + set(SECP256K1_VALGRIND OFF) + + set(SECP256K1_BUILD_BENCHMARK OFF) + set(SECP256K1_BUILD_TESTS OFF) + set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF) + set(SECP256K1_BUILD_CTIME_TESTS OFF) + + if(USE_ASM) + set(SECP256K1_ASM "AUTO") + else() + set(SECP256K1_ASM "OFF") + endif() + + add_subdirectory(secp256k1 EXCLUDE_FROM_ALL) + target_include_directories(secp256k1 PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/secp256k1/include" + ) + set(LIBSECP256K1 secp256k1) +endif() + +if(SYSTEM_UNIVALUE) + set(LIBUNIVALUE PkgConfig::UNIVALUE) +else() + add_subdirectory(univalue) + set(LIBUNIVALUE univalue) +endif() + + +# libgridcoin_crypto +# ================== + +add_subdirectory(crypto) + + +# libgridcoin_util +# ================ + +add_library(gridcoin_util STATIC + addrdb.cpp + addrman.cpp + alert.cpp + arith_uint256.cpp + banman.cpp + base58.cpp + chainparams.cpp + chainparamsbase.cpp + checkpoints.cpp + clientversion.cpp + consensus/merkle.cpp + consensus/tx_verify.cpp + crypter.cpp + dbwrapper.cpp + fs.cpp + gridcoin/backup.cpp + gridcoin/beacon.cpp + gridcoin/boinc.cpp + gridcoin/claim.cpp + gridcoin/contract/contract.cpp + gridcoin/contract/message.cpp + gridcoin/contract/registry.cpp + gridcoin/cpid.cpp + gridcoin/gridcoin.cpp + gridcoin/mrc.cpp + gridcoin/project.cpp + gridcoin/protocol.cpp + gridcoin/quorum.cpp + gridcoin/researcher.cpp + gridcoin/scraper/http.cpp + gridcoin/scraper/scraper.cpp + gridcoin/scraper/scraper_net.cpp + gridcoin/scraper/scraper_registry.cpp + gridcoin/sidestake.cpp + gridcoin/staking/difficulty.cpp + gridcoin/staking/exceptions.cpp + gridcoin/staking/kernel.cpp + gridcoin/staking/reward.cpp + gridcoin/staking/status.cpp + gridcoin/superblock.cpp + gridcoin/support/block_finder.cpp + gridcoin/tally.cpp + gridcoin/tx_message.cpp + gridcoin/upgrade.cpp + gridcoin/voting/builders.cpp + gridcoin/voting/claims.cpp + gridcoin/voting/poll.cpp + gridcoin/voting/registry.cpp + gridcoin/voting/result.cpp + gridcoin/voting/vote.cpp + hash.cpp + init.cpp + key.cpp + key_io.cpp + keystore.cpp + logging.cpp + main.cpp + miner.cpp + net.cpp + netaddress.cpp + netbase.cpp + node/blockstorage.cpp + node/ui_interface.cpp + noui.cpp + pbkdf2.cpp + policy/policy.cpp + primitives/transaction.cpp + protocol.cpp + pubkey.cpp + random.cpp + randomenv.cpp + rpc/blockchain.cpp + rpc/client.cpp + rpc/dataacq.cpp + rpc/mining.cpp + rpc/misc.cpp + rpc/net.cpp + rpc/protocol.cpp + rpc/rawtransaction.cpp + rpc/server.cpp + rpc/voting.cpp + scheduler.cpp + script.cpp + scrypt-x86.S + scrypt-x86_64.S + scrypt.cpp + support/cleanse.cpp + support/lockedpool.cpp + sync.cpp + uint256.cpp + util.cpp + util/bip32.cpp + util/settings.cpp + util/strencodings.cpp + util/string.cpp + util/syserror.cpp + util/system.cpp + util/threadinterrupt.cpp + util/threadnames.cpp + util/time.cpp + util/tokenpipe.cpp + validation.cpp + wallet/db.cpp + wallet/diagnose.cpp + wallet/rpcdump.cpp + wallet/rpcwallet.cpp + wallet/wallet.cpp + wallet/walletdb.cpp + wallet/walletutil.cpp +) + +target_include_directories(gridcoin_util PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src +) + +target_link_libraries(gridcoin_util PUBLIC + -lm ${ATOMICS_LIBRARIES} ${RT_LIBRARIES} + ${LIBBDB_CXX} ${LIBLEVELDB} ${LIBSECP256K1} ${LIBUNIVALUE} + Boost::filesystem Boost::headers Boost::iostreams Boost::thread + CURL::libcurl + OpenSSL::Crypto OpenSSL::SSL + Threads::Threads + libzip::zip +) +target_link_libraries(gridcoin_util PUBLIC ${LIBGRIDCOIN_CRYPTO}) + +target_compile_definitions(gridcoin_util PUBLIC + HAVE_CONFIG_H + HAVE_BUILD_INFO +) + +if(ENABLE_UPNP) + if(DEFAULT_UPNP) + target_compile_definitions(gridcoin_util PRIVATE USE_UPNP=1) + else() + target_compile_definitions(gridcoin_util PRIVATE USE_UPNP=0) + endif() + + if(WIN32) + target_compile_definitions(gridcoin_util PRIVATE MINIUPNP_STATICLIB) + endif() + + target_link_libraries(gridcoin_util PUBLIC PkgConfig::MINIUPNPC) +endif() + + +# Daemon +# ====== + +if(ENABLE_DAEMON) + add_executable(gridcoinresearchd gridcoinresearchd.cpp) + target_link_libraries(gridcoinresearchd PUBLIC gridcoin_util) + + if(WIN32) + target_sources(gridcoinresearchd PRIVATE gridcoinresearchd-res.rc) + endif() + + if(UNIX AND NOT APPLE) + include(GNUInstallDirs) + install(TARGETS gridcoinresearchd + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + install(FILES "${CMAKE_SOURCE_DIR}/doc/gridcoinresearchd.1" + DESTINATION "${CMAKE_INSTALL_MANDIR}" + ) + install(FILES "${CMAKE_SOURCE_DIR}/contrib/init/gridcoinresearchd.service" + DESTINATION /lib/systemd/system + ) + endif() +endif() + + +# Qt GUI +# ====== + +if(ENABLE_GUI) + add_subdirectory(qt) +endif() + + +# Tests +# ===== + +if(ENABLE_TESTS) + add_subdirectory(test) +endif() diff --git a/src/Makefile.am b/src/Makefile.am index c2e0592520..c521bd11fe 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ LIBUNIVALUE = $(UNIVALUE_LIBS) endif GRIDCOIN_CONFIG_INCLUDES=-I$(builddir)/config -GRIDCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -I$(builddir)/bdb53 $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) $(UNIVALUE_CFLAGS) $(CURL_CFLAGS) $(LIBZIP_CFLAGS) +GRIDCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj -I$(srcdir)/secp256k1/include $(BDB_CFLAGS) -I$(builddir)/bdb53 $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) $(UNIVALUE_CFLAGS) $(CURL_CFLAGS) $(LIBZIP_CFLAGS) LIBGRIDCOIN_UTIL=libgridcoin_util.a LIBGRIDCOINQT=qt/libgridcoinqt.a @@ -81,7 +81,6 @@ GRIDCOIN_CORE_H = \ attributes.h \ banman.h \ base58.h \ - bignum.h \ chainparams.h \ chainparamsbase.h \ checkpoints.h \ @@ -115,17 +114,22 @@ GRIDCOIN_CORE_H = \ gridcoin/contract/handler.h \ gridcoin/contract/message.h \ gridcoin/contract/payload.h \ + gridcoin/contract/registry.h \ + gridcoin/contract/registry_db.h \ gridcoin/cpid.h \ gridcoin/gridcoin.h \ gridcoin/magnitude.h \ gridcoin/mrc.h \ gridcoin/project.h \ + gridcoin/protocol.h \ gridcoin/quorum.h \ gridcoin/researcher.h \ gridcoin/scraper/fwd.h \ gridcoin/scraper/http.h \ gridcoin/scraper/scraper.h \ gridcoin/scraper/scraper_net.h \ + gridcoin/scraper/scraper_registry.h \ + gridcoin/sidestake.h \ gridcoin/staking/chain_trust.h \ gridcoin/staking/difficulty.h \ gridcoin/staking/exceptions.h \ @@ -237,22 +241,25 @@ GRIDCOIN_CORE_CPP = addrdb.cpp \ crypter.cpp \ dbwrapper.cpp \ fs.cpp \ - gridcoin/appcache.cpp \ gridcoin/backup.cpp \ gridcoin/beacon.cpp \ gridcoin/boinc.cpp \ gridcoin/claim.cpp \ gridcoin/contract/contract.cpp \ gridcoin/contract/message.cpp \ + gridcoin/contract/registry.cpp \ gridcoin/cpid.cpp \ gridcoin/gridcoin.cpp \ gridcoin/mrc.cpp \ gridcoin/project.cpp \ + gridcoin/protocol.cpp \ gridcoin/quorum.cpp \ gridcoin/researcher.cpp \ gridcoin/scraper/http.cpp \ gridcoin/scraper/scraper.cpp \ gridcoin/scraper/scraper_net.cpp \ + gridcoin/scraper/scraper_registry.cpp \ + gridcoin/sidestake.cpp \ gridcoin/staking/difficulty.cpp \ gridcoin/staking/exceptions.cpp \ gridcoin/staking/kernel.cpp \ @@ -352,6 +359,7 @@ nodist_libgridcoin_util_a_SOURCES = $(srcdir)/obj/build.h # crypto primitives library +crypto_libgridcoin_crypto_base_a_CFLAGS = $(AM_CFLAGS) $(PIE_FLAGS) crypto_libgridcoin_crypto_base_a_CPPFLAGS = $(AM_CPPFLAGS) crypto_libgridcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libgridcoin_crypto_base_a_SOURCES = \ @@ -377,7 +385,9 @@ crypto_libgridcoin_crypto_base_a_SOURCES = \ crypto/sha512.cpp \ crypto/sha512.h \ crypto/siphash.cpp \ - crypto/siphash.h + crypto/siphash.h \ + gridcoin/md5.c \ + gridcoin/md5.h if USE_ASM crypto_libgridcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp @@ -429,7 +439,11 @@ gridcoinresearchd_LDADD = \ $(LIBSECP256K1) if ENABLE_WALLET +if EMBEDDED_BDB gridcoinresearchd_LDADD += $(LIBDB) +else +gridcoinresearchd_LDADD += $(BDB_LIBS) +endif endif gridcoinresearchd_LDADD += $(CURL_LIBS) $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(LIBZIP_LIBS) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 18e48d7236..ffc7dad3cb 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -82,6 +82,7 @@ QT_FORMS_UI = \ qt/forms/consolidateunspentwizardsendpage.ui \ qt/forms/diagnosticsdialog.ui \ qt/forms/editaddressdialog.ui \ + qt/forms/editsidestakedialog.ui \ qt/forms/favoritespage.ui \ qt/forms/intro.ui \ qt/forms/mrcrequestpage.ui \ @@ -105,6 +106,7 @@ QT_FORMS_UI = \ qt/forms/sendcoinsentry.ui \ qt/forms/signverifymessagedialog.ui \ qt/forms/transactiondescdialog.ui \ + qt/forms/updatedialog.ui \ qt/forms/voting/additionalfieldstableview.ui \ qt/forms/voting/pollcard.ui \ qt/forms/voting/pollcardview.ui \ @@ -143,6 +145,7 @@ QT_MOC_CPP = \ qt/moc_csvmodelwriter.cpp \ qt/moc_diagnosticsdialog.cpp \ qt/moc_editaddressdialog.cpp \ + qt/moc_editsidestakedialog.cpp \ qt/moc_favoritespage.cpp \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ @@ -161,6 +164,7 @@ QT_MOC_CPP = \ qt/moc_rpcconsole.cpp \ qt/moc_sendcoinsdialog.cpp \ qt/moc_sendcoinsentry.cpp \ + qt/moc_sidestaketablemodel.cpp \ qt/moc_signverifymessagedialog.cpp \ qt/moc_trafficgraphwidget.cpp \ qt/moc_transactiondesc.cpp \ @@ -168,6 +172,7 @@ QT_MOC_CPP = \ qt/moc_transactionfilterproxy.cpp \ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ + qt/moc_updatedialog.cpp \ qt/moc_walletmodel.cpp \ qt/researcher/moc_projecttablemodel.cpp \ qt/researcher/moc_researchermodel.cpp \ @@ -249,6 +254,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/decoration.h \ qt/diagnosticsdialog.h \ qt/editaddressdialog.h \ + qt/editsidestakedialog.h \ qt/favoritespage.h \ qt/guiconstants.h \ qt/guiutil.h \ @@ -285,6 +291,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/rpcconsole.h \ qt/sendcoinsdialog.h \ qt/sendcoinsentry.h \ + qt/sidestaketablemodel.h \ qt/signverifymessagedialog.h \ qt/trafficgraphwidget.h \ qt/transactiondesc.h \ @@ -293,6 +300,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/transactionrecord.h \ qt/transactiontablemodel.h \ qt/transactionview.h \ + qt/updatedialog.h \ qt/upgradeqt.h \ qt/voting/additionalfieldstableview.h \ qt/voting/additionalfieldstablemodel.h \ @@ -340,6 +348,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/decoration.cpp \ qt/diagnosticsdialog.cpp \ qt/editaddressdialog.cpp \ + qt/editsidestakedialog.cpp \ qt/favoritespage.cpp \ qt/guiutil.cpp \ qt/intro.cpp \ @@ -372,6 +381,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/rpcconsole.cpp \ qt/sendcoinsdialog.cpp \ qt/sendcoinsentry.cpp \ + qt/sidestaketablemodel.cpp \ qt/signverifymessagedialog.cpp \ qt/trafficgraphwidget.cpp \ qt/transactiondesc.cpp \ @@ -381,6 +391,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ qt/upgradeqt.cpp \ + qt/updatedialog.cpp \ qt/voting/additionalfieldstableview.cpp \ qt/voting/additionalfieldstablemodel.cpp \ qt/voting/poll_types.cpp \ @@ -668,7 +679,11 @@ qt_gridcoinresearch_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL qt_gridcoinresearch_LIBTOOLFLAGS = $(AM_LIBTOOLFLAGS) --tag CXX if ENABLE_WALLET +if EMBEDDED_BDB qt_gridcoinresearch_LDADD += $(LIBDB) +else +qt_gridcoinresearch_LDADD += $(BDB_LIBS) +endif endif diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 62f9e8f6de..117467c004 100755 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -30,7 +30,11 @@ qt_test_test_gridcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LI qt_test_test_gridcoin_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) if ENABLE_WALLET +if EMBEDDED_BDB qt_test_test_gridcoin_qt_LDADD += $(LIBDB) +else +qt_test_test_gridcoin_qt_LDADD += $(BDB_LIBS) +endif endif CLEAN_GRIDCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8f4ae0c6c8..53d41ea1a5 100755 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -28,6 +28,7 @@ TEXT_TEST_FILES = \ GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(BINARY_TEST_FILES:.bin=.bin.h) $(TEXT_TEST_FILES:.txt=.txt.h) # test_n binary # + GRIDCOIN_TESTS =\ test/checkpoints_tests.cpp \ test/dos_tests.cpp \ @@ -37,16 +38,14 @@ GRIDCOIN_TESTS =\ test/base32_tests.cpp \ test/base58_tests.cpp \ test/base64_tests.cpp \ - test/bignum_tests.cpp \ test/bip32_tests.cpp \ test/compilerbug_tests.cpp \ test/crypto_tests.cpp \ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/gridcoin_tests.cpp \ - test/gridcoin/appcache_tests.cpp \ - test/gridcoin/block_finder_tests.cpp \ test/gridcoin/beacon_tests.cpp \ + test/gridcoin/block_finder_tests.cpp \ test/gridcoin/claim_tests.cpp \ test/gridcoin/contract_tests.cpp \ test/gridcoin/cpid_tests.cpp \ @@ -54,7 +53,10 @@ GRIDCOIN_TESTS =\ test/gridcoin/magnitude_tests.cpp \ test/gridcoin/mrc_tests.cpp \ test/gridcoin/project_tests.cpp \ + test/gridcoin/protocol_tests.cpp \ test/gridcoin/researcher_tests.cpp \ + test/gridcoin/scraper_registry_tests.cpp \ + test/gridcoin/sidestake_tests.cpp \ test/gridcoin/superblock_tests.cpp \ test/key_tests.cpp \ test/merkle_tests.cpp \ @@ -88,7 +90,11 @@ test_test_gridcoin_LDADD += $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBGR test_test_gridcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static if ENABLE_WALLET +if EMBEDDED_BDB test_test_gridcoin_LDADD += $(LIBDB) +else +test_test_gridcoin_LDADD += $(BDB_LIBS) +endif endif nodist_test_test_gridcoin_SOURCES = $(GENERATED_TEST_FILES) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index c16078c41a..135ebf9176 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -40,7 +40,8 @@ template bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) { // Generate random temporary filename - std::string tmpfn = strprintf("%s.%" PRIx64, prefix, GetPerformanceCounter()); + const uint16_t randv{GetRand()}; + std::string tmpfn = strprintf("%s.%04x", prefix, randv); // open temp output file, and associate with CAutoFile fs::path pathTmp = GetDataDir() / tmpfn; diff --git a/src/addrman.cpp b/src/addrman.cpp index fb896a629a..7d62aef2c9 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -221,7 +221,7 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime) return; // find a bucket it is in now - int nRnd = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT); + int nRnd = GetRand(ADDRMAN_NEW_BUCKET_COUNT); int nUBucket = -1; for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT; @@ -278,7 +278,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP int nFactor = 1; for (int n = 0; n < pinfo->nRefCount; n++) nFactor *= 2; - if (nFactor > 1 && (GetRandInt(nFactor) != 0)) + if (nFactor > 1 && (GetRand(nFactor) != 0)) return false; } else { pinfo = Create(addr, source, &nId); @@ -467,7 +467,7 @@ void CAddrMan::GetAddr_(std::vector &vAddr) if (vAddr.size() >= nNodes) break; - int nRndPos = GetRandInt(vRandom.size() - n) + n; + int nRndPos = GetRand(vRandom.size() - n) + n; SwapRandom(n, nRndPos); assert(mapInfo.count(vRandom[n]) == 1); @@ -516,5 +516,5 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) } int CAddrMan::RandomInt(int nMax){ - return GetRandInt(nMax); -} \ No newline at end of file + return GetRand(nMax); +} diff --git a/src/addrman.h b/src/addrman.h index 7b37f9a9bc..47a590054e 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -234,7 +234,7 @@ class CAddrMan //! Select an address to connect to, if newOnly is set to true, only the new table is selected from. CAddrInfo Select_(bool newOnly); - //! Wraps GetRandInt to allow tests to override RandomInt and make it determinismistic. + //! Wraps GetRand to allow tests to override RandomInt and make it determinismistic. virtual int RandomInt(int nMax); #ifdef DEBUG_ADDRMAN diff --git a/src/alert.cpp b/src/alert.cpp index 0ee441818a..b66293d36b 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -113,11 +113,11 @@ bool CAlert::Cancels(const CAlert& alert) const return (alert.nID <= nCancel || setCancel.count(alert.nID)); } -bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const +bool CAlert::AppliesTo(int version, std::string strSubVerIn) const { // TODO: rework for client-version-embedded-in-strSubVer ? return (IsInEffect() && - nMinVer <= nVersion && nVersion <= nMaxVer && + nMinVer <= version && version <= nMaxVer && (setSubVer.empty() || setSubVer.count(strSubVerIn))); } diff --git a/src/alert.h b/src/alert.h index 50636eca8a..c085de4081 100644 --- a/src/alert.h +++ b/src/alert.h @@ -94,7 +94,7 @@ class CAlert : public CUnsignedAlert uint256 GetHash() const; bool IsInEffect() const; bool Cancels(const CAlert& alert) const; - bool AppliesTo(int nVersion, std::string strSubVerIn) const; + bool AppliesTo(int version, std::string strSubVerIn) const; bool AppliesToMe() const; bool RelayTo(CNode* pnode) const; bool CheckSignature() const; diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index 536a787fee..946c47a384 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -7,6 +7,7 @@ #include #include +#include template @@ -146,7 +147,13 @@ double base_uint::getdouble() const template std::string base_uint::GetHex() const { - return ArithToUint256(*this).GetHex(); + static constexpr ssize_t BYTES = BITS / 8; + + uint8_t pn_rev[BYTES]; + for (int i = 0; i < BYTES; ++i) { + pn_rev[i] = ((uint8_t*)&pn)[BYTES - 1 - i]; + } + return HexStr(pn_rev); } template @@ -257,3 +264,15 @@ arith_uint256 UintToArith256(const uint256 &a) b.pn[x] = ReadLE32(a.begin() + x*4); return b; } + +// Explicit instantiations for base_uint<320> +template base_uint<320>& base_uint<320>::operator<<=(unsigned int); +template base_uint<320>& base_uint<320>::operator*=(const base_uint<320>& b); +template int base_uint<320>::CompareTo(const base_uint<320>&) const; +template std::string base_uint<320>::GetHex() const; + +arith_uint320::arith_uint320(const uint256& b) { + std::memset(pn, 0, sizeof(pn)); + std::memcpy(pn, b.data(), b.size()); +} + diff --git a/src/arith_uint256.h b/src/arith_uint256.h index c63a76ea03..26d6a45ca7 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -13,6 +13,7 @@ #include class uint256; +class arith_uint320; class uint_error : public std::runtime_error { public: @@ -279,6 +280,22 @@ class arith_uint256 : public base_uint<256> { friend uint256 ArithToUint256(const arith_uint256 &); friend arith_uint256 UintToArith256(const uint256 &); + friend class arith_uint320; +}; + +/** 320-bit unsigned big integer. */ +class arith_uint320 : public base_uint<320> { +public: + arith_uint320() {} + arith_uint320(const base_uint<320>& b) : base_uint<320>(b) {} + arith_uint320(uint64_t b) : base_uint<320>(b) {} + + arith_uint320(const arith_uint256& b) { + std::memset(pn, 0, sizeof(pn)); + std::memcpy(pn, b.pn, sizeof(b.pn)); + } + + arith_uint320(const uint256& b); }; uint256 ArithToUint256(const arith_uint256 &); diff --git a/src/bdb53/CMakeLists.txt b/src/bdb53/CMakeLists.txt new file mode 100644 index 0000000000..1c98273b73 --- /dev/null +++ b/src/bdb53/CMakeLists.txt @@ -0,0 +1,67 @@ +include(ExternalProject) + +# Configure flags +# =============== +set(BDB_FLAGS + --disable-java + --disable-jdbc + --disable-replication + --enable-cxx +) +if(ENABLE_PIE) + list(APPEND BDB_FLAGS + --with-pic + ) +endif() +if(MINGW) + list(APPEND BDB_FLAGS + --enable-mingw + ) +endif() +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND BDB_FLAGS + CFLAGS=-Wno-implicit-function-declaration + ) +endif() + + +# Make flags +# ========== + +include(ProcessorCount) +ProcessorCount(N) +if(N EQUAL 0) + set(N 1) +endif() + +set(MAKEOPTS "-j${N}" CACHE STRING "Options for the 'make' program") + + +# External project +# ================ + +set(LIBDB_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/libdb_build) +set(libdb_cxx_library ${LIBDB_BUILD_DIR}/libdb_cxx.a) +set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM 1) + +ExternalProject_Add(BerkeleyDB_Project + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} + BINARY_DIR ${LIBDB_BUILD_DIR} + CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/dist/configure ${BDB_FLAGS} + BUILD_COMMAND + COMMAND ${MAKE_EXE} ${MAKEOPTS} clean + COMMAND ${MAKE_EXE} ${MAKEOPTS} libdb_cxx.a + INSTALL_COMMAND "" + BUILD_BYPRODUCTS ${libdb_cxx_library} + LOG_CONFIGURE TRUE + LOG_BUILD TRUE + LOG_OUTPUT_ON_FAILURE TRUE +) + +add_library(libdb_cxx STATIC IMPORTED GLOBAL) +add_dependencies(libdb_cxx BerkeleyDB_Project) + +set_target_properties(libdb_cxx PROPERTIES + IMPORTED_LOCATION ${libdb_cxx_library} +) +target_include_directories(libdb_cxx INTERFACE ${LIBDB_BUILD_DIR}) diff --git a/src/bignum.h b/src/bignum.h deleted file mode 100644 index d5a62d1782..0000000000 --- a/src/bignum.h +++ /dev/null @@ -1,731 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2012 The Bitcoin developers -// Distributed under the MIT/X11 software license, see the accompanying -// file COPYING or https://opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_BIGNUM_H -#define BITCOIN_BIGNUM_H - -#include -#include "serialize.h" -#include "uint256.h" -#include "version.h" - -#include - -#include -#include - -#include - -/** Errors thrown by the bignum class */ -class bignum_error : public std::runtime_error -{ -public: - explicit bignum_error(const std::string& str) : std::runtime_error(str) {} -}; - - -/** RAII encapsulated BN_CTX (OpenSSL bignum context) */ -class CAutoBN_CTX -{ -protected: - BN_CTX* pctx; - BN_CTX* operator=(BN_CTX* pnew) { return pctx = pnew; } - -public: - CAutoBN_CTX() - { - pctx = BN_CTX_new(); - if (pctx == nullptr) - throw bignum_error("CAutoBN_CTX : BN_CTX_new() returned nullptr"); - } - - ~CAutoBN_CTX() - { - if (pctx != nullptr) - BN_CTX_free(pctx); - } - - operator BN_CTX*() { return pctx; } - BN_CTX& operator*() { return *pctx; } - BN_CTX** operator&() { return &pctx; } - bool operator!() { return (pctx == nullptr); } -}; - -/* RAII wrapper for BIGNUM instance */ -class CBigNumBase -{ -protected: - BIGNUM* pbn; - -public: - CBigNumBase() - : pbn(BN_new()) - { - if (pbn == nullptr) - throw bignum_error("CBigNum : BN_new() returned nullptr"); - } - - ~CBigNumBase() - { - BN_clear_free(pbn); - } -}; - -/** C++ wrapper for BIGNUM (OpenSSL bignum) */ -class CBigNum : public CBigNumBase -{ -public: - CBigNum() - {} - - CBigNum(const CBigNum& b) - { - if (!BN_copy(pbn, &b)) - throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_copy failed"); - } - - CBigNum& operator=(const CBigNum& b) - { - if (!BN_copy(pbn, &b)) - throw bignum_error("CBigNum::operator= : BN_copy failed"); - return (*this); - } - - //CBigNum(char n) is not portable. Use 'signed char' or 'unsigned char'. - CBigNum(signed char n) { if (n >= 0) setulong(n); else setint64(n); } - CBigNum(short n) { if (n >= 0) setulong(n); else setint64(n); } - CBigNum(int n) { if (n >= 0) setulong(n); else setint64(n); } - CBigNum(long n) { if (n >= 0) setulong(n); else setint64(n); } - CBigNum(long long n) { setint64(n); } - CBigNum(unsigned char n) { setulong(n); } - CBigNum(unsigned short n) { setulong(n); } - CBigNum(unsigned int n) { setulong(n); } - CBigNum(unsigned long n) { setulong(n); } - CBigNum(unsigned long long n) { setuint64(n); } - explicit CBigNum(uint256 n) { setuint256(n); } - - explicit CBigNum(const std::vector& vch) - { - setvch(vch); - } - - /** Generates a cryptographically secure random number between zero and range exclusive - * i.e. 0 < returned number < range - * @param range The upper bound on the number. - * @return - */ - static CBigNum randBignum(const CBigNum& range) { - CBigNum ret; - if(!BN_rand_range(&ret, &range)){ - throw bignum_error("CBigNum:rand element : BN_rand_range failed"); - } - return ret; - } - - /** Generates a cryptographically secure random k-bit number - * @param k The bit length of the number. - * @return - */ - static CBigNum RandKBitBigum(const uint32_t k){ - CBigNum ret; - if(!BN_rand(&ret, k, -1, 0)){ - throw bignum_error("CBigNum:rand element : BN_rand failed"); - } - return ret; - } - - /**Returns the size in bits of the underlying bignum. - * - * @return the size - */ - int bitSize() const{ - return BN_num_bits(pbn); - } - - - void setulong(unsigned long n) - { - if (!BN_set_word(pbn, n)) - throw bignum_error("CBigNum conversion from unsigned long : BN_set_word failed"); - } - - unsigned long getulong() const - { - return BN_get_word(pbn); - } - - unsigned int getuint() const - { - return BN_get_word(pbn); - } - - int getint() const - { - unsigned long n = BN_get_word(pbn); - if (!BN_is_negative(pbn)) - return (n > (unsigned long)std::numeric_limits::max() ? std::numeric_limits::max() : n); - else - return (n > (unsigned long)std::numeric_limits::max() ? std::numeric_limits::min() : -(int)n); - } - - void setint64(int64_t sn) - { - unsigned char pch[sizeof(sn) + 6]; - unsigned char* p = pch + 4; - bool fNegative; - uint64_t n; - - if (sn < (int64_t)0) - { - // Since the minimum signed integer cannot be represented as positive so long as its type is signed, and it's not well-defined what happens if you make it unsigned before negating it, we instead increment the negative integer by 1, convert it, then increment the (now positive) unsigned integer by 1 to compensate - n = -(sn + 1); - ++n; - fNegative = true; - } else { - n = sn; - fNegative = false; - } - - bool fLeadingZeroes = true; - for (int i = 0; i < 8; i++) - { - unsigned char c = (n >> 56) & 0xff; - n <<= 8; - if (fLeadingZeroes) - { - if (c == 0) - continue; - if (c & 0x80) - *p++ = (fNegative ? 0x80 : 0); - else if (fNegative) - c |= 0x80; - fLeadingZeroes = false; - } - *p++ = c; - } - unsigned int nSize = p - (pch + 4); - pch[0] = (nSize >> 24) & 0xff; - pch[1] = (nSize >> 16) & 0xff; - pch[2] = (nSize >> 8) & 0xff; - pch[3] = (nSize) & 0xff; - BN_mpi2bn(pch, p - pch, pbn); - } - - uint64_t getuint64() - { - unsigned int nSize = BN_bn2mpi(pbn, nullptr); - if (nSize < 4) - return 0; - std::vector vch(nSize); - BN_bn2mpi(pbn, &vch[0]); - if (vch.size() > 4) - vch[4] &= 0x7f; - uint64_t n = 0; - for (unsigned int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--) - ((unsigned char*)&n)[i] = vch[j]; - return n; - } - - void setuint64(uint64_t n) - { - unsigned char pch[sizeof(n) + 6]; - unsigned char* p = pch + 4; - bool fLeadingZeroes = true; - for (int i = 0; i < 8; i++) - { - unsigned char c = (n >> 56) & 0xff; - n <<= 8; - if (fLeadingZeroes) - { - if (c == 0) - continue; - if (c & 0x80) - *p++ = 0; - fLeadingZeroes = false; - } - *p++ = c; - } - unsigned int nSize = p - (pch + 4); - pch[0] = (nSize >> 24) & 0xff; - pch[1] = (nSize >> 16) & 0xff; - pch[2] = (nSize >> 8) & 0xff; - pch[3] = (nSize) & 0xff; - BN_mpi2bn(pch, p - pch, pbn); - } - - void setuint256(uint256 n) - { - unsigned char pch[sizeof(n) + 6]; - unsigned char* p = pch + 4; - bool fLeadingZeroes = true; - unsigned char* pbegin = (unsigned char*)&n; - unsigned char* psrc = pbegin + sizeof(n); - while (psrc != pbegin) - { - unsigned char c = *(--psrc); - if (fLeadingZeroes) - { - if (c == 0) - continue; - if (c & 0x80) - *p++ = 0; - fLeadingZeroes = false; - } - *p++ = c; - } - unsigned int nSize = p - (pch + 4); - pch[0] = (nSize >> 24) & 0xff; - pch[1] = (nSize >> 16) & 0xff; - pch[2] = (nSize >> 8) & 0xff; - pch[3] = (nSize >> 0) & 0xff; - BN_mpi2bn(pch, p - pch, pbn); - } - - uint256 getuint256() const - { - unsigned int nSize = BN_bn2mpi(pbn, nullptr); - if (nSize < 4) - return uint256(); - std::vector vch(nSize); - BN_bn2mpi(pbn, &vch[0]); - if (vch.size() > 4) - vch[4] &= 0x7f; - uint256 n; - for (unsigned int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--) - ((unsigned char*)&n)[i] = vch[j]; - return n; - } - - - void setvch(const std::vector& vch) - { - std::vector vch2(vch.size() + 4); - unsigned int nSize = vch.size(); - // BIGNUM's byte stream format expects 4 bytes of - // big endian size data info at the front - vch2[0] = (nSize >> 24) & 0xff; - vch2[1] = (nSize >> 16) & 0xff; - vch2[2] = (nSize >> 8) & 0xff; - vch2[3] = (nSize >> 0) & 0xff; - // swap data to big endian - reverse_copy(vch.begin(), vch.end(), vch2.begin() + 4); - BN_mpi2bn(&vch2[0], vch2.size(), pbn); - } - - std::vector getvch() const - { - unsigned int nSize = BN_bn2mpi(pbn, nullptr); - if (nSize <= 4) - return std::vector(); - std::vector vch(nSize); - BN_bn2mpi(pbn, &vch[0]); - vch.erase(vch.begin(), vch.begin() + 4); - reverse(vch.begin(), vch.end()); - return vch; - } - - CBigNum& SetCompact(unsigned int nCompact) - { - unsigned int nSize = nCompact >> 24; - std::vector vch(4 + nSize); - vch[3] = nSize; - if (nSize >= 1) vch[4] = (nCompact >> 16) & 0xff; - if (nSize >= 2) vch[5] = (nCompact >> 8) & 0xff; - if (nSize >= 3) vch[6] = (nCompact >> 0) & 0xff; - BN_mpi2bn(&vch[0], vch.size(), pbn); - return *this; - } - - unsigned int GetCompact() const - { - unsigned int nSize = BN_bn2mpi(pbn, nullptr); - std::vector vch(nSize); - nSize -= 4; - BN_bn2mpi(pbn, &vch[0]); - unsigned int nCompact = nSize << 24; - if (nSize >= 1) nCompact |= (vch[4] << 16); - if (nSize >= 2) nCompact |= (vch[5] << 8); - if (nSize >= 3) nCompact |= (vch[6] << 0); - return nCompact; - } - - void SetHex(const std::string& str) - { - // skip 0x - const char* psz = str.c_str(); - while (IsSpace(*psz)) - psz++; - bool fNegative = false; - if (*psz == '-') - { - fNegative = true; - psz++; - } - if (psz[0] == '0' && ToLower((unsigned char)psz[1]) == 'x') - psz += 2; - while (IsSpace(*psz)) - psz++; - - // hex string to bignum - *this = 0; - while (HexDigit(*psz) >= 0) - { - *this <<= 4; - int n = HexDigit((unsigned char)*psz++); - *this += n; - } - if (fNegative) - *this = 0 - *this; - } - - std::string ToString(int nBase=10) const - { - CAutoBN_CTX pctx; - CBigNum bnBase = nBase; - CBigNum bn0 = 0; - std::string str; - CBigNum bn = *this; - BN_set_negative(&bn, false); - CBigNum dv; - CBigNum rem; - if (BN_cmp(&bn, &bn0) == 0) - return "0"; - while (BN_cmp(&bn, &bn0) > 0) - { - if (!BN_div(&dv, &rem, &bn, &bnBase, pctx)) - throw bignum_error("CBigNum::ToString() : BN_div failed"); - bn = dv; - unsigned int c = rem.getulong(); - str += "0123456789abcdef"[c]; - } - if (BN_is_negative(pbn)) - str += "-"; - reverse(str.begin(), str.end()); - return str; - } - - std::string GetHex() const - { - return ToString(16); - } - - template - void Serialize(Stream& s) const - { - ::Serialize(s, getvch()); - } - - template - void Unserialize(Stream& s) - { - std::vector vch; - ::Unserialize(s, vch); - setvch(vch); - } - - /** - * exponentiation with an int. this^e - * @param e the exponent as an int - * @return - */ - CBigNum pow(const int e) const { - return this->pow(CBigNum(e)); - } - - /** - * exponentiation this^e - * @param e the exponent - * @return - */ - CBigNum pow(const CBigNum& e) const { - CAutoBN_CTX pctx; - CBigNum ret; - if (!BN_exp(&ret, pbn, &e, pctx)) - throw bignum_error("CBigNum::pow : BN_exp failed"); - return ret; - } - - /** - * modular multiplication: (this * b) mod m - * @param b operand - * @param m modulus - */ - CBigNum mul_mod(const CBigNum& b, const CBigNum& m) const { - CAutoBN_CTX pctx; - CBigNum ret; - if (!BN_mod_mul(&ret, pbn, &b, &m, pctx)) - throw bignum_error("CBigNum::mul_mod : BN_mod_mul failed"); - - return ret; - } - - /** - * modular exponentiation: this^e mod n - * @param e exponent - * @param m modulus - */ - CBigNum pow_mod(const CBigNum& e, const CBigNum& m) const { - CAutoBN_CTX pctx; - CBigNum ret; - if( e < 0){ - // g^-x = (g^-1)^x - CBigNum inv = this->inverse(m); - CBigNum posE = e * -1; - if (!BN_mod_exp(&ret, &inv, &posE, &m, pctx)) - throw bignum_error("CBigNum::pow_mod: BN_mod_exp failed on negative exponent"); - }else - if (!BN_mod_exp(&ret, pbn, &e, &m, pctx)) - throw bignum_error("CBigNum::pow_mod : BN_mod_exp failed"); - - return ret; - } - - /** - * Calculates the inverse of this element mod m. - * i.e. i such this*i = 1 mod m - * @param m the modu - * @return the inverse - */ - CBigNum inverse(const CBigNum& m) const { - CAutoBN_CTX pctx; - CBigNum ret; - if (!BN_mod_inverse(&ret, pbn, &m, pctx)) - throw bignum_error("CBigNum::inverse*= :BN_mod_inverse"); - return ret; - } - - /** - * Generates a random (safe) prime of numBits bits - * @param numBits the number of bits - * @param safe true for a safe prime - * @return the prime - */ - static CBigNum generatePrime(const unsigned int numBits, bool safe = false) { - CBigNum ret; - if (!BN_generate_prime_ex(&ret, numBits, safe, nullptr, nullptr, nullptr)) - throw bignum_error("CBigNum::generatePrime*= :BN_generate_prime_ex"); - return ret; - } - - /** - * Calculates the greatest common divisor (GCD) of two numbers. - * @param m the second element - * @return the GCD - */ - CBigNum gcd( const CBigNum& b) const{ - CAutoBN_CTX pctx; - CBigNum ret; - if (!BN_gcd(&ret, pbn, &b, pctx)) - throw bignum_error("CBigNum::gcd*= :BN_gcd"); - return ret; - } - - /** - * Miller-Rabin primality test on this element - * @param checks: optional, the number of Miller-Rabin tests to run - * default causes error rate of 2^-80. - * @return true if prime - */ - bool isPrime(const int checks=BN_prime_checks) const { - CAutoBN_CTX pctx; - int ret = BN_is_prime_ex(pbn, checks, pctx, nullptr); - if(ret < 0){ - throw bignum_error("CBigNum::isPrime :BN_is_prime_ex"); - } - return ret; - } - - bool isOne() const - { - return BN_is_one(pbn); - } - - - bool operator!() const - { - return BN_is_zero(pbn); - } - - CBigNum& operator+=(const CBigNum& b) - { - if (!BN_add(pbn, pbn, &b)) - throw bignum_error("CBigNum::operator+= : BN_add failed"); - return *this; - } - - CBigNum& operator-=(const CBigNum& b) - { - *this = *this - b; - return *this; - } - - CBigNum& operator*=(const CBigNum& b) - { - CAutoBN_CTX pctx; - if (!BN_mul(pbn, pbn, &b, pctx)) - throw bignum_error("CBigNum::operator*= : BN_mul failed"); - return *this; - } - - CBigNum& operator/=(const CBigNum& b) - { - *this = *this / b; - return *this; - } - - CBigNum& operator%=(const CBigNum& b) - { - *this = *this % b; - return *this; - } - - CBigNum& operator<<=(unsigned int shift) - { - if (!BN_lshift(pbn, pbn, shift)) - throw bignum_error("CBigNum:operator<<= : BN_lshift failed"); - return *this; - } - - CBigNum& operator>>=(unsigned int shift) - { - // Note: BN_rshift segfaults on 64-bit if 2^shift is greater than the number - // if built on ubuntu 9.04 or 9.10, probably depends on version of OpenSSL - CBigNum a = 1; - a <<= shift; - if (BN_cmp(&a, pbn) > 0) - { - *this = 0; - return *this; - } - - if (!BN_rshift(pbn, pbn, shift)) - throw bignum_error("CBigNum:operator>>= : BN_rshift failed"); - return *this; - } - - - CBigNum& operator++() - { - // prefix operator - if (!BN_add(pbn, pbn, BN_value_one())) - throw bignum_error("CBigNum::operator++ : BN_add failed"); - return *this; - } - - const CBigNum operator++(int) - { - // postfix operator - const CBigNum ret = *this; - ++(*this); - return ret; - } - - CBigNum& operator--() - { - // prefix operator - CBigNum r; - if (!BN_sub(&r, pbn, BN_value_one())) - throw bignum_error("CBigNum::operator-- : BN_sub failed"); - *this = r; - return *this; - } - - const CBigNum operator--(int) - { - // postfix operator - const CBigNum ret = *this; - --(*this); - return ret; - } - - BIGNUM* operator&() { return pbn; } - const BIGNUM* operator&() const { return pbn; } - - friend inline const CBigNum operator-(const CBigNum& a, const CBigNum& b); - friend inline const CBigNum operator/(const CBigNum& a, const CBigNum& b); - friend inline const CBigNum operator%(const CBigNum& a, const CBigNum& b); - friend inline const CBigNum operator*(const CBigNum& a, const CBigNum& b); - friend inline bool operator<(const CBigNum& a, const CBigNum& b); -}; - - - -inline const CBigNum operator+(const CBigNum& a, const CBigNum& b) -{ - CBigNum r; - if (!BN_add(&r, &a, &b)) - throw bignum_error("CBigNum::operator+ : BN_add failed"); - return r; -} - -inline const CBigNum operator-(const CBigNum& a, const CBigNum& b) -{ - CBigNum r; - if (!BN_sub(&r, &a, &b)) - throw bignum_error("CBigNum::operator- : BN_sub failed"); - return r; -} - -inline const CBigNum operator-(const CBigNum& a) -{ - CBigNum r(a); - BN_set_negative(&r, !BN_is_negative(&r)); - return r; -} - -inline const CBigNum operator*(const CBigNum& a, const CBigNum& b) -{ - CAutoBN_CTX pctx; - CBigNum r; - if (!BN_mul(&r, &a, &b, pctx)) - throw bignum_error("CBigNum::operator* : BN_mul failed"); - return r; -} - -inline const CBigNum operator/(const CBigNum& a, const CBigNum& b) -{ - CAutoBN_CTX pctx; - CBigNum r; - if (!BN_div(&r, nullptr, &a, &b, pctx)) - throw bignum_error("CBigNum::operator/ : BN_div failed"); - return r; -} - -inline const CBigNum operator%(const CBigNum& a, const CBigNum& b) -{ - CAutoBN_CTX pctx; - CBigNum r; - if (!BN_nnmod(&r, &a, &b, pctx)) - throw bignum_error("CBigNum::operator% : BN_div failed"); - return r; -} - -inline const CBigNum operator<<(const CBigNum& a, unsigned int shift) -{ - CBigNum r; - if (!BN_lshift(&r, &a, shift)) - throw bignum_error("CBigNum:operator<< : BN_lshift failed"); - return r; -} - -inline const CBigNum operator>>(const CBigNum& a, unsigned int shift) -{ - CBigNum r = a; - r >>= shift; - return r; -} - -inline bool operator==(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) == 0); } -inline bool operator!=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) != 0); } -inline bool operator<=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) <= 0); } -inline bool operator>=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) >= 0); } -inline bool operator<(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) < 0); } -inline bool operator>(const CBigNum& a, const CBigNum& b) { return (BN_cmp(&a, &b) > 0); } - -inline std::ostream& operator<<(std::ostream &strm, const CBigNum &b) { return strm << b.ToString(10); } - -typedef CBigNum Bignum; - -#endif diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 952802ffb1..fa9e268052 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -67,12 +67,18 @@ class CMainParams : public CChainParams { consensus.BlockV10Height = 1420000; consensus.BlockV11Height = 2053000; consensus.BlockV12Height = 2671700; + consensus.BlockV13Height = std::numeric_limits::max(); consensus.PollV3Height = 2671700; consensus.ProjectV2Height = 2671700; // Immediately post zero payment interval fees 40% for mainnet consensus.InitialMRCFeeFractionPostZeroInterval = Fraction(2, 5); // Zero day interval is 14 days on mainnet consensus.MRCZeroPaymentInterval = 14 * 24 * 60 * 60; + // The maximum ratio of rewards that can be allocated to all of the mandatory sidestakes. + consensus.MaxMandatorySideStakeTotalAlloc = Fraction(1, 4); + // The "standard" contract replay lookback for those contract types + // that do not have a registry db. + consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60; // "standard" scrypt target limit for proof of work, results in 0,000244140625 proof-of-work difficulty. // Equivalent to ~arith_uint256() >> 20 or 1e0fffff in compact notation. consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -176,12 +182,18 @@ class CTestNetParams : public CChainParams { consensus.BlockV10Height = 629409; consensus.BlockV11Height = 1301500; consensus.BlockV12Height = 1871830; + consensus.BlockV13Height = std::numeric_limits::max(); consensus.PollV3Height = 1944820; consensus.ProjectV2Height = 1944820; // Immediately post zero payment interval fees 40% for testnet, the same as mainnet consensus.InitialMRCFeeFractionPostZeroInterval = Fraction(2, 5); // Zero day interval is 10 minutes on testnet. The very short interval facilitates testing. consensus.MRCZeroPaymentInterval = 10 * 60; + // The maximum ratio of rewards that can be allocated to all of the mandatory sidestakes. + consensus.MaxMandatorySideStakeTotalAlloc = Fraction(1, 4); + // The "standard" contract replay lookback for those contract types + // that do not have a registry db. + consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60; // Equivalent to ~arith_uint256() >> 16 or 1f00ffff in compact notation. consensus.powLimit = uint256S("0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); diff --git a/src/chainparams.h b/src/chainparams.h index 96d3f193fa..908d217824 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -10,13 +10,15 @@ #include "consensus/params.h" #include "protocol.h" -// system.h included only for temporary V12 fork point overrides for testing. +// system.h and extern reference to cs_main included only for temporary V13 fork point overrides for testing. #include "util/system.h" #include #include #include +extern CCriticalSection cs_main; + typedef std::map MapCheckpoints; typedef std::map> MapMasterKeys; @@ -151,6 +153,13 @@ inline bool IsV12Enabled(int nHeight) return nHeight >= Params().GetConsensus().BlockV12Height; } +inline bool IsV13Enabled(int nHeight) +{ + // The argument driven override temporarily here to facilitate testing. + + return nHeight >= gArgs.GetArg("-blockv13height", Params().GetConsensus().BlockV13Height); +} + inline bool IsPollV3Enabled(int nHeight) { return nHeight >= Params().GetConsensus().PollV3Height; diff --git a/src/config/.empty b/src/config/.empty deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/config/gridcoin-config.h.cmake.in b/src/config/gridcoin-config.h.cmake.in new file mode 100644 index 0000000000..94a13f41a3 --- /dev/null +++ b/src/config/gridcoin-config.h.cmake.in @@ -0,0 +1,79 @@ +#ifndef GRIDCOIN_CONFIG_H +#define GRIDCOIN_CONFIG_H + +// Define if thread_local is supported +#define HAVE_THREAD_LOCAL + +#define PACKAGE_NAME "@CMAKE_PROJECT_NAME@" +#define PACKAGE_VERSION "@CMAKE_PROJECT_VERSION@" +#define CLIENT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define CLIENT_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define CLIENT_VERSION_REVISION @PROJECT_VERSION_PATCH@ +#define CLIENT_VERSION_BUILD @PROJECT_VERSION_TWEAK@ +#define CLIENT_VERSION_IS_RELEASE @CLIENT_VERSION_IS_RELEASE@ +#define COPYRIGHT_YEAR "@COPYRIGHT_YEAR@" +#define COPYRIGHT_HOLDERS_FINAL "@COPYRIGHT_HOLDERS_FINAL@" + +#cmakedefine ENABLE_SSE41 +#cmakedefine ENABLE_AVX2 +#cmakedefine ENABLE_X86_SHANI +#cmakedefine ENABLE_ARM_CRC +#cmakedefine ENABLE_ARM_SHANI +#cmakedefine USE_ASM + +#cmakedefine USE_DBUS +#cmakedefine USE_QRCODE + +#cmakedefine HAVE_STRERROR_R +#cmakedefine STRERROR_R_CHAR_P + +#cmakedefine WORDS_BIGENDIAN + +#cmakedefine HAVE_BYTESWAP_H +#cmakedefine HAVE_ENDIAN_H +#cmakedefine HAVE_SYS_ENDIAN_H +#cmakedefine HAVE_SYS_PRCTL_H + +#cmakedefine01 HAVE_DECL_LE16TOH +#cmakedefine01 HAVE_DECL_LE32TOH +#cmakedefine01 HAVE_DECL_LE64TOH + +#cmakedefine01 HAVE_DECL_HTOLE16 +#cmakedefine01 HAVE_DECL_HTOLE32 +#cmakedefine01 HAVE_DECL_HTOLE64 + +#cmakedefine01 HAVE_DECL_BE16TOH +#cmakedefine01 HAVE_DECL_BE32TOH +#cmakedefine01 HAVE_DECL_BE64TOH + +#cmakedefine01 HAVE_DECL_HTOBE16 +#cmakedefine01 HAVE_DECL_HTOBE32 +#cmakedefine01 HAVE_DECL_HTOBE64 + +#cmakedefine01 HAVE_DECL_BSWAP_16 +#cmakedefine01 HAVE_DECL_BSWAP_32 +#cmakedefine01 HAVE_DECL_BSWAP_64 + +#cmakedefine HAVE_BUILTIN_CLZL +#cmakedefine HAVE_BUILTIN_CLZLL + +#cmakedefine HAVE_MSG_NOSIGNAL +#cmakedefine HAVE_MSG_DONTWAIT + +#cmakedefine HAVE_MALLOC_INFO +#cmakedefine HAVE_MALLOPT_ARENA_MAX + +#cmakedefine01 HAVE_SYSTEM +#cmakedefine HAVE_GMTIME_R + +// Define if the Linux getrandom system call is available +#cmakedefine HAVE_SYS_GETRANDOM +// Define if the BSD getentropy system call is available +#cmakedefine HAVE_GETENTROPY +// Define if the BSD sysctl(KERN_ARND) is available +#cmakedefine HAVE_SYSCTL_ARND + +#cmakedefine01 HAVE_O_CLOEXEC +#cmakedefine HAVE_STRONG_GETAUXVAL + +#endif //GRIDCOIN_CONFIG_H diff --git a/src/consensus/params.h b/src/consensus/params.h index 3dc27bda6d..b4e5e1a82e 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -33,6 +33,8 @@ struct Params { int BlockV11Height; /** Block height at which v12 blocks are created */ int BlockV12Height; + /** Block height at which v13 blocks are created */ + int BlockV13Height; /** Block height at which poll v3 contract payloads are valid */ int PollV3Height; /** Block height at which project v2 contracts are allowed */ @@ -45,6 +47,12 @@ struct Params { * forfeiture of fees to the staker and/or foundation. Only consensus critical at BlockV12Height or above. */ int64_t MRCZeroPaymentInterval; + /** + * @brief The maximum allocation (as a Fraction) that can be used by all of the mandatory sidestakes + */ + Fraction MaxMandatorySideStakeTotalAlloc; + + int64_t StandardContractReplayLookback; uint256 powLimit; }; diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt new file mode 100644 index 0000000000..f7972902be --- /dev/null +++ b/src/crypto/CMakeLists.txt @@ -0,0 +1,58 @@ +set(LIBGRIDCOIN_CRYPTO gridcoin_crypto_base) +add_library(gridcoin_crypto_base STATIC + aes.cpp + chacha20.cpp + hmac_sha256.cpp + hmac_sha512.cpp + poly1305.cpp + ripemd160.cpp + sha1.cpp + sha256.cpp + sha3.cpp + sha512.cpp + siphash.cpp + ../gridcoin/md5.c +) + +if(USE_ASM) + target_sources(gridcoin_crypto_base PRIVATE sha256_sse4.cpp) +endif() + +if(ENABLE_SSE41) + list(APPEND LIBGRIDCOIN_CRYPTO gridcoin_crypto_sse41) + add_library(gridcoin_crypto_sse41 STATIC sha256_sse41.cpp) + target_compile_definitions(gridcoin_crypto_sse41 PRIVATE ENABLE_SSE41) + target_compile_options(gridcoin_crypto_sse41 PRIVATE -msse4.1) +endif() + +if(ENABLE_AVX2) + list(APPEND LIBGRIDCOIN_CRYPTO gridcoin_crypto_avx2) + add_library(gridcoin_crypto_avx2 STATIC sha256_avx2.cpp) + target_compile_definitions(gridcoin_crypto_avx2 PRIVATE ENABLE_AVX2) + target_compile_options(gridcoin_crypto_avx2 PRIVATE -mavx -mavx2) +endif() + +if(ENABLE_X86_SHANI) + list(APPEND LIBGRIDCOIN_CRYPTO gridcoin_crypto_x86_shani) + add_library(gridcoin_crypto_x86_shani STATIC sha256_x86_shani.cpp) + target_compile_definitions(gridcoin_crypto_x86_shani PRIVATE ENABLE_X86_SHANI) + target_compile_options(gridcoin_crypto_x86_shani PRIVATE -msse4 -msha) +endif() + +if(ENABLE_ARM_SHANI) + list(APPEND LIBGRIDCOIN_CRYPTO gridcoin_crypto_arm_shani) + add_library(gridcoin_crypto_arm_shani STATIC sha256_arm_shani.cpp) + target_compile_definitions(gridcoin_crypto_arm_shani PRIVATE ENABLE_ARM_SHANI) +endif() + +foreach(library IN LISTS LIBGRIDCOIN_CRYPTO) + target_include_directories(${library} PRIVATE + "${CMAKE_SOURCE_DIR}/src" + "${CMAKE_BINARY_DIR}/src" + ) + target_compile_definitions(${library} PRIVATE HAVE_CONFIG_H) +endforeach() + +set(LIBGRIDCOIN_CRYPTO ${LIBGRIDCOIN_CRYPTO} + CACHE INTERNAL "Gridcoin crypto libraries" +) diff --git a/src/fs.cpp b/src/fs.cpp index d75f0771c1..d472fa4cc0 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -57,36 +57,20 @@ FileLock::~FileLock() } } -static bool IsWSL() -{ - struct utsname uname_data; - return uname(&uname_data) == 0 && std::string(uname_data.version).find("Microsoft") != std::string::npos; -} - bool FileLock::TryLock() { if (fd == -1) { return false; } - // Exclusive file locking is broken on WSL using fcntl (issue #18622) - // This workaround can be removed once the bug on WSL is fixed - static const bool is_wsl = IsWSL(); - if (is_wsl) { - if (flock(fd, LOCK_EX | LOCK_NB) == -1) { - reason = GetErrorReason(); - return false; - } - } else { - struct flock lock; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - if (fcntl(fd, F_SETLK, &lock) == -1) { - reason = GetErrorReason(); - return false; - } + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLK, &lock) == -1) { + reason = GetErrorReason(); + return false; } return true; diff --git a/src/gridcoin/appcache.h b/src/gridcoin/appcache.h index 20ec977759..a01a5f1975 100644 --- a/src/gridcoin/appcache.h +++ b/src/gridcoin/appcache.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2023 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -9,17 +9,9 @@ #include #include -enum class Section -{ - PROTOCOL, - SCRAPER, - - // Enum counting entry. Using it will throw. - NUM_CACHES -}; - //! -//! \brief An entry in the application cache. +//! \brief An entry in the application cache. This is provided as a legacy shim only and will be replaced by +//! native calls. //! struct AppCacheEntry { @@ -28,77 +20,32 @@ struct AppCacheEntry }; //! -//! \brief Application cache section type. +//! \brief Application cache section type. This is provided as a legacy shim only and will be replaced by +//! native calls. //! typedef std::unordered_map AppCacheSection; //! -//! \brief Application cache section sorted by key. +//! \brief Application cache section sorted by key. This is provided as a legacy shim only and will be replaced by +//! native calls. //! typedef std::map SortedAppCacheSection; //! -//! \brief Application cache type. -//! -typedef std::unordered_map AppCache; - -//! -//! \brief Write value into application cache. -//! \param section Cache section to write to. -//! \param key Entry key to write. -//! \param value Entry value to write. -//! -void WriteCache( - Section section, - const std::string& key, - const std::string& value, - int64_t locktime); - -//! -//! \brief Read values from appcache section. -//! \param section Cache section to read from. -//! \param key Entry key to read. -//! \returns Value for \p key in \p section if available, or an empty string -//! if either the section or the key don't exist. -//! -AppCacheEntry ReadCache( - Section section, - const std::string& key); - -//! -//! \brief Read section from cache. -//! \param section Section to read. -//! \returns The data for \p section if available. -//! -AppCacheSection& ReadCacheSection(Section section); - -//! -//! \brief Reads a section from cache and sorts it. -//! \param section Section to read. -//! \returns The data for \p section if available. -//! -//! Reads a cache section and transfers it to a sorted map. This can be an -//! expensive operation and should not be used unless there is a need -//! for sorted traversal. -//! -//! \see ReadCacheSection -//! -SortedAppCacheSection ReadSortedCacheSection(Section section); - -//! -//! \brief Clear all values in a cache section. -//! \param section Cache section to clear. -//! \note This only clears the values. It does not erase them. +//! \brief Extended AppCache structure similar to those in AppCache.h, except a deleted flag is provided. This +//! is provided as a legacy shim only and will be replaced by native calls. //! -void ClearCache(Section section); +struct AppCacheEntryExt +{ + std::string value; // Value of entry. + int64_t timestamp; // Timestamp of entry/deletion + bool deleted; // Deleted flag. +}; //! -//! \brief Erase key from appcache section. -//! \param section Cache section to erase from. -//! \param key Entry key to erase. +//! \brief Extended AppCache map typedef similar to AppCacheSection, except a deleted flag is provided for use by +//! the scraper. This is provided as a legacy shim only and will be replaced by native calls. //! -void DeleteCache(Section section, const std::string& key); - -Section StringToSection(const std::string& section); +typedef std::unordered_map AppCacheSectionExt; #endif // GRIDCOIN_APPCACHE_H diff --git a/src/gridcoin/beacon.cpp b/src/gridcoin/beacon.cpp index 43a333f3cc..e529fdff07 100644 --- a/src/gridcoin/beacon.cpp +++ b/src/gridcoin/beacon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -63,7 +63,7 @@ Beacon::Beacon(CPubKey public_key, int64_t timestamp, uint256 hash) , m_public_key(std::move(public_key)) , m_timestamp(timestamp) , m_hash(hash) - , m_prev_beacon_hash() + , m_previous_hash() , m_status(BeaconStatusForStorage::UNKNOWN) { } @@ -103,6 +103,51 @@ bool Beacon::WellFormed() const return m_public_key.IsValid(); } +std::pair Beacon::KeyValueToString() const +{ + return std::make_pair(m_cpid.ToString(), GetAddress().ToString()); +} + +std::string Beacon::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string Beacon::StatusToString(const BeaconStatusForStorage& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case BeaconStatusForStorage::UNKNOWN: return _("Unknown"); + case BeaconStatusForStorage::PENDING: return _("Pending"); + case BeaconStatusForStorage::ACTIVE: return _("Active"); + case BeaconStatusForStorage::RENEWAL: return _("Renewal"); + case BeaconStatusForStorage::EXPIRED_PENDING: return _("Expired while pending"); + case BeaconStatusForStorage::DELETED: return _("Deleted"); + case BeaconStatusForStorage::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case BeaconStatusForStorage::UNKNOWN: return "Unknown"; + case BeaconStatusForStorage::PENDING: return "Pending"; + case BeaconStatusForStorage::ACTIVE: return "Active"; + case BeaconStatusForStorage::RENEWAL: return "Renewal"; + case BeaconStatusForStorage::EXPIRED_PENDING: return "Expired while pending"; + case BeaconStatusForStorage::DELETED: return "Deleted"; + case BeaconStatusForStorage::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + + int64_t Beacon::Age(const int64_t now) const { return now - m_timestamp; @@ -126,12 +171,12 @@ bool Beacon::Expired(const int64_t now) const bool Beacon::Renewable(const int64_t now) const { - return Age(now) > RENEWAL_AGE; + return (!Expired(now) && Age(now) > RENEWAL_AGE); } bool Beacon::Renewed() const { - return (m_status == BeaconStatusForStorage::RENEWAL && m_prev_beacon_hash != uint256()); + return (m_status == BeaconStatusForStorage::RENEWAL && m_previous_hash != uint256()); } CKeyID Beacon::GetId() const @@ -177,7 +222,7 @@ bool Beacon::operator==(Beacon b) result &= (m_public_key == b.m_public_key); result &= (m_timestamp == b.m_timestamp); result &= (m_hash == b.m_hash); - result &= (m_prev_beacon_hash == b.m_prev_beacon_hash); + result &= (m_previous_hash == b.m_previous_hash); result &= (m_status == b.m_status); return result; @@ -266,6 +311,11 @@ const BeaconRegistry::PendingBeaconMap& BeaconRegistry::PendingBeacons() const return m_pending; } +const std::set& BeaconRegistry::ExpiredBeacons() const +{ + return m_expired_pending; +} + BeaconOption BeaconRegistry::Try(const Cpid& cpid) const { const auto iter = m_beacons.find(cpid); @@ -308,6 +358,17 @@ std::vector BeaconRegistry::FindPending(const Cpid& cpid) const return found; } +const BeaconOption BeaconRegistry::FindHistorical(const uint256& hash) +{ + auto beacon_iter = m_beacon_db.find(hash); + + if (beacon_iter != m_beacon_db.end()) { + return beacon_iter->second; + } + + return nullptr; +} + bool BeaconRegistry::ContainsActive(const Cpid& cpid, const int64_t now) const { if (const BeaconOption beacon = Try(cpid)) { @@ -323,13 +384,13 @@ bool BeaconRegistry::ContainsActive(const Cpid& cpid) const } //! -//! \brief This resets the in-memory maps of the registry. It does NOT -//! clear the LevelDB storage. +//! \brief This resets the in-memory maps of the registry and the LevelDB backing storage. //! void BeaconRegistry::Reset() { m_beacons.clear(); m_pending.clear(); + m_expired_pending.clear(); m_beacon_db.clear(); } @@ -358,7 +419,7 @@ bool BeaconRegistry::TryRenewal(Beacon_ptr& current_beacon_ptr, int& height, con // Set the status to RENEWAL. renewal.m_status = BeaconStatusForStorage::RENEWAL; - renewal.m_prev_beacon_hash = current_beacon_ptr->m_hash; + renewal.m_previous_hash = current_beacon_ptr->m_hash; // Put the renewal beacon into the db. if (!m_beacon_db.insert(renewal.m_hash, height, renewal)) @@ -415,11 +476,11 @@ void BeaconRegistry::Add(const ContractContext& ctx) current_beacon_ptr = beacon_pair_iter->second; // Set the payload m_beacon's prev beacon ctx hash = to the existing beacon's hash. - payload.m_beacon.m_prev_beacon_hash = current_beacon_ptr->m_hash; + payload.m_beacon.m_previous_hash = current_beacon_ptr->m_hash; } else // Effectively Newbie. { - payload.m_beacon.m_prev_beacon_hash = uint256 {}; + payload.m_beacon.m_previous_hash = uint256 {}; } // Legacy beacon contracts before block version 11--just load the beacon: @@ -517,7 +578,7 @@ void BeaconRegistry::Delete(const ContractContext& ctx) deleted_beacon.m_cpid = payload->m_cpid; deleted_beacon.m_hash = ctx.m_tx.GetHash(); - deleted_beacon.m_prev_beacon_hash = last_active_ctx_hash; + deleted_beacon.m_previous_hash = last_active_ctx_hash; deleted_beacon.m_status = BeaconStatusForStorage::DELETED; // Insert the deleted beacon entry in the storage db. @@ -627,14 +688,14 @@ void BeaconRegistry::Revert(const ContractContext& ctx) // Let's proceed anyway. A renewed beacon will have a non-null m_prev_beacon_hash, referring to the // prior beacon record, which could itself be a renewal, or the original advertisement. Regardless, // if found, resurrect that record. - if (!renewal->m_prev_beacon_hash.IsNull()) + if (!renewal->m_previous_hash.IsNull()) { Cpid cpid = iter->first; // Get the hash of the previous beacon. This normally could be a renewal, but // it could also be a gap advertisement or a force advertisement. uint256 renewal_hash = renewal->m_hash; - uint256 resurrect_hash = renewal->m_prev_beacon_hash; + uint256 resurrect_hash = renewal->m_previous_hash; // Erase the beacon that was ordered deleted. m_beacons.erase(iter); @@ -682,7 +743,7 @@ void BeaconRegistry::Revert(const ContractContext& ctx) if (deleted_beacon_record != m_beacon_db.end()) { - auto record_to_restore = m_beacon_db.find(deleted_beacon_record->second->m_prev_beacon_hash); + auto record_to_restore = m_beacon_db.find(deleted_beacon_record->second->m_previous_hash); if (record_to_restore != m_beacon_db.end()) { @@ -736,6 +797,144 @@ int BeaconRegistry::GetDBHeight() return height; } +Beacon_ptr BeaconRegistry::GetBeaconChainletRoot(Beacon_ptr beacon, + std::shared_ptr>> beacon_chain_out) +{ + const auto ChainletErrorHandle = [this](unsigned int i, Beacon_ptr beacon, std::string error_message) { + error("%s: Beacon chainlet is corrupted at link %u for cpid %s: timestamp = %s" PRId64 ", ctx_hash = %s," + "prev_beacon_ctx_hash = %s, status = %s: %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString(), + error_message); + + std::string str_error = strprintf("ERROR: %s: Beacon chainlet is corrupted at link %u for cpid %s: timestamp = %s" + PRId64 ", ctx_hash = %s, prev_beacon_ctx_hash = %s, status = %s: %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString(), + error_message); + + Reset(); + + uiInterface.ThreadSafeMessageBox(str_error, "Gridcoin", CClientUIInterface::MSG_ERROR); + + throw std::runtime_error(std::string {"The beacon registry is corrupted and will be rebuilt on the next start. " + "Please restart."}); + }; + + const auto ChainletLinkLog = [&beacon_chain_out](unsigned int i, Beacon_ptr beacon) { + LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: beacon chainlet link %u for cpid %s: timestamp = %" PRId64 ", ctx_hash = %s," + " prev_beacon_ctx_hash = %s, status = %s.", + __func__, + i, + beacon->m_cpid.ToString(), + beacon->m_timestamp, + beacon->m_hash.GetHex(), + beacon->m_previous_hash.GetHex(), + beacon->StatusToString()); + + if (beacon_chain_out == nullptr) { + return; + } + + beacon_chain_out->push_back(std::make_pair(beacon->m_hash, beacon->m_timestamp)); + }; + + unsigned int i = 0; + + // Given that we have had rare situations where somehow circularity has occurred in the beacon chainlet, which either + // results in the current hash and previous hash being the same, or even suspected previous hash of another entry pointing + // back to a beacon in a circular manner, this vector is used to detect the circularity. + std::vector encountered_hashes; + + // The chain head itself. (This uses a scope to separate beacon_iter.) + { + auto beacon_iter = m_beacon_db.find(beacon->m_hash); + + if (beacon_iter == m_beacon_db.end()) { + // Beacon chainlet chainhead cannot be found. This is fatal. + ChainletErrorHandle(i, beacon, "not found in the registry"); + } + + // Make sure status is renewed or active. + if (beacon_iter->second->m_status != BeaconStatusForStorage::ACTIVE + && beacon_iter->second->m_status != BeaconStatusForStorage::RENEWAL) { + ChainletErrorHandle(i, beacon, "beacon status is not active or renewal"); + } + + encountered_hashes.push_back(beacon->m_hash); + + ChainletLinkLog(i, beacon); + + ++i; + } + + // Walk back the entries in the historical beacon map linked by renewal prev tx hash until the first + // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier + // than here. + while (beacon->Renewed()) + { + // Select previous beacon in chainlet + auto beacon_iter = m_beacon_db.find(beacon->m_previous_hash); + + if (beacon_iter == m_beacon_db.end()) { + ChainletErrorHandle(i, beacon, "previous beacon not found in the registry"); + } + + if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_previous_hash) != encountered_hashes.end()) { + // If circularity is found this is an indication of corruption of beacon state and is fatal. + // Produce an error message, reset the beacon registry, and require a restart of the node. + ChainletErrorHandle(i, beacon, "circularity encountered"); + } + + // Reassign previous beacon to beacon. + beacon = beacon_iter->second; + + encountered_hashes.push_back(beacon->m_hash); + + if (beacon_chain_out != nullptr) { + ChainletLinkLog(i, beacon); + } + + ++i; + } + + // Check of initial advertised beacon's previous hash. This should point to the pending beacon that was activated and not + // anywhere else. + { + // Select previous beacon in chainlet + auto beacon_iter = m_beacon_db.find(beacon->m_previous_hash); + + if (beacon_iter == m_beacon_db.end()) { + ChainletErrorHandle(i, beacon, "previous beacon not found in the registry"); + } + + // Make sure status of previous beacon is pending. + if (beacon_iter->second->m_status != BeaconStatusForStorage::PENDING) { + ChainletErrorHandle(i, beacon, "previous beacon to the beacon marked active is not pending"); + } + + if (std::find(encountered_hashes.begin(), encountered_hashes.end(), beacon->m_previous_hash) != encountered_hashes.end()) { + // If circularity is found this is an indication of corruption of beacon state and is fatal. + // Produce an error message, reset the beacon registry, and require a restart of the node. + ChainletErrorHandle(i, beacon, "circularity encountered"); + } + + // Note that we do not actually walk back to the pending beacon. The parameter beacon remains at the activated beacon. + } + + return beacon; +} + bool BeaconRegistry::NeedsIsContractCorrection() { return m_beacon_db.NeedsIsContractCorrection(); @@ -828,61 +1027,78 @@ void BeaconRegistry::ActivatePending( { LogPrint(LogFlags::BEACON, "INFO: %s: Called for superblock at height %i.", __func__, height); - // Activate the pending beacons that are not expired with respect to pending age. + // It is possible that more than one pending beacon with the same CPID can be attempted to be + // activated in the same superblock. The behavior here to agree with the original implementation + // is the last one. Here we are going to use a map keyed by the CPID with the array style insert + // to ensure that the LAST pending beacon verified is the one activated. + BeaconMap verified_beacons; + for (const auto& id : beacon_ids) { auto iter_pair = m_pending.find(id); if (iter_pair != m_pending.end()) { + bool already_found = (verified_beacons.find(iter_pair->second->m_cpid) != verified_beacons.end()); - Beacon_ptr found_pending_beacon = iter_pair->second; + if (already_found) { + LogPrint(LogFlags::BEACON, "INFO: %s: More than one pending beacon verified for the same CPID %s. Overriding previous" + "verified beacon.", + __func__, + iter_pair->second->m_cpid.ToString()); + } - // Create a new beacon to activate from the found pending beacon. - Beacon activated_beacon(*iter_pair->second); + verified_beacons[iter_pair->second->m_cpid] = iter_pair->second; + } + } - // Update the new beacon's prev hash to be the hash of the pending beacon that is being activated. - activated_beacon.m_prev_beacon_hash = found_pending_beacon->m_hash; + // Activate the pending beacons that are not expired with respect to pending age as of the time of verification (the + // committing of the superblock). + for (const auto& iter_pair : verified_beacons) { - // We are going to have to use a composite hash for these because activation is not done as - // individual transactions. Rather groups are done in each superblock under one hash. The - // hash of the block hash, and the pending beacon that is being activated's hash is sufficient. - activated_beacon.m_status = BeaconStatusForStorage::ACTIVE; + Beacon_ptr last_pending_beacon = iter_pair.second; - activated_beacon.m_hash = Hash(block_hash, found_pending_beacon->m_hash); + // Create a new beacon to activate from the found pending beacon. + Beacon activated_beacon(*iter_pair.second); - LogPrint(LogFlags::BEACON, "INFO: %s: Activating beacon for cpid %s, address %s, hash %s.", - __func__, - activated_beacon.m_cpid.ToString(), - activated_beacon.GetAddress().ToString(), - activated_beacon.m_hash.GetHex()); - - // It is possible that more than one pending beacon with the same CPID can be attempted to be - // activated in the same superblock. The behavior here to agree with the original implementation - // is the last one. So get rid of any previous one activated/inserted. - auto found_already_activated_beacon = m_beacon_db.find(activated_beacon.m_hash); - if (found_already_activated_beacon != m_beacon_db.end()) - { - m_beacon_db.erase(activated_beacon.m_hash); - } + // Update the new beacon's prev hash to be the hash of the pending beacon that is being activated. + activated_beacon.m_previous_hash = last_pending_beacon->m_hash; - m_beacon_db.insert(activated_beacon.m_hash, height, activated_beacon); + // We are going to have to use a composite hash for these because activation is not done as + // individual transactions. Rather groups are done in each superblock under one hash. The + // hash of the block hash, and the pending beacon that is being activated's hash is sufficient. + activated_beacon.m_status = BeaconStatusForStorage::ACTIVE; - // This is the subscript form of insert. Important here because an activated beacon should - // overwrite any existing entry in the m_beacons map. - m_beacons[activated_beacon.m_cpid] = m_beacon_db.find(activated_beacon.m_hash)->second; + activated_beacon.m_hash = Hash(block_hash, last_pending_beacon->m_hash); - // Remove the pending beacon entry from the pending map. (Note this entry still exists in the historical - // table and the db. - m_pending.erase(iter_pair); - } + LogPrint(LogFlags::BEACON, "INFO: %s: Activating beacon for cpid %s, address %s, hash %s.", + __func__, + activated_beacon.m_cpid.ToString(), + activated_beacon.GetAddress().ToString(), + activated_beacon.m_hash.GetHex()); + + m_beacon_db.insert(activated_beacon.m_hash, height, activated_beacon); + + // This is the subscript form of insert. Important here because an activated beacon should + // overwrite any existing entry in the m_beacons map. + m_beacons[activated_beacon.m_cpid] = m_beacon_db.find(activated_beacon.m_hash)->second; + + // Remove the pending beacon entry from the pending map. (Note this entry still exists in the historical + // table and the db. + m_pending.erase(iter_pair.second->GetId()); } - // Discard pending beacons that are expired with respect to pending age. + // Clear the expired pending beacon set. There is no need to retain expired beacons beyond one SB boundary (which is when + // this method is called) as this gives ~960 blocks of reorganization depth before running into the slight possibility that + // a different SB could verify a different pending beacon that should be resurrected to be verified. + m_expired_pending.clear(); + + // Mark remaining pending beacons that are expired with respect to pending age as expired and move to the expired map. for (auto iter = m_pending.begin(); iter != m_pending.end(); /* no-op */) { PendingBeacon pending_beacon(*iter->second); + // If the pending beacon has expired with no action remove the pending beacon. if (pending_beacon.PendingExpired(superblock_time)) { // Set the expired pending beacon's previous beacon hash to the beacon entry's hash. - pending_beacon.m_prev_beacon_hash = pending_beacon.m_hash; + pending_beacon.m_previous_hash = pending_beacon.m_hash; // Mark the status as EXPIRED_PENDING. pending_beacon.m_status = BeaconStatusForStorage::EXPIRED_PENDING; @@ -896,7 +1112,19 @@ void BeaconRegistry::ActivatePending( pending_beacon.m_hash.GetHex()); // Insert the expired pending beacon into the db. - m_beacon_db.insert(pending_beacon.m_hash, height, static_cast(pending_beacon)); + if (!m_beacon_db.insert(pending_beacon.m_hash, height, static_cast(pending_beacon))) { + LogPrintf("WARN: %s: Attempt to insert an expired pending beacon entry for cpid %s in the beacon registry where " + "one with that hash key (%s) already exists.", + __func__, + pending_beacon.m_cpid.ToString(), + pending_beacon.m_hash.GetHex()); + } + + // Insert the expired pending beacon into the m_expired_pending set. We do the find here because the insert above + // created a shared pointer to the beacon object we want to hold a reference to. To save memory we do not want to + // use a copy. + m_expired_pending.insert(m_beacon_db.find(pending_beacon.m_hash)->second); + // Remove the pending beacon entry from the m_pending map. iter = m_pending.erase(iter); } else { @@ -913,13 +1141,14 @@ void BeaconRegistry::Deactivate(const uint256 superblock_hash) // Find beacons that were activated by the superblock to be reverted and restore them to pending status. These come // from the beacon db. for (auto iter = m_beacons.begin(); iter != m_beacons.end();) { - Cpid cpid = iter->second->m_cpid; - - uint256 activation_hash = Hash(superblock_hash, iter->second->m_prev_beacon_hash); + uint256 activation_hash = Hash(superblock_hash, iter->second->m_previous_hash); // If we have an active beacon whose hash matches the composite hash assigned by ActivatePending... if (iter->second->m_hash == activation_hash) { - // Find the pending beacon entry in the db before the activation. This is the previous state record. - auto pending_beacon_entry = m_beacon_db.find(iter->second->m_prev_beacon_hash); + Cpid cpid = iter->second->m_cpid; + + // Find the pending beacon entry in the db before the activation. This is the previous state record. NOTE that this + // find pulls the record from leveldb back into memory if the record had been passivated for memory savings before. + auto pending_beacon_entry = m_beacon_db.find(iter->second->m_previous_hash); // If not found for some reason, move on. if (pending_beacon_entry == m_beacon_db.end()) @@ -948,333 +1177,198 @@ void BeaconRegistry::Deactivate(const uint256 superblock_hash) } } - // Find pending beacons that were removed from the pending beacon map and marked PENDING_EXPIRED and restore them - // back to pending status. Unfortunately, the beacon_db has to be traversed for this, because it is the only entity - // that has the records at this point. This will only be done very rarely, when a reorganization crosses a - // superblock commit. - auto iter = m_beacon_db.begin(); - - while (iter != m_beacon_db.end()) - { - // The cpid in the historical beacon record to be matched. - Cpid cpid = iter->second->m_cpid; - - uint256 match_hash = Hash(superblock_hash, iter->second->m_prev_beacon_hash); - - // If the calculated match_hash matches the key (hash) of the historical beacon record, then - // restore the previous record pointed to by the historical beacon record to the pending map. - if (match_hash == iter->first) - { - uint256 resurrect_pending_hash = iter->second->m_prev_beacon_hash; - - if (!resurrect_pending_hash.IsNull()) - { - Beacon_ptr resurrected_pending = m_beacon_db.find(resurrect_pending_hash)->second; - - // Check that the status of the beacon to resurrect is PENDING. If it is not log an error but continue - // anyway. - if (resurrected_pending->m_status != BeaconStatusForStorage::PENDING) - { - error("%s: Superblock hash %s: The beacon for cpid %s pointed to by an EXPIRED_PENDING beacon to be " - "put back in PENDING status does not have the expected status of PENDING. The beacon hash is %s " - "and the status is %i", - __func__, - superblock_hash.GetHex(), - cpid.ToString(), - resurrected_pending->m_hash.GetHex(), - resurrected_pending->m_status.Raw()); - } - - // Put the record in m_pending. - m_pending[resurrected_pending->GetId()] = resurrected_pending; - } - else - { - error("%s: Superblock hash %s: The beacon for cpid %s with an EXPIRED_PENDING status has no valid " - "previous beacon hash with which to restore the PENDING beacon.", + // With the newer m_expired_pending set, the resurrection of expired pending beacons is relatively painless. We traverse + // the m_expired_pending set and simply restore the pending beacon pointed to as the antecedent of each expired beacon in + // the map. This is done by the m_beacon_db.find which will pull the beacon record from leveldb if it does not exist in + // memory, which makes this passivation-proof up to a reorganization depth of the interval between two SB's (approximately + // 960 blocks). + for (const auto& iter : m_expired_pending) { + // Get the pending beacon entry that is the antecedent of the expired entry. + auto pending_beacon_entry = m_beacon_db.find(iter->m_previous_hash); + + // Resurrect pending beacon entry + if (!m_pending.insert(std::make_pair(pending_beacon_entry->second->GetId(), pending_beacon_entry->second)).second) { + LogPrintf("WARN: %s: Resurrected pending beacon entry, hash %s, from expired pending beacon for cpid %s during deactivation " + " of superblock hash %s already exists in the pending beacon map corresponding to beacon address %s.", __func__, + pending_beacon_entry->second->m_hash.GetHex(), + pending_beacon_entry->second->m_cpid.ToString(), superblock_hash.GetHex(), - cpid.ToString()); - } - } //matched EXPIRED_PENDING record - - iter = m_beacon_db.advance(iter); - } // m_beacon_db traversal -} - -int BeaconRegistry::Initialize() -{ - int height = m_beacon_db.Initialize(m_pending, m_beacons); - - LogPrint(LogFlags::BEACON, "INFO %s: m_beacon_db size after load: %u", __func__, m_beacon_db.size()); - LogPrint(LogFlags::BEACON, "INFO %s: m_beacons size after load: %u", __func__, m_beacons.size()); + pending_beacon_entry->second->GetAddress().ToString() + ); + } + } - return height; -} + // We clear the expired pending beacon map, as when the chain moves forward (perhaps on a different fork), the SB boundary will + // (during the activation) repopulate the m_expired_pending map with a new set of expired_beacons. (This is very, very likely + // to be the same set, BTW.) + m_expired_pending.clear(); + + // Note that making this foolproof in a reorganization across more than one SB boundary means we would have to repopulate the + // expired pending beacon map from the PREVIOUS set of expired pending beacons. This would require a traversal of the entire + // leveldb beacon structure for beacons, as it is keyed by beacon hash, not CPID or CKeyID. The expense is not worth it. In + // artificial reorgs for testing purposes on testnet, where the chain is reorganized back thousands of blocks and then reorganized + // forward along the same effective branch, the same superblocks will be restored using the same beacon activations as before, + // which means in effect none of the expired beacons are ever used. In a real fork scenario, not repopulating the expired_pending + // map limits the 100% foolproof reorg to the interval between SB's, which is approximately 960 blocks. This depth of reorg + // in an operational network scenario is almost inconceivable, and if it actually happens we have other problems much worse + // than the SLIGHT possibility of a different pending beacon being activated with the committed SB. + + // The original algorithm, which traversed m_beacon_db using an iterator, was actually broken, because passivation removes + // elements from the m_beacon_db in memory map if there is only one remaining reference, which is the m_historical map that holds + // references to all historical (non-current) entries. In the original algorithm, expired_pending entries were created in the + // m_beacon_db, and the pending beacon pointer references were removed from m_pending, but no in memory map other than + // m_historical kept a reference to the expired entry. This qualified the expired entry for passivation, so would + // not necessarily be present to find in an iterator traversal of m_beacon_db. The iterator style traversal of m_beacon_db, unlike + // the find, does NOT have the augmentation to pull passivated items from leveldb not in memory, because this would be + // exceedingly expensive. + } -int BeaconRegistry::BeaconDB::Initialize(PendingBeaconMap& m_pending, BeaconMap& m_beacons) -{ - bool status = true; - int height = 0; - uint32_t version = 0; - bool needs_IsContract_correction = false; - - // First load the beacon db version from LevelDB and check it against the constant in the class. +//! +//! \brief BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries. This is a specialization of the RegistryDB template +//! HandleCurrentHistoricalEntries specific to Beacons. It handles the pending/active/renewal/expired pending/deleted +//! states and their interaction with the active entries map (m_beacons) and the pending entries map (m_pending) when loading +//! the beacon history from the beacon leveldb backing store during registry initialization. It is not intended to be used +//! for other specializations/overrides. +//! +//! \param entries +//! \param pending_entries +//! \param entry +//! \param historical_entry_ptr +//! \param recnum +//! \param key_type +//! +template<> void BeaconRegistry::BeaconDB::HandleCurrentHistoricalEntries(GRC::BeaconRegistry::BeaconMap& entries, + GRC::BeaconRegistry::PendingBeaconMap& pending_entries, + std::set& expired_entries, + const Beacon& entry, + entry_ptr& historical_entry_ptr, + const uint64_t& recnum, + const std::string& key_type) +{ + // Note that in this specialization, entry.m_cpid and entry.GetId() are used for the map keys. In the general template, + // entry.Key() is used (which here is the same as entry.m_cpid). No generalized method to implement entry.PendingKey() + // has been implemented up to this point, because the pending map is actually only used here in the beacon + // specialization. + + // If there is another registry class that arises that actually needs to use the "pending" state then it would be + // necessary to implement the PendingKey() call in the template. + + if (entry.m_status == BeaconStatusForStorage::PENDING) { - CTxDB txdb("r"); - - std::pair key = std::make_pair("beacon_db", "version"); - - bool status = txdb.ReadGenericSerializable(key, version); - - if (!status) version = 0; + LogPrint(LogFlags::CONTRACT, "INFO: %s: %ss: pending entry insert: cpid %s, address %s, timestamp %" PRId64 ", " + "hash %s, previous_hash %s, status %s, recnum %" PRId64 ".", + __func__, + key_type, + entry.KeyValueToString().first, // cpid + entry.KeyValueToString().second, // address + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev beacon transaction hash + entry.StatusToString(), // status + recnum + ); + + // Insert the pending beacon in the pending map. + pending_entries[entry.GetId()] = historical_entry_ptr; } - if (version != CURRENT_VERSION) + if (entry.m_status == BeaconStatusForStorage::ACTIVE || entry.m_status == BeaconStatusForStorage::RENEWAL) { - LogPrint(LogFlags::BEACON, "WARNING: %s: Version level of the beacon db stored in LevelDB, %u, does not " - "match that required in this code level, version %u. Clearing the LevelDB beacon " - "storage and setting version level to match this code level.", + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: entry insert: cpid %s, address %s, timestamp %" PRId64 ", " + "hash %s, previous_hash %s, status %s, recnum %" PRId64 ".", __func__, - version, - CURRENT_VERSION); - - // Version 1, which corresponds to the 5.2.1.0 release contained a bug in ApplyContracts which prevented the - // application of multiple beacon contracts contained in a block under certain circumstances. In particular, - // the case with superblock 2053368, which contained beacon activations AND two beacon contracts was affected - // and once recorded in CBlockIndex, IsContract() returns the incorrect value. This flag will be used by - // ApplyContracts to check every block during the ContractReplay to correct the situation. - if (version == 1) + key_type, + entry.KeyValueToString().first, // cpid + entry.KeyValueToString().second, // address + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev beacon transaction hash + entry.StatusToString(), // status + recnum + ); + + // Insert or replace the existing map entry for the cpid with the latest active or renewed for that CPID. + entries[entry.m_cpid] = historical_entry_ptr; + + // Delete any entry in the pending map with THE SAME public key. + auto pending_to_delete = pending_entries.find(entry.GetId()); + if (pending_to_delete != pending_entries.end()) { - needs_IsContract_correction = true; + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: pending entry delete after active insert: cpid %s, address %s, " + "timestamp %" PRId64 ", hash %s, previous_hash %s, beacon status %s, recnum %" PRId64 ".", + __func__, + key_type, + pending_to_delete->second->KeyValueToString().first, // cpid + pending_to_delete->second->KeyValueToString().second, // address + pending_to_delete->second->m_timestamp, // timestamp + pending_to_delete->second->m_hash.GetHex(), // transaction hash + pending_to_delete->second->m_previous_hash.GetHex(), // prev beacon transaction hash + pending_to_delete->second->StatusToString(), // status + recnum + ); + + pending_entries.erase(pending_to_delete); } - - clear_leveldb(); - - // After clearing the LevelDB state, set the needs IsContract correction to the proper state. If the version that - // was on disk was 1, then this will be set to true. - SetNeedsIsContractCorrection(needs_IsContract_correction); - - LogPrint(LogFlags::BEACON, "INFO: %s: LevelDB beacon area cleared. Version level set to %u.", - __func__, - CURRENT_VERSION); } - - // If LoadDBHeight not successful or height is zero then LevelDB has not been initialized before. - // LoadDBHeight will also set the private member variable m_height_stored from LevelDB for this first call. - if (!LoadDBHeight(height) || !height) - { - return height; + if (entry.m_status == BeaconStatusForStorage::ACTIVE) { + // Note that in the original activation, all the activations happen for a superblock, and then the expired_entry set is + // cleared and then new expired entries recorded from the just committed SB. This method operates at the record level, but + // clearing the expired_entries for each ACTIVE record posting will achieve the same effect, because the entries are ordered + // the proper way. It is a little bit of undesired work, but it is not worth the complexity of feeding the boundaries + // of the group of verified beacons to activate. + expired_entries.clear(); } - else // LevelDB already initialized from a prior run. - { - // Set m_database_init to true. This will cause LoadDBHeight hereinafter to simply report - // the value of m_height_stored rather than loading the stored height from LevelDB. - m_database_init = true; - // We are in a restart where at least some of a rescan was completed during a prior run. It is possible - // that the rescan may have not been completed before a shutdown was issued. In that case the - // needs_IsContract_correction flag will be set to true in LevelDB, so restore that state for this run - // to ensure the correction finishes. When ReplayContracts finishes the corrections, it will mark the flag - // false. - CTxDB txdb("r"); - - std::pair key = std::make_pair("beacon_db", "needs_IsContract_correction"); - - bool status = txdb.ReadGenericSerializable(key, needs_IsContract_correction); - - if (!status) needs_IsContract_correction = false; - - m_needs_IsContract_correction = needs_IsContract_correction; - } - - LogPrint(LogFlags::BEACON, "INFO: %s: db stored height at block %i.", - __func__, - height); - - - // Now load the beacons from LevelDB. - - std::string key_type = "beacon"; - - // This temporary map is keyed by record number, which insures the replay down below occurs in the right order. - StorageBeaconMapByRecordNum storage_by_record_num; - - // Code block to scope the txdb object. + if (entry.m_status == BeaconStatusForStorage::EXPIRED_PENDING) { - CTxDB txdb("r"); - - uint256 hash_hint = uint256(); - - // Load the temporary which is similar to m_historical, except the elements are of type BeaconStorage instead - // of Beacon. - status = txdb.ReadGenericSerializablesToMapWithForeignKey(key_type, storage_by_record_num, hash_hint); - } - - if (!status) - { - if (height > 0) - { - // For the height be greater than zero from the height K-V, but the read into the map to fail - // means the storage in LevelDB must be messed up in the beacon area and not be in concordance with - // the beacon_db K-V's. Therefore clear the whole thing. - clear(); - } - - // Return height of zero. - return 0; + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: expired pending entry delete: cpid %s, address %s, timestamp %" PRId64 ", " + "hash %s, previous_hash %s, beacon status %s, recnum %" PRId64 ".", + __func__, + key_type, + entry.KeyValueToString().first, // cpid + entry.KeyValueToString().second, // address + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev beacon transaction hash + entry.StatusToString(), // status + recnum + ); + + // Insert the expired pending entry into the expired entries set. + expired_entries.insert(historical_entry_ptr); + + // Delete any entry in the pending map that is marked expired. + pending_entries.erase(entry.GetId()); } - uint64_t recnum_high_watermark = 0; - uint64_t number_passivated = 0; - - // Replay the storage map. The iterator is ordered by record number, which ensures that the correct - // elements end up in m_beacons and m_pending. Storage entries that are "mark deletions" are also inserted - // and any entry in m_beacons (the active map) is removed. - for (const auto& iter : storage_by_record_num) + if (entry.m_status == BeaconStatusForStorage::DELETED) // Erase any entry in m_beacons and m_pending for the CPID. { - const uint64_t& recnum = iter.first; - Beacon beacon = static_cast(iter.second); - - recnum_high_watermark = std::max(recnum_high_watermark, recnum); - - LogPrint(LogFlags::BEACON, "INFO: %s: m_historical insert: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u, recnum = %" PRId64 ".", + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s: entry delete: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " + "previous_hash %s, beacon status %s, recnum %" PRId64 ".", __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw(), // status - recnum - ); - - // Insert the entry into the historical map. This includes ctx's where the beacon is marked deleted. - // --------------- hash ---------- does NOT include the Cpid. - m_historical[iter.second.m_hash] = std::make_shared(beacon); - Beacon_ptr& historical_beacon_ptr = m_historical[iter.second.m_hash]; - - BeaconRegistry::HistoricalBeaconMap::iterator prev_historical_iter = m_historical.end(); - - if (!historical_beacon_ptr->m_prev_beacon_hash.IsNull()) - { - prev_historical_iter = m_historical.find(historical_beacon_ptr->m_prev_beacon_hash); - } - - if (beacon.m_status == BeaconStatusForStorage::PENDING) - { - LogPrint(LogFlags::BEACON, "INFO: %s: m_pending insert: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u - (1) PENDING, recnum = %" PRId64 ".", - __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw(), // status - recnum - ); - - // Insert the pending beacon in the pending map. - m_pending[beacon.GetId()] = historical_beacon_ptr; - } - - if (beacon.m_status == BeaconStatusForStorage::ACTIVE || beacon.m_status == BeaconStatusForStorage::RENEWAL) - { - LogPrint(LogFlags::BEACON, "INFO: %s: m_beacons insert: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u - (2) ACTIVE or (3) RENEWAL, recnum = %" PRId64 ".", - __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw(), // status - recnum - ); - - // Insert or replace the existing map entry for the cpid with the latest active or renewed for that CPID. - m_beacons[beacon.m_cpid] = historical_beacon_ptr; - - // Delete any entry in the pending map with THE SAME public key. - auto pending_to_delete = m_pending.find(beacon.GetId()); - if (pending_to_delete != m_pending.end()) - { - LogPrint(LogFlags::BEACON, "INFO: %s: m_pending delete after active insert: cpid %s, address %s, " - "timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u - (2), recnum = %" PRId64 ".", - __func__, - pending_to_delete->second->m_cpid.ToString(), // cpid - pending_to_delete->second->GetAddress().ToString(), // address - pending_to_delete->second->m_timestamp, // timestamp - pending_to_delete->second->m_hash.GetHex(), // transaction hash - pending_to_delete->second->m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - pending_to_delete->second->m_status.Raw(), // status - recnum - ); - - m_pending.erase(pending_to_delete); - } - } - - if (beacon.m_status == BeaconStatusForStorage::EXPIRED_PENDING) - { - LogPrint(LogFlags::BEACON, "INFO: %s: m_pending delete: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u, recnum = %" PRId64 ".", - __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw(), // status - recnum - ); - - // Delete any entry in the pending map that is marked expired. - m_pending.erase(beacon.GetId()); - } - - if (beacon.m_status == BeaconStatusForStorage::DELETED) // Erase any entry in m_beacons and m_pending for the CPID. - { - LogPrint(LogFlags::BEACON, "INFO: %s: m_beacons delete: cpid %s, address %s, timestamp %" PRId64 ", hash %s, " - "prev_beacon_hash %s, beacon status = %u, recnum = %" PRId64 ".", - __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw(), // status - recnum - ); - - m_beacons.erase(beacon.m_cpid); - m_pending.erase(beacon.m_public_key.GetID()); - } - - if (prev_historical_iter != m_historical.end()) - { - // Note that passivation is not expected to be successful for every call. See the comments - // in the passivate() function. - std::pair passivation_result - = passivate(prev_historical_iter); - - number_passivated += passivation_result.second; - } + key_type, + entry.KeyValueToString().first, // cpid + entry.KeyValueToString().second, // address + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev beacon transaction hash + entry.StatusToString(), // status + recnum + ); + + entries.erase(entry.m_cpid); + pending_entries.erase(entry.m_public_key.GetID()); } +} - LogPrint(LogFlags::BEACON, "INFO: %s: number of historical records passivated: %" PRId64 ".", - __func__, - number_passivated); - - // Set the in-memory record number stored variable to the highest recnum encountered during the replay above. - m_recnum_stored = recnum_high_watermark; +int BeaconRegistry::Initialize() +{ + int height = m_beacon_db.Initialize(m_beacons, m_pending, m_expired_pending); - // Set the needs passivation flag to true, because the one-by-one passivation done above may not catch everything. - m_needs_passivation = true; + LogPrint(LogFlags::BEACON, "INFO: %s: m_beacon_db size after load: %u", __func__, m_beacon_db.size()); + LogPrint(LogFlags::BEACON, "INFO: %s: m_beacons size after load: %u", __func__, m_beacons.size()); return height; } @@ -1283,6 +1377,7 @@ void BeaconRegistry::ResetInMemoryOnly() { m_beacons.clear(); m_pending.clear(); + m_expired_pending.clear(); m_beacon_db.clear_in_memory_only(); } @@ -1291,8 +1386,13 @@ uint64_t BeaconRegistry::PassivateDB() return m_beacon_db.passivate_db(); } +BeaconRegistry::BeaconDB &BeaconRegistry::GetBeaconDB() +{ + return m_beacon_db; +} + // This is static and called by the scheduler. -void BeaconRegistry::RunBeaconDBPassivation() +void BeaconRegistry::RunDBPassivation() { TRY_LOCK(cs_main, locked_main); @@ -1306,308 +1406,7 @@ void BeaconRegistry::RunBeaconDBPassivation() beacons.PassivateDB(); } -// Required to make the linker happy. -constexpr uint32_t BeaconRegistry::BeaconDB::CURRENT_VERSION; - -void BeaconRegistry::BeaconDB::clear_in_memory_only() -{ - m_historical.clear(); - m_database_init = false; - m_height_stored = 0; - m_recnum_stored = 0; - m_needs_passivation = false; -} - -bool BeaconRegistry::BeaconDB::clear_leveldb() -{ - bool status = true; - - CTxDB txdb("rw"); - - std::string key_type = "beacon"; - uint256 start_key_hint_beacon = uint256(); - - status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_beacon); - - key_type = "beacon_db"; - std::string start_key_hint_beacon_db {}; - - status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_beacon_db); - - // We want to write back into LevelDB the revision level of the db in the running code. - std::pair key = std::make_pair(key_type, "version"); - status &= txdb.WriteGenericSerializable(key, CURRENT_VERSION); - - m_height_stored = 0; - m_recnum_stored = 0; - m_database_init = false; - m_needs_passivation = false; - m_needs_IsContract_correction = false; - - return status; -} - -uint64_t BeaconRegistry::BeaconDB::passivate_db() -{ - uint64_t number_passivated = 0; - - // Don't bother to go through the historical beacon map unless the needs passivation flag is set. This makes - // this function extremely light for most calls from the periodic schedule. - if (m_needs_passivation) - { - for (auto iter = m_historical.begin(); iter != m_historical.end(); /*no-op*/) - { - // The passivate function increments the iterator. - std::pair result = passivate(iter); - - iter = result.first; - number_passivated += result.second; - - } - } - - LogPrint(BCLog::LogFlags::BEACON, "INFO %s: Passivated %" PRId64 " elements from beacon db.", - __func__, - number_passivated); - - // Set needs passivation flag to false after passivating the db. - m_needs_passivation = false; - - return number_passivated; -} - -bool BeaconRegistry::BeaconDB::clear() -{ - clear_in_memory_only(); - - return clear_leveldb(); -} - -size_t BeaconRegistry::BeaconDB::size() -{ - return m_historical.size(); -} - -bool BeaconRegistry::BeaconDB::NeedsIsContractCorrection() -{ - return m_needs_IsContract_correction; -} - -bool BeaconRegistry::BeaconDB::SetNeedsIsContractCorrection(bool flag) -{ - // Update the in-memory flag. - m_needs_IsContract_correction = flag; - - // Update LevelDB - CTxDB txdb("rw"); - - std::pair key = std::make_pair("beacon_db", "needs_IsContract_correction"); - - return txdb.WriteGenericSerializable(key, m_needs_IsContract_correction); -} - -bool BeaconRegistry::BeaconDB::StoreDBHeight(const int& height_stored) -{ - // Update the in-memory bookmark variable. - m_height_stored = height_stored; - - // Update LevelDB. - CTxDB txdb("rw"); - - std::pair key = std::make_pair("beacon_db", "height_stored"); - - return txdb.WriteGenericSerializable(key, height_stored); -} - -bool BeaconRegistry::BeaconDB::LoadDBHeight(int& height_stored) -{ - bool status = true; - - // If the database has already been initialized (which includes loading the height to what the - // beacon storage was updated), then just report the valud of m_height_stored, otherwise - // pull the value from LevelDB. - if (m_database_init) - { - height_stored = m_height_stored; - } - else - { - CTxDB txdb("r"); - - std::pair key = std::make_pair("beacon_db", "height_stored"); - - bool status = txdb.ReadGenericSerializable(key, height_stored); - - if (!status) height_stored = 0; - - m_height_stored = height_stored; - } - - return status; -} - -bool BeaconRegistry::BeaconDB::insert(const uint256 &hash, const int& height, const Beacon &beacon) -{ - bool status = false; - - - if (m_historical.find(hash) != m_historical.end()) - { - return status; - } - else - { - LogPrint(LogFlags::BEACON, "INFO %s - store beacon: cpid %s, address %s, height %i, timestamp %" PRId64 - ", hash %s, prev_beacon_hash %s, status = %u.", - __func__, - beacon.m_cpid.ToString(), // cpid - beacon.GetAddress().ToString(), // address - height, // height - beacon.m_timestamp, // timestamp - beacon.m_hash.GetHex(), // transaction hash - beacon.m_prev_beacon_hash.GetHex(), // prev beacon transaction hash - beacon.m_status.Raw() // status - ); - - m_historical.insert(std::make_pair(hash, std::make_shared(beacon))); - - status = Store(hash, static_cast(beacon)); - - if (height) status &= StoreDBHeight(height); - - // Set needs passivation flag to true to allow the scheduled passivation to remove unnecessary records from - // memory. - m_needs_passivation = true; - - return status; - } -} - -bool BeaconRegistry::BeaconDB::erase(const uint256& hash) -{ - auto iter = m_historical.find(hash); - - if (iter != m_historical.end()) - { - m_historical.erase(hash); - } - - return Delete(hash); -} - -// Note that this function uses the shared pointer use_count() to determine whether an element in -// m_historical is referenced by either the m_beacons or m_pending map and if not, erases it, leaving the backing -// state in LevelDB untouched. Note that the use of use_count() in multithreaded environments must be carefully -// considered because it is only approximate. In this case it is exact. Access to the entire BeaconRegistry class -// and everything in it is protected by the cs_main lock and is therefore single threaded. This method of passivating -// is MUCH faster than searching through m_beacons and m_pending for each element, because they are not keyed by hash. -// -// Note that this function acts very similarly to the map erase function with an iterator argument, but with a standard -// pair returned. The first part of the pair a boolean as to whether the element was passivated, and the -// second is an iterator to the next element. This is designed to be traversed in a for loop just like map erase. -std::pair - BeaconRegistry::BeaconDB::passivate(BeaconRegistry::HistoricalBeaconMap::iterator& iter) -{ - // m-historical itself holds one reference, additional references can be held by m_beacons and m_pending. - // If there is only one reference then remove the shared_pointer from m_historical, which will implicitly destroy - // the shared_pointer object. - if (iter->second.use_count() == 1) - { - iter = m_historical.erase(iter); - return std::make_pair(iter, true); - } - else - { - LogPrint(BCLog::LogFlags::BEACON, "INFO: %s: Passivate called for historical beacon record with hash %s that " - "has existing reference count %li. This is expected under certain situations, " - "such as a forced (re)advertisement, where the new pending beacon is allowed " - "to expire, while the original is still active.", - __func__, - iter->second->m_hash.GetHex(), - iter->second.use_count()); - - ++iter; - return std::make_pair(iter, false); - } -} - -BeaconRegistry::HistoricalBeaconMap::iterator BeaconRegistry::BeaconDB::begin() +template<> const std::string BeaconRegistry::BeaconDB::KeyType() { - return m_historical.begin(); -} - -BeaconRegistry::HistoricalBeaconMap::iterator BeaconRegistry::BeaconDB::end() -{ - return m_historical.end(); -} - -BeaconRegistry::HistoricalBeaconMap::iterator BeaconRegistry::BeaconDB::find(const uint256& hash) -{ - // See if beacon from that ctx_hash is already in the historical map. If so, get iterator. - auto iter = m_historical.find(hash); - - // If it isn't, attempt to load the beacon from LevelDB into the map. - if (iter == m_historical.end()) - { - StorageBeacon beacon; - - // If the load from LevelDB is successful, insert into the historical map and return the iterator. - if (Load(hash, beacon)) - { - iter = m_historical.insert(std::make_pair(hash, std::make_shared(static_cast(beacon)))).first; - - // Set the needs passivation flag to true - m_needs_passivation = true; - } - } - - // Note that if there is no entry in m_historical, and also there is no K-V in LevelDB, then an - // iterator at end() will be returned. - return iter; -} - -// TODO: A poor man's forward iterator. Implement a full wrapper iterator. Maybe don't need it though. -BeaconRegistry::HistoricalBeaconMap::iterator BeaconRegistry::BeaconDB::advance(HistoricalBeaconMap::iterator iter) -{ - return ++iter; -} - -bool BeaconRegistry::BeaconDB::Store(const uint256& hash, const StorageBeacon& beacon) -{ - CTxDB txdb("rw"); - - ++m_recnum_stored; - - std::pair key = std::make_pair("beacon", hash); - - return txdb.WriteGenericSerializable(key, std::make_pair(m_recnum_stored, beacon)); -} - -bool BeaconRegistry::BeaconDB::Load(const uint256& hash, StorageBeacon& beacon) -{ - CTxDB txdb("r"); - - std::pair key = std::make_pair("beacon", hash); - - std::pair beacon_pair; - - bool status = txdb.ReadGenericSerializable(key, beacon_pair); - - beacon = beacon_pair.second; - - return status; -} - -bool BeaconRegistry::BeaconDB::Delete(const uint256& hash) -{ - CTxDB txdb("rw"); - - std::pair key = std::make_pair("beacon", hash); - - return txdb.EraseGenericSerializable(key); -} - -BeaconRegistry::BeaconDB &BeaconRegistry::GetBeaconDB() -{ - return m_beacon_db; + return std::string("beacon"); } diff --git a/src/gridcoin/beacon.h b/src/gridcoin/beacon.h index 17c84f65bc..64970d7bac 100644 --- a/src/gridcoin/beacon.h +++ b/src/gridcoin/beacon.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -10,9 +10,9 @@ #include "key.h" #include "gridcoin/contract/handler.h" #include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" #include "gridcoin/cpid.h" #include "gridcoin/support/enumbytes.h" - #include #include #include @@ -99,7 +99,7 @@ class Beacon uint256 m_hash; //!< The hash of the transaction that advertised the beacon, or the block containing the SB. - uint256 m_prev_beacon_hash; //!< The m_hash of the previous beacon. + uint256 m_previous_hash; //!< The m_hash of the previous beacon. Status m_status; //!< The status of the beacon. It is of type int instead of enum for serialization. //! @@ -145,6 +145,29 @@ class Beacon //! bool WellFormed() const; + //! + //! \brief Provides the beacon CPID (key) and status (value) as a pair of strings. + //! \return std::pair of strings + //! + std::pair KeyValueToString() const; + + //! + //! \brief Returns the string representation of the current beacon status + //! + //! \return Translated string representation of beacon status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input beacon status + //! + //! \param status. BeaconStatusForStorage status + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return Beacon status string. + //! + std::string StatusToString(const BeaconStatusForStorage& status, const bool& translated = true) const; + //! //! \brief Get the elapsed time since advertisement. //! @@ -501,7 +524,7 @@ class StorageBeacon : public PendingBeacon READWRITE(m_public_key); READWRITE(m_timestamp); READWRITE(m_hash); - READWRITE(m_prev_beacon_hash); + READWRITE(m_previous_hash); READWRITE(m_cpid); READWRITE(m_status); } @@ -513,6 +536,21 @@ class StorageBeacon : public PendingBeacon class BeaconRegistry : public IContractHandler { public: + //! + //! \brief BeaconRegistry constructor. The parameter is the version number of the underlying + //! beacon entry db. This must be incremented when implementing format changes to the beacon + //! entries to force a reinit. + //! + //! Version 0: <= 5.2.0.0 + //! Version 1: = 5.2.1.0 + //! Version 2: 5.2.1.0 with hotfix and > 5.2.1.0 + //! Version 3: 5.4.5.5+ + //! + BeaconRegistry() + : m_beacon_db(3) + { + }; + //! //! \brief The type that associates beacons with CPIDs in the registry. This //! is done via smart pointers to save memory. @@ -547,6 +585,12 @@ class BeaconRegistry : public IContractHandler //! const PendingBeaconMap& PendingBeacons() const; + //! + //! \brief Get the set of beacons that have expired while pending (awaiting verification) + //! \return A reference to the expired pending beacon set. + //! + const std::set& ExpiredBeacons() const; + //! //! \brief Get the beacon for the specified CPID. //! @@ -577,6 +621,14 @@ class BeaconRegistry : public IContractHandler //! std::vector FindPending(const Cpid& cpid) const; + //! + //! \brief Find a historical beacon entry from the beacon (txid) hash; + //! \param txid hash + //! \return An object that either contains a reference to a historical + //! beacon entry if found or does not. + //! + const BeaconOption FindHistorical(const uint256& hash); + //! //! \brief Determine whether a beacon is active for the specified CPID. //! @@ -682,14 +734,14 @@ class BeaconRegistry : public IContractHandler //! there is some issue in LevelDB beacon retrieval. (This will cause the contract replay to change scope //! and initialize the BeaconRegistry from contract replay and store in LevelDB.) //! - int Initialize(); + int Initialize() override; //! //! \brief Gets the block height through which is stored in the beacon registry database. //! //! \return block height. //! - int GetDBHeight(); + int GetDBHeight() override; //! //! \brief Function normally only used after a series of reverts during block disconnects, because @@ -700,7 +752,7 @@ class BeaconRegistry : public IContractHandler //! //! \param height to set the storage DB bookmark. //! - void SetDBHeight(int& height); + void SetDBHeight(int& height) override; //! //! \brief Resets the maps in the BeaconRegistry but does not disturb the underlying LevelDB @@ -718,6 +770,18 @@ class BeaconRegistry : public IContractHandler //! uint64_t PassivateDB(); + //! + //! \brief This function walks the linked beacon entries back (using the m_previous_hash member) from a provided + //! beacon to find the initial advertisement. Note that this does NOT traverse non-continuous beacon ownership, + //! which occurs when a beacon is allowed to expire and must be reverified under a new key. + //! + //! \param beacon smart shared pointer to beacon entry to begin walking back + //! \param beacon_chain_out shared pointer to UniValue beacon chain out report array + //! \return root (advertisement) beacon entry smart shared pointer + //! + Beacon_ptr GetBeaconChainletRoot(Beacon_ptr beacon, + std::shared_ptr>> beacon_chain_out = nullptr); + //! //! \brief Returns whether IsContract correction is needed in ReplayContracts during initialization //! \return @@ -734,260 +798,43 @@ class BeaconRegistry : public IContractHandler //! //! \brief A static function that is called by the scheduler to run the beacon database passivation. //! - static void RunBeaconDBPassivation(); + static void RunDBPassivation(); + //! + //! \brief Specializes the template RegistryDB for the ScraperEntry class + //! + typedef RegistryDB, + HistoricalBeaconMap> BeaconDB; private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + BeaconMap m_beacons; //!< Contains the active registered beacons. PendingBeaconMap m_pending; //!< Contains beacons awaiting verification. //! - //! \brief A class private to the BeaconRegistry class that implements LevelDB backing storage for beacons. + //! \brief Contains pending beacons that have expired. //! - class BeaconDB - { - public: - //! - //! \brief Version number of the beacon db. - //! - //! CONSENSUS: Increment this value when introducing a breaking change to the beacon db. This - //! will ensure that when the wallet is restarted, the level db beacon storage will be cleared and - //! reloaded from the contract replay with the correct lookback scope. - //! - //! Version 0: <= 5.2.0.0 - //! Version 1: = 5.2.1.0 - //! Version 2: 5.2.1.0 with hotfix and > 5.2.1.0 - //! - static constexpr uint32_t CURRENT_VERSION = 2; - - //! - //! \brief Initializes the Beacon Registry map structures from the replay of the beacon states stored - //! in the beacon database. - //! - //! \param m_pending The map of pending beacons. - //! \param m_beacons The map of active beacons. - //! - //! \return block height up to and including which the beacon records were stored. - //! - int Initialize(PendingBeaconMap& m_pending, BeaconMap& m_beacons); - - //! - //! \brief Clears the historical beacon map of the database. This is only used during testing. - //! - void clear_in_memory_only(); - - //! - //! \brief Clears the LevelDB beacon storage area. - //! - //! \return Success or failure. - //! - bool clear_leveldb(); - - //! - //! \brief Removes in memory elements for all historical records not in m_beacons or m_pending. - //! \return Number of elements passivated. - //! - uint64_t passivate_db(); - - //! - //! \brief Clear the historical map and LevelDB beacon storage area. - //! - //! \return Success or failure. - //! - bool clear(); - - //! - //! \brief The number of beacon historical elements in the beacon database. - //! - //! \return The number of elements. - //! - size_t size(); - - //! - //! \brief Returns whether IsContract correction is needed in ReplayContracts during initialization - //! \return - //! - bool NeedsIsContractCorrection(); - - //! - //! \brief Sets the state of the IsContract needs correction flag in memory and LevelDB - //! \param flag The state to set - //! \return - //! - bool SetNeedsIsContractCorrection(bool flag); - - //! - //! \brief This stores the height to which the database entries are valid (the db scope). Note that it - //! is not desired to expose this function as a public function, but currently the Revert function - //! only operates on a single transaction context, and does not encapsulate the post reversion height - //! after the reversion state. TODO: Create a Revert overload that takes a vector of contract contexts - //! to be reverted (in order in which they are in the vector) and the post revert batch height (i.e. - //! the common block of the fork/reorg). - //! - //! \param height_stored - //! - //! \return Success or failure. - //! - bool StoreDBHeight(const int& height_stored); - - //! - //! \brief Provides the block height to which the beacon db covers. This is persisted in LevelDB. - //! - //! \param height_stored - //! - //! \return - //! - bool LoadDBHeight(int& height_stored); - - //! - //! \brief Insert a beacon state record into the historical database. - //! - //! \param hash The hash for the key to the historical record. (This must be unique. It is usually - //! the transaction hash that of the transaction that contains the beacon contract, but also can be - //! a synthetic hash created from the hash of the superblock hash and the cpid hash if recording - //! beacon activations or expired pendings, which are handled in ActivatePending. - //! \param height The height of the block from which the beacon state record originates. - //! \param beacon The beacon state record to insert (which includes the appropriate status). - //! - //! \return Success or Failure. This will fail if a record with the same key already exists in the - //! database. - //! - bool insert(const uint256& hash, const int& height, const Beacon& beacon); - - //! - //! \brief Erase a record from the database. - //! - //! \param hash The key of the record to erase. - //! - //! \return Success or failure. - //! - bool erase(const uint256& hash); - - //! - //! \brief Remove an individual in memory element that is backed by LevelDB that is not in m_beacons or m_pending. - //! - //! \param hash The hash that is the key to the element. - //! - //! \return A pair, the first part of which is an iterator to the next element, or map::end() if the last one, and - //! the second is success or failure of the passivation. - //! - std::pair - passivate(BeaconRegistry::HistoricalBeaconMap::iterator& iter); - - //! - //! \brief Iterator to the beginning of the database records. - //! - //! \return Iterator. - //! - HistoricalBeaconMap::iterator begin(); - - //! - //! \brief Iterator to end(). - //! - //! \return Iterator. - //! - HistoricalBeaconMap::iterator end(); - - //! - //! \brief Provides an iterator pointing to the element which key value matches the provided hash. Note that - //! this wrapper extends the behavior of the normal find function and will, in the case the element is not - //! present in the in-memory map, look in LevelDB and attempt to load the element from LevelDB, place in the - //! map, and return an iterator. end() is returned if the element is not found. - //! - //! \param hash The hash value with which to match on the key. - //! - //! \return Iterator. - //! - HistoricalBeaconMap::iterator find(const uint256& hash); - - //! - //! \brief Advances the iterator to the next element. - //! \param iter - //! \return iter - //! - HistoricalBeaconMap::iterator advance(HistoricalBeaconMap::iterator iter); - - private: - //! - //! \brief Type definition for the storage beacon map used in Initialize. Note that the uint64_t - //! is the record number, which unfortunately is required to preserve the contract application order - //! since they are applied in the order of the block's transaction vector rather than the transaction time. - //! - typedef std::map> StorageBeaconMap; - - //! - //! \brief Type definition for the map used to replay state from LevelDB beacon area. - //! - typedef std::map StorageBeaconMapByRecordNum; - - //! - //! \brief This is a map keyed by uint256 (SHA256) hash that stores the historical beacon state elements. - //! It is persisted in LevelDB storage. - //! - HistoricalBeaconMap m_historical; - - //! - //!//! \brief Boolan to indicate whether the database has been successfully initialized from LevelDB during - //! startup. - //! - bool m_database_init = false; - - //! - //! \brief The block height for beacon records stored in the beacon database. This is a bookmark. It is - //! adjusted by StoreDBHeight, persisted in memory by this private member variable, and persisted in storage - //! to LevelDB. - //! - int m_height_stored = 0; - - //! - //! \brief The record number stored watermark. This effectively a sequence number for records stored in - //! the LevelDB beacon area. The value in memory will be at the highest record number inserted (or played - //! back during initialization). - //! - uint64_t m_recnum_stored = 0; - - //! - //! \brief The flag that indicates whether memory optimization can occur by passivating the database. This - //! flag is set true when find() retrieves a beacon element from LevelDB to satisfy a hash search. - //! This would typically occur on a beacon renewal or reorganization (revert). - //! - bool m_needs_passivation = false; - - //! - //! \brief The flag that indicates whether IsContract correction is needed in ReplayContracts during initialization. - //! - bool m_needs_IsContract_correction = false; - - //! - //! \brief Store a beacon object in LevelDB with the provided key value. - //! - //! \param hash The SHA256 hash key value for the element. - //! \param beacon The beacon historical state element to be stored. - //! - //! \return Success or failure. - //! - bool Store(const uint256& hash, const StorageBeacon& beacon); - - //! - //! \brief Load a beacon object from LevelDB using the provided key value. - //! - //! \param hash The SHA256 hash key value for the element. - //! \param beacon The beacon historical state element loaded. - //! - //! \return Success or failure. - //! - bool Load(const uint256 &hash, StorageBeacon& beacon); - - //! - //! \brief Delete a beacon object from LevelDB with the provided key value (if it exists). - //! - //! \param hash The SHA256 hash key value for the element. - //! - //! \return Success or failure. - //! - bool Delete(const uint256& hash); - - }; // BeaconDB + //! Contains pending beacons that have expired but need to be retained until the next SB (activation) to ensure a + //! reorganization will successfully resurrect expired pending beacons back into pending ones up to the depth equal to one SB to + //! the next, which is about 960 blocks. The reason this is necessary is two fold: 1) it makes the lookup for expired + //! pending beacons in the deactivate method much simpler in the case of a reorg across a SB boundary, and 2) it holds + //! a reference to the pending beacon shared pointer object in the history map, which prevents it from being passivated. + //! Otherwise, a passivation event, which would remove the pending deleted beacons, followed by a reorganization across + //! SB boundary could have a small possibility of removing a pending beacon that could be verified in the alternative SB + //! eventually staked. + //! + //! This set is cleared and repopulated at each SB accepted by the node with the current expired pending beacons. + //! + std::set m_expired_pending; //! //! \brief The member variable that is the instance of the beacon database. This is private to the diff --git a/src/gridcoin/contract/contract.cpp b/src/gridcoin/contract/contract.cpp index 4b00ade80f..8630b5f532 100644 --- a/src/gridcoin/contract/contract.cpp +++ b/src/gridcoin/contract/contract.cpp @@ -8,11 +8,15 @@ #include "gridcoin/appcache.h" #include "gridcoin/claim.h" #include "gridcoin/mrc.h" +#include "gridcoin/protocol.h" #include "gridcoin/contract/contract.h" #include "gridcoin/contract/handler.h" +#include "gridcoin/contract/registry.h" #include "gridcoin/beacon.h" #include "gridcoin/project.h" #include "gridcoin/researcher.h" +#include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "gridcoin/support/block_finder.h" #include "gridcoin/support/xml.h" #include "gridcoin/tx_message.h" @@ -24,6 +28,24 @@ using namespace GRC; +// ----------------------------------------------------------------------------- +// Contract Context (see handler.h) +// ----------------------------------------------------------------------------- + +void ContractContext::Log(const std::string& prefix) const +{ + LogPrint(BCLog::LogFlags::CONTRACT, + ": %s: block %i, txid %s, v%u, %s, %s, %s, %s", + prefix, + m_pindex->nHeight, + m_tx.GetHash().ToString(), + m_contract.m_version, + m_contract.m_type.ToString(), + m_contract.m_action.ToString(), + m_contract.SharePayload()->LegacyKeyString(), + m_contract.SharePayload()->LegacyValueString()); +} + namespace { //! //! \brief An empty, invalid contract payload. @@ -71,123 +93,6 @@ class EmptyPayload : public IContractPayload } }; // EmptyPayload -//! -//! \brief A payload parsed from a legacy, version 1 contract. -//! -//! Version 2+ contracts provide support for binary representation of payload -//! data. Legacy contract data exists as strings. This class provides for use -//! of the contract payload API with legacy string contracts. -//! -class LegacyPayload : public IContractPayload -{ -public: - std::string m_key; //!< Legacy representation of a contract key. - std::string m_value; //!< Legacy representation of a contract value. - - //! - //! \brief Initialize an empty, invalid legacy payload. - //! - LegacyPayload() - { - } - - //! - //! \brief Initialize a legacy payload with data from a legacy contract. - //! - //! \param key Legacy contract key as it exists in a transaction. - //! \param value Legacy contract value as it exists in a transaction. - //! - LegacyPayload(std::string key, std::string value) - : m_key(std::move(key)) - , m_value(std::move(value)) - { - } - - GRC::ContractType ContractType() const override - { - return GRC::ContractType::UNKNOWN; - } - - bool WellFormed(const ContractAction action) const override - { - return !m_key.empty() - && (action == ContractAction::REMOVE || !m_value.empty()); - } - - std::string LegacyKeyString() const override - { - return m_key; - } - - std::string LegacyValueString() const override - { - return m_value; - } - - CAmount RequiredBurnAmount() const override - { - return Contract::STANDARD_BURN_AMOUNT; - } - - ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; - - template - inline void SerializationOp( - Stream& s, - Operation ser_action, - const ContractAction contract_action) - { - READWRITE(m_key); - - if (contract_action != ContractAction::REMOVE) { - READWRITE(m_value); - } - } -}; // LegacyPayload - -//! -//! \brief Temporary interface implementation that reads and writes contracts -//! to AppCache to use while we refactor away each of the AppCache sections: -//! -class AppCacheContractHandler : public IContractHandler -{ -public: - void Reset() override - { - ClearCache(Section::PROTOCOL); - ClearCache(Section::SCRAPER); - } - - bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override - { - return true; // No contextual validation needed yet - } - - bool BlockValidate(const ContractContext& ctx, int& DoS) const override - { - return true; // No contextual validation needed yet - } - - void Add(const ContractContext& ctx) override - { - const auto payload = ctx->SharePayloadAs(); - - WriteCache( - StringToSection(ctx->m_type.ToString()), - payload->m_key, - payload->m_value, - ctx.m_tx.nTime); - } - - void Delete(const ContractContext& ctx) override - { - const auto payload = ctx->SharePayloadAs(); - - DeleteCache( - StringToSection(ctx->m_type.ToString()), - payload->m_key); - } -}; //! //! \brief Handles unknown contract message types by logging a message. @@ -217,7 +122,7 @@ class UnknownContractHandler : public IContractHandler //! void Add(const ContractContext& ctx) override { - ctx->Log("WARNING: Add unknown contract type ignored"); + ctx.Log("WARNING: Add unknown contract type ignored"); } //! @@ -227,7 +132,7 @@ class UnknownContractHandler : public IContractHandler //! void Delete(const ContractContext& ctx) override { - ctx->Log("WARNING: Delete unknown contract type ignored"); + ctx.Log("WARNING: Delete unknown contract type ignored"); } //! @@ -237,7 +142,7 @@ class UnknownContractHandler : public IContractHandler //! void Revert(const ContractContext& ctx) override { - ctx->Log("WARNING: Revert unknown contract type ignored"); + ctx.Log("WARNING: Revert unknown contract type ignored"); } }; @@ -249,18 +154,18 @@ class Dispatcher { public: //! - //! \brief Reset the cached state of each contract handler to prepare for - //! historical contract replay. + //! \brief Reset the cached state of any contract handler to prepare for + //! historical contract replay. Note that all handlers are now native. The + //! appcache is formally retired. + //! + //! The contract replay will skip contracts for other handler types where + //! the backing store exists (beacons, scraper entries, protocol entries, and + //! projects), or the objects are independent and unique by key and admit to + //! simple reversion, such as polls/votes. //! void ResetHandlers() { - // Don't reset the beacon registry as it is now backed by a database. - // GetBeaconRegistry().Reset(); - - // Don't reset the poll registry as reorgs are properly handled. - // GetPollRegistry().Reset(); - GetWhitelist().Reset(); - m_appcache_handler.Reset(); + // Nothing to do. } //! @@ -272,18 +177,18 @@ class Dispatcher void Apply(const ContractContext& ctx) { if (ctx->m_action == ContractAction::ADD) { - ctx->Log("INFO: Add contract"); + ctx.Log("INFO: Add contract"); GetHandler(ctx->m_type.Value()).Add(ctx); return; } if (ctx->m_action == ContractAction::REMOVE) { - ctx->Log("INFO: Delete contract"); + ctx.Log("INFO: Delete contract"); GetHandler(ctx->m_type.Value()).Delete(ctx); return; } - ctx.m_contract.Log("WARNING: Unknown contract action ignored"); + ctx.Log("WARNING: Unknown contract action ignored"); } //! @@ -330,19 +235,30 @@ class Dispatcher //! void Revert(const ContractContext& ctx) { - ctx->Log("INFO: Revert contract"); + ctx.Log("INFO: Revert contract"); // The default implementation of IContractHandler reverses an action // (addition or deletion) declared in the contract argument, but the - // type-specific handlers may override this behavior as needed: + // type-specific handlers may override this behavior as needed. The + // default implementation can ONLY be used for those contracts whose + // objects are unique. A good example is polls and votes. Each poll + // and each vote is a unique object (by key). In this case the simple + // reversion works. For objects that effectively are "revised", such + // as beacons, which have a complex lifecycle, and a history of + // revisions for the same key (CPID for beacon), a much more complex + // implementation, along with a backing db that stores historical + // objects and a linkage from current to previous objects is required. + // The scraper entry, protocol entry, project (whitelist) and beacon + // registry are all examples of this type which are backed by + // implementations of the RegistryDB template class. GetHandler(ctx->m_type.Value()).Revert(ctx); } private: - AppCacheContractHandler m_appcache_handler; //( + uint32_t {2}, action, std::move(key), std::move(value)); @@ -448,10 +368,11 @@ void GRC::ReplayContracts(CBlockIndex* pindex_end, CBlockIndex* pindex_start) // If there is no pindex_start (i.e. default value of nullptr), then set standard lookback. A Non-standard lookback // where there is a specific pindex_start argument supplied, is only used in the GRC InitializeContracts call for - // when the beacon database in LevelDB has not already been populated. + // when the corresponding RegistryDB instantiations and initialization in LevelDB has not already been populated + // for the registry types that use the RegistryDB. if (!pindex) { - pindex = GRC::BlockFinder::FindByMinTime(pindexBest->nTime - Beacon::MAX_AGE); + pindex = GRC::BlockFinder::FindByMinTime(pindexBest->nTime - Params().GetConsensus().StandardContractReplayLookback); } if (pindex->nHeight < (fTestNet ? 1 : 164618)) { @@ -460,14 +381,29 @@ void GRC::ReplayContracts(CBlockIndex* pindex_end, CBlockIndex* pindex_start) LogPrint(BCLog::LogFlags::CONTRACT, "Replaying contracts from block %" PRId64 "...", pindex->nHeight); - // This no longer includes beacons or polls. + // This is actually a no-op now, because all existing contract types do proper reversion, either through implementations + // of the RegistryDB, or because they use independent objects that have no linked history and admit simple reverts + // provided by the default add/delete/revert. g_dispatcher.ResetHandlers(); - BeaconRegistry& beacons = GetBeaconRegistry(); + RegistryBookmarks db_heights; - int beacon_db_height = beacons.GetDBHeight(); + // Logs db_heights for reference in logs. + for (const auto& contract_type : CONTRACT_TYPES) { + std::optional db_height = db_heights.GetRegistryBlockHeight(contract_type); - LogPrint(BCLog::LogFlags::BEACON, "Beacon database at height %i", beacon_db_height); + if (!db_height) continue; + + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: %s entry database at height %i", + __func__, + Contract::Type::ToString(contract_type), + *db_height); + } + + // This provides a convenient reference for the beacon registry, which has special processing below due to activations + // and the IsContract flag corrections. The scraper entries require no such special processing and are handled + // by the ApplyContracts call. + BeaconRegistry& beacons = GetBeaconRegistry(); if (beacons.NeedsIsContractCorrection()) { @@ -490,8 +426,10 @@ void GRC::ReplayContracts(CBlockIndex* pindex_end, CBlockIndex* pindex_start) continue; } + // The ApplyContracts below handles all of the contract types. The rest of this is special + // processing required for beacons. bool found_contract; - ApplyContracts(block, pindex, beacon_db_height, found_contract); + ApplyContracts(block, pindex, db_heights, found_contract); // If a contract was found and the NeedsIsContractCorrection flag is set, then // record that a contract was found in the block index. This corrects the block index @@ -526,23 +464,24 @@ void GRC::ReplayContracts(CBlockIndex* pindex_end, CBlockIndex* pindex_start) // Only apply activations that have not already been stored/loaded into // the beacon DB. This is at the block level, so we have to be careful here. - // If the pindex->nHeight is equal to the beacon_db_height, then the ActivatePending + // If the pindex->nHeight is equal to the beacon db height, then the ActivatePending // has already been replayed for this block and we do not need to call it again for that block. // BECAUSE ActivatePending is called at the block level. We do not need to worry about multiple // calls within the same block like below in ApplyContracts. - if (pindex->nHeight > beacon_db_height) - { - GetBeaconRegistry().ActivatePending( - block.GetSuperblock()->m_verified_beacons.m_verified, - block.GetBlockTime(), - block.GetHash(), - pindex->nHeight); - } - else - { - LogPrint(BCLog::LogFlags::BEACON, "INFO: %s: GetBeaconRegistry().ActivatePending() " - "skipped for superblock: pindex->height = %i <= beacon_db_height = %i." - , __func__, pindex->nHeight, beacon_db_height); + std::optional beacon_db_height = db_heights.GetRegistryBlockHeight(ContractType::BEACON); + + if (beacon_db_height) { + if (pindex->nHeight > *beacon_db_height) { + beacons.ActivatePending( + block.GetSuperblock()->m_verified_beacons.m_verified, + block.GetBlockTime(), + block.GetHash(), + pindex->nHeight); + } else { + LogPrint(BCLog::LogFlags::BEACON, "INFO: %s: GetBeaconRegistry().ActivatePending() " + "skipped for superblock: pindex->height = %i <= beacon_db_height = %i.", + __func__, pindex->nHeight, *beacon_db_height); + } } } @@ -561,7 +500,7 @@ void GRC::ReplayContracts(CBlockIndex* pindex_end, CBlockIndex* pindex_start) void GRC::ApplyContracts( const CBlock& block, - const CBlockIndex* const pindex, const int& beacon_db_height, + const CBlockIndex* const pindex, const RegistryBookmarks& db_heights, bool& out_found_contract) { out_found_contract = false; @@ -571,34 +510,42 @@ void GRC::ApplyContracts( iter != end; ++iter) { - ApplyContracts(*iter, pindex, beacon_db_height, out_found_contract); + ApplyContracts(*iter, pindex, db_heights, out_found_contract); } } void GRC::ApplyContracts( const CTransaction& tx, - const CBlockIndex* const pindex, const int& beacon_db_height, + const CBlockIndex* const pindex, const RegistryBookmarks& db_heights, bool& out_found_contract) { for (const auto& contract : tx.GetContracts()) { // Do not (re)apply contracts that have already been stored/loaded into - // the beacon DB up to the block BEFORE the beacon db height. Because the beacon - // db height is at the block level, and is updated on each beacon insert, when - // in a sync from zero situation where the contracts are played as each block is validated, - // any beacon contract in the block EQUAL to the beacon db height must fail this test - // and be inserted again, because otherwise the second and succeeding contracts on the - // same block will not be inserted and those CPID's will not be recorded properly. - // This was the cause of the failure to sync through 2069264 that started on 20210312. See - // GitHub issue #2045. - if ((pindex->nHeight < beacon_db_height) && contract.m_type == ContractType::BEACON) - { - LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: ApplyContract tx skipped: " - "pindex->height = %i <= beacon_db_height = %i and " - "ContractType is BEACON." - , __func__, pindex->nHeight, beacon_db_height); - continue; + // the relevant entry dbs up to the block BEFORE the relevant db height. Because + // these db heights are at the block level, and are updated on each relevant entry + // insert, when in a sync from zero situation where the contracts are played as each block + // is validated, any relevant contract in the block EQUAL to the relevant db height + // must fail this test and be inserted again, because otherwise the second and succeeding + // contracts on the same block will not be inserted and those relevant entries will + // not be recorded properly. For beacons, this was the cause of the failure to sync through + // 2069264 that started on 20210312. See GitHub issue #2045. + + bool skip_apply_contract = false; + + for (const auto& contract_type : CONTRACT_TYPES) { + if (contract.m_type == contract_type) { + + std::optional db_height = db_heights.GetRegistryBlockHeight(contract_type); + + if (db_height && pindex->nHeight < *db_height) { + skip_apply_contract = true; + break; + } + } } + if (skip_apply_contract) continue; + // V2 contracts are checked upon receipt: if (contract.m_version == 1 && !CheckLegacyContract(contract, tx, pindex->nHeight)) { continue; @@ -732,12 +679,13 @@ bool Contract::RequiresMasterKey() const // beacons by signing them with the original private key: return m_version == 1 && m_action == ContractAction::REMOVE; - case ContractType::POLL: return m_action == ContractAction::REMOVE; - case ContractType::PROJECT: return true; - case ContractType::PROTOCOL: return true; - case ContractType::SCRAPER: return true; - case ContractType::VOTE: return m_action == ContractAction::REMOVE; - default: return false; + case ContractType::POLL: return m_action == ContractAction::REMOVE; + case ContractType::PROJECT: return true; + case ContractType::PROTOCOL: return true; + case ContractType::SCRAPER: return true; + case ContractType::VOTE: return m_action == ContractAction::REMOVE; + case ContractType::SIDESTAKE: return true; + default: return false; } } @@ -748,19 +696,36 @@ CAmount Contract::RequiredBurnAmount() const bool Contract::WellFormed() const { - return m_version > 0 && m_version <= Contract::CURRENT_VERSION - && m_type != ContractType::UNKNOWN - && m_action != ContractAction::UNKNOWN - && m_body.WellFormed(m_action.Value()); + bool result = m_version > 0 && m_version <= Contract::CURRENT_VERSION + && m_type != ContractType::UNKNOWN + && m_action != ContractAction::UNKNOWN + && m_body.WellFormed(m_action.Value()); + + if (!result) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Contract was not well formed. m_version = %u, m_type = %s, " + "m_action = %s, m_body.Wellformed(m_action.Value()) = %u", + __func__, + m_version, + m_type.ToString(), + m_action.ToString(), + m_body.WellFormed(m_action.Value()) + ); + } + + return result; } ContractPayload Contract::SharePayload() const { - if (m_version > 1) { - return m_body.m_payload; + // The scraper and protocol entry formats were changed to native later than the others and a new contract + // version three is introduced for that. This will be coincident with block v13. + if (m_version < 2 + || (m_type == ContractType::SCRAPER && m_version < 3) + || (m_type == ContractType::PROTOCOL && m_version < 3)) { + return m_body.ConvertFromLegacy(m_type.Value(), m_version); } - return m_body.ConvertFromLegacy(m_type.Value()); + return m_body.m_payload; } void Contract::Log(const std::string& prefix) const @@ -812,6 +777,7 @@ Contract::Type Contract::Type::Parse(std::string input) if (input == "scraper") return ContractType::SCRAPER; if (input == "protocol") return ContractType::PROTOCOL; if (input == "message") return ContractType::MESSAGE; + if (input == "sidestake") return ContractType::SIDESTAKE; return ContractType::UNKNOWN; } @@ -828,6 +794,41 @@ std::string Contract::Type::ToString() const case ContractType::PROTOCOL: return "protocol"; case ContractType::SCRAPER: return "scraper"; case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; + default: return ""; + } +} + +std::string Contract::Type::ToString(ContractType contract_type) +{ + switch (contract_type) { + case ContractType::BEACON: return "beacon"; + case ContractType::CLAIM: return "claim"; + case ContractType::MRC: return "mrc"; + case ContractType::MESSAGE: return "message"; + case ContractType::POLL: return "poll"; + case ContractType::PROJECT: return "project"; + case ContractType::PROTOCOL: return "protocol"; + case ContractType::SCRAPER: return "scraper"; + case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; + default: return ""; + } +} + +std::string Contract::Type::ToTranslatedString(ContractType contract_type) +{ + switch (contract_type) { + case ContractType::BEACON: return _("beacon"); + case ContractType::CLAIM: return _("claim"); + case ContractType::MRC: return _("mrc"); + case ContractType::MESSAGE: return _("message"); + case ContractType::POLL: return _("poll"); + case ContractType::PROJECT: return _("project"); + case ContractType::PROTOCOL: return _("protocol"); + case ContractType::SCRAPER: return _("scraper"); + case ContractType::VOTE: return _("vote"); + case ContractType::SIDESTAKE: return _("sidestake"); default: return ""; } } @@ -881,12 +882,17 @@ ContractPayload Contract::Body::AssumeLegacy() const return m_payload; } -ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type) const +ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type, uint32_t version) const { // We use static_cast here instead of dynamic_cast to avoid the lookup. The // value of m_payload is guaranteed to be a LegacyPayload for v1 contracts. // - const auto& legacy = static_cast(*m_payload); + LegacyPayload legacy; + + //TODO: Evaluate if this condition is relevant anymore + //if (version < 2) { + legacy = static_cast(*m_payload); + //} switch (type) { case ContractType::UNKNOWN: @@ -909,14 +915,19 @@ ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type) const return ContractPayload::Make( Poll::Parse(legacy.m_key, legacy.m_value)); case ContractType::PROJECT: - return ContractPayload::Make(legacy.m_key, legacy.m_value, 0); + return ContractPayload::Make(legacy.m_key, legacy.m_value); case ContractType::PROTOCOL: - return m_payload; + return ContractPayload::Make( + ProtocolEntryPayload::Parse(legacy.m_key, legacy.m_value)); case ContractType::SCRAPER: - return m_payload; + return ContractPayload::Make( + ScraperEntryPayload::Parse(legacy.m_key, legacy.m_value)); case ContractType::VOTE: return ContractPayload::Make( LegacyVote::Parse(legacy.m_key, legacy.m_value)); + case ContractType::SIDESTAKE: + // Sidestakes have no legacy representation as a contract. + assert(false && "Attempted to convert non-existent legacy sidestake contract."); case ContractType::OUT_OF_BOUND: assert(false); } @@ -950,17 +961,32 @@ void Contract::Body::ResetType(const ContractType type) m_payload.Reset(new PollPayload(IsPollV3Enabled(nBestHeight) ? 3 : 2)); break; case ContractType::PROJECT: - m_payload.Reset(new Project()); + // Note that the contract code expects cs_main to already be taken which + // means that the access to nBestHeight is safe. + // TODO: This ternary should be removed at the next mandatory after + // Kermit's Mom. + m_payload.Reset(new Project(IsV13Enabled(nBestHeight) ? 3 : 2)); break; case ContractType::PROTOCOL: - m_payload.Reset(new LegacyPayload()); + // Note that the contract code expects cs_main to already be taken which + // means that the access to nBestHeight is safe. + // TODO: This ternary should be removed at the next mandatory after + // Kermit's Mom. + m_payload.Reset(new ProtocolEntryPayload(IsV13Enabled(nBestHeight) ? 2 : 1)); break; case ContractType::SCRAPER: - m_payload.Reset(new LegacyPayload()); + // Note that the contract code expects cs_main to already be taken which + // means that the access to nBestHeight is safe. + // TODO: This ternary should be removed at the next mandatory after + // Kermit's Mom. + m_payload.Reset(new ScraperEntryPayload(IsV13Enabled(nBestHeight) ? 2 : 1)); break; case ContractType::VOTE: m_payload.Reset(new Vote()); break; + case ContractType::SIDESTAKE: + m_payload.Reset(new SideStakePayload()); + break; case ContractType::OUT_OF_BOUND: assert(false); } @@ -984,3 +1010,17 @@ void IContractHandler::Revert(const ContractContext& ctx) error("Unknown contract action ignored: %s", ctx->m_action.ToString()); } + +int IContractHandler::Initialize() +{ + return 0; +} + +int IContractHandler::GetDBHeight() +{ + return 0; +} + +void IContractHandler::SetDBHeight(int& height) +{ +} diff --git a/src/gridcoin/contract/contract.h b/src/gridcoin/contract/contract.h index 8005ea73e2..438a052222 100644 --- a/src/gridcoin/contract/contract.h +++ b/src/gridcoin/contract/contract.h @@ -20,6 +20,8 @@ class CBlockIndex; class CTransaction; namespace GRC { +class RegistryBookmarks; + //! //! \brief Represents a Gridcoin contract embedded in a transaction message. //! @@ -46,7 +48,7 @@ class Contract //! ensure that the serialization/deserialization routines also handle all //! of the previous versions. //! - static constexpr uint32_t CURRENT_VERSION = 2; + static constexpr uint32_t CURRENT_VERSION = 3; //! //! \brief The amount of coin set for a burn output in a transaction that @@ -94,6 +96,21 @@ class Contract //! \return The string as it would appear in a legacy transaction message. //! std::string ToString() const; + + //! + //! \brief Get the string representation of the provided contract type. + //! + //! \return The string as it would appear in a legacy transaction message. + //! + static std::string ToString(ContractType contract_type); + + //! + //! \brief Get the translated string representation of the provided contract type. + //! + //! \return The string as it would appear in a legacy transaction message. + //! + static std::string ToTranslatedString(ContractType contract_type); + }; // Contract::Type //! @@ -186,7 +203,7 @@ class Contract //! //! \return An IContractPayload implementation for the specified type. //! - ContractPayload ConvertFromLegacy(const ContractType type) const; + ContractPayload ConvertFromLegacy(const ContractType type, const uint32_t version) const; //! //! \brief Serialize the object to the provided stream. @@ -237,6 +254,10 @@ class Contract //! transaction's \c vContracts field. It excludes the legacy signature //! and public key from version 1. //! + //! Version 3: Contract data serializable in binary format as version 2 + //! but also with remaining payloads that were still legacy in version + //! 2 (scraper and protocol) now native. + //! uint32_t m_version = CURRENT_VERSION; Type m_type; //!< Determines how to handle the contract. @@ -439,6 +460,80 @@ class Contract } }; // Contract +//! +//! \brief A payload parsed from a legacy, version 1 contract. +//! +//! Version 2+ contracts provide support for binary representation of payload +//! data. Legacy contract data exists as strings. This class provides for use +//! of the contract payload API with legacy string contracts. +//! +class LegacyPayload : public IContractPayload +{ +public: + std::string m_key; //!< Legacy representation of a contract key. + std::string m_value; //!< Legacy representation of a contract value. + + //! + //! \brief Initialize an empty, invalid legacy payload. + //! + LegacyPayload() + { + } + + //! + //! \brief Initialize a legacy payload with data from a legacy contract. + //! + //! \param key Legacy contract key as it exists in a transaction. + //! \param value Legacy contract value as it exists in a transaction. + //! + LegacyPayload(std::string key, std::string value) + : m_key(std::move(key)) + , m_value(std::move(value)) + { + } + + GRC::ContractType ContractType() const override + { + return GRC::ContractType::UNKNOWN; + } + + bool WellFormed(const ContractAction action) const override + { + return !m_key.empty() + && (action == ContractAction::REMOVE || !m_value.empty()); + } + + std::string LegacyKeyString() const override + { + return m_key; + } + + std::string LegacyValueString() const override + { + return m_value; + } + + CAmount RequiredBurnAmount() const override + { + return Contract::STANDARD_BURN_AMOUNT; + } + + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + READWRITE(m_key); + + if (contract_action != ContractAction::REMOVE) { + READWRITE(m_value); + } + } +}; // LegacyPayload + //! //! \brief Initialize a new contract. //! @@ -462,6 +557,31 @@ Contract MakeContract(const ContractAction action, Args&&... args) return Contract(type, action, std::move(payload)); } +//! +//! \brief Initialize a new contract with the specified contract version. +//! +//! \tparam PayloadType A type that implements the IContractPayload interface. +//! +//! \param contract_version The version of the contract to create +//! \param action The action of the contract to publish. +//! \param body Arguments to pass to the constructor of the contract payload. +//! +//! \return A contract object for submission in a transaction. +//! +template +Contract MakeContract(const uint32_t contract_version, const ContractAction action, Args&&... args) +{ + static_assert( + std::is_base_of::value, + "Contract::PullPayloadAs: T not derived from IContractPayload."); + + auto payload = ContractPayload::Make(std::forward(args)...); + const ContractType type = payload->ContractType(); + + return Contract(contract_version, type, action, std::move(payload)); +} + + //! //! \brief Initialize a new legacy contract. //! @@ -495,7 +615,7 @@ void ReplayContracts(CBlockIndex *pindex_end, CBlockIndex *pindex_start = nullpt //! \param out_found Will update to \c true when a block contains a contract. //! void ApplyContracts(const CBlock& block, - const CBlockIndex* const pindex, const int& beacon_db_height, + const CBlockIndex* const pindex, const RegistryBookmarks& db_heights, bool& out_found_contract); //! @@ -506,9 +626,8 @@ void ApplyContracts(const CBlock& block, //! \param pindex Block index for the block that contains the transaction. //! \param beacon_db_height Height that db is updated to prior to call //! -void ApplyContracts( - const CTransaction& tx, - const CBlockIndex* const pindex, const int& beacon_db_height, +void ApplyContracts(const CTransaction& tx, + const CBlockIndex* const pindex, const RegistryBookmarks& db_heights, bool& out_found_contract); //! diff --git a/src/gridcoin/contract/handler.h b/src/gridcoin/contract/handler.h index 7d5e93f4bf..9d2d462d5a 100644 --- a/src/gridcoin/contract/handler.h +++ b/src/gridcoin/contract/handler.h @@ -5,6 +5,8 @@ #ifndef GRIDCOIN_CONTRACT_HANDLER_H #define GRIDCOIN_CONTRACT_HANDLER_H +#include + class CBlockIndex; class CTransaction; @@ -47,6 +49,13 @@ class ContractContext { } + //! + //! \brief Superset of contract logger at ctx level. + //! + //! \param prefix. String to be prefixed + //! + void Log(const std::string& prefix) const; + const Contract& operator*() const noexcept { return m_contract; } const Contract* operator->() const noexcept { return &m_contract; } }; @@ -125,7 +134,27 @@ struct IContractHandler //! \param ctx References the contract and associated context. //! virtual void Revert(const ContractContext& ctx); + + //! + //! \brief This method is implemented for those contract handlers that have a registry (backing) database. + //! \return + //! + virtual int Initialize(); + + //! + //! \brief This method is implemented for those contract handlers that have a registry (backing) database. + //! \return + //! + virtual int GetDBHeight(); + + //! + //! \brief This method is implemented for those contract handlers that have a registry (backing) database. + //! + //! \param height + //! + virtual void SetDBHeight(int& height); }; -} + +} // namespace GRC #endif // GRIDCOIN_CONTRACT_HANDLER_H diff --git a/src/gridcoin/contract/message.cpp b/src/gridcoin/contract/message.cpp index 7309afeb68..a08ff23e4f 100644 --- a/src/gridcoin/contract/message.cpp +++ b/src/gridcoin/contract/message.cpp @@ -5,6 +5,7 @@ #include "amount.h" #include "gridcoin/contract/message.h" #include "gridcoin/contract/contract.h" +#include "gridcoin/sidestake.h" #include "script.h" #include "wallet/wallet.h" @@ -143,16 +144,29 @@ std::string SendContractTx(CWalletTx& wtx_new) if (balance < COIN || balance < burn_fee + nTransactionFee) { std::string strError = _("Balance too low to create a contract."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } if (!CreateContractTx(wtx_new, reserve_key, burn_fee)) { std::string strError = _("Error: Transaction creation failed."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } + for (const auto& pool_tx : mempool.mapTx) { + for (const auto& pool_tx_contract : pool_tx.second.GetContracts()) { + if (pool_tx_contract.m_type == GRC::ContractType::SIDESTAKE) { + std::string strError = _( + "Error: The mandatory sidestake transaction was rejected. " + "There is already a mandatory sidestake transaction in the mempool. " + "Wait until that transaction is bound in a block."); + error("%s: %s", __func__, strError); + return strError; + } + } + } + if (!pwalletMain->CommitTransaction(wtx_new, reserve_key)) { std::string strError = _( "Error: The transaction was rejected. This might happen if some of " @@ -160,7 +174,7 @@ std::string SendContractTx(CWalletTx& wtx_new) "a copy of wallet.dat and coins were spent in the copy but not " "marked as spent here."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } diff --git a/src/gridcoin/contract/payload.h b/src/gridcoin/contract/payload.h index bd723ec4bd..be608b5e67 100644 --- a/src/gridcoin/contract/payload.h +++ b/src/gridcoin/contract/payload.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -65,9 +65,28 @@ enum class ContractType SCRAPER, //!< Scraper node authorization grants and revocations. VOTE, //!< A vote cast by a wallet for a poll. MRC, //!< A manual rewards claim (MRC) request to pay rewards + SIDESTAKE, //!< Mandatory sidestakes OUT_OF_BOUND, //!< Marker value for the end of the valid range. }; +//! +//! \brief Allows use of the ContractType enum in range based for loops. +//! +static constexpr GRC::ContractType CONTRACT_TYPES[] = { + ContractType::UNKNOWN, + ContractType::BEACON, + ContractType::CLAIM, + ContractType::MESSAGE, + ContractType::POLL, + ContractType::PROJECT, + ContractType::PROTOCOL, + ContractType::SCRAPER, + ContractType::VOTE, + ContractType::MRC, + ContractType::SIDESTAKE, + ContractType::OUT_OF_BOUND +}; + //! //! \brief The type of action that a contract declares. //! diff --git a/src/gridcoin/contract/registry.cpp b/src/gridcoin/contract/registry.cpp new file mode 100644 index 0000000000..65d90f8652 --- /dev/null +++ b/src/gridcoin/contract/registry.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "gridcoin/contract/registry.h" + +namespace GRC { + +const std::vector RegistryBookmarks::CONTRACT_TYPES_WITH_REG_DB = { + ContractType::BEACON, + ContractType::PROJECT, + ContractType::PROTOCOL, + ContractType::SCRAPER, + ContractType::SIDESTAKE +}; + +const std::vector RegistryBookmarks::CONTRACT_TYPES_SUPPORTING_REVERT = { + ContractType::BEACON, + ContractType::POLL, + ContractType::PROJECT, + ContractType::PROTOCOL, + ContractType::SCRAPER, + ContractType::VOTE, + ContractType::SIDESTAKE +}; + +} // namespace GRC diff --git a/src/gridcoin/contract/registry.h b/src/gridcoin/contract/registry.h new file mode 100644 index 0000000000..3896864316 --- /dev/null +++ b/src/gridcoin/contract/registry.h @@ -0,0 +1,186 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_CONTRACT_REGISTRY_H +#define GRIDCOIN_CONTRACT_REGISTRY_H + +#include "gridcoin/contract/payload.h" +#include "gridcoin/beacon.h" +#include "gridcoin/project.h" +#include "gridcoin/protocol.h" +#include "gridcoin/sidestake.h" +#include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/voting/registry.h" + +namespace GRC { + +class RegistryBookmarks_error : public std::runtime_error +{ +public: + explicit RegistryBookmarks_error(const std::string& str) + : std::runtime_error("ERROR: " + str) + { + LogPrintf("ERROR: %s", str); + } +}; + +//! +//! \brief Registry is simply a mnemonic alias for IContractHandler. +//! +typedef IContractHandler Registry; + +typedef std::unordered_map RegistryBlockHeights; + +class RegistryBookmarks +{ +public: + static const std::vector CONTRACT_TYPES_WITH_REG_DB; + + static const std::vector CONTRACT_TYPES_SUPPORTING_REVERT; + + RegistryBookmarks() + { + UpdateRegistryBookmarks(); + } + + static Registry& GetRegistryWithDB(const ContractType type) + { + switch (type) { + case ContractType::BEACON: return GetBeaconRegistry(); + case ContractType::PROJECT: return GetWhitelist(); + case ContractType::PROTOCOL: return GetProtocolRegistry(); + case ContractType::SCRAPER: return GetScraperRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); + case ContractType::UNKNOWN: + [[fallthrough]]; + case ContractType::CLAIM: + [[fallthrough]]; + case ContractType::MESSAGE: + [[fallthrough]]; + case ContractType::POLL: + [[fallthrough]]; + case ContractType::VOTE: + [[fallthrough]]; + case ContractType::MRC: + [[fallthrough]]; + case ContractType::OUT_OF_BOUND: + break; + } + + throw RegistryBookmarks_error("Contract type has no registry db."); + } + + static Registry& GetRegistryWithRevert(const ContractType type) + { + switch (type) { + case ContractType::BEACON: return GetBeaconRegistry(); + case ContractType::POLL: return GetPollRegistry(); + case ContractType::PROJECT: return GetWhitelist(); + case ContractType::PROTOCOL: return GetProtocolRegistry(); + case ContractType::SCRAPER: return GetScraperRegistry(); + case ContractType::VOTE: return GetPollRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); + [[fallthrough]]; + case ContractType::UNKNOWN: + [[fallthrough]]; + case ContractType::CLAIM: + [[fallthrough]]; + case ContractType::MESSAGE: + [[fallthrough]]; + case ContractType::MRC: + [[fallthrough]]; + case ContractType::OUT_OF_BOUND: + break; + } + + throw RegistryBookmarks_error("Contract type has no contract handler reversion capability."); + } + + + static bool IsRegistryBackedByDB(const ContractType& type) + { + auto iter = std::find(CONTRACT_TYPES_WITH_REG_DB.begin(), + CONTRACT_TYPES_WITH_REG_DB.end(), type); + + return (iter != CONTRACT_TYPES_WITH_REG_DB.end()); + } + + static bool IsRegistryRevertCapable(const ContractType& type) + { + auto iter = std::find(CONTRACT_TYPES_SUPPORTING_REVERT.begin(), + CONTRACT_TYPES_SUPPORTING_REVERT.end(), type); + + return (iter != CONTRACT_TYPES_SUPPORTING_REVERT.end()); + } + + std::optional GetRegistryBlockHeight(const ContractType type) const + { + auto db_height_entry = m_db_heights.find(type); + + if (db_height_entry == m_db_heights.end()) { + return std::nullopt; + } + + return db_height_entry->second; + } + + void UpdateRegistryBookmarks() + { + // We use array notation here, because we want the latest to override, and if one doesn't exist it will + // be created. + for (const auto& registry_type : CONTRACT_TYPES_WITH_REG_DB) { + m_db_heights[registry_type] = GetRegistryWithDB(registry_type).GetDBHeight(); + } + } + + //! + //! \brief This method is used in the cleanup after disconnecting blocks in DisconnectBlocksBatch to reset the db + //! bookmark heights. It will reset the DB block height AND bookmark for a registry if the new (head of chain) + //! height is less than the recorded bookmark for that contract type. + //! + //! \param block_height. + //! + void UpdateRegistryBlockHeights(int& block_height) + { + for (const auto& registry_type : CONTRACT_TYPES_WITH_REG_DB) { + if (GetRegistryBlockHeight(registry_type) > block_height) { + GetRegistryWithDB(registry_type).SetDBHeight(block_height); + + m_db_heights[registry_type] = block_height; + } + } + } + + int GetLowestRegistryBlockHeight() + { + int lowest_height = std::numeric_limits::max(); + + for (const auto& iter : m_db_heights) { + int db_height = iter.second; + + //! When below the operational range of the sidestake contracts and registry, initialization of the sidestake + //! registry will report zero for height. It is undesirable to return this in the GetLowestRegistryBlockHeight() + //! method, because it will cause the contract replay clamp to go to the Fern mandatory blockheight. Setting + //! the db_height recorded in the bookmarks at V13 height for the sidestake registry for the purpose of contract + //! replay solves the problem. + //! + //! This code can be removed after the V13 mandatory blockheight has been reached. + if (iter.first == GRC::ContractType::SIDESTAKE and db_height < Params().GetConsensus().BlockV13Height) { + db_height = Params().GetConsensus().BlockV13Height; + } + + if (iter.second < lowest_height) { + lowest_height = db_height; + } + } + + return lowest_height; + } + +private: + RegistryBlockHeights m_db_heights; +}; + +} // GRC namespace +#endif // GRIDCOIN_CONTRACT_REGISTRY_H diff --git a/src/gridcoin/contract/registry_db.h b/src/gridcoin/contract/registry_db.h new file mode 100644 index 0000000000..41f37e2627 --- /dev/null +++ b/src/gridcoin/contract/registry_db.h @@ -0,0 +1,737 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_CONTRACT_REGISTRY_DB_H +#define GRIDCOIN_CONTRACT_REGISTRY_DB_H + +#include "dbwrapper.h" +#include "gridcoin/contract/handler.h" +#include "gridcoin/contract/payload.h" +#include "gridcoin/support/enumbytes.h" + +using LogFlags = BCLog::LogFlags; + +namespace GRC { +//! +//! \brief This is a template class generalization of the original beacon registry db in leveldb. This has been made into +//! a template to facilitate usage of the db with strong typing for additional registries, but not have to repeat essentially +//! the same code over and over. The original beacon registry db code has been refactored to use this template, with some +//! necessary specializations. +//! +//! The class template parameters are +//! E: the entry type +//! SE: the storage entry type. For all except beacons this is the same as E. +//! S: the entry status enum +//! M: the map type for the entries +//! P: the map type for pending entries. This is really only used for beacons. In all other registries it is typedef'd to +//! the same as M. +//! X: the map type for expired pending entries. This is really only used for beacons. In all other registries it is typedef'd to +//! the same as M. +//! H: the historical map type for historical entries +//! +template +class RegistryDB +{ +public: + //! + //! \brief The RegistryDB template constructor. The parameter is the version for the DB for the registry class. + //! \param version + //! + RegistryDB(uint32_t version) + : m_version(version) + { + }; + + //! + //! \brief Version number of the template entry db. This is dependent on the registry class instantiated, because + //! different registry classes were done at different times. + //! + //! CONSENSUS: Increment this value in the constructor of the registry class when introducing a breaking change + //! to the corresponding entry class. This will ensure that when the wallet is restarted, the level db entry storage + //! for the appropriate class will be cleared and reloaded from the contract replay with the correct lookback scope. + //! + const uint32_t m_version; + + typedef const std::shared_ptr entry_ptr; + + typedef const entry_ptr entry_option; + + //! + //! \brief Initializes the template registry entry map structures from the replay of the entry states stored + //! in the entry database. + //! + //! \param entries The map of current entries. + //! \param pending_entries. The map of pending entries. This is not used in the general template, only in the beacons + //! specialization. + //! \param expired_entries. The map of expired pending entries. This is not used in the general template, only in the + //! beacons specialization. + //! + //! \return block height up to and including which the entry records were stored. + //! + int Initialize(M& entries, P& pending_entries, X& expired_entries) + { + bool status = true; + int height = 0; + uint32_t version = 0; + std::string key_type = KeyType(); + + // First load the KeyType() db version from LevelDB and check it against the constant in the class. + { + CTxDB txdb("r"); + + std::pair key = std::make_pair(key_type + "_db", "version"); + + bool status = txdb.ReadGenericSerializable(key, version); + + if (!status) version = 0; + } + + if (version != m_version) { + LogPrint(LogFlags::CONTRACT, "WARNING: %s: Version level of the %s entry db stored in LevelDB, %u, does not " + "match that required in this code level, version %u. Clearing the LevelDB %s entry " + "storage and setting version level to match this code level.", + __func__, + key_type, + version, + m_version, + key_type); + + clear_leveldb(); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: LevelDB %s area cleared. Version level set to %u.", + __func__, + key_type, + m_version); + } + + // If LoadDBHeight not successful or height is zero then LevelDB has not been initialized before. + // LoadDBHeight will also set the private member variable m_height_stored from LevelDB for this first call. + if (!LoadDBHeight(height) || !height) { + return height; + } else { // LevelDB already initialized from a prior run. + + // Set m_database_init to true. This will cause LoadDBHeight hereinafter to simply report + // the value of m_height_stored rather than loading the stored height from LevelDB. + m_database_init = true; + } + + LogPrint(LogFlags::CONTRACT, "INFO: %s: db stored height at block %i.", + __func__, + height); + + // Now load the entries from LevelDB. + + // This temporary map is keyed by record number, which insures the replay down below occurs in the right order. + StorageMapByRecordNum storage_by_record_num; + + // Code block to scope the txdb object. + { + CTxDB txdb("r"); + + uint256 hash_hint = uint256(); + + // Load the temporary which is similar to m_historical, except the key is by record number not hash. + status = txdb.ReadGenericSerializablesToMapWithForeignKey(key_type, storage_by_record_num, hash_hint); + } + + if (!status) { + if (height > 0){ + // For the height be greater than zero from the height K-V, but the read into the map to fail + // means the storage in LevelDB must be messed up in the template type area and not be in concordance with + // the template type K-V's. Therefore clear the whole thing. + clear(); + } + + // Return height of zero. + return 0; + } + + uint64_t recnum_high_watermark = 0; + uint64_t number_passivated = 0; + + for (const auto& iter : storage_by_record_num) { + const uint64_t& recnum = iter.first; + const E& entry = iter.second; + + recnum_high_watermark = std::max(recnum_high_watermark, recnum); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s historical entry insert: key %s, value %s, timestamp %" PRId64 ", hash %s, " + "previous_hash %s, status %s, recnum %" PRId64 ".", + __func__, + key_type, + entry.KeyValueToString().first, // key + entry.KeyValueToString().second, //value + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev entry transaction hash + entry.StatusToString(), // status + recnum + ); + + // Insert the entry into the historical map. + m_historical[iter.second.m_hash] = std::make_shared(entry); + entry_ptr& historical_entry_ptr = m_historical[iter.second.m_hash]; + + HandleCurrentHistoricalEntries(entries, pending_entries, expired_entries, entry, + historical_entry_ptr, recnum, key_type); + + number_passivated += (uint64_t) HandlePreviousHistoricalEntries(historical_entry_ptr); + } // storage_by_record_num iteration + + LogPrint(LogFlags::CONTRACT, "INFO: %s: number of historical records passivated: %" PRId64 ".", + __func__, + number_passivated); + + // Set the in-memory record number stored variable to the highest recnum encountered during the replay above. + m_recnum_stored = recnum_high_watermark; + + // Set the needs passivation flag to true, because the one-by-one passivation done above may not catch everything. + m_needs_passivation = true; + + return height; + } + + //! + //! \brief Handles the insertion/deletion of entries in the current entry map and pending entry map. Note that this + //! method is specialized for beacons, and the standard template method does not actually use pending_entries. + //! + //! \param entries + //! \param pending_entries + //! \param entry + //! \param historical_entry_ptr + //! \param recnum + //! \param key_type + //! + void HandleCurrentHistoricalEntries(M& entries, P& pending_entries, X& expired_entries, const E& entry, + entry_ptr& historical_entry_ptr, const uint64_t& recnum, + const std::string& key_type) + { + // The unknown or out of bound status conditions should have never made it into leveldb to begin with, since + // the entry contract will fail validation, but to be thorough, include the filter condition anyway. + // Unlike beacons, this is a straight replay. + if (entry.m_status != S::UNKNOWN && entry.m_status != S::OUT_OF_BOUND) { + LogPrint(LogFlags::CONTRACT, "INFO: %s: %s entry insert: key %s, value %s, timestamp %" PRId64 "; hash %s; " + "previous_hash %s; status %s, recnum %" PRId64 ".", + __func__, + key_type, + entry.KeyValueToString().first, // key + entry.KeyValueToString().second, //value + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev entry transaction hash + entry.StatusToString(), // status + recnum + ); + + // Insert or replace the existing map entry with the latest. + entries[entry.Key()] = historical_entry_ptr; + } + } + + //! + //! \brief Handles the passivation of previous historical entries that have been superseded by current entries. + //! + //! \param historical_entry_ptr. Shared smart pointer to current historical entry already inserted into historical map. + //! + //! \return Boolean - true if a passivation occurred. + //! + bool HandlePreviousHistoricalEntries(const entry_ptr& historical_entry_ptr) + { + bool passivated = false; + + typename H::iterator prev_historical_iter = m_historical.end(); + + // prev_historical_iter here is for purposes of passivation later. If insertion of records by recnum results in a + // second or succeeding record for the same key, then m_previous_hash will not be null. If the prior record + // pointed to by that hash is found, then it can be removed from memory, since only the current record by recnum + // needs to be retained. + if (!historical_entry_ptr->m_previous_hash.IsNull()) { + prev_historical_iter = m_historical.find(historical_entry_ptr->m_previous_hash); + } + + if (prev_historical_iter != m_historical.end()) { + // Note that passivation is not expected to be successful for every call. See the comments + // in the passivate() function. + std::pair passivation_result + = passivate(prev_historical_iter); + + passivated = passivation_result.second; + } + + return passivated; + } + + //! + //! \brief Clears the historical entry map of the database. This is only used during testing. + //! + void clear_in_memory_only() + { + m_historical.clear(); + m_database_init = false; + m_height_stored = 0; + m_recnum_stored = 0; + m_needs_passivation = false; + } + + //! + //! \brief Clears the LevelDB entry storage area. + //! + //! \return Success or failure. + //! + bool clear_leveldb() + { + bool status = true; + + CTxDB txdb("rw"); + + std::string key_type = KeyType(); + uint256 start_key_hint = uint256(); + + status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint); + + key_type = KeyType() + "_db"; + std::string start_key_hint_db {}; + + status &= txdb.EraseGenericSerializablesByKeyType(key_type, start_key_hint_db); + + // We want to write back into LevelDB the revision level of the db in the running code. + std::pair key = std::make_pair(key_type, "version"); + status &= txdb.WriteGenericSerializable(key, m_version); + + m_height_stored = 0; + m_recnum_stored = 0; + m_database_init = false; + m_needs_passivation = false; + + return status; + } + + //! + //! \brief Removes in memory elements for all historical records not in the entries map. + //! \return Number of elements passivated. + //! + uint64_t passivate_db() + { + uint64_t number_passivated = 0; + + // Don't bother to go through the historical entry map unless the needs passivation flag is set. This makes + // this function extremely light for most calls from the periodic schedule. + if (m_needs_passivation) { + for (auto iter = m_historical.begin(); iter != m_historical.end(); /*no-op*/) { + // The passivate function increments the iterator. + std::pair result = passivate(iter); + + iter = result.first; + number_passivated += result.second; + + } + } + + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Passivated %" PRId64 " elements from %s entry db.", + __func__, + number_passivated, + KeyType()); + + // Set needs passivation flag to false after passivating the db. + m_needs_passivation = false; + + return number_passivated; + } + + //! + //! \brief Clear the historical map and LevelDB entry storage area. + //! + //! \return Success or failure. + //! + bool clear() + { + clear_in_memory_only(); + + return clear_leveldb(); + } + + //! + //! \brief The number of entry historical elements in the entry database. This includes in memory + //! entries only and not passivated entries. + //! + //! \return The number of elements. + //! + size_t size() + { + return m_historical.size(); + } + + //! + //! \brief Returns whether IsContract correction is needed in ReplayContracts during initialization. + //! \return + //! + bool NeedsIsContractCorrection() + { + return m_needs_IsContract_correction; + } + + //! + //! \brief Sets the state of the IsContract needs correction flag in memory and LevelDB + //! \param flag The state to set + //! \return + //! + bool SetNeedsIsContractCorrection(bool flag) + { + // Update the in-memory flag. + m_needs_IsContract_correction = flag; + + // Update LevelDB + CTxDB txdb("rw"); + + std::pair key = std::make_pair(KeyType() + "_db", "needs_IsContract_correction"); + + return txdb.WriteGenericSerializable(key, m_needs_IsContract_correction); + } + + //! + //! \brief This stores the height to which the database entries are valid (the db scope). Note that it + //! is not desired to expose this function as a public function, but currently the Revert function + //! only operates on a single transaction context, and does not encapsulate the post reversion height + //! after the reversion state. TODO: Create a Revert overload that takes a vector of contract contexts + //! to be reverted (in order in which they are in the vector) and the post revert batch height (i.e. + //! the common block of the fork/reorg). + //! + //! \param height_stored + //! + //! \return Success or failure. + //! + bool StoreDBHeight(const int& height_stored) + { + // Update the in-memory bookmark variable. + m_height_stored = height_stored; + + // Update LevelDB. + CTxDB txdb("rw"); + + std::string key_name = KeyType() + "_db"; + + std::pair key = std::make_pair(key_name, "height_stored"); + + return txdb.WriteGenericSerializable(key, height_stored); + } + + + //! + //! \brief Provides the block height to which the entry db covers. This is persisted in LevelDB. + //! + //! \param height_stored + //! + //! \return + //! + bool LoadDBHeight(int& height_stored) + { + bool status = true; + + // If the database has already been initialized (which includes loading the height to what the + // template type entry storage was updated), then just report the value of m_height_stored, otherwise + // pull the value from LevelDB. + if (m_database_init) { + height_stored = m_height_stored; + } else { + CTxDB txdb("r"); + + std::string key_name = KeyType() + "_db"; + + std::pair key = std::make_pair(key_name, "height_stored"); + + bool status = txdb.ReadGenericSerializable(key, height_stored); + + if (!status) height_stored = 0; + + m_height_stored = height_stored; + } + + return status; + } + + //! + //! \brief Insert an entry record into the historical database. + //! + //! \param hash The hash for the key to the historical record which is the txid (hash) of the transaction + //! containing the entry contract. + //! \param height The height of the block from which the entry record originates. + //! \param entry The entry record to insert (which includes the appropriate status). Note that this entry + //! will be cast into the SE type for storage if the SE type is different from the E type. In general if + //! SE is different from E, it will be to implement different serialization for storage (such as the beacon + //! implementation). + //! + //! \return Success or Failure. This will fail if a record with the same key already exists in the + //! database. + //! + bool insert(const uint256& hash, const int& height, const E& entry) + { + bool status = false; + + if (m_historical.find(hash) != m_historical.end()) { + return status; + } else { + LogPrint(LogFlags::CONTRACT, "INFO: %s: store %s entry: key %s, value %s, height %i, timestamp %" PRId64 + ", hash %s, previous_hash %s, status %s.", + __func__, + KeyType(), + entry.KeyValueToString().first, // key + entry.KeyValueToString().second, //value + height, // height + entry.m_timestamp, // timestamp + entry.m_hash.GetHex(), // transaction hash + entry.m_previous_hash.GetHex(), // prev entry transaction hash + entry.StatusToString() // status + ); + + m_historical.insert(std::make_pair(hash, std::make_shared(entry))); + + status = Store(hash, static_cast(entry)); + + if (height) { + status &= StoreDBHeight(height); + } + + // Set needs passivation flag to true to allow the scheduled passivation to remove unnecessary records from + // memory. + m_needs_passivation = true; + + return status; + } + } + + //! + //! \brief Erase a record from the database. + //! + //! \param hash The key of the record to erase. + //! + //! \return Success or failure. + //! + bool erase(const uint256& hash) + { + auto iter = m_historical.find(hash); + + if (iter != m_historical.end()) { + m_historical.erase(hash); + } + + return Delete(hash); + } + + //! + //! \brief Remove an individual in memory element that is backed by LevelDB that is not in the active entry map. + //! + //! \param hash The hash that is the key to the element. + //! + //! \return A pair, the first part of which is an iterator to the next element, or map::end() if the last one, and + //! the second is success or failure of the passivation. + //! + std::pair + passivate(typename H::iterator& iter) + { + // m_historical itself holds one reference, additional references can be held by the active entries map. + // If there is only one reference then remove the shared_pointer from m_historical, which will implicitly destroy + // the shared_pointer object. + if (iter->second.use_count() == 1) { + iter = m_historical.erase(iter); + return std::make_pair(iter, true); + } else { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Passivate called for historical entry record with hash %s that " + "has existing reference count %li. This is expected under certain situations.", + __func__, + iter->second->m_hash.GetHex(), + iter->second.use_count()); + + ++iter; + return std::make_pair(iter, false); + } + } + + //! + //! \brief Iterator to the beginning of the database records. + //! + //! \return Iterator. + //! + typename H::iterator begin() + { + return m_historical.begin(); + } + + //! + //! \brief Iterator to end(). + //! + //! \return Iterator. + //! + typename H::iterator end() + { + return m_historical.end(); + } + + //! + //! \brief Provides an iterator pointing to the element which key value matches the provided hash. Note that + //! this wrapper extends the behavior of the normal find function and will, in the case the element is not + //! present in the in-memory map, look in LevelDB and attempt to load the element from LevelDB, place in the + //! map, and return an iterator. end() is returned if the element is not found. + //! + //! \param hash The hash value with which to match on the key. + //! + //! \return Iterator. + //! + typename H::iterator find(const uint256& hash) + { + // See if entry from that ctx_hash is already in the historical map. If so, get iterator. + auto iter = m_historical.find(hash); + + // If it isn't, attempt to load the entry from LevelDB into the map. + if (iter == m_historical.end()) { + SE entry; + + // If the load from LevelDB is successful, insert into the historical map and return the iterator. + if (Load(hash, entry)) { + iter = m_historical.insert(std::make_pair(hash, std::make_shared(entry))).first; + + // Set the needs passivation flag to true + m_needs_passivation = true; + } + } + + // Note that if there is no entry in m_historical, and also there is no K-V in LevelDB, then an + // iterator at end() will be returned. + return iter; + } + + //! + //! \brief Advances the iterator to the next element. + //! + //! \param iter + //! + //! \return iter + //! + typename H::iterator advance(typename H::iterator iter) + { + return ++iter; + } + +private: + //! + //! \brief Type definition for the storage typename SE entry map used in Initialize. Note that the uint64_t + //! is the record number, which unfortunately is required to preserve the contract application order + //! since they are applied in the order of the block's transaction vector rather than the transaction time. + //! + typedef std::map> StorageMap; + + //! + //! \brief Type definition for the map used to replay state from LevelDB KeyType() entry area. + //! + typedef std::map StorageMapByRecordNum; + + //! + //! \brief This is a map keyed by uint256 (SHA256) hash that stores the historical entry elements. + //! It is persisted in LevelDB storage. + //! + H m_historical; + + //! + //! \brief Boolan to indicate whether the database has been successfully initialized from LevelDB during + //! startup. + //! + bool m_database_init = false; + + //! + //! \brief The block height for entry records stored in the database. This is a bookmark. It is + //! adjusted by StoreDBHeight, persisted in memory by this private member variable, and persisted in storage + //! to LevelDB. + //! + int m_height_stored = 0; + + //! + //! \brief The record number stored watermark. This effectively a sequence number for records stored in + //! the LevelDB entry area. The value in memory will be at the highest record number inserted (or played + //! back during initialization). + //! + uint64_t m_recnum_stored = 0; + + //! + //! \brief The flag that indicates whether memory optimization can occur by passivating the database. This + //! flag is set true when find() retrieves an entry element from LevelDB to satisfy a hash search. + //! This would typically occur on a reorganization (revert). + //! + bool m_needs_passivation = false; + + //! + //! \brief The flag that indicates whether IsContract correction is needed in ReplayContracts during initialization. + //! This is relevant only for the Beacon Registry specialization. + //! + bool m_needs_IsContract_correction = false; + + //! + //! \brief The function that returns the string value to be used in leveldb as the key prefix for the registry. + //! For example, the ScaperRegistry uses "scraper". This must be implemented in the class specialization. + + //! \return std::string representing the key prefix to be used for the registry db entries. + //! + const std::string KeyType(); + + //! + //! \brief Store an entry object in LevelDB with the provided key value. + //! + //! \param hash The SHA256 hash key value for the element. + //! \param entry The entry historical state element to be stored. + //! + //! \return Success or failure. + //! + bool Store(const uint256& hash, const SE& entry) + { + CTxDB txdb("rw"); + + ++m_recnum_stored; + + std::pair key = std::make_pair(KeyType(), hash); + + return txdb.WriteGenericSerializable(key, std::make_pair(m_recnum_stored, entry)); + } + + //! + //! \brief Load an entry object from LevelDB using the provided key value. + //! + //! \param hash The SHA256 hash key value for the element. + //! \param entry The entry historical state element loaded. + //! + //! \return Success or failure. + //! + bool Load(const uint256& hash, SE& entry) + { + CTxDB txdb("r"); + + std::pair key = std::make_pair(KeyType(), hash); + + std::pair entry_pair; + + bool status = txdb.ReadGenericSerializable(key, entry_pair); + + entry = entry_pair.second; + + return status; + } + + //! + //! \brief Delete an entry object from LevelDB with the provided key value (if it exists). + //! + //! \param hash The SHA256 hash key value for the element. + //! + //! \return Success or failure. + //! + bool Delete(const uint256& hash) + { + CTxDB txdb("rw"); + + std::pair key = std::make_pair(KeyType(), hash); + + return txdb.EraseGenericSerializable(key); + } + +}; // RegistryDB class template + +} // namespace GRC + +#endif // GRIDCOIN_CONTRACT_REGISTRY_DB_H diff --git a/src/gridcoin/cpid.cpp b/src/gridcoin/cpid.cpp index ff8a689c2d..f0edebf5b9 100644 --- a/src/gridcoin/cpid.cpp +++ b/src/gridcoin/cpid.cpp @@ -3,10 +3,10 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "gridcoin/cpid.h" +#include #include "util.h" #include -#include using namespace GRC; @@ -67,7 +67,7 @@ Cpid Cpid::Hash(const std::string& internal, const std::string& email) std::vector input(internal.begin(), internal.end()); input.insert(input.end(), email.begin(), email.end()); - MD5(input.data(), input.size(), cpid.m_bytes.data()); + GRC__MD5(input.data(), input.size(), cpid.m_bytes.data()); return cpid; } diff --git a/src/gridcoin/gridcoin.cpp b/src/gridcoin/gridcoin.cpp index 4f065bfaab..39853e113f 100644 --- a/src/gridcoin/gridcoin.cpp +++ b/src/gridcoin/gridcoin.cpp @@ -1,12 +1,14 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include "chainparams.h" +#include "gridcoin/scraper/scraper_registry.h" #include "main.h" #include "util/threadnames.h" #include "gridcoin/backup.h" #include "gridcoin/contract/contract.h" +#include "gridcoin/contract/registry.h" #include "gridcoin/gridcoin.h" #include "gridcoin/quorum.h" #include "gridcoin/researcher.h" @@ -141,50 +143,83 @@ bool InitializeResearchRewardAccounting(CBlockIndex* pindexBest) //! void InitializeContracts(CBlockIndex* pindexBest) { - LogPrintf("Gridcoin: Loading beacon history..."); - uiInterface.InitMessage(_("Loading beacon history...")); + // This loop initializes the registry for each contract type in CONTRACT_TYPES_WITH_REG_DB. + for (const auto& contract_type : RegistryBookmarks::CONTRACT_TYPES_WITH_REG_DB) { + Registry& registry = RegistryBookmarks::GetRegistryWithDB(contract_type); - BeaconRegistry& beacons = GetBeaconRegistry(); + std::string contract_type_string = Contract::Type::ToString(contract_type); + std::string tr_contract_type_string = Contract::Type::ToTranslatedString(contract_type); - // If the clearbeaconhistory argument is provided, then clear everything from the beacon registry, - // including the beacon_db and beacon key type elements from LevelDB. - if (gArgs.GetBoolArg("-clearbeaconhistory", false)) - { - beacons.Reset(); - } + LogPrintf("INFO: %s: Loading stored history for contract type %s...", + __func__, + contract_type_string); - LogPrintf("Gridcoin: Initializing beacon registry from stored history..."); - uiInterface.InitMessage(_("Initializing beacon registry from stored history...")); - int beacon_db_height = beacons.Initialize(); + uiInterface.InitMessage(_("Loading history for contract type ") + tr_contract_type_string + "..."); - if (beacon_db_height > 0) - { - LogPrintf("Gridcoin: beacon history loaded through height = %i.", beacon_db_height); - } - else - { - LogPrintf("Gridcoin: beacon history load not successful. Will initialize from contract replay."); + std::string history_arg = "-clear" + GRC::Contract::Type::ToString(contract_type) + "history"; + if (gArgs.GetBoolArg(history_arg, false)) { + registry.Reset(); + } + + LogPrintf("INFO: %s: Initializing registry from stored history for contract type %s...", + __func__, + contract_type_string); + + uiInterface.InitMessage(_("Initializing registry from stored history for contract type ") + + tr_contract_type_string + "..."); + + int db_height = registry.Initialize(); + + if (db_height > 0) { + LogPrintf("INFO: %s: History loaded through height %i for contract type %s", + __func__, + db_height, + contract_type_string); + } else { + LogPrintf("INFO: %s: History load not successful for contract type %s. Will initialize " + "from contract replay.", + __func__, + contract_type_string); + } } LogPrintf("Gridcoin: replaying contracts..."); uiInterface.InitMessage(_("Replaying contracts...")); - CBlockIndex* pindex_start = GRC::BlockFinder::FindByMinTime(pindexBest->nTime - Beacon::MAX_AGE); + CBlockIndex* pindex_start = GRC::BlockFinder::FindByMinTime(pindexBest->nTime + - Params().GetConsensus().StandardContractReplayLookback); const int& V11_height = Params().GetConsensus().BlockV11Height; const int& lookback_window_low_height = pindex_start->nHeight; + // Gets a registry db height bookmark object with the heights loaded now that the registries are loaded. + RegistryBookmarks db_heights; + // This tricky clamp ensures the correct start height for the contract replay. Note that the current - // implementation will skip beacon contracts that overlap the already loaded beacon history. See - // ReplayContracts. The worst case replay is a window that starts at V11_height and extends to current height. - // This is the replay that will be encountered when starting a wallet that was in sync with this code, and the - // head of the chain is more than MAX AGE above the V11Height. When the contracts are replayed, the beacon db - // will then be initialized and the controlling window will be consistent with MAX_AGE on restarts and reorgs. - const int& start_height = std::min(std::max(beacon_db_height, V11_height), lookback_window_low_height); + // implementation will skip beacon, scraper entry, project, poll and vote contracts that overlap the already loaded + // history. See ReplayContracts. The worst case replay is a window that starts at V11_height and extends to current + // height. This is the replay that will be encountered when starting a wallet that was in sync with this code but has + // uninitialized registry dbs, and the head of the chain is more than MAX AGE above the V11_height, because + // GetLowestRegistryBlockHeight() is 0, and then the maximum of V11_height and GetLowestRegistryBlockHeight() will be + // V11_height and the minimum of V11_height and lookback_window_low_height will be V11_height. When the contracts are + // replayed, the beacon db and scraper entry db will then be initialized and the controlling window will be either the + // GetLowestRegistryBlockHeight() or the lookback_window_low_height, whichever is higher. The MAX_AGE (i.e. + // lookback_window_low_height) condition once the contract types that have a backing db are initialized is now driven + // by the following remaining contract types which have no registry (backing) db: + // + // CONTRACT type Wallet startup replay requirement Block reorg replay requirement + // POLL/VOTE (polls and voting) V11 or std lookback false + // + // Note that the handler reset and contract replay forwards from lookback_window_low_height no longer is required + // for polls and votes. The reason for this is quite simple. Polls and votes are UNIQUE. The reversion of an add + // is simply to delete them. The wallet startup replay requirement is still required for polls and votes, because + // the Poll/Vote classes do not have a backing registry db yet. + int start_height = std::min(std::max(db_heights.GetLowestRegistryBlockHeight(), V11_height), + lookback_window_low_height); LogPrintf("Gridcoin: Starting contract replay from height %i.", start_height); - CBlockIndex* pblock_index = mapBlockIndex[hashBestChain]; + CBlockIndex* pblock_index = pindexBest; while (pblock_index->nHeight > start_height) { @@ -194,8 +229,8 @@ void InitializeContracts(CBlockIndex* pindexBest) // Reset pindex_start to the index for the block at start_height pindex_start = pblock_index; - // The replay contract window here may overlap with the beacon db coverage. Logic is now included in - // the ApplyContracts to ignore beacon contracts that have already been covered by the beacon db. + // The replay contract window here may overlap with the registry db coverage for the various registries. Logic + // is now included in the ApplyContracts to ignore contracts that have already been covered by the registry dbs. ReplayContracts(pindexBest, pindex_start); } @@ -427,11 +462,15 @@ void ScheduleUpdateChecks(CScheduler& scheduler) }, std::chrono::minutes{1}); } -void ScheduleBeaconDBPassivation(CScheduler& scheduler) +void ScheduleRegistriesPassivation(CScheduler& scheduler) { - // Run beacon database passivation every 5 minutes. This is a very thin call most of the time. + // Run registry database passivation every 5 minutes. This is a very thin call most of the time. // Please see the PassivateDB function and passivate_db. - scheduler.scheduleEvery(BeaconRegistry::RunBeaconDBPassivation, std::chrono::minutes{5}); + // TODO: Turn into a loop using extension of RegistryBookmarks + scheduler.scheduleEvery(BeaconRegistry::RunDBPassivation, std::chrono::minutes{5}); + scheduler.scheduleEvery(ScraperRegistry::RunDBPassivation, std::chrono::minutes{5}); + scheduler.scheduleEvery(ProtocolRegistry::RunDBPassivation, std::chrono::minutes{5}); + scheduler.scheduleEvery(Whitelist::RunDBPassivation, std::chrono::minutes{5}); } } // Anonymous namespace @@ -496,7 +535,7 @@ void GRC::ScheduleBackgroundJobs(CScheduler& scheduler) ScheduleBackups(scheduler); ScheduleUpdateChecks(scheduler); - ScheduleBeaconDBPassivation(scheduler); + ScheduleRegistriesPassivation(scheduler); } bool GRC::CleanConfig() { diff --git a/src/gridcoin/md5.c b/src/gridcoin/md5.c new file mode 100644 index 0000000000..65ccee979f --- /dev/null +++ b/src/gridcoin/md5.c @@ -0,0 +1,421 @@ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are adhered to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the routines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] */ + +#include + +#include +#include +#include + +#define CRYPTO_load_u32_le(data) (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24) +#define CRYPTO_store_u32_le(dst, src) (dst)[0] = (src & 0xFF); (dst)[1] = (src & 0xFF00) >> 8; (dst)[2] = (src & 0xFF0000) >> 16; (dst)[3] = (src & 0xFF000000) >> 24 +#define CRYPTO_load_u32_be(data) (uint32_t)data[3] | ((uint32_t)data[2] << 8) | ((uint32_t)data[1] << 16) | ((uint32_t)data[0] << 24) +#define CRYPTO_store_u32_be(dst, src) (dst)[3] = (src & 0xFF); (dst)[2] = (src & 0xFF00) >> 8; (dst)[1] = (src & 0xFF0000) >> 16; (dst)[0] = (src & 0xFF000000) >> 24 + +static inline uint32_t CRYPTO_rotl_u32(uint32_t value, int shift) { +#if defined(_MSC_VER) + return _rotl(value, shift); +#else + return (value << shift) | (value >> ((-shift) & 31)); +#endif +} + +// This is a generic 32-bit "collector" for message digest algorithms. It +// collects input character stream into chunks of 32-bit values and invokes the +// block function that performs the actual hash calculations. +// +// To make use of this mechanism, the hash context should be defined with the +// following parameters. +// +// typedef struct _state_st { +// uint32_t h[ / sizeof(uint32_t)]; +// uint32_t Nl, Nh; +// uint8_t data[]; +// unsigned num; +// ... +// } _CTX; +// +// is the output length of the hash in bytes, before +// any truncation (e.g. 64 for SHA-224 and SHA-256, 128 for SHA-384 and +// SHA-512). +// +// |h| is the hash state and is updated by a function of type +// |crypto_md32_block_func|. |data| is the partial unprocessed block and has +// |num| bytes. |Nl| and |Nh| maintain the number of bits processed so far. + +// A crypto_md32_block_func should incorporate |num_blocks| of input from |data| +// into |state|. It is assumed the caller has sized |state| and |data| for the +// hash function. +typedef void (*crypto_md32_block_func)(uint32_t *state, const uint8_t *data, + size_t num_blocks); + +// crypto_md32_update adds |len| bytes from |in| to the digest. |data| must be a +// buffer of length |block_size| with the first |*num| bytes containing a +// partial block. This function combines the partial block with |in| and +// incorporates any complete blocks into the digest state |h|. It then updates +// |data| and |*num| with the new partial block and updates |*Nh| and |*Nl| with +// the data consumed. +static inline void crypto_md32_update(crypto_md32_block_func block_func, + uint32_t *h, uint8_t *data, + size_t block_size, unsigned *num, + uint32_t *Nh, uint32_t *Nl, + const uint8_t *in, size_t len) { + if (len == 0) { + return; + } + + uint32_t l = *Nl + (((uint32_t)len) << 3); + if (l < *Nl) { + // Handle carries. + (*Nh)++; + } + *Nh += (uint32_t)(len >> 29); + *Nl = l; + + size_t n = *num; + if (n != 0) { + if (len >= block_size || len + n >= block_size) { + memcpy(data + n, in, block_size - n); + block_func(h, data, 1); + n = block_size - n; + in += n; + len -= n; + *num = 0; + // Keep |data| zeroed when unused. + memset(data, 0, block_size); + } else { + memcpy(data + n, in, len); + *num += (unsigned)len; + return; + } + } + + n = len / block_size; + if (n > 0) { + block_func(h, in, n); + n *= block_size; + in += n; + len -= n; + } + + if (len != 0) { + *num = (unsigned)len; + memcpy(data, in, len); + } +} + +// crypto_md32_final incorporates the partial block and trailing length into the +// digest state |h|. The trailing length is encoded in little-endian if +// |is_big_endian| is zero and big-endian otherwise. |data| must be a buffer of +// length |block_size| with the first |*num| bytes containing a partial block. +// |Nh| and |Nl| contain the total number of bits processed. On return, this +// function clears the partial block in |data| and +// |*num|. +// +// This function does not serialize |h| into a final digest. This is the +// responsibility of the caller. +static inline void crypto_md32_final(crypto_md32_block_func block_func, + uint32_t *h, uint8_t *data, + size_t block_size, unsigned *num, + uint32_t Nh, uint32_t Nl, + int is_big_endian) { + // |data| always has room for at least one byte. A full block would have + // been consumed. + size_t n = *num; + assert(n < block_size); + data[n] = 0x80; + n++; + + // Fill the block with zeros if there isn't room for a 64-bit length. + if (n > block_size - 8) { + memset(data + n, 0, block_size - n); + n = 0; + block_func(h, data, 1); + } + memset(data + n, 0, block_size - 8 - n); + + // Append a 64-bit length to the block and process it. + if (is_big_endian) { + CRYPTO_store_u32_be(data + block_size - 8, Nh); + CRYPTO_store_u32_be(data + block_size - 4, Nl); + } else { + CRYPTO_store_u32_le(data + block_size - 8, Nl); + CRYPTO_store_u32_le(data + block_size - 4, Nh); + } + block_func(h, data, 1); + *num = 0; + memset(data, 0, block_size); +} + +static int MD5_Init(MD5_CTX *md5) { + memset(md5, 0, sizeof(MD5_CTX)); + md5->h[0] = 0x67452301UL; + md5->h[1] = 0xefcdab89UL; + md5->h[2] = 0x98badcfeUL; + md5->h[3] = 0x10325476UL; + return 1; +} + +static void md5_block_data_order(uint32_t *state, const uint8_t *data, + size_t num); + +static void MD5_Transform(MD5_CTX *c, const uint8_t data[MD5_CBLOCK]) { + md5_block_data_order(c->h, data, 1); +} + +static int MD5_Update(MD5_CTX *c, const void *data, size_t len) { + crypto_md32_update(&md5_block_data_order, c->h, c->data, MD5_CBLOCK, &c->num, + &c->Nh, &c->Nl, data, len); + return 1; +} + +static int MD5_Final(uint8_t out[MD5_DIGEST_LENGTH], MD5_CTX *c) { + crypto_md32_final(&md5_block_data_order, c->h, c->data, MD5_CBLOCK, &c->num, + c->Nh, c->Nl, /*is_big_endian=*/0); + + CRYPTO_store_u32_le(out, c->h[0]); + CRYPTO_store_u32_le(out + 4, c->h[1]); + CRYPTO_store_u32_le(out + 8, c->h[2]); + CRYPTO_store_u32_le(out + 12, c->h[3]); + return 1; +} + +uint8_t *GRC__MD5(const uint8_t *data, size_t len, uint8_t out[MD5_DIGEST_LENGTH]) { + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, data, len); + MD5_Final(out, &ctx); + + return out; +} + +// As pointed out by Wei Dai , the above can be +// simplified to the code below. Wei attributes these optimizations +// to Peter Gutmann's SSH code, and he attributes it to Rich Schroeppel. +#define F(b, c, d) ((((c) ^ (d)) & (b)) ^ (d)) +#define G(b, c, d) ((((b) ^ (c)) & (d)) ^ (c)) +#define H(b, c, d) ((b) ^ (c) ^ (d)) +#define I(b, c, d) (((~(d)) | (b)) ^ (c)) + +#define R0(a, b, c, d, k, s, t) \ + do { \ + (a) += ((k) + (t) + F((b), (c), (d))); \ + (a) = CRYPTO_rotl_u32(a, s); \ + (a) += (b); \ + } while (0) + +#define R1(a, b, c, d, k, s, t) \ + do { \ + (a) += ((k) + (t) + G((b), (c), (d))); \ + (a) = CRYPTO_rotl_u32(a, s); \ + (a) += (b); \ + } while (0) + +#define R2(a, b, c, d, k, s, t) \ + do { \ + (a) += ((k) + (t) + H((b), (c), (d))); \ + (a) = CRYPTO_rotl_u32(a, s); \ + (a) += (b); \ + } while (0) + +#define R3(a, b, c, d, k, s, t) \ + do { \ + (a) += ((k) + (t) + I((b), (c), (d))); \ + (a) = CRYPTO_rotl_u32(a, s); \ + (a) += (b); \ + } while (0) + +#ifndef MD5_ASM +#ifdef X +#undef X +#endif + + +static void md5_block_data_order(uint32_t *state, const uint8_t *data, + size_t num) { + uint32_t A, B, C, D; + uint32_t XX0, XX1, XX2, XX3, XX4, XX5, XX6, XX7, XX8, XX9, XX10, XX11, XX12, + XX13, XX14, XX15; +#define X(i) XX##i + + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + + for (; num--;) { + X(0) = CRYPTO_load_u32_le(data); + data += 4; + X(1) = CRYPTO_load_u32_le(data); + data += 4; + // Round 0 + R0(A, B, C, D, X(0), 7, 0xd76aa478L); + X(2) = CRYPTO_load_u32_le(data); + data += 4; + R0(D, A, B, C, X(1), 12, 0xe8c7b756L); + X(3) = CRYPTO_load_u32_le(data); + data += 4; + R0(C, D, A, B, X(2), 17, 0x242070dbL); + X(4) = CRYPTO_load_u32_le(data); + data += 4; + R0(B, C, D, A, X(3), 22, 0xc1bdceeeL); + X(5) = CRYPTO_load_u32_le(data); + data += 4; + R0(A, B, C, D, X(4), 7, 0xf57c0fafL); + X(6) = CRYPTO_load_u32_le(data); + data += 4; + R0(D, A, B, C, X(5), 12, 0x4787c62aL); + X(7) = CRYPTO_load_u32_le(data); + data += 4; + R0(C, D, A, B, X(6), 17, 0xa8304613L); + X(8) = CRYPTO_load_u32_le(data); + data += 4; + R0(B, C, D, A, X(7), 22, 0xfd469501L); + X(9) = CRYPTO_load_u32_le(data); + data += 4; + R0(A, B, C, D, X(8), 7, 0x698098d8L); + X(10) = CRYPTO_load_u32_le(data); + data += 4; + R0(D, A, B, C, X(9), 12, 0x8b44f7afL); + X(11) = CRYPTO_load_u32_le(data); + data += 4; + R0(C, D, A, B, X(10), 17, 0xffff5bb1L); + X(12) = CRYPTO_load_u32_le(data); + data += 4; + R0(B, C, D, A, X(11), 22, 0x895cd7beL); + X(13) = CRYPTO_load_u32_le(data); + data += 4; + R0(A, B, C, D, X(12), 7, 0x6b901122L); + X(14) = CRYPTO_load_u32_le(data); + data += 4; + R0(D, A, B, C, X(13), 12, 0xfd987193L); + X(15) = CRYPTO_load_u32_le(data); + data += 4; + R0(C, D, A, B, X(14), 17, 0xa679438eL); + R0(B, C, D, A, X(15), 22, 0x49b40821L); + // Round 1 + R1(A, B, C, D, X(1), 5, 0xf61e2562L); + R1(D, A, B, C, X(6), 9, 0xc040b340L); + R1(C, D, A, B, X(11), 14, 0x265e5a51L); + R1(B, C, D, A, X(0), 20, 0xe9b6c7aaL); + R1(A, B, C, D, X(5), 5, 0xd62f105dL); + R1(D, A, B, C, X(10), 9, 0x02441453L); + R1(C, D, A, B, X(15), 14, 0xd8a1e681L); + R1(B, C, D, A, X(4), 20, 0xe7d3fbc8L); + R1(A, B, C, D, X(9), 5, 0x21e1cde6L); + R1(D, A, B, C, X(14), 9, 0xc33707d6L); + R1(C, D, A, B, X(3), 14, 0xf4d50d87L); + R1(B, C, D, A, X(8), 20, 0x455a14edL); + R1(A, B, C, D, X(13), 5, 0xa9e3e905L); + R1(D, A, B, C, X(2), 9, 0xfcefa3f8L); + R1(C, D, A, B, X(7), 14, 0x676f02d9L); + R1(B, C, D, A, X(12), 20, 0x8d2a4c8aL); + // Round 2 + R2(A, B, C, D, X(5), 4, 0xfffa3942L); + R2(D, A, B, C, X(8), 11, 0x8771f681L); + R2(C, D, A, B, X(11), 16, 0x6d9d6122L); + R2(B, C, D, A, X(14), 23, 0xfde5380cL); + R2(A, B, C, D, X(1), 4, 0xa4beea44L); + R2(D, A, B, C, X(4), 11, 0x4bdecfa9L); + R2(C, D, A, B, X(7), 16, 0xf6bb4b60L); + R2(B, C, D, A, X(10), 23, 0xbebfbc70L); + R2(A, B, C, D, X(13), 4, 0x289b7ec6L); + R2(D, A, B, C, X(0), 11, 0xeaa127faL); + R2(C, D, A, B, X(3), 16, 0xd4ef3085L); + R2(B, C, D, A, X(6), 23, 0x04881d05L); + R2(A, B, C, D, X(9), 4, 0xd9d4d039L); + R2(D, A, B, C, X(12), 11, 0xe6db99e5L); + R2(C, D, A, B, X(15), 16, 0x1fa27cf8L); + R2(B, C, D, A, X(2), 23, 0xc4ac5665L); + // Round 3 + R3(A, B, C, D, X(0), 6, 0xf4292244L); + R3(D, A, B, C, X(7), 10, 0x432aff97L); + R3(C, D, A, B, X(14), 15, 0xab9423a7L); + R3(B, C, D, A, X(5), 21, 0xfc93a039L); + R3(A, B, C, D, X(12), 6, 0x655b59c3L); + R3(D, A, B, C, X(3), 10, 0x8f0ccc92L); + R3(C, D, A, B, X(10), 15, 0xffeff47dL); + R3(B, C, D, A, X(1), 21, 0x85845dd1L); + R3(A, B, C, D, X(8), 6, 0x6fa87e4fL); + R3(D, A, B, C, X(15), 10, 0xfe2ce6e0L); + R3(C, D, A, B, X(6), 15, 0xa3014314L); + R3(B, C, D, A, X(13), 21, 0x4e0811a1L); + R3(A, B, C, D, X(4), 6, 0xf7537e82L); + R3(D, A, B, C, X(11), 10, 0xbd3af235L); + R3(C, D, A, B, X(2), 15, 0x2ad7d2bbL); + R3(B, C, D, A, X(9), 21, 0xeb86d391L); + + A = state[0] += A; + B = state[1] += B; + C = state[2] += C; + D = state[3] += D; + } +} +#undef X +#endif + +#undef F +#undef G +#undef H +#undef I +#undef R0 +#undef R1 +#undef R2 +#undef R3 +#undef CRYPTO_load_u32_le +#undef CRYPTO_store_u32_le +#undef CRYPTO_load_u32_be +#undef CRYPTO_store_u32_be diff --git a/src/gridcoin/md5.h b/src/gridcoin/md5.h new file mode 100644 index 0000000000..9bd5be4ecc --- /dev/null +++ b/src/gridcoin/md5.h @@ -0,0 +1,31 @@ +// Copyright (c) 2023 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_MD5_H +#define GRIDCOIN_MD5_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +#define MD5_CBLOCK 64 +#define MD5_DIGEST_LENGTH 16 + +typedef struct MD5_CTX { + uint32_t h[4]; + uint32_t Nl, Nh; + uint8_t data[MD5_CBLOCK]; + unsigned num; +} MD5_CTX; + +uint8_t *GRC__MD5(const uint8_t *data, size_t len, uint8_t out[MD5_DIGEST_LENGTH]); + +#if defined(__cplusplus) +} +#endif + +#endif // GRIDCOIN_MD5_H diff --git a/src/gridcoin/mrc.cpp b/src/gridcoin/mrc.cpp index 8abf739e98..ef8fcc7205 100644 --- a/src/gridcoin/mrc.cpp +++ b/src/gridcoin/mrc.cpp @@ -153,7 +153,7 @@ CAmount MRC::ComputeMRCFee() const // If the beacon was renewed and the time stamp of this beacon is greater than // the time of the last_payment_index, then walk the beacon chain back to the previous beacon. while (beacon->m_timestamp > prev_block_pindex->nTime && beacon->Renewed()) { - beacon = GetBeaconRegistry().GetBeaconDB().find(beacon->m_prev_beacon_hash)->second; + beacon = GetBeaconRegistry().GetBeaconDB().find(beacon->m_previous_hash)->second; } last_reward_time = beacon->m_timestamp; @@ -280,8 +280,6 @@ bool TrySignMRC( CBlockIndex* pindex, GRC::MRC& mrc) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - AssertLockHeld(cs_main); - // lock needs to be taken on pwallet here. LOCK(pwallet->cs_wallet); diff --git a/src/gridcoin/project.cpp b/src/gridcoin/project.cpp index 2a53b4c12b..6db6511067 100644 --- a/src/gridcoin/project.cpp +++ b/src/gridcoin/project.cpp @@ -1,52 +1,125 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include "main.h" #include "gridcoin/project.h" +#include "node/ui_interface.h" #include #include using namespace GRC; +using LogFlags = BCLog::LogFlags; -namespace -{ - Whitelist whitelist; -} +namespace { + Whitelist g_whitelist; +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Global Functions +// ----------------------------------------------------------------------------- Whitelist& GRC::GetWhitelist() { - return whitelist; + return g_whitelist; } // ----------------------------------------------------------------------------- -// Class: Project +// Class: ProjectEntry // ----------------------------------------------------------------------------- -constexpr uint32_t Project::CURRENT_VERSION; // For clang +constexpr uint32_t ProjectEntry::CURRENT_VERSION; // For clang -Project::Project() - : m_timestamp(0) +ProjectEntry::ProjectEntry(uint32_t version) + : m_version(version) + , m_name() + , m_url() + , m_timestamp(0) + , m_hash() + , m_previous_hash() , m_gdpr_controls(false) + , m_public_key(CPubKey {}) + , m_status(ProjectEntryStatus::UNKNOWN) { } -Project::Project(std::string name, std::string url, int64_t timestamp) - : Project(std::move(name), std::move(url), timestamp, CURRENT_VERSION, false) +ProjectEntry::ProjectEntry(uint32_t version, std::string name, std::string url) + : ProjectEntry(version, name, url, false, ProjectEntryStatus::UNKNOWN, int64_t {0}) { } -Project::Project(std::string name, std::string url, int64_t timestamp, uint32_t version, bool gdpr_controls) +ProjectEntry::ProjectEntry(uint32_t version, std::string name, std::string url, bool gdpr_controls) + : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, int64_t {0}) +{ +} + +ProjectEntry::ProjectEntry(uint32_t version, std::string name, std::string url, + bool gdpr_controls, Status status, int64_t timestamp) : m_version(version) - , m_name(std::move(name)) - , m_url(std::move(url)) + , m_name(name) + , m_url(url) , m_timestamp(timestamp) + , m_hash() + , m_previous_hash() , m_gdpr_controls(gdpr_controls) + , m_public_key(CPubKey {}) + , m_status(status) +{ +} + +bool ProjectEntry::WellFormed() const +{ + return (!m_name.empty() + && !m_url.empty() + && m_status != ProjectEntryStatus::UNKNOWN + && m_status != ProjectEntryStatus::OUT_OF_BOUND); +} + +std::string ProjectEntry::Key() const +{ + return m_name; +} + +std::pair ProjectEntry::KeyValueToString() const { + return std::make_pair(m_name, m_url); } -std::string Project::DisplayName() const +std::string ProjectEntry::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string ProjectEntry::StatusToString(const ProjectEntryStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case ProjectEntryStatus::UNKNOWN: return _("Unknown"); + case ProjectEntryStatus::DELETED: return _("Deleted"); + case ProjectEntryStatus::ACTIVE: return _("Active"); + case ProjectEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case ProjectEntryStatus::UNKNOWN: return "Unknown"; + case ProjectEntryStatus::DELETED: return "Deleted"; + case ProjectEntryStatus::ACTIVE: return "Active"; + case ProjectEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +std::string ProjectEntry::DisplayName() const { std::string display_name = m_name; std::replace(display_name.begin(), display_name.end(), '_', ' '); @@ -54,14 +127,14 @@ std::string Project::DisplayName() const return display_name; } -std::string Project::BaseUrl() const +std::string ProjectEntry::BaseUrl() const { // Remove the "@" from the URL in the contract. We assume that it always // occurs at the very end: return m_url.substr(0, m_url.size() - 1); } -std::string Project::DisplayUrl() const +std::string ProjectEntry::DisplayUrl() const { // TODO: remove this after project contracts support arbitrary URLs. // WCG project URL refers to a location inaccessible to the end user. @@ -72,7 +145,7 @@ std::string Project::DisplayUrl() const return BaseUrl(); } -std::string Project::StatsUrl(const std::string& type) const +std::string ProjectEntry::StatsUrl(const std::string& type) const { if (type.empty()) { return BaseUrl() + "stats/"; @@ -81,7 +154,7 @@ std::string Project::StatsUrl(const std::string& type) const return BaseUrl() + "stats/" + type + ".gz"; } -std::optional Project::HasGDPRControls() const +std::optional ProjectEntry::HasGDPRControls() const { std::optional has_gdpr_controls; @@ -92,6 +165,52 @@ std::optional Project::HasGDPRControls() const return has_gdpr_controls; } +// ----------------------------------------------------------------------------- +// Class: Project +// ----------------------------------------------------------------------------- + +// TODO: Evaluate and remove some of these constructors, some of which are identical +// except the arguments are in a different order to support existing code. +Project::Project(uint32_t version) + : ProjectEntry(version) +{ +} + +Project::Project(std::string name, std::string url) + : ProjectEntry(1, name, url, false, ProjectEntryStatus::UNKNOWN, int64_t {0}) +{ +} + +Project::Project(uint32_t version, std::string name, std::string url) + : ProjectEntry(version, name, url, false, ProjectEntryStatus::UNKNOWN, int64_t {0}) +{ +} + +Project::Project(std::string name, std::string url, int64_t timestamp, uint32_t version) + : ProjectEntry(version, name, url, false, ProjectEntryStatus::UNKNOWN, timestamp) +{ +} + +Project::Project(uint32_t version, std::string name, std::string url, bool gdpr_controls) + : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, int64_t {0}) +{ +} + +Project::Project(uint32_t version, std::string name, std::string url, bool gdpr_controls, int64_t timestamp) + : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, timestamp) +{ +} + +Project::Project(std::string name, std::string url, int64_t timestamp, uint32_t version, bool gdpr_controls) + : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, timestamp) +{ +} + +Project::Project(ProjectEntry entry) + : ProjectEntry(entry) +{ +} + // ----------------------------------------------------------------------------- // Class: WhitelistSnapshot // ----------------------------------------------------------------------------- @@ -143,7 +262,7 @@ WhitelistSnapshot WhitelistSnapshot::Sorted() const { ProjectList sorted(m_projects->begin(), m_projects->end()); - auto ascending_by_name = [](const Project& a, const Project& b) { + auto ascending_by_name = [](const ProjectEntry& a, const ProjectEntry& b) { return std::lexicographical_compare( a.m_name.begin(), a.m_name.end(), @@ -160,55 +279,285 @@ WhitelistSnapshot WhitelistSnapshot::Sorted() const } // ----------------------------------------------------------------------------- -// Class: Whitelist +// Class: Whitelist (Registry) // ----------------------------------------------------------------------------- -Whitelist::Whitelist() - : m_projects(std::make_shared()) -{ -} - WhitelistSnapshot Whitelist::Snapshot() const { - // With C++20, use std::atomic>::load() instead: - return WhitelistSnapshot(std::atomic_load(&m_projects)); + LOCK(cs_lock); + + ProjectList projects; + + for (const auto& iter : m_project_entries) { + if (iter.second->m_status == ProjectEntryStatus::ACTIVE) { + projects.push_back(*iter.second); + } + } + + return WhitelistSnapshot(std::make_shared(projects)); } void Whitelist::Reset() { - std::atomic_store(&m_projects, std::make_shared()); + LOCK(cs_lock); + + m_project_entries.clear(); + m_project_db.clear(); } -void Whitelist::Add(const ContractContext& ctx) +void Whitelist::AddDelete(const ContractContext& ctx) { - Project project = ctx->CopyPayloadAs(); - project.m_timestamp = ctx.m_tx.nTime; + // Poor man's mock. This is to prevent the tests from polluting the LevelDB database + int height = -1; + + if (ctx.m_pindex) + { + height = ctx.m_pindex->nHeight; + } + + Project payload = ctx->CopyPayloadAs(); + + // Fill this in from the transaction context because these are not done during payload + // initialization. + payload.m_hash = ctx.m_tx.GetHash(); + payload.m_timestamp = ctx.m_tx.nTime; + + // If the contract status is ADD, then ProjectEntryStatus will be ACTIVE. If contract status + // is REMOVE then ProjectEntryStatus will be DELETED. + if (ctx->m_action == ContractAction::ADD) { + payload.m_status = ProjectEntryStatus::ACTIVE; + } else if (ctx->m_action == ContractAction::REMOVE) { + payload.m_status = ProjectEntryStatus::DELETED; + } + + LOCK(cs_lock); + + auto project_entry_pair_iter = m_project_entries.find(payload.m_name); - ProjectListPtr copy = CopyFilteredWhitelist(project.m_name); + ProjectEntry_ptr current_project_entry_ptr = nullptr; - copy->emplace_back(std::move(project)); + // Is there an existing project entry in the map? + bool current_project_entry_present = (project_entry_pair_iter != m_project_entries.end()); - // With C++20, use std::atomic>::store() instead: - std::atomic_store(&m_projects, std::move(copy)); + // If so, then get a smart pointer to it. + if (current_project_entry_present) { + current_project_entry_ptr = project_entry_pair_iter->second; + + // Set the payload m_entry's prev entry ctx hash = to the existing entry's hash. + payload.m_previous_hash = current_project_entry_ptr->m_hash; + } else { // Original entry for this project entry key + payload.m_previous_hash = uint256 {}; + } + + LogPrint(LogFlags::CONTRACT, "INFO: %s: project entry add/delete: contract m_version = %u, payload " + "m_version = %u, name = %s, url = %s, m_timestamp = %" PRId64 ", " + "m_hash = %s, m_previous_hash = %s, m_gdpr_contracts = %u, m_status = %s", + __func__, + ctx->m_version, + payload.m_version, + payload.m_name, + payload.m_url, + payload.m_timestamp, + payload.m_hash.ToString(), + payload.m_previous_hash.ToString(), + payload.m_gdpr_controls, + payload.StatusToString() + ); + + // This does an implicit cast of Project to ProjectEntry, which gets rid of the payload version and uses + // the entry serialization for going into leveldb. + ProjectEntry& historical = payload; + + if (!m_project_db.insert(ctx.m_tx.GetHash(), height, historical)) + { + LogPrint(LogFlags::CONTRACT, "INFO: %s: In recording of the project entry for name %s, url %s, hash %s, " + "the project entry db record already exists. This can be expected on a restart " + "of the wallet to ensure multiple contracts in the same block get stored/replayed.", + __func__, + historical.m_name, + historical.m_url, + historical.m_hash.GetHex()); + } + + // Finally, insert the new project entry (payload) smart pointer into the m_project_entries map. + m_project_entries[payload.m_name] = m_project_db.find(ctx.m_tx.GetHash())->second; + + return; + +} + +void Whitelist::Add(const ContractContext& ctx) +{ + AddDelete(ctx); } void Whitelist::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void Whitelist::Revert(const ContractContext& ctx) { const auto payload = ctx->SharePayloadAs(); - // With C++20, use std::atomic>::store() instead: - std::atomic_store(&m_projects, CopyFilteredWhitelist(payload->m_name)); + // For project entries, both adds and removes will have records to revert in the m_project_entries map, + // and also, if not the first entry for that project key, will have a historical record to + // resurrect. + LOCK(cs_lock); + + auto entry_to_revert = m_project_entries.find(payload->m_name); + + if (entry_to_revert == m_project_entries.end()) { + error("%s: The project entry for key %s to revert was not found in the project entry map.", + __func__, + entry_to_revert->second->m_name); + + // If there is no record in the current m_project_entries map, then there is nothing to do here. This + // should not occur. + return; + } + + // If this is not a null hash, then there will be a prior entry to resurrect. + std::string key = entry_to_revert->second->m_name; + uint256 resurrect_hash = entry_to_revert->second->m_previous_hash; + + // Revert the ADD or REMOVE action. Unlike the beacons, this is symmetric. + if (ctx->m_action == ContractAction::ADD || ctx->m_action == ContractAction::REMOVE) { + // Erase the record from m_project_entries. + if (m_project_entries.erase(payload->m_name) == 0) { + error("%s: The project entry to erase during a project entry revert for key %s was not found.", + __func__, + key); + // If the record to revert is not found in the m_project_entries map, no point in continuing. + return; + } + + // Also erase the record from the db. + if (!m_project_db.erase(ctx.m_tx.GetHash())) { + error("%s: The db entry to erase during a project entry revert for key %s was not found.", + __func__, + key); + + // Unlike the above we will keep going even if this record is not found, because it is identical to the + // m_project_entries record above. This should not happen, because during contract adds and removes, + // entries are made simultaneously to the m_project_entries and m_project_db. + } + + if (resurrect_hash.IsNull()) { + return; + } + + auto resurrect_entry = m_project_db.find(resurrect_hash); + + if (resurrect_entry == m_project_db.end()) { + error("%s: The prior entry to resurrect during a project entry ADD revert for key %s was not found.", + __func__, + key); + return; + } + + // Resurrect the entry prior to the reverted one. It is safe to use the bracket form here, because of the protection + // of the logic above. There cannot be any entry in m_project_entries with that key value left if we made it here. + m_project_entries[resurrect_entry->second->m_name] = resurrect_entry->second; + } } -ProjectListPtr Whitelist::CopyFilteredWhitelist(const std::string& name) const +bool Whitelist::Validate(const Contract& contract, const CTransaction& tx, int &DoS) const { - ProjectListPtr copy = std::make_shared(); + // No validation is done with contract versions of 2 or less. + if (contract.m_version <= 2) { + return true; + } - for (const auto& project : *m_projects) { - if (project.m_name != name) { - copy->push_back(project); - } + const auto payload = contract.SharePayloadAs(); + + if (contract.m_version >= 3 && payload->m_version < 3) { + DoS = 25; + error("%s: Project entry contract in contract v3 is wrong version.", __func__); + return false; } - return copy; + if (!payload->WellFormed(contract.m_action.Value())) { + DoS = 25; + error("%s: Malformed project entry contract", __func__); + return false; + } + + return true; } + +bool Whitelist::BlockValidate(const ContractContext& ctx, int& DoS) const +{ + return Validate(ctx.m_contract, ctx.m_tx, DoS); +} + +int Whitelist::Initialize() +{ + LOCK(cs_lock); + + int height = m_project_db.Initialize(m_project_entries, m_pending_project_entries, m_expired_project_entries); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_project_db size after load: %u", __func__, m_project_db.size()); + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_project_entries size after load: %u", __func__, m_project_entries.size()); + + return height; +} + +void Whitelist::SetDBHeight(int& height) +{ + LOCK(cs_lock); + + m_project_db.StoreDBHeight(height); +} + +int Whitelist::GetDBHeight() +{ + int height = 0; + + LOCK(cs_lock); + + m_project_db.LoadDBHeight(height); + + return height; +} + +void Whitelist::ResetInMemoryOnly() +{ + LOCK(cs_lock); + + m_project_entries.clear(); + m_project_db.clear_in_memory_only(); +} + +uint64_t Whitelist::PassivateDB() +{ + LOCK(cs_lock); + + return m_project_db.passivate_db(); +} + +Whitelist::ProjectEntryDB &Whitelist::GetProjectDB() +{ + return m_project_db; +} + +// This is static and called by the scheduler. +void Whitelist::RunDBPassivation() +{ + TRY_LOCK(cs_main, locked_main); + + if (!locked_main) + { + return; + } + + Whitelist& project_entries = GetWhitelist(); + + project_entries.PassivateDB(); +} + +template<> const std::string Whitelist::ProjectEntryDB::KeyType() +{ + return std::string("project"); +} + diff --git a/src/gridcoin/project.h b/src/gridcoin/project.h index 9b7a5ae35e..5b6ff5fae2 100644 --- a/src/gridcoin/project.h +++ b/src/gridcoin/project.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2024 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -9,8 +9,10 @@ #include "contract/contract.h" #include "gridcoin/contract/handler.h" #include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" #include "serialize.h" #include "pubkey.h" +#include "sync.h" #include #include @@ -19,11 +21,34 @@ namespace GRC { //! -//! \brief Represents a BOINC project in the Gridcoin whitelist. +//! \brief Enumeration of project entry status. Unlike beacons this is for both storage +//! and memory. +//! +//! UNKNOWN status is only encountered in trivially constructed empty +//! project entries and should never be seen on the blockchain. +//! +//! DELETED status corresponds to a removed entry. +//! +//! ACTIVE corresponds to an active entry. //! -class Project : public IContractPayload +//! OUT_OF_BOUND must go at the end and be retained for the EnumBytes wrapper. +//! +enum class ProjectEntryStatus +{ + UNKNOWN, + DELETED, + ACTIVE, + OUT_OF_BOUND +}; + +class ProjectEntry { public: + //! + //! \brief Wrapped Enumeration of scraper entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + //! //! \brief Version number of the current format for a serialized project. //! @@ -31,8 +56,177 @@ class Project : public IContractPayload //! ensure that the serialization/deserialization routines also handle all //! of the previous versions. //! - static constexpr uint32_t CURRENT_VERSION = 2; + static constexpr uint32_t CURRENT_VERSION = 3; + //! + //! \brief Version number of the serialized project format. + //! + //! Defaults to the most recent version for a new project instance. + //! + uint32_t m_version = CURRENT_VERSION; + + std::string m_name; //!< As it exists in the contract key field, this is the project name. + std::string m_url; //!< As it exists in the contract url field. + int64_t m_timestamp; //!< Timestamp of the contract. + uint256 m_hash; //!< The txid of the transaction that contains the project entry. + uint256 m_previous_hash; //!< The m_hash of the previous project entry with the same key. + bool m_gdpr_controls; //!< Boolean to indicate whether project has GDPR stats export controls. + CPubKey m_public_key; //!< Project public key. + Status m_status; //!< The status of the project entry. (Note serialization converts to/from int.) + + //! + //! \brief Initialize an empty, invalid project entry instance. + //! + ProjectEntry(uint32_t version = CURRENT_VERSION); + + //! + //! \brief Initialize a new project entry for submission in a contract (with ACTIVE status) + //! + //! \param version. Project entry version. This is identical to the payload version that formed it. + //! \param name. The key of the project entry. + //! \param url. The url of the project entry. + //! \param gdpr_controls. The gdpr control flag of the project entry + //! + ProjectEntry(uint32_t version, std::string name, std::string url); + + //! + //! \brief Initialize a new project entry for submission in a contract (with ACTIVE status) + //! + //! \param version. Project entry version. This is identical to the payload version that formed it. + //! \param name. The key of the project entry. + //! \param url. The url of the project entry. + //! \param gdpr_controls. The gdpr control flag of the project entry + //! + ProjectEntry(uint32_t version, std::string name, std::string url, bool gdpr_controls); + + //! + //! \brief Initialize a project entry instance with data from a contract. + //! + //! \param version. Project entry version. This is identical to the payload version that formed it. + //! \param name. The key of the project entry. + //! \param url. The value of the project entry. + //! \param gdpr_controls. The gdpr control flag of the project entry + //! \param status. the status of the project entry. + //! \param timestamp. The timestamp of the project entry that comes from the containing transaction + //! + ProjectEntry(uint32_t version, std::string name, std::string url, bool gdpr_controls, Status status, int64_t timestamp); + + //! + //! \brief Determine whether a project entry contains each of the required elements. + //! + //! \return \c true if the project entry is complete. + //! + bool WellFormed() const; + + //! + //! \brief This is the standardized method that returns the key value for the project entry (for + //! the registry_db.h template.) + //! + //! \return std::string project name, which is the key for the project entry + //! + std::string Key() const; + + //! + //! \brief Provides the project name and url (the key and principal value) as a pair. + //! \return std::pair of strings + //! + std::pair KeyValueToString() const; + + //! + //! \brief Returns the string representation of the current project entry status + //! + //! \return Translated string representation of project status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input project entry status + //! + //! \param status. ProjectEntryStatus + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return Project entry status string. + //! + std::string StatusToString(const ProjectEntryStatus& status, const bool& translated = true) const; + + //! + //! \brief Get a user-friendly display name created from the project key. + //! + std::string DisplayName() const; + + //! + //! \brief Get the root URL of the project's BOINC server website. + //! + std::string BaseUrl() const; + + //! + //! \brief Get a user-accessible URL to the project's BOINC website. + //! + std::string DisplayUrl() const; + + //! + //! \brief Get the URL to the project's statistics export files. + //! + //! \param type If empty, return a URL to the project's stats directory. + //! Otherwise, return a URL to the specified export archive. + //! + std::string StatsUrl(const std::string& type = "") const; + + //! + //! \brief Returns true if project has project stats GDPR export controls + //! + std::optional HasGDPRControls() const; + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side project entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(ProjectEntry b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side project entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(ProjectEntry b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_version); + READWRITE(m_name); + READWRITE(m_url); + READWRITE(m_timestamp); + READWRITE(m_hash); + READWRITE(m_gdpr_controls); + READWRITE(m_previous_hash); + READWRITE(m_status); + } +}; + +//! +//! \brief The type that defines a shared pointer to a ProjectEntry +//! +typedef std::shared_ptr ProjectEntry_ptr; + +//! +//! \brief A type that either points to some ProjectEntry or does not. +//! +typedef const ProjectEntry_ptr ProjectEntryOption; + +//! +//! \brief Represents a BOINC project in the Gridcoin whitelist. +//! +class Project : public IContractPayload, public ProjectEntry +{ +public: //! //! \brief The maximum number of characters allowed for a serialized project //! name field. @@ -49,41 +243,75 @@ class Project : public IContractPayload static constexpr size_t MAX_URL_SIZE = 500; //! - //! \brief Version number of the serialized project format. + //! \brief Initialize an empty, invalid project object. //! - //! Defaults to the most recent version for a new project instance. + Project(uint32_t version = CURRENT_VERSION); + //! - uint32_t m_version = CURRENT_VERSION; + //! \brief Initialize a \c Project using data from the contract. + //! + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! + Project(std::string name, std::string url); - std::string m_name; //!< As it exists in the contract key field. - std::string m_url; //!< As it exists in the contract value field. - int64_t m_timestamp; //!< Timestamp of the contract. - bool m_gdpr_controls; //!< Boolean to indicate whether project has GDPR stats export controls. - CPubKey m_public_key; //!< Project public key. + //! + //! \brief Initialize a \c Project using data from the contract. + //! + //! \param version Project payload version. + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! + Project(uint32_t version, std::string name, std::string url); //! - //! \brief Initialize an empty, invalid project object. + //! \brief Initialize a \c Project using data from the contract. + //! + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! \param timestamp Timestamp + //! \param version Project payload version. //! - Project(); + Project(std::string name, std::string url, int64_t timestamp, uint32_t version = CURRENT_VERSION); //! - //! \brief Initialize a new project for submission in a transaction. + //! \brief Initialize a \c Project using data from the contract. //! - //! \param name Project name from contract message key. - //! \param url Project URL from contract message value. - //! \param timestamp Contract timestamp. + //! \param version Project payload version. + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! \param gdpr_controls Boolean to indicate gdpr stats export controls enforced //! - Project(std::string name, std::string url, int64_t timestamp = 0); + Project(uint32_t version, std::string name, std::string url, bool gdpr_controls); //! //! \brief Initialize a \c Project using data from the contract. //! + //! \param version Project payload version. //! \param name Project name from contract message key. //! \param url Project URL from contract message value. - //! \param timestamp Contract timestamp. //! \param gdpr_controls Boolean to indicate gdpr stats export controls enforced + //! \param timestamp Timestamp + //! + Project(uint32_t version, std::string name, std::string url, bool gdpr_controls, int64_t timestamp); + + //! + //! \brief Initialize a \c Project using data from the contract. + //! + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! \param timestamp Timestamp + //! \param version Project payload version. + //! \param gdpr_controls Boolean to indicate gdpr stats export controls enforced + //! + Project(std::string name, std::string url, int64_t timestamp, uint32_t version, bool gdpr_controls); + //! - Project(std::string name, std::string url, int64_t timestamp, uint32_t version, bool gdpr_controls = false); + //! \brief Initialize a \c Project payloard from a provided project entry + //! \param version. Project Payload version. + //! \param entry. Project entry to place in the payload. + //! + Project(ProjectEntry entry); //! //! \brief Get the type of contract that this payload contains data for. @@ -145,34 +373,6 @@ class Project : public IContractPayload return Contract::STANDARD_BURN_AMOUNT; } - //! - //! \brief Get a user-friendly display name created from the project key. - //! - std::string DisplayName() const; - - //! - //! \brief Get the root URL of the project's BOINC server website. - //! - std::string BaseUrl() const; - - //! - //! \brief Get a user-accessible URL to the project's BOINC website. - //! - std::string DisplayUrl() const; - - //! - //! \brief Get the URL to the project's statistics export files. - //! - //! \param type If empty, return a URL to the project's stats directory. - //! Otherwise, return a URL to the specified export archive. - //! - std::string StatsUrl(const std::string& type = "") const; - - //! - //! \brief Returns true if project has project stats GDPR export controls - //! - std::optional HasGDPRControls() const; - ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; template @@ -192,13 +392,20 @@ class Project : public IContractPayload READWRITE(m_public_key); } } + + if (m_version >= 3) { + READWRITE(m_hash); + READWRITE(m_previous_hash); + READWRITE(m_status); + } } -}; +}; // Project (entry payload) //! //! \brief A collection of projects in the Gridcoin whitelist. //! -typedef std::vector ProjectList; +typedef std::map ProjectEntryMap; +typedef std::vector ProjectList; //! //! \brief Smart pointer around a collection of projects. @@ -216,7 +423,7 @@ class WhitelistSnapshot typedef ProjectList::const_iterator const_iterator; //! - //! \brief Initialize a snapshot for the provided project list. + //! \brief Initialize a snapshot for the provided project list from the project entry map. //! //! \param projects A copy of the smart pointer to the project list. //! @@ -264,50 +471,46 @@ class WhitelistSnapshot WhitelistSnapshot Sorted() const; private: - const ProjectListPtr m_projects; //!< The set of whitelisted projects. + const ProjectListPtr m_projects; //!< The vector of whitelisted projects. }; //! -//! \brief Manages the collection of BOINC projects in the Gridcoin whitelist. -//! -//! An object of this class stores in memory the set of data that represents -//! the BOINC projects in the Gridcoin whitelist. The application collects this -//! data from network policy messages published to the network as contracts in -//! a transaction. -//! -//! A \c Whitelist instance provides no direct access to the data it stores. -//! Consumers call the \c Snapshot() method to obtain a view of the data. These -//! \c WhitelistSnapshot instances enable read-only access to the project data -//! as it existed at the time of their construction. -//! -//! This class implements copy-on-write semantics to facilitate thread-safety -//! and to reduce overhead. It uses smart pointers to share ownership of project -//! data with snapshots until the application mutates the data by adding or -//! removing a project. During mutation, a \c Whitelist object copies its data -//! and stores that behind a new smart pointer as an atomic operation, so any -//! existing \c WhitelistSnapshot instances become obsolete. -//! -//! For this reason, consumers of this data shall not hold long-lived snapshot -//! objects. Instead, they poll for the current whitelist data and retain it -//! briefly as needed for each local routine. Because whitelisted projects -//! change so infrequently, this design enables efficient, lock-free access to -//! the data. -//! -//! Although this class protects against undefined behavior that results from -//! access to its data by multiple threads, it does not guard against a data -//! race that occurs when two threads mutate the project data at the same time. -//! Only the result of one thread's operation will persist because of the time -//! needed to copy the project data before the atomic update. The application -//! only modifies this data from one thread now. The implementation needs more -//! coarse locking if it will support multiple writers in the future. +//! \brief Registry that manages the collection of BOINC projects in the Gridcoin whitelist. //! class Whitelist : public IContractHandler { public: //! - //! \brief Initializes the project whitelist manager. + //! \brief Initializes the project whitelist manager. The version must be incremented when + //! introducing a breaking change in the storage format (serialization) of the project entry. + //! + //! Version 0: <= 5.4.2.0 where there was no backing db. + //! Version 1: TBD. + //! + Whitelist() + :m_project_db(1) + { + }; + + //! + //! \brief The type that keys project entries by their key strings. Note that the entries + //! in this map are actually smart shared pointer wrappers, so that the same actual object + //! can be held by both this map and the historical map without object duplication. + //! + typedef std::map ProjectEntryMap; + + //! + //! \brief PendingProjectEntryMap. Not actually used here, but required for the template. //! - Whitelist(); + typedef ProjectEntryMap PendingProjectEntryMap; + + //! + //! \brief The type that keys historical project entries by the contract hash (txid). + //! Note that the entries in this map are actually smart shared pointer wrappers, so that + //! the same actual object can be held by both this map and the (current) project entry map + //! without object duplication. + //! + typedef std::map HistoricalProjectEntryMap; //! //! \brief Get a read-only view of the projects in the whitelist. @@ -329,10 +532,7 @@ class Whitelist : public IContractHandler //! //! \return \c false If the contract fails validation. //! - bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override - { - return true; // No contextual validation needed yet - } + bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override; //! //! \brief Perform contextual validation for the provided contract including block context. This is used @@ -343,10 +543,7 @@ class Whitelist : public IContractHandler //! //! \return \c false If the contract fails validation. //! - bool BlockValidate(const ContractContext& ctx, int& DoS) const override - { - return true; // No contextual validation needed yet - } + bool BlockValidate(const ContractContext& ctx, int& DoS) const override; //! //! \brief Add a project to the whitelist from contract data. @@ -362,27 +559,107 @@ class Whitelist : public IContractHandler //! void Delete(const ContractContext& ctx) override; -private: - // With C++20, use std::atomic> instead: - ProjectListPtr m_projects; //!< The set of whitelisted projects. + //! + //! \brief Revert the registry state for the project entry to the state prior + //! to this ContractContext application. This is typically an issue + //! during reorganizations, where blocks are disconnected. + //! + //! \param ctx References the project entry contract and associated context. + //! + void Revert(const ContractContext& ctx) override; //! - //! \brief Create a copy of the current whitelist that excludes the project - //! with the specified name if it exists. + //! \brief Initialize the Project Whitelist (registry), which now includes restoring the state of the whitelist from + //! LevelDB on wallet start. //! - //! \param name Project name to exclude from the copy. + //! \return Block height of the database restored from LevelDB. Zero if no LevelDB project entry data is found or + //! there is some issue in LevelDB project entry retrieval. (This will cause the contract replay to change scope + //! and initialize the Whitelist from contract replay and store in LevelDB.) //! - //! \return A smart pointer to the copy of the whitelist. + int Initialize() override; + //! - ProjectListPtr CopyFilteredWhitelist(const std::string& name) const; -}; + //! \brief Gets the block height through which is stored in the project entry registry database. + //! + //! \return block height. + //! + int GetDBHeight() override; + + //! + //! \brief Function normally only used after a series of reverts during block disconnects, because + //! block disconnects are done in groups back to a common ancestor, and will include a series of reverts. + //! This is essentially atomic, and therefore the final (common) height only needs to be set once. TODO: + //! reversion should be done with a vector argument of the contract contexts, along with a final height to + //! clean this up and move the logic to here from the calling function. + //! + //! \param height to set the storage DB bookmark. + //! + void SetDBHeight(int& height) override; + + //! + //! \brief Resets the maps in the Whitelist but does not disturb the underlying LevelDB + //! storage. This is only used during testing in the testing harness. + //! + void ResetInMemoryOnly(); + + //! + //! \brief Passivates the elements in the scraper db, which means remove from memory elements in the + //! historical map that are not referenced by m_projects. The backing store of the element removed + //! from memory is retained and will be transparently restored if find() is called on the hash key + //! for the element. + //! + //! \return The number of elements passivated. + //! + uint64_t PassivateDB(); + + //! + //! \brief A static function that is called by the scheduler to run the project entry database passivation. + //! + static void RunDBPassivation(); + + //! + //! \brief Specializes the template RegistryDB for the ScraperEntry class. Note that std::set is not + //! actually used. + //! + typedef RegistryDB, + HistoricalProjectEntryMap> ProjectEntryDB; + +private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + + //! + //! \brief Private helper method for the Add and Delete methods above. They both use identical code (with + //! different input statuses). + //! + //! \param ctx The contract context for the add or delete. + //! + void AddDelete(const ContractContext& ctx); + + ProjectEntryMap m_project_entries; //!< The set of whitelisted projects. + PendingProjectEntryMap m_pending_project_entries {}; //!< Not actually used. Only to satisfy the template. + + std::set m_expired_project_entries {}; //!< Not actually used. Only to satisfy the template. + + ProjectEntryDB m_project_db; //!< The project db member +public: + + ProjectEntryDB& GetProjectDB(); +}; // Whitelist (Project Registry) //! -//! \brief Get the global project whitelist manager. +//! \brief Get the global project whitelist registry. //! -//! \return Current global whitelist manager instance. +//! \return Current global whitelist registry instance. //! Whitelist& GetWhitelist(); -} +} // namespace GRC #endif // GRIDCOIN_PROJECT_H diff --git a/src/gridcoin/protocol.cpp b/src/gridcoin/protocol.cpp new file mode 100644 index 0000000000..ec5f0831e9 --- /dev/null +++ b/src/gridcoin/protocol.cpp @@ -0,0 +1,549 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "gridcoin/protocol.h" +#include "node/ui_interface.h" + +using namespace GRC; +using LogFlags = BCLog::LogFlags; + +namespace { +ProtocolRegistry g_protocol_entries; +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Global Functions +// ----------------------------------------------------------------------------- + +ProtocolRegistry& GRC::GetProtocolRegistry() +{ + return g_protocol_entries; +} + +// ----------------------------------------------------------------------------- +// Class: ProtocolEntry +// ----------------------------------------------------------------------------- + +ProtocolEntry::ProtocolEntry() + : m_key() + , m_value() + , m_timestamp(0) + , m_hash() + , m_previous_hash() + , m_status(ProtocolEntryStatus::UNKNOWN) +{ +} + +ProtocolEntry::ProtocolEntry(std::string key, std::string value) + : ProtocolEntry(key, value, ProtocolEntryStatus::ACTIVE) +{ +} + + +ProtocolEntry::ProtocolEntry(std::string key, std::string value, Status status) + : ProtocolEntry(std::move(key), std::move(value), std::move(status), 0, uint256 {}) +{ +} + +ProtocolEntry::ProtocolEntry(std::string key, std::string value, Status status, int64_t tx_timestamp, uint256 hash) + : m_key(key) + , m_value(value) + , m_timestamp(tx_timestamp) + , m_hash(hash) + , m_previous_hash() + , m_status(status) +{ +} + +bool ProtocolEntry::WellFormed() const +{ + return (!m_key.empty() + && !m_value.empty() + && m_status != ProtocolEntryStatus::UNKNOWN + && m_status != ProtocolEntryStatus::OUT_OF_BOUND); +} + +std::string ProtocolEntry::Key() const +{ + return m_key; +} + +std::pair ProtocolEntry::KeyValueToString() const +{ + return std::make_pair(m_key, m_value); +} + +std::string ProtocolEntry::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string ProtocolEntry::StatusToString(const ProtocolEntryStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case ProtocolEntryStatus::UNKNOWN: return _("Unknown"); + case ProtocolEntryStatus::DELETED: return _("Deleted"); + case ProtocolEntryStatus::ACTIVE: return _("Active"); + case ProtocolEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case ProtocolEntryStatus::UNKNOWN: return "Unknown"; + case ProtocolEntryStatus::DELETED: return "Deleted"; + case ProtocolEntryStatus::ACTIVE: return "Active"; + case ProtocolEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +AppCacheEntryExt ProtocolEntry::GetLegacyProtocolEntry() +{ + AppCacheEntryExt entry; + + entry.value = m_value; + entry.timestamp = m_timestamp; + entry.deleted = (m_status == ProtocolEntryStatus::DELETED); + + return entry; +} + +bool ProtocolEntry::operator==(ProtocolEntry b) +{ + bool result = true; + + result &= (m_key == b.m_key); + result &= (m_value == m_value); + result &= (m_timestamp == b.m_timestamp); + result &= (m_hash == b.m_hash); + result &= (m_previous_hash == b.m_previous_hash); + result &= (m_status == b.m_status); + + return result; +} + +bool ProtocolEntry::operator!=(ProtocolEntry b) +{ + return !(*this == b); +} + +// ----------------------------------------------------------------------------- +// Class: ProtocolEntryPayload +// ----------------------------------------------------------------------------- + +constexpr uint32_t ProtocolEntryPayload::CURRENT_VERSION; // For clang + +ProtocolEntryPayload::ProtocolEntryPayload(uint32_t version) + : LegacyPayload() + , m_version(version) +{ +} + +ProtocolEntryPayload::ProtocolEntryPayload(const uint32_t version, std::string key, std::string value, ProtocolEntryStatus status) + : LegacyPayload() + , m_version(version) + , m_entry(ProtocolEntry(key, value, status)) +{ + assert(version > 1); +} + +ProtocolEntryPayload::ProtocolEntryPayload(const uint32_t version, ProtocolEntry entry) + : LegacyPayload() + , m_version(version) + , m_entry(std::move(entry)) +{ +} + +ProtocolEntryPayload::ProtocolEntryPayload(ProtocolEntry entry) + : ProtocolEntryPayload(CURRENT_VERSION, std::move(entry)) +{ +} + +ProtocolEntryPayload::ProtocolEntryPayload(const std::string& key, const std::string& value) + : LegacyPayload(key, value) + , m_version(1) + , m_entry(key, value) +{ +} + +ProtocolEntryPayload ProtocolEntryPayload::Parse(const std::string& key, const std::string& value) +{ + // This constructor assigns the entry with a status of active and also fills out the legacy payload. + ProtocolEntryPayload payload(key, value); + + return payload; +} + +// ----------------------------------------------------------------------------- +// Class: ProtocolRegistry +// ----------------------------------------------------------------------------- +const ProtocolRegistry::ProtocolEntryMap& ProtocolRegistry::ProtocolEntries() const +{ + return m_protocol_entries; +} + +const AppCacheSection ProtocolRegistry::GetProtocolEntriesLegacy() const +{ + AppCacheSection protocol_entries; + + // Only includes active protocol entries. + for (const auto& iter : GetProtocolEntriesLegacyExt(true)) { + AppCacheEntry entry; + + entry.timestamp = iter.second.timestamp; + entry.value = iter.second.value; + + protocol_entries[iter.first] = entry; + } + + return protocol_entries; +} + +const AppCacheSectionExt ProtocolRegistry::GetProtocolEntriesLegacyExt(const bool& active_only) const +{ + AppCacheSectionExt protocol_entries_ext; + + LOCK(cs_lock); + + for (const auto& entry : m_protocol_entries) { + + const std::string& key = entry.first; + const std::string& value = entry.second->m_value; + + switch (entry.second->m_status.Value()) { + case ProtocolEntryStatus::DELETED: + // Mark entry in protocol_entries_ext as deleted at the timestamp of the deletion. + if (!active_only) { + protocol_entries_ext[key] = AppCacheEntryExt {value, entry.second->m_timestamp, true}; + } + break; + + case ProtocolEntryStatus::ACTIVE: + protocol_entries_ext[key] = AppCacheEntryExt {value, entry.second->m_timestamp, false}; + break; + + // Ignore UNKNOWN and OUT_OF_BOUND. + case ProtocolEntryStatus::UNKNOWN: + [[fallthrough]]; + case ProtocolEntryStatus::OUT_OF_BOUND: + break; + } + } + + return protocol_entries_ext; +} + +const AppCacheEntry ProtocolRegistry::GetProtocolEntryByKeyLegacy(std::string key) const +{ + AppCacheEntry entry; + + entry.value = std::string {}; + entry.timestamp = 0; + + LOCK(cs_lock); + + auto iter = m_protocol_entries.find(key); + + // Only want to return entries that exist and are active. If not return an empty AppCacheEntry. + if (iter == m_protocol_entries.end() || iter->second->m_status != ProtocolEntryStatus::ACTIVE) { + return entry; + } + + entry.value = iter->second->m_value; + entry.timestamp = iter->second->m_timestamp; + + return entry; +} + +ProtocolEntryOption ProtocolRegistry::Try(const std::string& key) const +{ + LOCK(cs_lock); + + const auto iter = m_protocol_entries.find(key); + + if (iter == m_protocol_entries.end()) { + return nullptr; + } + + return iter->second; +} + +ProtocolEntryOption ProtocolRegistry::TryActive(const std::string& key) const +{ + LOCK(cs_lock); + + if (const ProtocolEntryOption protocol_entry = Try(key)) { + if (protocol_entry->m_status == ProtocolEntryStatus::ACTIVE) { + return protocol_entry; + } + } + + return nullptr; +} + +void ProtocolRegistry::Reset() +{ + LOCK(cs_lock); + + m_protocol_entries.clear(); + m_protocol_db.clear(); +} + +void ProtocolRegistry::AddDelete(const ContractContext& ctx) +{ + // Poor man's mock. This is to prevent the tests from polluting the LevelDB database + int height = -1; + + if (ctx.m_pindex) + { + height = ctx.m_pindex->nHeight; + } + + ProtocolEntryPayload payload = ctx->CopyPayloadAs(); + + // Fill this in from the transaction context because these are not done during payload + // initialization. + payload.m_entry.m_hash = ctx.m_tx.GetHash(); + payload.m_entry.m_timestamp = ctx.m_tx.nTime; + + // Ensure status is DELETED if the contract action was REMOVE, regardless of what was actually + // specified. + if (ctx->m_action == ContractAction::REMOVE) { + payload.m_entry.m_status = ProtocolEntryStatus::DELETED; + } + + LOCK(cs_lock); + + auto protocol_entry_pair_iter = m_protocol_entries.find(payload.m_entry.m_key); + + ProtocolEntry_ptr current_protocol_entry_ptr = nullptr; + + // Is there an existing protocol entry in the map? + bool current_protocol_entry_present = (protocol_entry_pair_iter != m_protocol_entries.end()); + + // If so, then get a smart pointer to it. + if (current_protocol_entry_present) { + current_protocol_entry_ptr = protocol_entry_pair_iter->second; + + // Set the payload m_entry's prev entry ctx hash = to the existing entry's hash. + payload.m_entry.m_previous_hash = current_protocol_entry_ptr->m_hash; + } else { // Original entry for this protocol entry key + payload.m_entry.m_previous_hash = uint256 {}; + } + + LogPrint(LogFlags::CONTRACT, "INFO: %s: protocol entry add/delete: contract m_version = %u, payload " + "m_version = %u, key = %s, value = %s, m_timestamp = %" PRId64 ", " + "m_hash = %s, m_previous_hash = %s, m_status = %s", + __func__, + ctx->m_version, + payload.m_version, + payload.m_entry.m_key, + payload.m_entry.m_value, + payload.m_entry.m_timestamp, + payload.m_entry.m_hash.ToString(), + payload.m_entry.m_previous_hash.ToString(), + payload.m_entry.StatusToString() + ); + + ProtocolEntry& historical = payload.m_entry; + + if (!m_protocol_db.insert(ctx.m_tx.GetHash(), height, historical)) + { + LogPrint(LogFlags::CONTRACT, "INFO: %s: In recording of the protocol entry for key %s, value %s, hash %s, " + "the protocol entry db record already exists. This can be expected on a restart " + "of the wallet to ensure multiple contracts in the same block get stored/replayed.", + __func__, + historical.m_key, + historical.m_value, + historical.m_hash.GetHex()); + } + + // Finally, insert the new protocol entry (payload) smart pointer into the m_protocol_entries map. + m_protocol_entries[payload.m_entry.m_key] = m_protocol_db.find(ctx.m_tx.GetHash())->second; + + return; +} + +void ProtocolRegistry::Add(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void ProtocolRegistry::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void ProtocolRegistry::Revert(const ContractContext& ctx) +{ + const auto payload = ctx->SharePayloadAs(); + + // For protocol entries, both adds and removes will have records to revert in the m_protocol_entries map, + // and also, if not the first entry for that protocol key, will have a historical record to + // resurrect. + LOCK(cs_lock); + + auto entry_to_revert = m_protocol_entries.find(payload->m_entry.m_key); + + if (entry_to_revert == m_protocol_entries.end()) { + error("%s: The protocol entry for key %s to revert was not found in the protocol entry map.", + __func__, + entry_to_revert->second->m_key); + + // If there is no record in the current m_protocol_entries map, then there is nothing to do here. This + // should not occur. + return; + } + + // If this is not a null hash, then there will be a prior entry to resurrect. + std::string key = entry_to_revert->second->m_key; + uint256 resurrect_hash = entry_to_revert->second->m_previous_hash; + + // Revert the ADD or REMOVE action. Unlike the beacons, this is symmetric. + if (ctx->m_action == ContractAction::ADD || ctx->m_action == ContractAction::REMOVE) { + // Erase the record from m_protocol_entries. + if (m_protocol_entries.erase(payload->m_entry.m_key) == 0) { + error("%s: The protocol entry to erase during a protocol entry revert for key %s was not found.", + __func__, + key); + // If the record to revert is not found in the m_protocol_entries map, no point in continuing. + return; + } + + // Also erase the record from the db. + if (!m_protocol_db.erase(ctx.m_tx.GetHash())) { + error("%s: The db entry to erase during a protocol entry revert for key %s was not found.", + __func__, + key); + + // Unlike the above we will keep going even if this record is not found, because it is identical to the + // m_protocol_entries record above. This should not happen, because during contract adds and removes, + // entries are made simultaneously to the m_protocol_entries and m_protocol_db. + } + + if (resurrect_hash.IsNull()) { + return; + } + + auto resurrect_entry = m_protocol_db.find(resurrect_hash); + + if (resurrect_entry == m_protocol_db.end()) { + error("%s: The prior entry to resurrect during a protocol entry ADD revert for key %s was not found.", + __func__, + key); + return; + } + + // Resurrect the entry prior to the reverted one. It is safe to use the bracket form here, because of the protection + // of the logic above. There cannot be any entry in m_protocol_entries with that key value left if we made it here. + m_protocol_entries[resurrect_entry->second->m_key] = resurrect_entry->second; + } +} + +bool ProtocolRegistry::Validate(const Contract& contract, const CTransaction& tx, int &DoS) const +{ + if (contract.m_version < 1) { + return true; + } + + const auto payload = contract.SharePayloadAs(); + + if (contract.m_version >= 3 && payload->m_version < 2) { + DoS = 25; + error("%s: Legacy protocol entry contract in contract v3", __func__); + return false; + } + + if (!payload->WellFormed(contract.m_action.Value())) { + DoS = 25; + error("%s: Malformed protocol entry contract", __func__); + return false; + } + + return true; +} + +bool ProtocolRegistry::BlockValidate(const ContractContext& ctx, int& DoS) const +{ + return Validate(ctx.m_contract, ctx.m_tx, DoS); +} + +int ProtocolRegistry::Initialize() +{ + LOCK(cs_lock); + + int height = m_protocol_db.Initialize(m_protocol_entries, m_pending_protocol_entries, m_expired_protocol_entries); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_protocol_db size after load: %u", __func__, m_protocol_db.size()); + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_protocol_entries size after load: %u", __func__, m_protocol_entries.size()); + + return height; +} + +void ProtocolRegistry::SetDBHeight(int& height) +{ + LOCK(cs_lock); + + m_protocol_db.StoreDBHeight(height); +} + +int ProtocolRegistry::GetDBHeight() +{ + int height = 0; + + LOCK(cs_lock); + + m_protocol_db.LoadDBHeight(height); + + return height; +} + +void ProtocolRegistry::ResetInMemoryOnly() +{ + LOCK(cs_lock); + + m_protocol_entries.clear(); + m_protocol_db.clear_in_memory_only(); +} + +uint64_t ProtocolRegistry::PassivateDB() +{ + LOCK(cs_lock); + + return m_protocol_db.passivate_db(); +} + +ProtocolRegistry::ProtocolEntryDB &ProtocolRegistry::GetProtocolEntryDB() +{ + return m_protocol_db; +} + +// This is static and called by the scheduler. +void ProtocolRegistry::RunDBPassivation() +{ + TRY_LOCK(cs_main, locked_main); + + if (!locked_main) + { + return; + } + + ProtocolRegistry& protocol_entries = GetProtocolRegistry(); + + protocol_entries.PassivateDB(); +} + +template<> const std::string ProtocolRegistry::ProtocolEntryDB::KeyType() +{ + return std::string("protocol"); +} diff --git a/src/gridcoin/protocol.h b/src/gridcoin/protocol.h new file mode 100644 index 0000000000..37aa39bc53 --- /dev/null +++ b/src/gridcoin/protocol.h @@ -0,0 +1,615 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_PROTOCOL_H +#define GRIDCOIN_PROTOCOL_H + + +#include "amount.h" +#include "serialize.h" +#include "gridcoin/scraper/fwd.h" +#include "gridcoin/contract/handler.h" +#include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" +#include "gridcoin/support/enumbytes.h" + +namespace GRC { + +//! +//! \brief Enumeration of protocol status. Unlike beacons this is for both storage +//! and memory. +//! +//! UNKNOWN status is only encountered in trivially constructed empty +//! protocol entries and should never be seen on the blockchain. +//! +//! DELETED status corresponds to the functionality implemented in the AppCacheEntryExt +//! structure in the protocol to compensate for the lack of presence of a deleted record +//! in the old appcache. Rather than removing a record when a REMOVE contract is +//! encountered, The existing record is linked to the new one, which will have a +//! DELETED status. +//! +//! ACTIVE corresponds to an active entry. +//! +//! OUT_OF_BOUND must go at the end and be retained for the EnumBytes wrapper. +//! +enum class ProtocolEntryStatus +{ + UNKNOWN, + DELETED, + ACTIVE, + OUT_OF_BOUND +}; + +//! +//! \brief This class formalizes the concept of an authorized protocol entry and replaces +//! the older appcache "protocol" section. For protocol entries, the "key" field is a string. +//! The datetime of the transaction containing the contract that gives rise to the entry is +//! stored as m_timestamp. The hash of the transaction is stored as m_hash. m_previous_hash +//! stores the transaction hash of a previous protocol entry with the same key, if there is +//! one. This has the effect of creating "chainlets", or a one-way linked list by hash of +//! protocol entries with the same key. This becomes very important to support reversion and +//! avoid expensive forward contract replays during a blockchain reorganization event. The +//! status of the protocol entry is stored as m_status in accordance with the class enum above. +//! +class ProtocolEntry +{ +public: + //! + //! \brief Wrapped Enumeration of protocol entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + + std::string m_key; //!< Identifies the protocol entry key. + std::string m_value; //!< Identifies the protocol entry value. + int64_t m_timestamp; //!< Time of the protocol entry contract transaction. + uint256 m_hash; //!< The txid of the transaction that contains the protocol entry. + uint256 m_previous_hash; //!< The m_hash of the previous protocol entry with the same key. + Status m_status; //!< The status of the protocol entry. (Note serialization converts to/from int.) + + //! + //! \brief Initialize an empty, invalid protocol entry instance. + //! + ProtocolEntry(); + + //! + //! \brief Initialize a new protocol entry for submission in a contract (with ACTIVE status) + //! + //! \param key. The key of the protocol entry. + //! \param value. The value of the protocol entry. + //! + ProtocolEntry(std::string key, std::string value); + + //! + //! \brief Initialize a new protocol entry for submission in a contract with provided status. + //! + //! \param key. The key of the protocol entry. + //! \param value. The value of the protocol entry. + //! \param status. the status of the protocol entry. + //! + ProtocolEntry(std::string key, std::string value, Status status); + + //! + //! \brief Initialize a protocol entry instance with data from a contract. + //! + //! \param key_id. The key of the protocol entry. + //! \param value. The value of the protocol entry. + //! \param tx_timestamp. Time of the transaction with the protocol entry contract. + //! \param hash. Hash of the transaction with the protocol entry contract. + //! + ProtocolEntry(std::string key, std::string value, Status status, int64_t tx_timestamp, uint256 hash); + + //! + //! \brief Determine whether a protocol entry contains each of the required elements. + //! + //! \return \c true if the protocol entry is complete. + //! + bool WellFormed() const; + + //! + //! \brief This is the standardized method that returns the key value for the protocol entry (for + //! the registry_db.h template.) + //! + //! \return std::string key value for the protocol entry + //! + std::string Key() const; + + //! + //! \brief Provides the protocol key and value as a pair. + //! \return std::pair of strings + //! + std::pair KeyValueToString() const; + + //! + //! \brief Returns the string representation of the current protocol entry status + //! + //! \return Translated string representation of protocol status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input protocol entry status + //! + //! \param status. ProtocolEntryStatus + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return Protocol entry status string. + //! + std::string StatusToString(const ProtocolEntryStatus& status, const bool& translated = true) const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. + //! + //! \return \c AppCacheEntryExt consisting of value, timestamp, and deleted boolean. + //! + AppCacheEntryExt GetLegacyProtocolEntry(); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side protocol entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(ProtocolEntry b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side protocol entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(ProtocolEntry b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_key); + READWRITE(m_value); + READWRITE(m_timestamp); + READWRITE(m_hash); + READWRITE(m_previous_hash); + READWRITE(m_status); + } +}; + +//! +//! \brief The type that defines a shared pointer to a ProtocolEntry +//! +typedef std::shared_ptr ProtocolEntry_ptr; + +//! +//! \brief A type that either points to some ProtocolEntry or does not. +//! +typedef const ProtocolEntry_ptr ProtocolEntryOption; + +//! +//! \brief The body of a protocol entry contract. Note that this body is bimodal. It +//! supports both the personality of the "LegacyPayload", and also the new native +//! ProtocolEntry format. In the Contract::Body::ConvertFromLegacy call, by the time +//! this call has been reached, the contract will have already been deserialized. +//! This will follow the legacy mode. For contracts at version 3+, the +//! Contract::SharePayload() will NOT call the ConvertFromLegacy. Note that because +//! the existing legacyPayloads are not versioned, the deserialization of +//! the payload first (de)serializes m_key, which is guaranteed to exist in either +//! legacy or native. If the key is empty, then payload v2+ is being deserialized +//! and the m_version and m_value are (de)serialized. This is ugly +//! but necessary to deal with the unversioned Legacy Payloads and maintain +//! compatibility. +//! +class ProtocolEntryPayload : public LegacyPayload +{ +public: + //! + //! \brief Version number of the current format for a serialized protocol entry. + //! + //! CONSENSUS: Increment this value when introducing a breaking change and + //! ensure that the serialization/deserialization routines also handle all + //! of the previous versions. + //! + static constexpr uint32_t CURRENT_VERSION = 2; + + //! + //! \brief Version number of the serialized protocol entry format. + //! + //! Initializes to the CURRENT_VERSION Note the + //! constructor that takes a ProtocolEntry defaults to CURRENT_VERSION. When the legacy + //! K-V fields are used which correspond to the legacy appcache implementation, the version is 1. + //! + //! Version 1: appcache string key value: + //! + //! Version 2: Protocol entry data serializable in binary format. Stored in a + //! contract's value field as bytes. + //! + uint32_t m_version = CURRENT_VERSION; + + ProtocolEntry m_entry; //!< The protocol entry in the payload. + + //! + //! \brief Initialize an empty, invalid protocol entry payload. + //! + ProtocolEntryPayload(uint32_t version = CURRENT_VERSION); + + //! + //! \brief Initialize a ProtocolEntryPayload from a protocol entry constructed from + //! string key and value. Not to be used for version 1 payloads. Will assert. Does NOT + //! initialize hash fields. + //! + //! \param key. Key string for the protocol entry + //! \param value. Value string for the protocol entry + //! \param status. Status of the protocol entry + //! + ProtocolEntryPayload(const uint32_t version, std::string key, std::string value, ProtocolEntryStatus status); + + //! + //! \brief Initialize a protocol entry payload from the given protocol entry + //! with the provided version number (and format). + //! + //! \param version Version of the serialized protocol entry format. + //! \param protocol_entry The protocol entry itself. + //! + ProtocolEntryPayload(const uint32_t version, ProtocolEntry protocol_entry); + + //! + //! \brief Initialize a protocol entry payload from the given protocol entry + //! with the CURRENT_VERSION. + //! + //! \param protocol_entry The protocol entry itself. + //! + ProtocolEntryPayload(ProtocolEntry protocol_entry); + + //! + //! \brief Initialize a protocol entry payload from legacy data. Does NOT + //! initialize hash fields. + //! + //! \param key + //! \param value + //! + ProtocolEntryPayload(const std::string& key, const std::string& value); + + //! + //! \brief Initialize a protocol entry payload from the legacy (appcache) string format. + //! + //! \param key. Key string for the protocol entry + //! \param value. Value string for the protocol entry + //! + //! \return The resultant protocol entry payload. The status is set to ACTIVE. + //! + static ProtocolEntryPayload Parse(const std::string& key, const std::string& value); + + //! + //! \brief Get the type of contract that this payload contains data for. + //! + GRC::ContractType ContractType() const override + { + return GRC::ContractType::PROTOCOL; + } + + //! + //! \brief Determine whether the instance represents a complete payload. + //! + //! \return \c true if the payload contains each of the required elements. + //! + bool WellFormed(const ContractAction action) const override + { + if (m_version <= 0 || m_version > CURRENT_VERSION) { + return false; + } + + // Upon contract receipt for version 1 payload, need to follow the rules for + // legacy payload. + if (m_version == 1) { + return !m_key.empty() && (action == GRC::ContractAction::REMOVE || !m_value.empty()); + } + + // Protocol Entry Payloads version 2+ follow full protocol entry validation rules + return m_entry.WellFormed(); + } + + //! + //! \brief Get a string for the key used to construct a legacy contract. + //! + std::string LegacyKeyString() const override + { + return m_entry.m_key; + } + + //! + //! \brief Get a string for the value used to construct a legacy contract. + //! + std::string LegacyValueString() const override + { + return m_entry.m_value; + } + + //! + //! \brief Get the burn fee amount required to send a particular contract. This + //! is the same as the LegacyPayload to insure compatibility between the protocol + //! registry and non-upgraded nodes before the block v13/contract version 3 height + //! + //! \return Burn fee in units of 1/100000000 GRC. + //! + CAmount RequiredBurnAmount() const override + { + return Contract::STANDARD_BURN_AMOUNT; + } + + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + // These will be filled in for legacy protocol entries, but will also be present as empties in + // native protocol records to solve the (de)serialization problem between legacy and native. + + READWRITE(m_key); + + if (contract_action != ContractAction::REMOVE) { + READWRITE(m_value); + + } + + if (m_key.empty()) { + READWRITE(m_version); + READWRITE(m_entry); + } else { + m_version = 1; + + } + } +}; // ProtocolEntryPayload + +//! +//! \brief Stores and manages protocol entries. +//! +class ProtocolRegistry : public IContractHandler +{ +public: + //! + //! \brief ProtocolRegistry constructor. The parameter is the version number of the underlying + //! protocol entry db. This must be incremented when implementing format changes to the protocol + //! entries to force a reinit. + //! + //! Version 0: <= 5.4.2.0 + //! Version 1: TBD. + //! + ProtocolRegistry() + : m_protocol_db(1) + { + }; + + //! + //! \brief The type that keys protocol entries by their key strings. Note that the entries + //! in this map are actually smart shared pointer wrappers, so that the same actual object + //! can be held by both this map and the historical map without object duplication. + //! + typedef std::map ProtocolEntryMap; + + //! + //! \brief PendingProtocolEntryMap. This is not actually used but defined to satisfy the template. + //! + typedef ProtocolEntryMap PendingProtocolEntryMap; + + //! + //! \brief The type that keys historical protocol entries by the contract hash (txid). + //! Note that the entries in this map are actually smart shared pointer wrappers, so that + //! the same actual object can be held by both this map and the (current) protocol entry map + //! without object duplication. + //! + typedef std::map HistoricalProtocolEntryMap; + + //! + //! \brief Get the collection of current protocol entries. Note that this INCLUDES deleted + //! protocol entries. + //! + //! \return \c A reference to the current protocol entries stored in the registry. + //! + const ProtocolEntryMap& ProtocolEntries() const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. Does NOT include deleted entries (entries with a status of + //! ProtocolEntryStatus::DELETED). + //! + //! \return \c AppCacheEntrySection consisting of key (address string) and + //! { value, timestamp }. + //! + const AppCacheSection GetProtocolEntriesLegacy() const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. + //! + //! \param authorized_only Boolean that if true requires that results include only those + //! protocol entries that are ACTIVE. + //! + //! \return \c AppCacheEntrySectionExt consisting of key string and + //! { value, timestamp, deleted }. + //! + const AppCacheSectionExt GetProtocolEntriesLegacyExt(const bool& active_only = false) const; + + const AppCacheEntry GetProtocolEntryByKeyLegacy(std::string key) const; + + //! + //! \brief Get the current protocol entry for the specified key string. + //! + //! \param key The key string of the protocol entry. + //! + //! \return An object that either contains a reference to some protocol entry if it exists + //! for the key or does not. + //! + ProtocolEntryOption Try(const std::string& key) const; + + //! + //! \brief Get the current protocol entry for the specified key string if it has a status of ACTIVE. + //! + //! \param key The key string of the protocol entry. + //! + //! \return An object that either contains a reference to some protocol entry if it exists + //! for the key and is in the required status or does not. + //! + ProtocolEntryOption TryActive(const std::string& key) const; + + //! + //! \brief Destroy the contract handler state in case of an error in loading + //! the protocol entry registry state from LevelDB to prepare for reload from contract + //! replay. This is not used for protocol entries, unless -clearprotocolentryhistory is specified + //! as a startup argument, because contract replay storage and full reversion has + //! been implemented for protocol entries. + //! + void Reset() override; + + //! + //! \brief Determine whether a protocol entry contract is valid. + //! + //! \param contract Contains the protocol entry contract to validate. + //! \param tx Transaction that contains the contract. + //! \param DoS Misbehavior out. + //! + //! \return \c true if the contract contains a valid protocol entry. + //! + bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override; + + //! + //! \brief Determine whether a protocol entry contract is valid including block context. This is used + //! in ConnectBlock. Note that for protocol entries this simply calls Validate as there is no + //! block level specific validation to be done. + //! + //! \param ctx ContractContext containing the protocol entry data to validate. + //! \param DoS Misbehavior score out. + //! + //! \return \c false If the contract fails validation. + //! + bool BlockValidate(const ContractContext& ctx, int& DoS) const override; + + //! + //! \brief Add a protocol entry to the registry from contract data. For the protocol registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Add(const ContractContext& ctx) override; + + //! + //! \brief Mark a protocol entry deleted in the registry from contract data. For the protocol registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Delete(const ContractContext& ctx) override; + + //! + //! \brief Revert the registry state for the protocol entry to the state prior + //! to this ContractContext application. This is typically an issue + //! during reorganizations, where blocks are disconnected. + //! + //! \param ctx References the protocol entry contract and associated context. + //! + void Revert(const ContractContext& ctx) override; + + //! + //! \brief Initialize the ProtocolRegistry, which now includes restoring the state of the ProtocolRegistry from + //! LevelDB on wallet start. + //! + //! \return Block height of the database restored from LevelDB. Zero if no LevelDB protocol entry data is found or + //! there is some issue in LevelDB protocol entry retrieval. (This will cause the contract replay to change scope + //! and initialize the ProtocolRegistry from contract replay and store in LevelDB.) + //! + int Initialize() override; + + //! + //! \brief Gets the block height through which is stored in the protocol entry registry database. + //! + //! \return block height. + //! + int GetDBHeight() override; + + //! + //! \brief Function normally only used after a series of reverts during block disconnects, because + //! block disconnects are done in groups back to a common ancestor, and will include a series of reverts. + //! This is essentially atomic, and therefore the final (common) height only needs to be set once. TODO: + //! reversion should be done with a vector argument of the contract contexts, along with a final height to + //! clean this up and move the logic to here from the calling function. + //! + //! \param height to set the storage DB bookmark. + //! + void SetDBHeight(int& height) override; + + //! + //! \brief Resets the maps in the ProtocolRegistry but does not disturb the underlying LevelDB + //! storage. This is only used during testing in the testing harness. + //! + void ResetInMemoryOnly(); + + //! + //! \brief Passivates the elements in the protocol db, which means remove from memory elements in the + //! historical map that are not referenced by the active entry map. The backing store of the element removed + //! from memory is retained and will be transparently restored if find() is called on the hash key + //! for the element. + //! + //! \return The number of elements passivated. + //! + uint64_t PassivateDB(); + + //! + //! \brief A static function that is called by the scheduler to run the protocol entry database passivation. + //! + static void RunDBPassivation(); + + //! + //! \brief Specializes the template RegistryDB for the ProtocolEntry class. Note that std::set + //! is not actually used. + //! + typedef RegistryDB, + HistoricalProtocolEntryMap> ProtocolEntryDB; + +private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + + //! + //! \brief Private helper method for the Add and Delete methods above. They both use identical code (with + //! different input statuses). + //! + //! \param ctx The contract context for the add or delete. + //! + void AddDelete(const ContractContext& ctx); + + ProtocolEntryMap m_protocol_entries; //!< Contains the current protocol entries including entries marked DELETED. + PendingProtocolEntryMap m_pending_protocol_entries {}; //!< Not used. Only to satisfy the template. + + std::set m_expired_protocol_entries {}; //!< Not used. Only to satisfy the template. + + ProtocolEntryDB m_protocol_db; + +public: + + ProtocolEntryDB& GetProtocolEntryDB(); +}; // ProtocolRegistry + +//! +//! \brief Get the global protocol entry registry. +//! +//! \return Current global protocol entry registry instance. +//! +ProtocolRegistry& GetProtocolRegistry(); +} // namespace GRC + +#endif // GRIDCOIN_PROTOCOL_H diff --git a/src/gridcoin/quorum.cpp b/src/gridcoin/quorum.cpp index 49626dd582..c0d04a4753 100644 --- a/src/gridcoin/quorum.cpp +++ b/src/gridcoin/quorum.cpp @@ -7,6 +7,7 @@ #include "main.h" #include "gridcoin/claim.h" #include "gridcoin/magnitude.h" +#include #include "gridcoin/quorum.h" #include "gridcoin/scraper/scraper_net.h" #include "gridcoin/superblock.h" @@ -14,7 +15,6 @@ #include "util/reverse_iterator.h" #include -#include #include using namespace GRC; @@ -313,9 +313,9 @@ class LegacyConsensus std::string input = grc_address + "_" + ToString(GetDayOfYear(time)); std::vector address_day_hash(16); - MD5(reinterpret_cast(input.data()), - input.size(), - address_day_hash.data()); + GRC__MD5(reinterpret_cast(input.data()), + input.size(), + address_day_hash.data()); return arith_uint256("0x" + HexStr(address_day_hash)) < reference_hash; } @@ -721,7 +721,7 @@ class SuperblockValidator std::vector m_resolved_parts; //! - //! \brief Divisor set by the \c ProjectCombiner for iteraton over each + //! \brief Divisor set by the \c ProjectCombiner for iteration over each //! convergence combination. //! size_t m_combiner_mask; diff --git a/src/gridcoin/researcher.cpp b/src/gridcoin/researcher.cpp index 9a714bc083..ad36cf0f3b 100644 --- a/src/gridcoin/researcher.cpp +++ b/src/gridcoin/researcher.cpp @@ -1,15 +1,16 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2023 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. #include "init.h" -#include "gridcoin/appcache.h" #include "gridcoin/backup.h" #include "gridcoin/beacon.h" #include "gridcoin/boinc.h" #include "gridcoin/contract/message.h" #include "gridcoin/magnitude.h" +#include #include "gridcoin/project.h" +#include "gridcoin/protocol.h" #include "gridcoin/quorum.h" #include "gridcoin/researcher.h" #include "gridcoin/support/xml.h" @@ -23,7 +24,6 @@ #include #include #include -#include #include #include @@ -147,7 +147,7 @@ bool CompareProjectHostname(const std::string& url_1, const std::string& url_2) //! //! \return A pointer to the whitelist project if it matches. //! -const Project* ResolveWhitelistProject( +const ProjectEntry* ResolveWhitelistProject( const MiningProject& project, const WhitelistSnapshot& whitelist) { @@ -282,7 +282,7 @@ std::vector FetchProjectsXml() //! bool ShouldEnforceTeamMembership() { - return ReadCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP").value != "false"; + return GetProtocolRegistry().GetProtocolEntryByKeyLegacy("REQUIRE_TEAM_WHITELIST_MEMBERSHIP").value != "false"; } //! @@ -298,7 +298,7 @@ std::set GetTeamWhitelist() return { }; } - const AppCacheEntry entry = ReadCache(Section::PROTOCOL, "TEAM_WHITELIST"); + const AppCacheEntry entry = GetProtocolRegistry().GetProtocolEntryByKeyLegacy("TEAM_WHITELIST"); if (entry.value.empty()) { return { "gridcoin" }; @@ -393,9 +393,9 @@ std::optional FallbackToCpidByEmail( const std::string email = Researcher::Email(); std::vector email_hash_bytes(16); - MD5(reinterpret_cast(email.data()), - email.size(), - email_hash_bytes.data()); + GRC__MD5(reinterpret_cast(email.data()), + email.size(), + email_hash_bytes.data()); if (HexStr(email_hash_bytes) != email_hash) { return std::nullopt; @@ -674,9 +674,9 @@ bool SignBeaconPayload(BeaconPayload& payload) //! \return An error that describes why the wallet cannot send a beacon if //! a transaction will not succeed. //! -BeaconError CheckBeaconTransactionViable(const CWallet& wallet) +BeaconError CheckBeaconTransactionViable(CWallet* wallet, const Cpid& cpid) { - if (pwalletMain->IsLocked()) { + if (wallet->IsLocked()) { LogPrintf("WARNING: %s: Wallet locked.", __func__); return BeaconError::WALLET_LOCKED; } @@ -687,11 +687,25 @@ BeaconError CheckBeaconTransactionViable(const CWallet& wallet) // TODO: refactor wallet so we can determine this dynamically. For now, we // require 1 GRC: // - if (pwalletMain->GetBalance() < COIN) { + if (wallet->GetBalance() < COIN) { LogPrintf("WARNING: %s: Insufficient funds.", __func__); return BeaconError::INSUFFICIENT_FUNDS; } + for (const auto& [_, pool_tx] : mempool.mapTx) { + for (const auto& pool_tx_contract : pool_tx.GetContracts()) { + if (pool_tx_contract.m_type == GRC::ContractType::BEACON) { + GRC::BeaconPayload pool_tx_beacon = pool_tx_contract.CopyPayloadAs(); + + GRC::Cpid other_cpid = pool_tx_beacon.m_cpid; + + if (cpid == other_cpid) { + return BeaconError::ALEADY_IN_MEMPOOL; + } + } + } + } + return BeaconError::NONE; } @@ -710,7 +724,7 @@ AdvertiseBeaconResult SendBeaconContract( Beacon beacon, ContractAction action = ContractAction::ADD) { - const BeaconError error = CheckBeaconTransactionViable(*pwalletMain); + const BeaconError error = CheckBeaconTransactionViable(pwalletMain, cpid); if (error != BeaconError::NONE) { return error; @@ -722,8 +736,10 @@ AdvertiseBeaconResult SendBeaconContract( return BeaconError::MISSING_KEY; } + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + const auto result_pair = SendContract( - MakeContract(action, std::move(payload))); + MakeContract(contract_version, action, std::move(payload))); if (!result_pair.second.empty()) { return BeaconError::TX_FAILED; @@ -747,7 +763,7 @@ AdvertiseBeaconResult SendNewBeacon(const Cpid& cpid) // transaction. Otherwise, we may create a bogus beacon key that lingers in // the wallet: // - const BeaconError error = CheckBeaconTransactionViable(*pwalletMain); + const BeaconError error = CheckBeaconTransactionViable(pwalletMain, cpid); if (error != BeaconError::NONE) { return error; @@ -780,7 +796,7 @@ AdvertiseBeaconResult RenewBeacon(const Cpid& cpid, const Beacon& beacon) LogPrintf("%s: Renewing beacon for %s", __func__, cpid.ToString()); - const BeaconError error = CheckBeaconTransactionViable(*pwalletMain); + const BeaconError error = CheckBeaconTransactionViable(pwalletMain, cpid); if (error != BeaconError::NONE) { return error; @@ -899,7 +915,7 @@ bool MiningProject::Eligible() const return m_error == Error::NONE; } -const Project* MiningProject::TryWhitelist(const WhitelistSnapshot& whitelist) const +const ProjectEntry* MiningProject::TryWhitelist(const WhitelistSnapshot& whitelist) const { return ResolveWhitelistProject(*this, whitelist); } diff --git a/src/gridcoin/researcher.h b/src/gridcoin/researcher.h index fbb765a9b2..cfaf8da840 100644 --- a/src/gridcoin/researcher.h +++ b/src/gridcoin/researcher.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2021 The Gridcoin developers +// Copyright (c) 2014-2023 The Gridcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -21,7 +21,7 @@ namespace GRC { class Beacon; class Magnitude; -class Project; +class ProjectEntry; class WhitelistSnapshot; //! @@ -160,7 +160,7 @@ struct MiningProject //! //! \return A pointer to the whitelist project if it matches. //! - const Project* TryWhitelist(const WhitelistSnapshot& whitelist) const; + const GRC::ProjectEntry* TryWhitelist(const WhitelistSnapshot& whitelist) const; //! //! \brief Determine whether the project is whitelisted. @@ -306,6 +306,7 @@ enum class BeaconError PENDING, //!< Not enough time elapsed for pending advertisement. TX_FAILED, //!< Beacon contract transacton failed to send. WALLET_LOCKED, //!< Wallet not fully unlocked. + ALEADY_IN_MEMPOOL //!< A beacon contract for this CPID is already in the mempool. }; //! diff --git a/src/gridcoin/scraper/fwd.h b/src/gridcoin/scraper/fwd.h index 2f62c02f84..d91f9c8eee 100644 --- a/src/gridcoin/scraper/fwd.h +++ b/src/gridcoin/scraper/fwd.h @@ -285,19 +285,4 @@ struct ScraperStatsAndVerifiedBeacons ScraperPendingBeaconMap mVerifiedMap; }; -/** Extended AppCache structure similar to those in AppCache.h, except a deleted flag is provided. This will be - * reimplemented in the future with a custom contract handler since the appcache is being retired. - */ -struct AppCacheEntryExt -{ - std::string value; // Value of entry. - int64_t timestamp; // Timestamp of entry/deletion - bool deleted; // Deleted flag. -}; - -/** Extended AppCache map typedef similar to those in AppCache.h, except a deleted flag is provided. This will be - * reimplemented in the future with a custom contract handler since the appcache is being retired. - */ -typedef std::unordered_map AppCacheSectionExt; - #endif // GRIDCOIN_SCRAPER_FWD_H diff --git a/src/gridcoin/scraper/http.cpp b/src/gridcoin/scraper/http.cpp index 149fc5a511..022aafe176 100644 --- a/src/gridcoin/scraper/http.cpp +++ b/src/gridcoin/scraper/http.cpp @@ -98,10 +98,18 @@ namespace { pg->lastruntime = currenttime; +#if LIBCURL_VERSION_NUM >= 0x073700 + curl_off_t speed; +#else double speed; +#endif CURLcode result; +#if LIBCURL_VERSION_NUM >= 0x073700 + result = curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD_T, &speed); +#else result = curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD, &speed); +#endif // Download speed update if (result == CURLE_OK) diff --git a/src/gridcoin/scraper/scraper.cpp b/src/gridcoin/scraper/scraper.cpp index 2d3bc3b459..9a65484b78 100755 --- a/src/gridcoin/scraper/scraper.cpp +++ b/src/gridcoin/scraper/scraper.cpp @@ -9,10 +9,12 @@ #include "gridcoin/appcache.h" #include "gridcoin/beacon.h" #include "gridcoin/project.h" +#include "gridcoin/protocol.h" #include "gridcoin/quorum.h" #include "gridcoin/scraper/http.h" #include "gridcoin/scraper/scraper.h" #include "gridcoin/scraper/scraper_net.h" +#include "gridcoin/scraper/scraper_registry.h" #include "gridcoin/superblock.h" #include "gridcoin/support/block_finder.h" #include "gridcoin/support/xml.h" @@ -57,10 +59,6 @@ CCriticalSection cs_Scraper; * @brief Protects the scraper globals */ CCriticalSection cs_ScraperGlobals; -/** - * @brief Protects the extended scraper app cache global map. - */ -CCriticalSection cs_mScrapersExt; /** * @brief Protects the main scraper file manifest structure. This is the primary global state machine for the scraper on the * file side. @@ -175,56 +173,6 @@ std::string EXTERNAL_ADAPTER_PROJECTS GUARDED_BY(cs_ScraperGlobals) = std::strin */ int64_t SCRAPER_DEAUTHORIZED_BANSCORE_GRACE_PERIOD GUARDED_BY(cs_ScraperGlobals) = 300; -/** Map that holds extended app cache entries for scrapers, which includes deleted entries. */ -AppCacheSectionExt mScrapersExt GUARDED_BY(cs_mScrapersExt) = {}; - -/** Enum for scraper log attributes */ -enum class logattribute -{ - // Can't use ERROR here because it is defined already in windows.h. - ERR, - INFO, - WARNING, - CRITICAL -}; - -/** Defines scraper file manifest entry. These are the entries for individual project stats file downloads. */ -struct ScraperFileManifestEntry -{ - std::string filename; // Filename - std::string project; - uint256 hash; // hash of file - int64_t timestamp = 0; - bool current = true; - bool excludefromcsmanifest = true; - std::string filetype; -}; - -/** - * @brief Defines the scaper file manifest map. - * --------- filename ---ScraperFileManifestEntry - * std::map ScraperFileManifestMap - */ -typedef std::map ScraperFileManifestMap; - -/** Defines a structure that combines the ScraperFileManifestMap along with a map hash, the block hash of the - * consensus block, and the time that the above fields were updated. - */ -struct ScraperFileManifest -{ - ScraperFileManifestMap mScraperFileManifest; - uint256 nFileManifestMapHash; - uint256 nConsensusBlockHash; - int64_t timestamp = 0; -}; - -// Both TeamIDMap and ProjTeamETags are protected by cs_TeamIDMap. -/** Stores the team IDs for each team keyed by project. (Team ID's are different for the same team across different - * projects.) - * --------- project -------------team name -- teamID - * std::map> mTeamIDs - */ -typedef std::map> mTeamIDs; mTeamIDs TeamIDMap GUARDED_BY(cs_TeamIDMap); /** ProjTeamETags is not persisted to disk. There would be little to be gained by doing so. The scrapers are restarted very @@ -260,7 +208,7 @@ std::map> CScraperManifest::mapManife ConvergedScraperStats ConvergedScraperStatsCache GUARDED_BY(cs_ConvergedScraperStatsCache) = {}; /** - * @brief Scraper loggger function + * @brief Scraper logger function * @param eType * @param sCall * @param sMessage @@ -1288,8 +1236,10 @@ class authdata template void ApplyCache(const std::string& key, T& result) { - // Local reference to avoid double lookup. - const auto& entry = ReadCache(Section::PROTOCOL, key); + // Local reference to avoid double lookup. This is changed from ReadCache with Section::PROTOCOL to + // the shunt call in ProtocolRegistry GetProtocolEntryByKeyLegacy. + // const auto& entry = ReadCache(Section::PROTOCOL, key); + const auto& entry = GetProtocolRegistry().GetProtocolEntryByKeyLegacy(key); // If the entry has an empty string (no value) then leave the original undisturbed. if (entry.value.empty()) @@ -1460,15 +1410,18 @@ void ScraperApplyAppCacheEntries() AppCacheSection GetScrapersCache() { - return ReadCacheSection(Section::SCRAPER); + // Includes authorized scraper entries only. + return GRC::GetScraperRegistry().GetScrapersLegacy(); } AppCacheSectionExt GetExtendedScrapersCache() { - AppCacheSection mScrapers = GetScrapersCache(); - // For the IsManifestAuthorized() function... + // The below is the old comment that provides original motivation behind AppCacheSection vs. AppCacheSectionExt. + // Note that the GetScrapersLegacy() and GetScrapersLegacyExt() mimic the old behavior. These will be changed + // out for native wiring as part of a scraper update. + /* We cannot use the AppCacheSection mScrapers in the raw, because there are two ways to deauthorize scrapers. * The first way is to change the value of an existing entry to false. This works fine with mScrapers. The second way * is to issue an addkey delete key. This will remove the key entirely, therefore deauthorizing the scraper. We need to @@ -1481,34 +1434,8 @@ AppCacheSectionExt GetExtendedScrapersCache() * scraper is deauthorized and the block containing that deauthorization is received by the sending node. */ - // So we are going to make use of AppCacheEntryExt and mScrapersExt, which are just like the normal AppCache structure, - // except they have an explicit deleted boolean. - - // First, walk the mScrapersExt map and see if it contains an entry that does not exist in mScrapers. If so, - // update the entry's value and timestamp and mark deleted. - LOCK(cs_mScrapersExt); - - for (auto const& entry : mScrapersExt) - { - const auto& iter = mScrapers.find(entry.first); - - if (iter == mScrapers.end()) - { - // Mark entry in mScrapersExt as deleted at the current adjusted time. The value is changed - // to false, because if it is deleted, it is also not authorized. - mScrapersExt[entry.first] = AppCacheEntryExt {"false", GetAdjustedTime(), true}; - } - - } - - // Now insert/update entries from mScrapers into mScrapersExt. - for (auto const& entry : mScrapers) - { - mScrapersExt[entry.first] = AppCacheEntryExt {entry.second.value, entry.second.timestamp, false}; - } - - // Return a copy of the global on purpose so the cs_mScrapersExt can be a short term lock. This map is very small. - return mScrapersExt; + // Includes deleted scraper entries. + return GRC::GetScraperRegistry().GetScrapersLegacyExt(false); } // This is the "main" scraper function. @@ -6248,7 +6175,7 @@ UniValue testnewsb(const UniValue& params, bool fHelp) if (PastConvergencesSize > 1) { - int i = GetRandInt(PastConvergencesSize - 1); + int i = GetRand(PastConvergencesSize - 1); _log(logattribute::INFO, "testnewsb", "ValidateSuperblock random past RandomPastConvergedManifest index " + ToString(i) + " selected."); diff --git a/src/gridcoin/scraper/scraper.h b/src/gridcoin/scraper/scraper.h index 1a88b9b003..f07c56f020 100644 --- a/src/gridcoin/scraper/scraper.h +++ b/src/gridcoin/scraper/scraper.h @@ -26,7 +26,6 @@ // Thread safety. See scraper.cpp for documentation. extern CCriticalSection cs_Scraper; extern CCriticalSection cs_ScraperGlobals; -extern CCriticalSection cs_mScrapersExt; extern CCriticalSection cs_StructScraperFileManifest; extern CCriticalSection cs_ConvergedScraperStatsCache; extern CCriticalSection cs_TeamIDMap; @@ -60,9 +59,53 @@ extern std::string TEAM_WHITELIST; extern std::string EXTERNAL_ADAPTER_PROJECTS; extern int64_t SCRAPER_DEAUTHORIZED_BANSCORE_GRACE_PERIOD; -extern CCriticalSection cs_mScrapersExt; +/** Enum for scraper log attributes */ +enum class logattribute +{ + // Can't use ERROR here because it is defined already in windows.h. + ERR, + INFO, + WARNING, + CRITICAL +}; + +/** Defines scraper file manifest entry. These are the entries for individual project stats file downloads. */ +struct ScraperFileManifestEntry +{ + std::string filename; // Filename + std::string project; + uint256 hash; // hash of file + int64_t timestamp = 0; + bool current = true; + bool excludefromcsmanifest = true; + std::string filetype; +}; + +/** + * @brief Defines the scaper file manifest map. + * --------- filename ---ScraperFileManifestEntry + * std::map ScraperFileManifestMap + */ +typedef std::map ScraperFileManifestMap; -extern AppCacheSectionExt mScrapersExt; +/** Defines a structure that combines the ScraperFileManifestMap along with a map hash, the block hash of the + * consensus block, and the time that the above fields were updated. + */ +struct ScraperFileManifest +{ + ScraperFileManifestMap mScraperFileManifest; + uint256 nFileManifestMapHash; + uint256 nConsensusBlockHash; + int64_t timestamp = 0; +}; + +// Both TeamIDMap and ProjTeamETags are protected by cs_TeamIDMap. +/** Stores the team IDs for each team keyed by project. (Team ID's are different for the same team across different + * projects.) + * --------- project -------------team name -- teamID + * std::map> mTeamIDs + */ +typedef std::map> mTeamIDs; /********************* * Functions * diff --git a/src/gridcoin/scraper/scraper_net.cpp b/src/gridcoin/scraper/scraper_net.cpp index 4fb19ebb57..25cc1360e4 100644 --- a/src/gridcoin/scraper/scraper_net.cpp +++ b/src/gridcoin/scraper/scraper_net.cpp @@ -31,7 +31,6 @@ extern CCriticalSection cs_ScraperGlobals; extern unsigned int nScraperSleep; extern std::atomic g_nTimeBestReceived; extern ConvergedScraperStats ConvergedScraperStatsCache; -extern CCriticalSection cs_mScrapersExt; extern CCriticalSection cs_ConvergedScraperStatsCache; extern AppCacheSectionExt GetExtendedScrapersCache(); extern bool IsScraperMaximumManifestPublishingRateExceeded(int64_t& nTime, CPubKey& PubKey); @@ -361,7 +360,7 @@ EXCLUSIVE_LOCKS_REQUIRED(CScraperManifest::cs_mapManifest) AppCacheSectionExt mScrapersExtended = GetExtendedScrapersCache(); - // Now mScrapersExt is up to date. Walk and see if there is an entry with a value of true that matches + // Now mScrapersExtended is up to date. Walk and see if there is an entry with a value of true that matches // manifest address. If so the manifest is authorized. Note that no grace period has to be considered // for the authorized case. To prevent islanding in the unauthorized case, we must allow a grace period // before we return a banscore > 0. The grace period must extend SCRAPER_DEAUTHORIZED_BANSCORE_GRACE_PERIOD diff --git a/src/gridcoin/scraper/scraper_registry.cpp b/src/gridcoin/scraper/scraper_registry.cpp new file mode 100644 index 0000000000..bf481e0216 --- /dev/null +++ b/src/gridcoin/scraper/scraper_registry.cpp @@ -0,0 +1,594 @@ +// Copyright (c) 2014-2023 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "scraper_registry.h" +#include "wallet/wallet.h" + +using namespace GRC; +using LogFlags = BCLog::LogFlags; + +extern int64_t g_v11_timestamp; + +namespace { +ScraperRegistry g_scrapers; +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Global Functions +// ----------------------------------------------------------------------------- + +ScraperRegistry& GRC::GetScraperRegistry() +{ + return g_scrapers; +} + +// ----------------------------------------------------------------------------- +// Class: ScraperEntry +// ----------------------------------------------------------------------------- + +ScraperEntry::ScraperEntry() + : m_key() + , m_timestamp(0) + , m_hash() + , m_previous_hash() + , m_status(ScraperEntryStatus::UNKNOWN) +{ +} + +ScraperEntry::ScraperEntry(CKeyID key_id, Status status) + : ScraperEntry(std::move(key_id), std::move(status), 0, uint256 {}) +{ +} + +ScraperEntry::ScraperEntry(CKeyID key_id, Status status, int64_t tx_timestamp, uint256 hash) + : m_key(key_id) + , m_timestamp(tx_timestamp) + , m_hash(hash) + , m_previous_hash() + , m_status(status) +{ +} + +bool ScraperEntry::WellFormed() const +{ + return (CBitcoinAddress(m_key).IsValid() + && m_status != ScraperEntryStatus::UNKNOWN + && m_status != ScraperEntryStatus::OUT_OF_BOUND); +} + +CKeyID ScraperEntry::Key() const +{ + return m_key; +} + +std::pair ScraperEntry::KeyValueToString() const +{ + return std::make_pair(CBitcoinAddress(m_key).ToString(), StatusToString()); +} + +CKeyID ScraperEntry::GetId() const +{ + return m_key; +} + +CBitcoinAddress ScraperEntry::GetAddress() const +{ + return CBitcoinAddress(m_key); +} + +std::string ScraperEntry::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string ScraperEntry::StatusToString(const ScraperEntryStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case ScraperEntryStatus::UNKNOWN: return _("Unknown"); + case ScraperEntryStatus::DELETED: return _("Deleted"); + case ScraperEntryStatus::NOT_AUTHORIZED: return _("Not authorized"); + case ScraperEntryStatus::AUTHORIZED: return _("Authorized"); + case ScraperEntryStatus::EXPLORER: return _("Explorer"); + case ScraperEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case ScraperEntryStatus::UNKNOWN: return "unknown"; + case ScraperEntryStatus::DELETED: return "deleted"; + case ScraperEntryStatus::NOT_AUTHORIZED: return "not_authorized"; + case ScraperEntryStatus::AUTHORIZED: return "authorized"; + case ScraperEntryStatus::EXPLORER: return "explorer"; + case ScraperEntryStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +bool ScraperEntry::WalletHasPrivateKey(const CWallet* const wallet) const +{ + LOCK(wallet->cs_wallet); + + return wallet->HaveKey(m_key); +} + +AppCacheEntryExt ScraperEntry::GetLegacyScraperEntry() +{ + AppCacheEntryExt entry; + + entry.value = CBitcoinAddress(m_key).ToString(); + entry.timestamp = m_timestamp; + entry.deleted = (m_status == ScraperEntryStatus::DELETED); + + return entry; +} + +bool ScraperEntry::operator==(ScraperEntry b) +{ + bool result = true; + + result &= (m_key == b.m_key); + result &= (m_timestamp == b.m_timestamp); + result &= (m_hash == b.m_hash); + result &= (m_previous_hash == b.m_previous_hash); + result &= (m_status == b.m_status); + + return result; +} + +bool ScraperEntry::operator!=(ScraperEntry b) +{ + return !(*this == b); +} + +// ----------------------------------------------------------------------------- +// Class: ScraperEntryPayload +// ----------------------------------------------------------------------------- + +constexpr uint32_t ScraperEntryPayload::CURRENT_VERSION; // For clang + +ScraperEntryPayload::ScraperEntryPayload(const uint32_t version) + : m_version(version) +{ +} + +ScraperEntryPayload::ScraperEntryPayload(const uint32_t version, CKeyID key_id, ScraperEntryStatus status) + : LegacyPayload() + , m_version(version) + , m_scraper_entry(ScraperEntry(key_id, status)) +{ + assert(version > 1); +} + +ScraperEntryPayload::ScraperEntryPayload(const uint32_t version, ScraperEntry scraper_entry) + : LegacyPayload() + , m_version(version) + , m_scraper_entry(std::move(scraper_entry)) +{ +} + +ScraperEntryPayload::ScraperEntryPayload(ScraperEntry scraper_entry) + : ScraperEntryPayload(CURRENT_VERSION, std::move(scraper_entry)) +{ +} + +ScraperEntryPayload::ScraperEntryPayload(const std::string& key, const std::string& value) + : LegacyPayload(key, value) +{ + m_version = 1; + + CBitcoinAddress address; + + address.SetString(m_key); + + if (!address.IsValid()) { + error("%s: Error during initialization of ScraperEntryPayload from legacy format: key = %s, value = %s", + __func__, + key, + value); + return; + } + + address.GetKeyID(m_scraper_entry.m_key); + + if (ToLower(m_value) == "true") { + m_scraper_entry.m_status = ScraperEntryStatus::AUTHORIZED; + } else { + // any other value than "true" in legacy scraper contract is interpreted as NOT_AUTHORIZED. + m_scraper_entry.m_status = ScraperEntryStatus::NOT_AUTHORIZED; + } +} + +ScraperEntryPayload ScraperEntryPayload::Parse(const std::string& key, const std::string& value) +{ + CBitcoinAddress address; + + address.SetString(key); + + if (!address.IsValid()) { + return ScraperEntryPayload(); + } + + CKeyID key_id; + address.GetKeyID(key_id); + ScraperEntryStatus scraper_entry_status = ScraperEntryStatus::UNKNOWN; + + if (ToLower(value) == "true") { + scraper_entry_status = ScraperEntryStatus::AUTHORIZED; + } else { + // any other value than "true" in legacy scraper contract is interpreted as NOT_AUTHORIZED. + scraper_entry_status = ScraperEntryStatus::NOT_AUTHORIZED; + } + + ScraperEntryPayload payload(1, ScraperEntry(key_id, scraper_entry_status)); + // The above constructor doesn't carry over the legacy K-V which we need. + payload.m_key = key; + payload.m_value = value; + + return payload; +} + +// ----------------------------------------------------------------------------- +// Class: ScraperRegistry +// ----------------------------------------------------------------------------- +const ScraperRegistry::ScraperMap& ScraperRegistry::Scrapers() const +{ + return m_scrapers; +} + +const AppCacheSection ScraperRegistry::GetScrapersLegacy() const +{ + AppCacheSection scrapers; + + // Only includes authorized scrapers. + for (const auto& iter : GetScrapersLegacyExt(true)) { + AppCacheEntry entry; + + entry.timestamp = iter.second.timestamp; + entry.value = iter.second.value; + + scrapers[iter.first] = entry; + } + + return scrapers; +} + +const AppCacheSectionExt ScraperRegistry::GetScrapersLegacyExt(const bool& authorized_only) const +{ + AppCacheSectionExt scrapers_ext; + + LOCK(cs_lock); + + for (const auto& entry : m_scrapers) { + + std::string key = CBitcoinAddress(entry.first).ToString(); + + switch (entry.second->m_status.Value()) { + case ScraperEntryStatus::DELETED: + // Mark entry in scrapers_ext as deleted at the timestamp of the deletion. The value is changed + // to false, because if it is deleted, it is also not authorized. + if (!authorized_only) { + scrapers_ext[key] = AppCacheEntryExt {"false", entry.second->m_timestamp, true}; + } + break; + + case ScraperEntryStatus::NOT_AUTHORIZED: + scrapers_ext[key] = AppCacheEntryExt {"false", entry.second->m_timestamp, false}; + break; + + case ScraperEntryStatus::AUTHORIZED: + [[fallthrough]]; + // For the legacy AppCacheEntryExt, this case really doesn't exist, but treat the same as AUTHORIZED. + case ScraperEntryStatus::EXPLORER: + scrapers_ext[key] = AppCacheEntryExt {"true", entry.second->m_timestamp, false}; + break; + + // Ignore UNKNOWN and OUT_OF_BOUND. + case ScraperEntryStatus::UNKNOWN: + [[fallthrough]]; + case ScraperEntryStatus::OUT_OF_BOUND: + break; + } + } + + return scrapers_ext; +} + +ScraperEntryOption ScraperRegistry::Try(const CKeyID& key_id) const +{ + LOCK(cs_lock); + + const auto iter = m_scrapers.find(key_id); + + if (iter == m_scrapers.end()) { + return nullptr; + } + + return iter->second; +} + +ScraperEntryOption ScraperRegistry::TryAuthorized(const CKeyID& key_id) const +{ + LOCK(cs_lock); + + if (const ScraperEntryOption scraper_entry = Try(key_id)) { + if (scraper_entry->m_status == ScraperEntryStatus::AUTHORIZED + || scraper_entry->m_status == ScraperEntryStatus::EXPLORER) { + return scraper_entry; + } + } + + return nullptr; +} + +void ScraperRegistry::Reset() +{ + LOCK(cs_lock); + + m_scrapers.clear(); + m_scraper_db.clear(); +} + +void ScraperRegistry::AddDelete(const ContractContext& ctx) +{ + // Poor man's mock. This is to prevent the tests from polluting the LevelDB database + int height = -1; + + if (ctx.m_pindex) + { + height = ctx.m_pindex->nHeight; + } + + ScraperEntryPayload payload = ctx->CopyPayloadAs(); + + // Fill in the hash and time from the transaction context, because this is not done during payload initialization. + payload.m_scraper_entry.m_hash = ctx.m_tx.GetHash(); + payload.m_scraper_entry.m_timestamp = ctx.m_tx.nTime; + + // If the contract action is to remove a scraper entry, then the record added must have a status of deleted, + // regardless of what was specified in the status. + if (ctx->m_action == ContractAction::REMOVE) { + payload.m_scraper_entry.m_status = ScraperEntryStatus::DELETED; + } + + LOCK(cs_lock); + + auto scraper_entry_pair_iter = m_scrapers.find(payload.m_scraper_entry.m_key); + + ScraperEntry_ptr current_scraper_entry_ptr = nullptr; + + // Make sure the payload m_scraper has the correct time and transaction hash. + //payload.m_scraper_entry.m_timestamp = ctx.m_tx.nTime; + //payload.m_scraper_entry.m_hash = ctx.m_tx.GetHash(); + + // Is there an existing scraper entry in the map? + bool current_scraper_entry_present = (scraper_entry_pair_iter != m_scrapers.end()); + + // If so, then get a smart pointer to it. + if (current_scraper_entry_present) { + current_scraper_entry_ptr = scraper_entry_pair_iter->second; + + // Set the payload m_scraper_entry's prev scraper entry ctx hash = to the existing scraper entry's hash. + payload.m_scraper_entry.m_previous_hash = current_scraper_entry_ptr->m_hash; + } else { // Original entry for this scraper keyid + payload.m_scraper_entry.m_previous_hash = uint256 {}; + } + + CBitcoinAddress address; + address.Set(payload.m_scraper_entry.m_key); + + LogPrint(LogFlags::SCRAPER, "INFO: %s: scraper entry add/delete: contract m_version = %u, payload " + "m_version = %u, address for m_key = %s, m_timestamp = %" PRId64 ", " + "m_hash = %s, m_previous_hash = %s, m_status = %i", + __func__, + ctx->m_version, + payload.m_version, + address.ToString(), + payload.m_scraper_entry.m_timestamp, + payload.m_scraper_entry.m_hash.ToString(), + payload.m_scraper_entry.m_previous_hash.ToString(), + payload.m_scraper_entry.m_status.Raw() + ); + + ScraperEntry& historical = payload.m_scraper_entry; + + if (!m_scraper_db.insert(ctx.m_tx.GetHash(), height, historical)) + { + LogPrint(LogFlags::SCRAPER, "INFO: %s: In recording of the scraper entry for address %s, hash %s, the scraper entry " + "db record already exists. This can be expected on a restart of the wallet to ensure " + "multiple contracts in the same block get stored/replayed.", + __func__, + historical.GetAddress().ToString(), + historical.m_hash.GetHex()); + } + + // Finally, insert the new scraper entry (payload) smart pointer into the m_scrapers map. + m_scrapers[payload.m_scraper_entry.m_key] = m_scraper_db.find(ctx.m_tx.GetHash())->second; + + return; +} + +void ScraperRegistry::Add(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void ScraperRegistry::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void ScraperRegistry::Revert(const ContractContext& ctx) +{ + const auto payload = ctx->SharePayloadAs(); + + // For scraper entries, both adds and removes will have records to revert in the m_scrapers map, + // and also, if not the first entry for that scraper keyid, will have a historical record to + // resurrect. + LOCK(cs_lock); + + auto entry_to_revert = m_scrapers.find(payload->m_scraper_entry.m_key); + + if (entry_to_revert == m_scrapers.end()) { + error("%s: The scraper entry for address %s to revert was not found in the scraper entry map.", + __func__, + entry_to_revert->second->GetAddress().ToString()); + + // If there is no record in the current m_scrapers map, then there is nothing to do here. This + // should not occur. + return; + } + + CBitcoinAddress address = entry_to_revert->second->GetAddress(); + + // If this is not a null hash, then there will be a prior entry to resurrect. + uint256 resurrect_hash = entry_to_revert->second->m_previous_hash; + + // Revert the ADD or REMOVE action. Unlike the beacons, this is symmetric. + if (ctx->m_action == ContractAction::ADD || ctx->m_action == ContractAction::REMOVE) { + // Erase the record from m_scrapers. + if (m_scrapers.erase(payload->m_scraper_entry.m_key) == 0) { + error("%s: The scraper entry to erase during a scraper entry revert for address %s was not found.", + __func__, + address.ToString()); + // If the record to revert is not found in the m_scrapers map, no point in continuing. + return; + } + + // Also erase the record from the db. + if (!m_scraper_db.erase(ctx.m_tx.GetHash())) { + error("%s: The db entry to erase during a scraper entry revert for address %s was not found.", + __func__, + address.ToString()); + + // Unlike the above we will keep going even if this record is not found, because it is identical to the + // m_scrapers record above. This should not happen, because during contract adds and removes, entries are + // made simultaneously to be the m_scrapers and m_scraper_db. + } + + if (resurrect_hash.IsNull()) { + return; + } + + auto resurrect_entry = m_scraper_db.find(resurrect_hash); + + if (resurrect_entry == m_scraper_db.end()) { + error("%s: The prior entry to resurrect during a scraper entry ADD revert for address %s was not found.", + __func__, + address.ToString()); + return; + } + + // Resurrect the entry prior to the reverted one. It is safe to use the bracket form here, because of the protection + // of the logic above. There cannot be any entry in m_scrapers with that keyid value left if we made it here. + m_scrapers[resurrect_entry->second->m_key] = resurrect_entry->second; + } +} + +bool ScraperRegistry::Validate(const Contract& contract, const CTransaction& tx, int &DoS) const +{ + if (contract.m_version < 1) { + return true; + } + + const auto payload = contract.SharePayloadAs(); + + // TODO review if this is correct for scraper entries. + if (contract.m_version >= 3 && payload->m_version < 2) { + DoS = 25; + error("%s: Legacy scraper contract in contract v3", __func__); + return false; + } + + if (!payload->WellFormed(contract.m_action.Value())) { + DoS = 25; + error("%s: Malformed scraper contract", __func__); + return false; + } + + return true; +} + +bool ScraperRegistry::BlockValidate(const ContractContext& ctx, int& DoS) const +{ + return Validate(ctx.m_contract, ctx.m_tx, DoS); +} + +int ScraperRegistry::Initialize() +{ + LOCK(cs_lock); + + int height = m_scraper_db.Initialize(m_scrapers, m_pending_scrapers, m_expired_scraper_entries); + + LogPrint(LogFlags::SCRAPER, "INFO: %s: m_scraper_db size after load: %u", __func__, m_scraper_db.size()); + LogPrint(LogFlags::SCRAPER, "INFO: %s: m_scrapers size after load: %u", __func__, m_scrapers.size()); + + return height; +} + +void ScraperRegistry::SetDBHeight(int& height) +{ + LOCK(cs_lock); + + m_scraper_db.StoreDBHeight(height); +} + +int ScraperRegistry::GetDBHeight() +{ + int height = 0; + + LOCK(cs_lock); + + m_scraper_db.LoadDBHeight(height); + + return height; +} + +void ScraperRegistry::ResetInMemoryOnly() +{ + LOCK(cs_lock); + + m_scrapers.clear(); + m_scraper_db.clear_in_memory_only(); +} + +uint64_t ScraperRegistry::PassivateDB() +{ + LOCK(cs_lock); + + return m_scraper_db.passivate_db(); +} + +ScraperRegistry::ScraperEntryDB &ScraperRegistry::GetScraperDB() +{ + return m_scraper_db; +} + +// This is static and called by the scheduler. +void ScraperRegistry::RunDBPassivation() +{ + TRY_LOCK(cs_main, locked_main); + + if (!locked_main) + { + return; + } + + ScraperRegistry& scraper_entries = GetScraperRegistry(); + + scraper_entries.PassivateDB(); +} + +template<> const std::string ScraperRegistry::ScraperEntryDB::KeyType() +{ + return std::string("scraper"); +} diff --git a/src/gridcoin/scraper/scraper_registry.h b/src/gridcoin/scraper/scraper_registry.h new file mode 100644 index 0000000000..5686c25991 --- /dev/null +++ b/src/gridcoin/scraper/scraper_registry.h @@ -0,0 +1,654 @@ +// Copyright (c) 2014-2023 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_SCRAPER_SCRAPER_REGISTRY_H +#define GRIDCOIN_SCRAPER_SCRAPER_REGISTRY_H + +#include "amount.h" +#include "base58.h" +#include "dbwrapper.h" +#include "serialize.h" +#include "gridcoin/scraper/fwd.h" +#include "gridcoin/contract/handler.h" +#include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" +#include "gridcoin/support/enumbytes.h" + + +namespace GRC { + +//! +//! \brief Enumeration of scraper status. Unlike beacons this is for both storage +//! and memory. +//! +//! UNKNOWN status is only encountered in trivially constructed empty +//! scraper entries and should never be seen on the blockchain. +//! +//! DELETED status corresponds to the functionality implemented in the AppCacheEntryExt +//! structure in the scraper to compensate for the lack of presence of a deleted record +//! in the old appcache. Rather than removing a record when a REMOVE contract is +//! encountered, The existing record is linked to the new one, which will have a +//! DELETED status. +//! +//! NOT_AUTHORIZED corresponds to the old appcache scraper section "false" value. +//! +//! AUTHORIZED corresponds to the old appcache scraper section "true" value +//! +//! EXPLORER is a new status that is meant to be able to establish differentiated +//! permission between a normal scraper node and one that is authorized to download +//! and retain all project stats files and store for an extended period. For legacy +//! purposes, this translates to "true' in the old appcache scraper section as well. Once +//! the scraper code has been converted over to use the native calls here from the +//! compatibility shims, then this new status can be used to specifically control +//! which scrapers are allowed to do extended explorer style statistics download and +//! retention. +//! +//! OUT_OF_BOUND must go at the end and be retained for the EnumBytes wrapper. +//! +enum class ScraperEntryStatus +{ + UNKNOWN, + DELETED, + NOT_AUTHORIZED, + AUTHORIZED, + EXPLORER, + OUT_OF_BOUND +}; + +//! +//! \brief This class formalizes the concept of an authorized scraper entry and replaces +//! the older appcache "scraper" section. Scrapers are authorized by their address, i.e. +//! CKeyID (since the other modes in CTxDestination do not apply). So the "key" field in +//! the ScraperEntry is m_key of type CKeyID. The datetime of the transaction containing +//! the contract that gives rise to the Entry is stored as m_timestamp. The hash of the +//! transaction is stored as m_hash. m_previous_hash stores the transaction hash of a previous +//! scraper entry with the same CKeyID, if there is one. This has the effect of creating +//! "chainlets", or a one-way linked list by hash of scraper entries with the same key. +//! This becomes very important to support reversion and avoid expensive forward contract +//! replays during a blockchain reorganization event. The status of the scraper entry +//! is stored as m_status in accordance with the class enum above. +//! +class ScraperEntry +{ +public: + //! + //! \brief Wrapped Enumeration of scraper entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + + CKeyID m_key; //!< Identifies the scraper (address) for the entry. + int64_t m_timestamp; //!< Time of the scraper entry contract transaction. + uint256 m_hash; //!< The txid of the transaction that contains the scraper entry. + uint256 m_previous_hash; //!< The m_hash of the previous scraper entry with the same m_key. + Status m_status; //!< The status of the scraper entry. (Note serialization converts to/from int.) + + //! + //! \brief Initialize an empty, invalid scraper entry instance. + //! + ScraperEntry(); + + //! + //! \brief Initialize a new scraper entry for submission in a contract. + //! + //! \param key_id. The CkeyID (i.e. address) of the scraper. + //! + ScraperEntry(CKeyID key_id, Status status); + + //! + //! \brief Initialize a scraper entry instance with data from a contract. + //! + //! \param key_id. The CkeyID (i.e. address) of the scraper. + //! \param status. The scraper entry status. + //! \param tx_timestamp. Time of the transaction with the scraper entry contract. + //! \param hash. Hash of the transaction with the scraper entry contract. + //! + ScraperEntry(CKeyID key_id, Status status, int64_t tx_timestamp, uint256 hash); + + //! + //! \brief Determine whether a scraper entry contains each of the required elements. + //! + //! \return \c true if the scraper entry is complete. + //! + bool WellFormed() const; + + //! + //! \brief This is the standardized method that returns the key value for the scraper entry (for + //! the registry_db.h template.) Here it is the same as the GetId() method below. + //! + //! \return CKeyID value for the scraper entry. + //! + CKeyID Key() const; + + //! + //! \brief Provides the key (address) and status as string + //! \return std::pair of strings + //! + std::pair KeyValueToString() const; + + //! + //! \brief Return the hash of scraper entry's public key (equivalent to address). + //! + //! \return RIPEMD-160 hash corresponding to the public key/address of the scraper. + //! + CKeyID GetId() const; + + //! + //! \brief Return the address from the m_key. + //! + //! \return \c CBitcoinAddress derived from the m_key. + //! + CBitcoinAddress GetAddress() const; + + //! + //! \brief Returns the string representation of the current scraper entry status + //! + //! \return Translated string representation of scraper status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input scraper entry status + //! + //! \param status. ScraperEntryStatus + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return Scraper entry status string. + //! + std::string StatusToString(const ScraperEntryStatus& status, const bool& translated = true) const; + + //! + //! \brief Determine whether the given wallet contains a private key for + //! this scraper entry's m_key. Because this function is intended to work + //! even if the wallet is locked, it does not check whether the key pair is + //! actually valid. This is used in authorizing a node to operate as a scraper + //! and in which mode. + //! + //! \return \c true if the wallet contains a matching private key. + //! + bool WalletHasPrivateKey(const CWallet* const wallet) const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. + //! + //! \return \c AppCacheEntryExt consisting of value, timestamp, and deleted boolean. + //! + AppCacheEntryExt GetLegacyScraperEntry(); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side scraper entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(ScraperEntry b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side scraper entry to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(ScraperEntry b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_key); + READWRITE(m_timestamp); + READWRITE(m_hash); + READWRITE(m_previous_hash); + READWRITE(m_status); + } +}; + +//! +//! \brief The type that defines a shared pointer to a ScraperEntry +//! +typedef std::shared_ptr ScraperEntry_ptr; + +//! +//! \brief A type that either points to some ScraperEntry or does not. +//! +typedef const ScraperEntry_ptr ScraperEntryOption; + +//! +//! \brief The body of a scraper entry contract. Note that this body is bimodal. It +//! supports both the personality of the "LegacyPayload", and also the new native +//! ScraperEntry format. In the Contract::Body::ConvertFromLegacy call, by the time +//! this call has been reached, the contract will have already been deserialized. +//! This will follow the legacy mode. For contracts at version 3+, the +//! Contract::SharePayload() will NOT call the ConvertFromLegacy. Note that because +//! the existing legacyPayloads are not versioned, the deserialization of +//! the payload first (de)serializes m_key, which is guaranteed to exist in either +//! legacy or native. If the key is empty, then payload v2+ is being deserialized +//! and the m_version and m_scraper_entry are (de)serialized. This is ugly +//! but necessary to deal with the unversioned Legacy Payloads and maintain +//! compatibility. +//! +class ScraperEntryPayload : public LegacyPayload +{ +public: + //! + //! \brief Version number of the current format for a serialized scraper entry. + //! + //! CONSENSUS: Increment this value when introducing a breaking change and + //! ensure that the serialization/deserialization routines also handle all + //! of the previous versions. + //! + static constexpr uint32_t CURRENT_VERSION = 2; + + //! + //! \brief Version number of the serialized scraper entry format. + //! + //! Initializes to the CURRENT_VERSION Note the constructor that takes a ScraperEntry + //! defaults to CURRENT_VERSION. When the legacy K-V fields are used which correspond + //! to the legacy appcache implementation, the version is 1. + //! + //! Version 1: appcache string key value: + //! + //! Version 2: Scraper entry data serializable in binary format. Stored in a + //! contract's value field as bytes. + //! + uint32_t m_version = CURRENT_VERSION; + + ScraperEntry m_scraper_entry; //!< The scraper entry in the payload. + + //! + //! \brief Initialize an empty, invalid scraper entry payload. + //! + ScraperEntryPayload(const uint32_t version = CURRENT_VERSION); + + //! + //! \brief Initialize a ScraperEntryPayload from a scraper entry constructed from + //! key_id and status + //! + //! \param version Version of the serialized scraper entry format. + //! \param key_id CKeyID of the scraper entry + //! \param status Status of the scraper entry + //! + ScraperEntryPayload(const uint32_t version, CKeyID key_id, ScraperEntryStatus status); + + //! + //! \brief Initialize a scraper entry payload from the given scraper entry + //! with the provided version number (and format). This can only be used for + //! payload version > 1. It will assert if used for payload version 1. + //! + //! \param version Version of the serialized scraper entry format. + //! \param scraper_entry The scraper entry itself. + //! + ScraperEntryPayload(const uint32_t version, ScraperEntry scraper_entry); + + //! + //! \brief Initialize a scraper entry payload from the given scraper entry + //! with the CURRENT_VERSION. + //! \param scraper_entry The scraper entry itself. + //! + ScraperEntryPayload(ScraperEntry scraper_entry); + + //! + //! \brief Initialize a scraper entry payload from legacy data. + //! + //! \param key + //! \param value + //! + ScraperEntryPayload(const std::string& key, const std::string& value); + + //! + //! \brief Initialize a scraper entry payload from the legacy (appcache) string format. + //! + //! \param key The address (NOT CKeyID) of the scraper (entry). + //! \param value The value of whether the scraper is authorized, "true" or "false". + //! + //! \return The resultant scraper entry payload. Note that there is no concept + //! of the "EXPLORER" status in the legacy parse, because it is introduced with + //! this class. + //! + static ScraperEntryPayload Parse(const std::string& key, const std::string& value); + + //! + //! \brief Get the type of contract that this payload contains data for. + //! + GRC::ContractType ContractType() const override + { + return GRC::ContractType::SCRAPER; + } + + //! + //! \brief Determine whether the instance represents a complete payload. + //! + //! \return \c true if the payload contains each of the required elements. + //! + bool WellFormed(const ContractAction action) const override + { + if (m_version <= 0 || m_version > CURRENT_VERSION) { + return false; + } + + // Upon contract receipt for version 1 payload, need to follow the rules for + // legacy payload. + if (m_version == 1) { + return !m_key.empty() && (action == GRC::ContractAction::REMOVE || !m_value.empty()); + } + + // Scraper Entry Payloads version 2+ follow full scraper entry validation rules + return m_scraper_entry.WellFormed(); + } + + //! + //! \brief Get a string for the key used to construct a legacy contract. + //! + std::string LegacyKeyString() const override + { + CBitcoinAddress address; + + address.Set(m_scraper_entry.m_key); + + return address.ToString(); + } + + //! + //! \brief Get a string for the value used to construct a legacy contract. + //! + std::string LegacyValueString() const override + { + if (m_scraper_entry.m_status <= ScraperEntryStatus::NOT_AUTHORIZED) { + return "false"; + } else { + return "true"; + } + } + + //! + //! \brief Get the burn fee amount required to send a particular contract. This + //! is the same as the LegacyPayload to insure compatibility between the scraper + //! registry and non-upgraded nodes before the block v13/contract version 3 height + //! + //! \return Burn fee in units of 1/100000000 GRC. + //! + CAmount RequiredBurnAmount() const override + { + return Contract::STANDARD_BURN_AMOUNT; + } + + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + // These will be filled in for legacy scraper entries, but will also be present as empties in + // native scraper records to solve the (de)serialization problem between legacy and native. + + READWRITE(m_key); + + if (contract_action != ContractAction::REMOVE) { + READWRITE(m_value); + + } + + if (m_key.empty()) { + READWRITE(m_version); + READWRITE(m_scraper_entry); + } else { + m_version = 1; + + } + } +}; // ScraperEntryPayload + +//! +//! \brief Stores and manages scraper entries. These represent scrapers known to the +//! network and their status. +//! +class ScraperRegistry : public IContractHandler +{ +public: + //! + //! \brief ScraperRegistry constructor. The parameter is the version number of the underlying + //! protocol entry db. This must be incremented when implementing format changes to the protocol + //! entries to force a reinit. + //! + //! Version 0: <= 5.4.2.0 (no backing db). + //! Version 1: TBD. + //! + ScraperRegistry() + : m_scraper_db(1) + { + }; + + //! + //! \brief The type that keys scraper entries by their CKeyID, which is equivalent + //! to the scraper's address. Note that the enties in this map are actually smart shared + //! pointer wrappers, so that the same actual object can be held by both this map + //! and the historical scraper map without object duplication. + //! + typedef std::map ScraperMap; + + //! + //! \brief PendingScraperMap. This is not actually used but defined to satisfy the template. + //! + typedef ScraperMap PendingScraperMap; + + //! + //! \brief The type that keys historical scraper entries by the contract hash (txid). + //! Note that the entries in this map are actually smart shared pointer wrappers, so that + //! the same actual object can be held by both this map and the (current) scraper map + //! without object duplication. + //! + typedef std::map HistoricalScraperMap; + + //! + //! \brief Get the collection of current scraper entries. Note that this INCLUDES deleted + //! scraper entries. + //! + //! \return \c A reference to the current scraper entries stored in the registry. + //! + const ScraperMap& Scrapers() const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. Only includes AUTHORIZED and EXPLORER scrapers, which are + //! both reported with a std::string status of "true". + //! + //! \return \c AppCacheEntrySection consisting of key (address string) and + //! { value, timestamp }. + //! + const AppCacheSection GetScrapersLegacy() const; + + //! + //! \brief A shim method to cross-wire this into the existing scraper code + //! for compatibility purposes until the scraper code can be upgraded to use the + //! native structures here. + //! + //! \param authorized_only Boolean that if true requires that results include scraper entries + //! with AUTHORIZED or EXPLORER status only. + //! + //! \return \c AppCacheEntrySectionExt consisting of key (address string) and + //! { value, timestamp, deleted }. + //! + const AppCacheSectionExt GetScrapersLegacyExt(const bool& authorized_only = false) const; + + //! + //! \brief Get the current scraper entry for the specified CKeyID key_id. + //! + //! \param key_id The CKeyID of the public key of the scraper (essentially the address). + //! + //! \return An object that either contains a reference to some scraper entry if it exists + //! for the key_id or does not. + //! + ScraperEntryOption Try(const CKeyID& key_id) const; + + //! + //! \brief Get the current scraper entry for the specified CKeyID key_id if it is in + //! status AUTHORIZED or EXPLORER. + //! + //! \param key_id The CKeyID of the public key of the scraper (essentially the address). + //! + //! \return An object that either contains a reference to some scraper entry if it exists + //! for the key_id and is in the required status or does not. + //! + ScraperEntryOption TryAuthorized(const CKeyID& key_id) const; + + //! + //! \brief Destroy the contract handler state in case of an error in loading + //! the scraper entry registry state from LevelDB to prepare for reload from contract + //! replay. This is not used for scraper entries, unless -clearscraperentryhistory is specified + //! as a startup argument, because contract replay storage and full reversion has + //! been implemented for scraper entries. + //! + void Reset() override; + + //! + //! \brief Determine whether a scraper entry contract is valid. + //! + //! \param contract Contains the scraper entry contract to validate. + //! \param tx Transaction that contains the contract. + //! \param DoS Misbehavior out. + //! + //! \return \c true if the contract contains a valid scraper entry. + //! + bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override; + + //! + //! \brief Determine whether a scraper entry contract is valid including block context. This is used + //! in ConnectBlock. Note that for scraper entries this simply calls Validate as there is no + //! block level specific validation to be done. + //! + //! \param ctx ContractContext containing the scraper entry data to validate. + //! \param DoS Misbehavior score out. + //! + //! \return \c false If the contract fails validation. + //! + bool BlockValidate(const ContractContext& ctx, int& DoS) const override; + + //! + //! \brief Add a scraper entry to the registry from contract data. For the scraper registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Add(const ContractContext& ctx) override; + + //! + //! \brief Mark a scraper entry deleted in the registry from contract data. For the scraper registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Delete(const ContractContext& ctx) override; + + //! + //! \brief Revert the registry state for the scraper entry to the state prior + //! to this ContractContext application. This is typically an issue + //! during reorganizations, where blocks are disconnected. + //! + //! \param ctx References the scraper entry contract and associated context. + //! + void Revert(const ContractContext& ctx) override; + + //! + //! \brief Initialize the ScraperRegistry, which now includes restoring the state of the ScraperRegistry from + //! LevelDB on wallet start. + //! + //! \return Block height of the database restored from LevelDB. Zero if no LevelDB scraper entry data is found or + //! there is some issue in LevelDB scraper entry retrieval. (This will cause the contract replay to change scope + //! and initialize the ScraperRegistry from contract replay and store in LevelDB.) + //! + int Initialize() override; + + //! + //! \brief Gets the block height through which is stored in the scraper entry registry database. + //! + //! \return block height. + //! + int GetDBHeight() override; + + //! + //! \brief Function normally only used after a series of reverts during block disconnects, because + //! block disconnects are done in groups back to a common ancestor, and will include a series of reverts. + //! This is essentially atomic, and therefore the final (common) height only needs to be set once. TODO: + //! reversion should be done with a vector argument of the contract contexts, along with a final height to + //! clean this up and move the logic to here from the calling function. + //! + //! \param height to set the storage DB bookmark. + //! + void SetDBHeight(int& height) override; + + //! + //! \brief Resets the maps in the ScraperRegistry but does not disturb the underlying LevelDB + //! storage. This is only used during testing in the testing harness. + //! + void ResetInMemoryOnly(); + + //! + //! \brief Passivates the elements in the scraper db, which means remove from memory elements in the + //! historical map that are not referenced by m_scrapers. The backing store of the element removed + //! from memory is retained and will be transparently restored if find() is called on the hash key + //! for the element. + //! + //! \return The number of elements passivated. + //! + uint64_t PassivateDB(); + + //! + //! \brief A static function that is called by the scheduler to run the scraper entry database passivation. + //! + static void RunDBPassivation(); + + //! + //! \brief Specializes the template RegistryDB for the ScraperEntry class. Note that std::set is + //! not actually used. + //! + typedef RegistryDB, + HistoricalScraperMap> ScraperEntryDB; + +private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + + //! + //! \brief Private helper method for the Add and Delete methods above. They both use identical code (with + //! different input statuses). + //! + //! \param ctx The contract context for the add or delete. + //! + void AddDelete(const ContractContext& ctx); + + ScraperMap m_scrapers; //!< Contains the current scraper entries, including entries marked DELETED. + PendingScraperMap m_pending_scrapers {}; //!< Not actually used for scrapers. To satisfy the template only. + + std::set m_expired_scraper_entries {}; //!< Not actually used for scrapers. To satisfy the template only. + + ScraperEntryDB m_scraper_db; + +public: + + ScraperEntryDB& GetScraperDB(); +}; // ScraperRegistry + +//! +//! \brief Get the global scraper entry registry. +//! +//! \return Current global scraper entry registry instance. +//! +ScraperRegistry& GetScraperRegistry(); +} // namespace GRC + +#endif // GRIDCOIN_SCRAPER_SCRAPER_REGISTRY_H diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp new file mode 100644 index 0000000000..f96bd840db --- /dev/null +++ b/src/gridcoin/sidestake.cpp @@ -0,0 +1,1159 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "sidestake.h" +#include "node/ui_interface.h" +#include "univalue.h" + +//! +//! \brief Model callback bound to the \c RwSettingsUpdated core signal. +//! +void RwSettingsUpdated(GRC::SideStakeRegistry* registry) +{ + LogPrint(BCLog::LogFlags::MISC, "INFO: %s: received RwSettingsUpdated() core signal", __func__); + + registry->LoadLocalSideStakesFromConfig(); +} + + +using namespace GRC; +using LogFlags = BCLog::LogFlags; + +namespace { +SideStakeRegistry g_sidestake_entries; +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Global Functions +// ----------------------------------------------------------------------------- + +SideStakeRegistry& GRC::GetSideStakeRegistry() +{ + return g_sidestake_entries; +} + +// ----------------------------------------------------------------------------- +// Class: Allocation +// ----------------------------------------------------------------------------- +Allocation::Allocation() + : Fraction() +{} + +Allocation::Allocation(const double& allocation) + : Fraction(static_cast(std::round(allocation * static_cast(10000.0))), static_cast(10000), true) +{} + +Allocation::Allocation(const Fraction& f) + : Fraction(f) +{} + +Allocation::Allocation(const int64_t& numerator, const int64_t& denominator) + : Fraction(numerator, denominator) +{} + +Allocation::Allocation(const int64_t& numerator, const int64_t& denominator, const bool& simplify) + : Fraction(numerator, denominator, simplify) +{} + +CAmount Allocation::ToCAmount() const +{ + return GetNumerator() / GetDenominator(); +} + +double Allocation::ToPercent() const +{ + return ToDouble() * 100.0; +} + +Allocation Allocation::operator+(const Allocation& rhs) const +{ + return static_cast(Fraction::operator+(rhs)); +} + +Allocation Allocation::operator+(const int64_t& rhs) const +{ + return static_cast(Fraction::operator+(rhs)); +} + +Allocation Allocation::operator-(const Allocation& rhs) const +{ + return static_cast(Fraction::operator-(rhs)); +} + +Allocation Allocation::operator-(const int64_t& rhs) const +{ + return static_cast(Fraction::operator-(rhs)); +} + +Allocation Allocation::operator*(const Allocation& rhs) const +{ + return static_cast(Fraction::operator*(rhs)); +} + +Allocation Allocation::operator*(const int64_t& rhs) const +{ + return static_cast(Fraction::operator*(rhs)); +} + +Allocation Allocation::operator/(const Allocation& rhs) const +{ + return static_cast(Fraction::operator/(rhs)); +} + +Allocation Allocation::operator/(const int64_t& rhs) const +{ + return static_cast(Fraction::operator/(rhs)); +} + +Allocation Allocation::operator+=(const Allocation& rhs) +{ + return static_cast(Fraction::operator+=(rhs)); +} + +Allocation Allocation::operator+=(const int64_t& rhs) +{ + return static_cast(Fraction::operator+=(rhs)); +} + +Allocation Allocation::operator-=(const Allocation& rhs) +{ + return static_cast(Fraction::operator-=(rhs)); +} + +Allocation Allocation::operator-=(const int64_t& rhs) +{ + return static_cast(Fraction::operator-=(rhs)); +} + +Allocation Allocation::operator*=(const Allocation& rhs) +{ + return static_cast(Fraction::operator*=(rhs)); +} + +Allocation Allocation::operator*=(const int64_t& rhs) +{ + return static_cast(Fraction::operator*=(rhs)); +} + +Allocation Allocation::operator/=(const Allocation& rhs) +{ + return static_cast(Fraction::operator/=(rhs)); +} + +Allocation Allocation::operator/=(const int64_t& rhs) +{ + return static_cast(Fraction::operator/=(rhs)); +} + +bool Allocation::operator==(const Allocation& rhs) const +{ + return Fraction::operator==(rhs); +} + +bool Allocation::operator!=(const Allocation& rhs) const +{ + return Fraction::operator!=(rhs); +} + +bool Allocation::operator<=(const Allocation& rhs) const +{ + return Fraction::operator<=(rhs); +} + +bool Allocation::operator>=(const Allocation& rhs) const +{ + return Fraction::operator>=(rhs); +} + +bool Allocation::operator<(const Allocation& rhs) const +{ + return Fraction::operator<(rhs); +} + +bool Allocation::operator>(const Allocation& rhs) const +{ + return Fraction::operator>(rhs); +} + +bool Allocation::operator==(const int64_t& rhs) const +{ + return Fraction::operator==(rhs); +} + +bool Allocation::operator!=(const int64_t& rhs) const +{ + return Fraction::operator!=(rhs); +} + +bool Allocation::operator<=(const int64_t& rhs) const +{ + return Fraction::operator<=(rhs); +} + +bool Allocation::operator>=(const int64_t& rhs) const +{ + return Fraction::operator>=(rhs); +} + +bool Allocation::operator<(const int64_t& rhs) const +{ + return Fraction::operator<(rhs); +} + +bool Allocation::operator>(const int64_t& rhs) const +{ + return Fraction::operator>(rhs); +} + +// ----------------------------------------------------------------------------- +// Class: LocalSideStake +// ----------------------------------------------------------------------------- +LocalSideStake::LocalSideStake() + : m_destination() + , m_allocation() + , m_description() + , m_status(LocalSideStakeStatus::UNKNOWN) +{} + +LocalSideStake::LocalSideStake(CTxDestination destination, Allocation allocation, std::string description) + : m_destination(destination) + , m_allocation(allocation) + , m_description(description) + , m_status(LocalSideStakeStatus::UNKNOWN) +{} + +LocalSideStake::LocalSideStake(CTxDestination destination, + Allocation allocation, + std::string description, + LocalSideStakeStatus status) + : m_destination(destination) + , m_allocation(allocation) + , m_description(description) + , m_status(status) +{} + +bool LocalSideStake::WellFormed() const +{ + return CBitcoinAddress(m_destination).IsValid() && m_allocation >= 0 && m_allocation <= 1; +} + +std::string LocalSideStake::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string LocalSideStake::StatusToString(const LocalSideStakeStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case LocalSideStakeStatus::UNKNOWN: return _("Unknown"); + case LocalSideStakeStatus::ACTIVE: return _("Active"); + case LocalSideStakeStatus::INACTIVE: return _("Inactive"); + case LocalSideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case LocalSideStakeStatus::UNKNOWN: return "Unknown"; + case LocalSideStakeStatus::ACTIVE: return "Active"; + case LocalSideStakeStatus::INACTIVE: return "Inactive"; + case LocalSideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +bool LocalSideStake::operator==(LocalSideStake b) +{ + bool result = true; + + result &= (m_destination == b.m_destination); + result &= (m_allocation == b.m_allocation); + result &= (m_description == b.m_description); + result &= (m_status == b.m_status); + + return result; +} + +bool LocalSideStake::operator!=(LocalSideStake b) +{ + return !(*this == b); +} + +// ----------------------------------------------------------------------------- +// Class: MandatorySideStake +// ----------------------------------------------------------------------------- +MandatorySideStake::MandatorySideStake() + : m_destination() + , m_allocation() + , m_description() + , m_timestamp(0) + , m_hash() + , m_previous_hash() + , m_status(MandatorySideStakeStatus::UNKNOWN) +{} + +MandatorySideStake::MandatorySideStake(CTxDestination destination, Allocation allocation, std::string description) + : m_destination(destination) + , m_allocation(allocation) + , m_description(description) + , m_timestamp(0) + , m_hash() + , m_previous_hash() + , m_status(MandatorySideStakeStatus::UNKNOWN) +{} + +MandatorySideStake::MandatorySideStake(CTxDestination destination, + Allocation allocation, + std::string description, + int64_t timestamp, + uint256 hash, + MandatorySideStakeStatus status) + : m_destination(destination) + , m_allocation(allocation) + , m_description(description) + , m_timestamp(timestamp) + , m_hash(hash) + , m_previous_hash() + , m_status(status) +{} + +bool MandatorySideStake::WellFormed() const +{ + return CBitcoinAddress(m_destination).IsValid() && m_allocation >= 0 && m_allocation <= 1; +} + +CTxDestination MandatorySideStake::Key() const +{ + return m_destination; +} + +std::pair MandatorySideStake::KeyValueToString() const +{ + return std::make_pair(CBitcoinAddress(m_destination).ToString(), StatusToString()); +} + +std::string MandatorySideStake::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string MandatorySideStake::StatusToString(const MandatorySideStakeStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case MandatorySideStakeStatus::UNKNOWN: return _("Unknown"); + case MandatorySideStakeStatus::DELETED: return _("Deleted"); + case MandatorySideStakeStatus::MANDATORY: return _("Mandatory"); + case MandatorySideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case MandatorySideStakeStatus::UNKNOWN: return "Unknown"; + case MandatorySideStakeStatus::DELETED: return "Deleted"; + case MandatorySideStakeStatus::MANDATORY: return "Mandatory"; + case MandatorySideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +bool MandatorySideStake::operator==(MandatorySideStake b) +{ + bool result = true; + + result &= (m_destination == b.m_destination); + result &= (m_allocation == b.m_allocation); + result &= (m_description == b.m_description); + result &= (m_timestamp == b.m_timestamp); + result &= (m_hash == b.m_hash); + result &= (m_previous_hash == b.m_previous_hash); + result &= (m_status == b.m_status); + + return result; +} + +bool MandatorySideStake::operator!=(MandatorySideStake b) +{ + return !(*this == b); +} + +// ----------------------------------------------------------------------------- +// Class: SideStake +// ----------------------------------------------------------------------------- +SideStake::SideStake() + : m_local_sidestake_ptr(nullptr) + , m_mandatory_sidestake_ptr(nullptr) + , m_type(Type::UNKNOWN) +{} + +SideStake::SideStake(LocalSideStake_ptr sidestake_ptr) + : m_local_sidestake_ptr(sidestake_ptr) + , m_mandatory_sidestake_ptr(nullptr) + , m_type(Type::LOCAL) +{} + +SideStake::SideStake(MandatorySideStake_ptr sidestake_ptr) + : m_local_sidestake_ptr(nullptr) + , m_mandatory_sidestake_ptr(sidestake_ptr) + , m_type(Type::MANDATORY) +{} + +bool SideStake::IsMandatory() const +{ + return (m_type == Type::MANDATORY) ? true : false; +} + +CTxDestination SideStake::GetDestination() const +{ + if (m_type == Type::MANDATORY && m_mandatory_sidestake_ptr != nullptr) { + return m_mandatory_sidestake_ptr->m_destination; + } else if (m_type == Type::LOCAL && m_local_sidestake_ptr != nullptr) { + return m_local_sidestake_ptr->m_destination; + } + + return CNoDestination(); +} + +Allocation SideStake::GetAllocation() const +{ + if (m_type == Type::MANDATORY && m_mandatory_sidestake_ptr != nullptr) { + return m_mandatory_sidestake_ptr->m_allocation; + } else if (m_type == Type::LOCAL && m_local_sidestake_ptr != nullptr) { + return m_local_sidestake_ptr->m_allocation; + } + + return Allocation(Fraction()); +} + +std::string SideStake::GetDescription() const +{ + if (m_type == Type::MANDATORY && m_mandatory_sidestake_ptr != nullptr) { + return m_mandatory_sidestake_ptr->m_description; + } else if (m_type == Type::LOCAL && m_local_sidestake_ptr != nullptr) { + return m_local_sidestake_ptr->m_description; + } + + return std::string {}; +} + +SideStake::Status SideStake::GetStatus() const +{ + // For trivial initializer case + if (m_mandatory_sidestake_ptr == nullptr && m_local_sidestake_ptr == nullptr) { + return {}; + } + + if (m_type == Type::MANDATORY && m_mandatory_sidestake_ptr != nullptr) { + return m_mandatory_sidestake_ptr->m_status; + } else if (m_type == Type::LOCAL && m_local_sidestake_ptr != nullptr) { + return m_local_sidestake_ptr->m_status; + } + + return {}; +} + +std::string SideStake::StatusToString() const +{ + // For trivial initializer case + if (m_mandatory_sidestake_ptr == nullptr && m_local_sidestake_ptr == nullptr) { + return {}; + } + + if (m_type == Type::MANDATORY && m_mandatory_sidestake_ptr != nullptr) { + return m_mandatory_sidestake_ptr->StatusToString(); + } else if (m_type == Type::LOCAL && m_local_sidestake_ptr != nullptr){ + return m_local_sidestake_ptr->StatusToString(); + } + + return std::string {}; +} + +// ----------------------------------------------------------------------------- +// Class: SideStakePayload +// ----------------------------------------------------------------------------- + +constexpr uint32_t SideStakePayload::CURRENT_VERSION; // For clang + +SideStakePayload::SideStakePayload(uint32_t version) + : IContractPayload() + , m_version(version) +{ +} + +SideStakePayload::SideStakePayload(const uint32_t version, + CTxDestination destination, + Allocation allocation, + std::string description, + MandatorySideStake::MandatorySideStakeStatus status) + : IContractPayload() + , m_version(version) + , m_entry(MandatorySideStake(destination, allocation, description, 0, uint256{}, status)) +{ +} + +SideStakePayload::SideStakePayload(const uint32_t version, MandatorySideStake entry) + : IContractPayload() + , m_version(version) + , m_entry(std::move(entry)) +{ +} + +SideStakePayload::SideStakePayload(MandatorySideStake entry) + : SideStakePayload(CURRENT_VERSION, std::move(entry)) +{ +} + +// ----------------------------------------------------------------------------- +// Class: SideStakeRegistry +// ----------------------------------------------------------------------------- +const std::vector SideStakeRegistry::SideStakeEntries() const +{ + std::vector sidestakes; + + LOCK(cs_lock); + + for (const auto& entry : m_mandatory_sidestake_entries) { + sidestakes.push_back(std::make_shared(entry.second)); + } + + for (const auto& entry : m_local_sidestake_entries) { + sidestakes.push_back(std::make_shared(entry.second)); + } + + return sidestakes; +} + +const std::vector SideStakeRegistry::ActiveSideStakeEntries(const SideStake::FilterFlag& filter, + const bool& include_zero_alloc) const +{ + std::vector sidestakes; + Allocation allocation_sum; + + // Note that LoadLocalSideStakesFromConfig is called upon a receipt of the core signal RwSettingsUpdated, which + // occurs immediately after the settings r-w file is updated. + + // The loops below prevent sidestakes from being added that cause a total allocation above 1.0 (100%). + + LOCK(cs_lock); + + if (filter & SideStake::FilterFlag::MANDATORY) { + for (const auto& entry : m_mandatory_sidestake_entries) + { + if (entry.second->m_status == MandatorySideStake::MandatorySideStakeStatus::MANDATORY + && allocation_sum + entry.second->m_allocation <= Params().GetConsensus().MaxMandatorySideStakeTotalAlloc) { + if ((include_zero_alloc && entry.second->m_allocation == 0) || entry.second->m_allocation > 0) { + sidestakes.push_back(std::make_shared(entry.second)); + allocation_sum += entry.second->m_allocation; + } + } + } + } + + if (filter & SideStake::FilterFlag::LOCAL) { + // Followed by local active sidestakes if sidestaking is enabled. Note that mandatory sidestaking cannot be disabled. + bool fEnableSideStaking = gArgs.GetBoolArg("-enablesidestaking"); + + if (fEnableSideStaking) { + LogPrint(BCLog::LogFlags::MINER, "INFO: %s: fEnableSideStaking = %u", __func__, fEnableSideStaking); + + for (const auto& entry : m_local_sidestake_entries) + { + if (entry.second->m_status == LocalSideStake::LocalSideStakeStatus::ACTIVE + && allocation_sum + entry.second->m_allocation <= 1) { + if ((include_zero_alloc && entry.second->m_allocation == 0) || entry.second->m_allocation > 0) { + sidestakes.push_back(std::make_shared(entry.second)); + allocation_sum += entry.second->m_allocation; + } + } + } + } + } + + return sidestakes; +} + +std::vector SideStakeRegistry::Try(const CTxDestination& key, const SideStake::FilterFlag& filter) const +{ + LOCK(cs_lock); + + std::vector result; + + if (filter & SideStake::FilterFlag::MANDATORY) { + const auto mandatory_entry = m_mandatory_sidestake_entries.find(key); + + if (mandatory_entry != m_mandatory_sidestake_entries.end()) { + result.push_back(std::make_shared(mandatory_entry->second)); + } + } + + if (filter & SideStake::FilterFlag::LOCAL) { + const auto local_entry = m_local_sidestake_entries.find(key); + + if (local_entry != m_local_sidestake_entries.end()) { + result.push_back(std::make_shared(local_entry->second)); + } + } + + return result; +} + +std::vector SideStakeRegistry::TryActive(const CTxDestination& key, const SideStake::FilterFlag& filter) const +{ + LOCK(cs_lock); + + std::vector result; + + for (const auto& iter : Try(key, filter)) { + if (iter->IsMandatory()) { + if (std::get(iter->GetStatus()) == MandatorySideStake::MandatorySideStakeStatus::MANDATORY) { + result.push_back(iter); + } + } else { + if (std::get(iter->GetStatus()) == LocalSideStake::LocalSideStakeStatus::ACTIVE) { + result.push_back(iter); + } + } + } + + return result; +} + +void SideStakeRegistry::Reset() +{ + LOCK(cs_lock); + + m_mandatory_sidestake_entries.clear(); + m_sidestake_db.clear(); +} + +void SideStakeRegistry::AddDelete(const ContractContext& ctx) +{ + // Poor man's mock. This is to prevent the tests from polluting the LevelDB database + int height = -1; + + if (ctx.m_pindex) + { + height = ctx.m_pindex->nHeight; + } + + SideStakePayload payload = ctx->CopyPayloadAs(); + + // Fill this in from the transaction context because these are not done during payload + // initialization. + payload.m_entry.m_hash = ctx.m_tx.GetHash(); + payload.m_entry.m_timestamp = ctx.m_tx.nTime; + + // Ensure status is DELETED if the contract action was REMOVE, regardless of what was actually + // specified. + if (ctx->m_action == ContractAction::REMOVE) { + payload.m_entry.m_status = MandatorySideStake::MandatorySideStakeStatus::DELETED; + } + + LOCK(cs_lock); + + auto sidestake_entry_pair_iter = m_mandatory_sidestake_entries.find(payload.m_entry.m_destination); + + MandatorySideStake_ptr current_sidestake_entry_ptr = nullptr; + + // Is there an existing SideStake entry in the map? + bool current_sidestake_entry_present = (sidestake_entry_pair_iter != m_mandatory_sidestake_entries.end()); + + // If so, then get a smart pointer to it. + if (current_sidestake_entry_present) { + current_sidestake_entry_ptr = sidestake_entry_pair_iter->second; + + // Set the payload m_entry's prev entry ctx hash = to the existing entry's hash. + payload.m_entry.m_previous_hash = current_sidestake_entry_ptr->m_hash; + } else { // Original entry for this SideStake entry key + payload.m_entry.m_previous_hash = uint256 {}; + } + + LogPrint(LogFlags::CONTRACT, "INFO: %s: SideStake entry add/delete: contract m_version = %u, payload " + "m_version = %u, address = %s, allocation = %f, m_timestamp = %" PRId64 ", " + "m_hash = %s, m_previous_hash = %s, m_status = %s", + __func__, + ctx->m_version, + payload.m_version, + CBitcoinAddress(payload.m_entry.m_destination).ToString(), + payload.m_entry.m_allocation.ToPercent(), + payload.m_entry.m_timestamp, + payload.m_entry.m_hash.ToString(), + payload.m_entry.m_previous_hash.ToString(), + payload.m_entry.StatusToString() + ); + + MandatorySideStake& historical = payload.m_entry; + + if (!m_sidestake_db.insert(ctx.m_tx.GetHash(), height, historical)) + { + LogPrint(LogFlags::CONTRACT, "INFO: %s: In recording of the SideStake entry for key %s, value %f, hash %s, " + "the SideStake entry db record already exists. This can be expected on a restart " + "of the wallet to ensure multiple contracts in the same block get stored/replayed.", + __func__, + CBitcoinAddress(historical.m_destination).ToString(), + historical.m_allocation.ToPercent(), + historical.m_hash.GetHex()); + } + + // Finally, insert the new SideStake entry (payload) smart pointer into the m_sidestake_entries map. + m_mandatory_sidestake_entries[payload.m_entry.m_destination] = m_sidestake_db.find(ctx.m_tx.GetHash())->second; + + return; +} + +void SideStakeRegistry::Add(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::NonContractAdd(const LocalSideStake& sidestake, const bool& save_to_file) +{ + LOCK(cs_lock); + + // Using this form of insert because we want the latest record with the same key to override any previous one. + m_local_sidestake_entries[sidestake.m_destination] = std::make_shared(sidestake); + + if (save_to_file) { + SaveLocalSideStakesToConfig(); + } +} + +void SideStakeRegistry::NonContractDelete(const CTxDestination& destination, const bool& save_to_file) +{ + LOCK(cs_lock); + + auto sidestake_entry_pair_iter = m_local_sidestake_entries.find(destination); + + if (sidestake_entry_pair_iter != m_local_sidestake_entries.end()) { + m_local_sidestake_entries.erase(sidestake_entry_pair_iter); + } + + if (save_to_file) { + SaveLocalSideStakesToConfig(); + } +} + +void SideStakeRegistry::Revert(const ContractContext& ctx) +{ + const auto payload = ctx->SharePayloadAs(); + + // For SideStake entries, both adds and removes will have records to revert in the m_sidestake_entries map, + // and also, if not the first entry for that SideStake key, will have a historical record to + // resurrect. + LOCK(cs_lock); + + auto entry_to_revert = m_mandatory_sidestake_entries.find(payload->m_entry.m_destination); + + if (entry_to_revert == m_mandatory_sidestake_entries.end()) { + error("%s: The SideStake entry for key %s to revert was not found in the SideStake entry map.", + __func__, + CBitcoinAddress(entry_to_revert->second->m_destination).ToString()); + + // If there is no record in the current m_sidestake_entries map, then there is nothing to do here. This + // should not occur. + return; + } + + // If this is not a null hash, then there will be a prior entry to resurrect. + CTxDestination key = entry_to_revert->second->m_destination; + uint256 resurrect_hash = entry_to_revert->second->m_previous_hash; + + // Revert the ADD or REMOVE action. Unlike the beacons, this is symmetric. + if (ctx->m_action == ContractAction::ADD || ctx->m_action == ContractAction::REMOVE) { + // Erase the record from m_sidestake_entries. + if (m_mandatory_sidestake_entries.erase(payload->m_entry.m_destination) == 0) { + error("%s: The SideStake entry to erase during a SideStake entry revert for key %s was not found.", + __func__, + CBitcoinAddress(key).ToString()); + // If the record to revert is not found in the m_sidestake_entries map, no point in continuing. + return; + } + + // Also erase the record from the db. + if (!m_sidestake_db.erase(ctx.m_tx.GetHash())) { + error("%s: The db entry to erase during a SideStake entry revert for key %s was not found.", + __func__, + CBitcoinAddress(key).ToString()); + + // Unlike the above we will keep going even if this record is not found, because it is identical to the + // m_sidestake_entries record above. This should not happen, because during contract adds and removes, + // entries are made simultaneously to the m_sidestake_entries and m_sidestake_db. + } + + if (resurrect_hash.IsNull()) { + return; + } + + auto resurrect_entry = m_sidestake_db.find(resurrect_hash); + + if (resurrect_entry == m_sidestake_db.end()) { + error("%s: The prior entry to resurrect during a SideStake entry ADD revert for key %s was not found.", + __func__, + CBitcoinAddress(key).ToString()); + return; + } + + // Resurrect the entry prior to the reverted one. It is safe to use the bracket form here, because of the protection + // of the logic above. There cannot be any entry in m_sidestake_entries with that key value left if we made it here. + m_mandatory_sidestake_entries[resurrect_entry->second->m_destination] = resurrect_entry->second; + } +} + +bool SideStakeRegistry::Validate(const Contract& contract, const CTransaction& tx, int &DoS) const +{ + const auto payload = contract.SharePayloadAs(); + + if (contract.m_version < 3) { + DoS = 25; + error("%s: Sidestake entries only valid in contract v3 and above", __func__); + return false; + } + + if (!payload->WellFormed(contract.m_action.Value())) { + DoS = 25; + error("%s: Malformed SideStake entry contract", __func__); + return false; + } + + Allocation allocation = payload->m_entry.m_allocation; + + // Contracts that would result in a total active mandatory sidestake allocation greater than the maximum allowed by consensus + // protocol must be rejected. Note that this is not a perfect validation, because there could be more than one sidestake + // contract transaction in the memory pool, and this is using already committed sidestake contracts (i.e. in blocks already + // accepted) as a basis. + if (GetMandatoryAllocationsTotal() + allocation > Params().GetConsensus().MaxMandatorySideStakeTotalAlloc) { + DoS = 25; + return false; + } + + return true; +} + +bool SideStakeRegistry::BlockValidate(const ContractContext& ctx, int& DoS) const +{ + return (IsV13Enabled(ctx.m_pindex->nHeight) && Validate(ctx.m_contract, ctx.m_tx, DoS)); +} + +int SideStakeRegistry::Initialize() +{ + LOCK(cs_lock); + + int height = m_sidestake_db.Initialize(m_mandatory_sidestake_entries, m_pending_sidestake_entries, m_expired_sidestake_entries); + + SubscribeToCoreSignals(); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_sidestake_db size after load: %u", __func__, m_sidestake_db.size()); + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_sidestake_entries size after load: %u", __func__, m_mandatory_sidestake_entries.size()); + + // Add the local sidestakes specified in the config file(s) to the local sidestakes map. + LoadLocalSideStakesFromConfig(); + + m_local_entry_already_saved_to_config = false; + + return height; +} + +void SideStakeRegistry::SetDBHeight(int& height) +{ + LOCK(cs_lock); + + m_sidestake_db.StoreDBHeight(height); +} + +int SideStakeRegistry::GetDBHeight() +{ + int height = 0; + + LOCK(cs_lock); + + m_sidestake_db.LoadDBHeight(height); + + return height; +} + +void SideStakeRegistry::ResetInMemoryOnly() +{ + LOCK(cs_lock); + + m_local_sidestake_entries.clear(); + m_mandatory_sidestake_entries.clear(); + m_sidestake_db.clear_in_memory_only(); +} + +uint64_t SideStakeRegistry::PassivateDB() +{ + LOCK(cs_lock); + + return m_sidestake_db.passivate_db(); +} + +void SideStakeRegistry::LoadLocalSideStakesFromConfig() +{ + // If the m_local_entry_already_saved_to_config is set, then SaveLocalSideStakeToConfig was just called, + // and we want to then ignore the update signal from the r-w file change that calls this function for + // that action (only) and then reset the flag to be responsive to any changes on the core r-w file side + // through changesettings, for example. + if (m_local_entry_already_saved_to_config) { + m_local_entry_already_saved_to_config = false; + + return; + } + + std::vector vLocalSideStakes; + std::vector> raw_vSideStakeAlloc; + Allocation sum_allocation; + + // Parse destinations and allocations. We don't need to worry about any that are rejected other than a warning + // message, because any unallocated rewards will go back into the coinstake output(s). + + // If -sidestakeaddresses and -sidestakeallocations is set in either the config file or the r-w settings file + // and the settings are not empty and they are the same size, this will take precedence over the multiple entry + // -sidestake format. Note that -descriptions is optional; however, if descriptions is used, the size must + // match the other two if present. + std::vector addresses; + std::vector allocations; + std::vector descriptions; + + ParseString(gArgs.GetArg("-sidestakeaddresses", ""), ',', addresses); + ParseString(gArgs.GetArg("-sidestakeallocations", ""), ',', allocations); + ParseString(gArgs.GetArg("-sidestakedescriptions", ""), ',', descriptions); + + bool new_format_valid = false; + + if (!addresses.empty()) { + if (addresses.size() != allocations.size() || (!descriptions.empty() && addresses.size() != descriptions.size())) { + LogPrintf("WARN: %s: Malformed new style sidestaking configuration entries. " + "Reverting to original format in read only gridcoinresearch.conf file.", + __func__); + } else { + new_format_valid = true; + + for (unsigned int i = 0; i < addresses.size(); ++i) + { + if (descriptions.empty()) { + raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], "")); + } else { + raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], descriptions[i])); + } + } + } + } + + if (new_format_valid == false && gArgs.GetArgs("-sidestake").size()) + { + for (auto const& sSubParam : gArgs.GetArgs("-sidestake")) + { + std::vector vSubParam; + + ParseString(sSubParam, ',', vSubParam); + if (vSubParam.size() < 2) + { + LogPrintf("WARN: %s: Incomplete SideStake Allocation specified. Skipping SideStake entry.", __func__); + continue; + } + + // Deal with optional description. + if (vSubParam.size() == 3) { + raw_vSideStakeAlloc.push_back(std::make_tuple(vSubParam[0], vSubParam[1], vSubParam[2])); + } else { + raw_vSideStakeAlloc.push_back(std::make_tuple(vSubParam[0], vSubParam[1], "")); + } + } + } + + // First, add the allocation already taken by mandatory sidestakes, because they must be allocated first. + sum_allocation += GetMandatoryAllocationsTotal(); + + LOCK(cs_lock); + + for (const auto& entry : raw_vSideStakeAlloc) + { + std::string sAddress = std::get<0>(entry); + std::string sAllocation = std::get<1>(entry); + std::string sDescription = std::get<2>(entry); + + CBitcoinAddress address(sAddress); + if (!address.IsValid()) + { + LogPrintf("WARN: %s: ignoring sidestake invalid address %s.", __func__, sAddress); + continue; + } + + double read_allocation = 0.0; + if (!ParseDouble(sAllocation, &read_allocation)) + { + LogPrintf("WARN: %s: Invalid allocation %s provided. Skipping allocation.", __func__, sAllocation); + continue; + } + + LogPrintf("INFO: %s: allocation = %f", __func__, read_allocation); + + //int64_t numerator = read_allocation * 100.0; + //Allocation allocation(Fraction(numerator, 10000, true)); + + Allocation allocation(read_allocation / 100.0); + + if (allocation < 0) + { + LogPrintf("WARN: %s: Negative allocation provided. Skipping allocation.", __func__); + continue; + } + + // The below will stop allocations if someone has made a mistake and the total adds up to more than 100%. + // Note this same check is also done in SplitCoinStakeOutput, but it needs to be done here for two reasons: + // 1. Early alertment in the debug log, rather than when the first kernel is found, and 2. When the UI is + // hooked up, the SideStakeAlloc vector will be filled in by other than reading the config file and will + // skip the above code. + sum_allocation += allocation; + if (sum_allocation > 1) + { + LogPrintf("WARN: %s: allocation percentage over 100 percent, ending sidestake allocations.", __func__); + break; + } + + LocalSideStake sidestake(address.Get(), + allocation, + sDescription, + LocalSideStake::LocalSideStakeStatus::ACTIVE); + + // This will add or update (replace) a non-contract entry in the registry for the local sidestake. + NonContractAdd(sidestake, false); + + // This is needed because we need to detect entries in the registry map that are no longer in the config file to mark + // them deleted. + vLocalSideStakes.push_back(sidestake); + + LogPrint(BCLog::LogFlags::MINER, "INFO: %s: SideStakeAlloc Address %s, Allocation %f", + __func__, sAddress, allocation.ToPercent()); + } + + for (auto& entry : m_local_sidestake_entries) + { + // Only look at active entries. The others are NA for this alignment. + if (entry.second->m_status == LocalSideStake::LocalSideStakeStatus::ACTIVE) { + auto iter = std::find(vLocalSideStakes.begin(), vLocalSideStakes.end(), *entry.second); + + if (iter == vLocalSideStakes.end()) { + // Entry in map is no longer found in config files, so mark map entry inactive. + + entry.second->m_status = LocalSideStake::LocalSideStakeStatus::INACTIVE; + } + } + } + + // If we get here and dSumAllocation is zero then the enablesidestaking flag was set, but no VALID distribution + // was provided in the config file, so warn in the debug log. + if (!sum_allocation) + LogPrintf("WARN: %s: enablesidestaking was set in config but nothing has been allocated for" + " distribution!", __func__); +} + +bool SideStakeRegistry::SaveLocalSideStakesToConfig() +{ + bool status = false; + + std::string addresses; + std::string allocations; + std::string descriptions; + + std::string separator; + + std::vector> settings; + + LOCK(cs_lock); + + unsigned int i = 0; + for (const auto& iter : m_local_sidestake_entries) { + if (i) { + separator = ","; + } + + addresses += separator + CBitcoinAddress(iter.second->m_destination).ToString(); + allocations += separator + ToString(iter.second->m_allocation.ToPercent()); + descriptions += separator + iter.second->m_description; + + ++i; + } + + settings.push_back(std::make_pair("sidestakeaddresses", addresses)); + settings.push_back(std::make_pair("sidestakeallocations", allocations)); + settings.push_back(std::make_pair("sidestakedescriptions", descriptions)); + + status = updateRwSettings(settings); + + m_local_entry_already_saved_to_config = true; + + return status; +} + +Allocation SideStakeRegistry::GetMandatoryAllocationsTotal() const +{ + std::vector sidestakes = ActiveSideStakeEntries(SideStake::FilterFlag::MANDATORY, false); + Allocation allocation_total; + + for (const auto& entry : sidestakes) { + allocation_total += entry->GetAllocation(); + } + + return allocation_total; +} + +void SideStakeRegistry::SubscribeToCoreSignals() +{ + uiInterface.RwSettingsUpdated_connect(std::bind(RwSettingsUpdated, this)); +} + +void SideStakeRegistry::UnsubscribeFromCoreSignals() +{ + // Disconnect signals from client (no-op currently) +} + +SideStakeRegistry::SideStakeDB &SideStakeRegistry::GetSideStakeDB() +{ + LOCK(cs_lock); + + return m_sidestake_db; +} + +// This is static and called by the scheduler. +void SideStakeRegistry::RunDBPassivation() +{ + TRY_LOCK(cs_main, locked_main); + + if (!locked_main) + { + return; + } + + SideStakeRegistry& SideStake_entries = GetSideStakeRegistry(); + + SideStake_entries.PassivateDB(); +} + +template<> const std::string SideStakeRegistry::SideStakeDB::KeyType() +{ + return std::string("SideStake"); +} + diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h new file mode 100644 index 0000000000..42613e938c --- /dev/null +++ b/src/gridcoin/sidestake.h @@ -0,0 +1,897 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_SIDESTAKE_H +#define GRIDCOIN_SIDESTAKE_H + +#include "base58.h" +#include "gridcoin/contract/handler.h" +#include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" +#include "gridcoin/support/enumbytes.h" +#include "serialize.h" +#include "logging.h" + +namespace GRC { + +//! +//! \brief The Allocation class extends the Fraction class to provide functionality useful for sidestake allocations. +//! +class Allocation : public Fraction +{ +public: + //! + //! \brief Default constructor. Creates a zero allocation fraction. + //! + Allocation(); + + //! + //! \brief Allocation constructor from a double input. This multiplies the double by 1000, rounds, casts to int64_t, + //! and then constructs Fraction(x, 1000, true), which essentially creates a fraction representative of the double + //! to the third decimal place. + //! + //! \param double allocation + //! + Allocation(const double& allocation); + + //! + //! \brief Initialize an allocation from a Fraction. This is primarily used for casting. Note that no attempt to + //! limit the denominator size or simplify the fraction is made. + //! + //! \param Fraction f + //! + Allocation(const Fraction& f); + + //! + //! \brief Initialize an allocation directly from specifying a numerator and denominator + //! \param numerator + //! \param denominator + //! + Allocation(const int64_t& numerator, const int64_t& denominator); + + //! + //! \brief Initialize an allocation directly from specifying a numerator and denominator, specifying the simplification + //! directive. + //! + //! \param numerator + //! \param denominator + //! \param simplify + //! + Allocation(const int64_t& numerator, const int64_t& denominator, const bool& simplify); + + //! + //! \brief Allocations extend the Fraction class and can also represent the result of the allocation constructed fraction + //! and the result of the multiplication of that fraction times the reward, which is in CAmount (i.e. int64_t). + //! + //! \return CAmount of the Fraction representation of the actual allocation. + //! + CAmount ToCAmount() const; + + //! + //! \brief Returns a double equivalent of the allocation fraction multiplied times 100. + //! + //! \return double percent representation of the allocation fraction. + //! + double ToPercent() const; + + Allocation operator+(const Allocation& rhs) const; + Allocation operator+(const int64_t& rhs) const; + Allocation operator-(const Allocation& rhs) const; + Allocation operator-(const int64_t& rhs) const; + Allocation operator*(const Allocation& rhs) const; + Allocation operator*(const int64_t& rhs) const; + Allocation operator/(const Allocation& rhs) const; + Allocation operator/(const int64_t& rhs) const; + Allocation operator+=(const Allocation& rhs); + Allocation operator+=(const int64_t& rhs); + Allocation operator-=(const Allocation& rhs); + Allocation operator-=(const int64_t& rhs); + Allocation operator*=(const Allocation& rhs); + Allocation operator*=(const int64_t& rhs); + Allocation operator/=(const Allocation& rhs); + Allocation operator/=(const int64_t& rhs); + bool operator==(const Allocation& rhs) const; + bool operator!=(const Allocation& rhs) const; + bool operator<=(const Allocation& rhs) const; + bool operator>=(const Allocation& rhs) const; + bool operator<(const Allocation& rhs) const; + bool operator>(const Allocation& rhs) const; + bool operator==(const int64_t& rhs) const; + bool operator!=(const int64_t& rhs) const; + bool operator<=(const int64_t& rhs) const; + bool operator>=(const int64_t& rhs) const; + bool operator<(const int64_t& rhs) const; + bool operator>(const int64_t& rhs) const; +}; + +//! +//! \brief The LocalSideStake class. This class formalizes the local sidestake, which is a directive to apportion +//! a percentage of the total stake value to a designated destination. This destination must be valid, but +//! may or may not be owned by the staker. This is the primary mechanism to do automatic "donations" to +//! defined network addresses. +//! +//! Local (voluntary) entries will be picked up from the config file(s) and will be managed dynamically based on the +//! initial load of the config file + the r-w file + any changes in any GUI implementation on top of this. +//! +class LocalSideStake +{ +public: + enum class LocalSideStakeStatus + { + UNKNOWN, + ACTIVE, //!< A user specified sidestake that is active + INACTIVE, //!< A user specified sidestake that is inactive + OUT_OF_BOUND + }; + + //! + //! \brief Wrapped Enumeration of sidestake entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + + CTxDestination m_destination; //!< The destination of the sidestake. + + Allocation m_allocation; //!< The allocation is a Fraction in the form x / 1000 where x is between 0 and 1000 inclusive. + + std::string m_description; //!< The description of the sidestake (optional) + + Status m_status; //!< The status of the sidestake. It is of type int instead of enum for serialization. + + + //! + //! \brief Initialize an empty, invalid sidestake instance. + //! + LocalSideStake(); + + //! + //! \brief Initialize a sidestake instance with the provided destination and allocation. This is used to construct a user + //! specified sidestake. + //! + //! \param destination + //! \param allocation + //! \param description (optional) + //! + LocalSideStake(CTxDestination destination, Allocation allocation, std::string description); + + //! + //! \brief Initialize a sidestake instance with the provided parameters. + //! + //! \param destination + //! \param allocation + //! \param description (optional) + //! \param status + //! + LocalSideStake(CTxDestination destination, Allocation allocation, std::string description, LocalSideStakeStatus status); + + //! + //! \brief Determine whether a sidestake contains each of the required elements. + //! \return true if the sidestake is well-formed. + //! + bool WellFormed() const; + + //! + //! \brief Returns the string representation of the current sidestake status + //! + //! \return Translated string representation of sidestake status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input sidestake status + //! + //! \param status. SideStake status + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return SideStake status string. + //! + std::string StatusToString(const LocalSideStakeStatus& status, const bool& translated = true) const; + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(LocalSideStake b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(LocalSideStake b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_destination); + READWRITE(m_allocation); + READWRITE(m_description); + READWRITE(m_status); + } +}; + +//! +//! \brief The type that defines a shared pointer to a local sidestake +//! +typedef std::shared_ptr LocalSideStake_ptr; + +//! +//! \brief The MandatorySideStake class. This class formalizes the mandatory sidestake, which is a directive to apportion +//! a percentage of the total stake value to a designated destination. This destination must be valid, but +//! may or may not be owned by the staker. This is the primary mechanism to do automatic "donations" to +//! defined network addresses. +//! +//! Mandatory entries will be picked up by contract handlers similar to other contract types (cf. protocol entries). +//! +class MandatorySideStake +{ +public: + enum class MandatorySideStakeStatus + { + UNKNOWN, + DELETED, //!< A mandatory sidestake that has been deleted by contract + MANDATORY, //!< An active mandatory sidetake by contract + OUT_OF_BOUND + }; + + //! + //! \brief Wrapped Enumeration of sidestake entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + + CTxDestination m_destination; //!< The destination of the sidestake. + + Allocation m_allocation; //!< The allocation is a Fraction in the form x / 1000 where x is between 0 and 1000 inclusive. + + std::string m_description; //!< The description of the sidestake (optional) + + int64_t m_timestamp; //!< Time of the sidestake contract transaction. + + uint256 m_hash; //!< The hash of the transaction that contains a mandatory sidestake. + + uint256 m_previous_hash; //!< The m_hash of the previous mandatory sidestake allocation with the same destination. + + Status m_status; //!< The status of the sidestake. It is of type EnumByte instead of enum for serialization. + + //! + //! \brief Initialize an empty, invalid sidestake instance. + //! + MandatorySideStake(); + + //! + //! \brief Initialize a sidestake instance with the provided destination and allocation. This is used to construct a user + //! specified sidestake. + //! + //! \param destination + //! \param allocation + //! \param description (optional) + //! + MandatorySideStake(CTxDestination destination, Allocation allocation, std::string description); + + //! + //! \brief Initialize a sidestake instance with the provided parameters. + //! + //! \param destination + //! \param allocation + //! \param description (optional) + //! \param status + //! + MandatorySideStake(CTxDestination destination, Allocation allocation, std::string description, MandatorySideStakeStatus status); + + //! + //! \brief Initialize a sidestake instance with the provided parameters. This form is normally used to construct a + //! mandatory sidestake from a contract. + //! + //! \param destination + //! \param allocation + //! \param description (optional) + //! \param timestamp + //! \param hash + //! \param status + //! + MandatorySideStake(CTxDestination destination, Allocation allocation, std::string description, int64_t timestamp, + uint256 hash, MandatorySideStakeStatus status); + + //! + //! \brief Determine whether a sidestake contains each of the required elements. + //! \return true if the sidestake is well-formed. + //! + bool WellFormed() const; + + //! + //! \brief This is the standardized method that returns the key value (in this case the destination) for the sidestake entry (for + //! the registry_db.h template.) + //! + //! \return CTxDestination key value for the sidestake entry + //! + CTxDestination Key() const; + + //! + //! \brief Provides the sidestake destination address and status as a pair of strings. + //! \return std::pair of strings + //! + std::pair KeyValueToString() const; + + //! + //! \brief Returns the string representation of the current sidestake status + //! + //! \return Translated string representation of sidestake status + //! + std::string StatusToString() const; + + //! + //! \brief Returns the translated or untranslated string of the input sidestake status + //! + //! \param status. SideStake status + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return SideStake status string. + //! + std::string StatusToString(const MandatorySideStakeStatus& status, const bool& translated = true) const; + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(MandatorySideStake b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(MandatorySideStake b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_destination); + READWRITE(m_allocation); + READWRITE(m_description); + READWRITE(m_timestamp); + READWRITE(m_hash); + READWRITE(m_previous_hash); + READWRITE(m_status); + } +}; + +//! +//! \brief The type that defines a shared pointer to a mandatory sidestake +//! +typedef std::shared_ptr MandatorySideStake_ptr; + +//! +//! \brief This is a facade that combines the mandatory and local sidestake classes into one for use in the registry +//! and the GUI code. +//! +class SideStake +{ +public: + enum class Type { + UNKNOWN, + LOCAL, + MANDATORY, + OUT_OF_BOUND + }; + + enum FilterFlag : uint8_t { + NONE = 0b00, + LOCAL = 0b01, + MANDATORY = 0b10, + ALL = 0b11, + }; + + //! + //! \brief A variant to hold the two different types of sidestake status enums. + //! + typedef std::variant Status; + + SideStake(); + + SideStake(LocalSideStake_ptr sidestake_ptr); + + SideStake(MandatorySideStake_ptr sidestake_ptr); + + //! + //! \brief IsMandatory returns true if the sidestake is mandatory + //! \return true or false + //! + bool IsMandatory() const; + + //! + //! \brief Gets the destination of the sidestake + //! \return CTxDestination of the sidestake + //! + CTxDestination GetDestination() const; + //! + //! \brief Gets the allocation of the sidestake + //! \return A Fraction representing the allocation fraction of the sidestake. + //! + Allocation GetAllocation() const; + //! + //! \brief Gets the description of the sidestake + //! \return The description string of the sidestake + //! + std::string GetDescription() const; + //! + //! \brief Gets a variant containing either the mandatory sidestake status or local sidestake status, whichever + //! is applicable. + //! \return std::variant of the applicable sidestake status + //! + Status GetStatus() const; + //! + //! \brief Gets the status string associated with the applicable sidestake status. + //! \return String of the applicable sidestake status + //! + std::string StatusToString() const; + +private: + //! + //! \brief m_local_sidestake_ptr that points to the local sidestake object if this sidestake object is local; + //! nullptr otherwise. + //! + LocalSideStake_ptr m_local_sidestake_ptr; + //! + //! \brief m_mandatory_sidestake_ptr that points to the mandatory sidestake object if this sidestake object is mandatory; + //! nullptr otherwise. + //! + MandatorySideStake_ptr m_mandatory_sidestake_ptr; + //! + //! \brief m_type holds the type of the sidestake, either mandatory or local. + //! + Type m_type; +}; + +//! +//! \brief The type that defines a shared pointer to a sidestake. This is the facade and in turn will point to either a +//! mandatory or local sidestake as applicable. +//! +typedef std::shared_ptr SideStake_ptr; + +//! +//! \brief The body of a sidestake entry contract. This payload does NOT support +//! legacy payload formatting, as this contract/payload type is introduced after +//! legacy payloads are retired. +//! +class SideStakePayload : public IContractPayload +{ +public: + //! + //! \brief Version number of the current format for a serialized sidestake entry. + //! + //! CONSENSUS: Increment this value when introducing a breaking change and + //! ensure that the serialization/deserialization routines also handle all + //! of the previous versions. + //! + static constexpr uint32_t CURRENT_VERSION = 1; + + //! + //! \brief Version number of the serialized sidestake entry format. + //! + //! Version 1: Initial version: + //! + uint32_t m_version = CURRENT_VERSION; + + MandatorySideStake m_entry; //!< The sidestake entry in the payload. + + //! + //! \brief Initialize an empty, invalid sidestake entry payload. + //! + SideStakePayload(uint32_t version = CURRENT_VERSION); + + //! + //! \brief Initialize a sidestakeEntryPayload from a sidestake destination, allocation, + //! description, and status. + //! + //! \param destination. Destination for the sidestake entry + //! \param allocation. Allocation for the sidestake entry + //! \param description. Description string for the sidstake entry + //! \param status. Status of the sidestake entry + //! + SideStakePayload(const uint32_t version, CTxDestination destination, Allocation allocation, + std::string description, MandatorySideStake::MandatorySideStakeStatus status); + + //! + //! \brief Initialize a sidestake entry payload from the given sidestake entry + //! with the provided version number (and format). + //! + //! \param version Version of the serialized sidestake entry format. + //! \param sidestake_entry The sidestake entry itself. + //! + SideStakePayload(const uint32_t version, MandatorySideStake sidestake_entry); + + //! + //! \brief Initialize a sidestake entry payload from the given sidestake entry + //! with the CURRENT_VERSION. + //! + //! \param sidestake_entry The sidestake entry itself. + //! + SideStakePayload(MandatorySideStake sidestake_entry); + + //! + //! \brief Get the type of contract that this payload contains data for. + //! + GRC::ContractType ContractType() const override + { + return GRC::ContractType::SIDESTAKE; + } + + //! + //! \brief Determine whether the instance represents a complete payload. + //! + //! \return \c true if the payload contains each of the required elements. + //! + bool WellFormed(const ContractAction action) const override + { + bool valid = !(m_version <= 0 || m_version > CURRENT_VERSION); + + if (!valid) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Payload is not well formed. " + "m_version = %u, CURRENT_VERSION = %u", + __func__, + m_version, + CURRENT_VERSION); + + return false; + } + + valid = m_entry.WellFormed(); + + if (!valid) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Sidestake entry is not well-formed. " + "m_entry.WellFormed = %u, m_entry.m_key = %s, m_entry.m_allocation = %f, " + "m_entry.StatusToString() = %s", + __func__, + valid, + CBitcoinAddress(m_entry.m_destination).ToString(), + m_entry.m_allocation.ToPercent(), + m_entry.StatusToString() + ); + + return false; + } + + return valid; + } + + //! + //! \brief Get a string for the key used to construct a legacy contract. + //! + std::string LegacyKeyString() const override + { + return CBitcoinAddress(m_entry.m_destination).ToString(); + } + + //! + //! \brief Get a string for the value used to construct a legacy contract. + //! + std::string LegacyValueString() const override + { + return ToString(m_entry.m_allocation.ToDouble()); + } + + //! + //! \brief Get the burn fee amount required to send a particular contract. This + //! is the same as the LegacyPayload to insure compatibility between the sidestake + //! registry and non-upgraded nodes before the block v13/contract version 3 height + //! + //! \return Burn fee in units of 1/100000000 GRC. + //! + CAmount RequiredBurnAmount() const override + { + return Contract::STANDARD_BURN_AMOUNT; + } + + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + READWRITE(m_version); + READWRITE(m_entry); + } +}; // SideStakePayload + +//! +//! \brief Stores and manages sidestake entries. Note that the mandatory sidestakes are stored in leveldb using +//! the registry db template. The local sidestakes are maintained in sync with the read-write gridcoinsettings.json file. +//! +class SideStakeRegistry : public IContractHandler +{ +public: + //! + //! \brief sidestakeRegistry constructor. The parameter is the version number of the underlying + //! sidestake entry db. This must be incremented when implementing format changes to the sidestake + //! entries to force a reinit. + //! + //! Version 1: 5.4.5.5+ + //! + SideStakeRegistry() + : m_sidestake_db(1) + { + }; + + //! + //! \brief The type that keys local sidestake entries by their destinations. + //! + typedef std::map LocalSideStakeMap; + + //! + //! \brief The type that keys mandatory sidestake entries by their destinations. Note that the entries + //! in this map are actually smart shared pointer wrappers, so that the same actual object + //! can be held by both this map and the historical map without object duplication. + //! + typedef std::map MandatorySideStakeMap; + + //! + //! \brief PendingSideStakeMap. This is not actually used but defined to satisfy the template. + //! + typedef MandatorySideStakeMap PendingSideStakeMap; + + //! + //! \brief The type that keys historical sidestake entries by the contract hash (txid). + //! Note that the entries in this map are actually smart shared pointer wrappers, so that + //! the same actual object can be held by both this map and the (current) sidestake entry map + //! without object duplication. + //! + typedef std::map HistoricalSideStakeMap; + + //! + //! \brief Get the collection of current sidestake entries. Note that this INCLUDES deleted + //! sidestake entries. + //! + //! \return \c A reference to the current sidestake entries stored in the registry. + //! + const std::vector SideStakeEntries() const; + + //! + //! \brief Get the collection of active sidestake entries. This is presented as a vector of + //! smart pointers to the relevant sidestake entries in the database. The entries included have + //! the status of active (for local sidestakes) and/or mandatory (for contract sidestakes). + //! Mandatory sidestakes come before local ones, and the method ensures that the mandatory sidestakes + //! returned do not total an allocation greater than MaxMandatorySideStakeTotalAlloc, and all of the + //! sidestakes combined do not total an allocation greater than 1.0. + //! + //! \param bitmask filter to return mandatory only, local only, or all + //! + //! \return A vector of smart pointers to sidestake entries. + //! + const std::vector ActiveSideStakeEntries(const SideStake::FilterFlag& filter, const bool& include_zero_alloc) const; + + //! + //! \brief Get the current sidestake entry for the specified destination. + //! + //! \param key The destination of the sidestake entry. + //! \param bitmask filter to try mandatory only, local only, or all + //! + //! \return A vector of smart pointers to entries matching the provided destination. Up to two elements + //! are returned, mandatory entry first, depending on the filter set. + //! + std::vector Try(const CTxDestination& key, const SideStake::FilterFlag& filter) const; + + //! + //! \brief Get the current sidestake entry for the specified destination if it has a status of ACTIVE or MANDATORY. + //! + //! \param key The destination of the sidestake entry. + //! \param bitmask filter to try mandatory only, local only, or all + //! + //! \return A vector of smart pointers to entries matching the provided destination that are in status of + //! MANDATORY or ACTIVE. Up to two elements are returned, mandatory entry first,, depending on the filter set. + //! + std::vector TryActive(const CTxDestination& key, const SideStake::FilterFlag& filter) const; + + //! + //! \brief Destroy the contract handler state in case of an error in loading + //! the sidestake entry registry state from LevelDB to prepare for reload from contract + //! replay. This is not used for sidestake entries, unless -clearsidestakehistory is specified + //! as a startup argument, because contract replay storage and full reversion has + //! been implemented for sidestake entries. + //! + void Reset() override; + + //! + //! \brief Determine whether a sidestake entry contract is valid. + //! + //! \param contract Contains the sidestake entry contract to validate. + //! \param tx Transaction that contains the contract. + //! \param DoS Misbehavior out. + //! + //! \return \c true if the contract contains a valid sidestake entry. + //! + bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override; + + //! + //! \brief Determine whether a sidestake entry contract is valid including block context. This is used + //! in ConnectBlock. Note that for sidestake entries this simply calls Validate as there is no + //! block level specific validation to be done at the current time. + //! + //! \param ctx ContractContext containing the sidestake entry data to validate. + //! \param DoS Misbehavior score out. + //! + //! \return \c false If the contract fails validation. + //! + bool BlockValidate(const ContractContext& ctx, int& DoS) const override; + + //! + //! \brief Add a sidestake entry to the registry from contract data. For the sidestake registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! + //! \param ctx + //! + void Add(const ContractContext& ctx) override; + + //! + //! \brief Mark a sidestake entry deleted in the registry from contract data. For the sidestake registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Delete(const ContractContext& ctx) override; + + //! + //! \brief Allows local (voluntary) sidestakes to be added to the in-memory local map and not persisted to + //! the registry db. + //! + //! \param SideStake object to add + //! \param bool save_to_file if true causes SaveLocalSideStakesToConfig() to be called. + //! + void NonContractAdd(const LocalSideStake& sidestake, const bool& save_to_file = true); + + //! + //! \brief Provides for deletion of local (voluntary) sidestakes from the in-memory local map that are not persisted + //! to the registry db. Deletion is by the map key (CTxDestination). + //! + //! \param destination + //! \param bool save_to_file if true causes SaveLocalSideStakesToConfig() to be called. + //! + void NonContractDelete(const CTxDestination& destination, const bool& save_to_file = true); + + //! + //! \brief Revert the registry state for the sidestake entry to the state prior + //! to this ContractContext application. This is typically used + //! during reorganizations, where blocks are disconnected. + //! + //! \param ctx References the sidestake entry contract and associated context. + //! + void Revert(const ContractContext& ctx) override; + + //! + //! \brief Initialize the sidestakeRegistry, which now includes restoring the state of the sidestakeRegistry from + //! LevelDB on wallet start. + //! + //! \return Block height of the database restored from LevelDB. Zero if no LevelDB sidestake entry data is found or + //! there is some issue in LevelDB sidestake entry retrieval. (This will cause the contract replay to change scope + //! and initialize the sidestakeRegistry from contract replay and store in LevelDB.) + //! + int Initialize() override; + + //! + //! \brief Gets the block height through which is stored in the sidestake entry registry database. + //! + //! \return block height. + //! + int GetDBHeight() override; + + //! + //! \brief Function normally only used after a series of reverts during block disconnects, because + //! block disconnects are done in groups back to a common ancestor, and will include a series of reverts. + //! This is essentially atomic, and therefore the final (common) height only needs to be set once. TODO: + //! reversion should be done with a vector argument of the contract contexts, along with a final height to + //! clean this up and move the logic to here from the calling function. + //! + //! \param height to set the storage DB bookmark. + //! + void SetDBHeight(int& height) override; + + //! + //! \brief Resets the maps in the sidestakeRegistry but does not disturb the underlying LevelDB + //! storage. This is only used during testing in the testing harness. + //! + void ResetInMemoryOnly(); + + //! + //! \brief Passivates the elements in the sidestake db, which means remove from memory elements in the + //! historical map that are not referenced by the active entry map. The backing store of the element removed + //! from memory is retained and will be transparently restored if find() is called on the hash key + //! for the element. + //! + //! \return The number of elements passivated. + //! + uint64_t PassivateDB(); + + //! + //! \brief This method parses the config file for local sidestakes. It is based on the original GetSideStakingStatusAndAlloc() + //! that was in miner.cpp prior to the implementation of the SideStake class. + //! + void LoadLocalSideStakesFromConfig(); + + //! + //! \brief A static function that is called by the scheduler to run the sidestake entry database passivation. + //! + static void RunDBPassivation(); + + //! + //! \brief Specializes the template RegistryDB for the SideStake class. Note that std::set + //! is not actually used. + //! + typedef RegistryDB, + HistoricalSideStakeMap> SideStakeDB; + +private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + + //! + //! \brief Private helper method for the Add and Delete methods above. They both use identical code (with + //! different input statuses). + //! + //! \param ctx The contract context for the add or delete. + //! + void AddDelete(const ContractContext& ctx); + + //! + //! \brief Private helper function for non-contract add and delete to align the config r-w file with + //! in memory local sidestake map. + //! + //! \return bool true if successful. + //! + bool SaveLocalSideStakesToConfig(); + + //! + //! \brief Provides the total allocation for all active mandatory sidestakes as a Fraction. + //! \return total active mandatory sidestake allocation as a Fraction. + //! + Allocation GetMandatoryAllocationsTotal() const; + + void SubscribeToCoreSignals(); + void UnsubscribeFromCoreSignals(); + + LocalSideStakeMap m_local_sidestake_entries; //!< Contains the local (non-contract) sidestake entries. + MandatorySideStakeMap m_mandatory_sidestake_entries; //!< Contains the mandatory sidestake entries, including DELETED. + PendingSideStakeMap m_pending_sidestake_entries {}; //!< Not used. Only to satisfy the template. + + std::set m_expired_sidestake_entries {}; //!< Not used. Only to satisfy the template. + + SideStakeDB m_sidestake_db; //!< The internal sidestake db object for leveldb persistence. + + bool m_local_entry_already_saved_to_config = false; //!< Flag to prevent reload on signal if individual entry saved already. + +public: + + SideStakeDB& GetSideStakeDB(); +}; // sidestakeRegistry + +//! +//! \brief Get the global sidestake entry registry. +//! +//! \return Current global sidestake entry registry instance. +//! +SideStakeRegistry& GetSideStakeRegistry(); +} // namespace GRC + +#endif // GRIDCOIN_SIDESTAKE_H diff --git a/src/gridcoin/staking/difficulty.cpp b/src/gridcoin/staking/difficulty.cpp index 5303197ca4..b2cb8f097a 100644 --- a/src/gridcoin/staking/difficulty.cpp +++ b/src/gridcoin/staking/difficulty.cpp @@ -4,7 +4,6 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "amount.h" -#include "bignum.h" #include "chainparams.h" #include "init.h" #include "gridcoin/staking/difficulty.h" @@ -21,7 +20,7 @@ using namespace GRC; namespace { constexpr int64_t TARGET_TIMESPAN = 16 * 60; // 16 mins in seconds -const CBigNum PROOF_OF_STAKE_LIMIT(ArithToUint256(~arith_uint256() >> 20)); +const arith_uint256 PROOF_OF_STAKE_LIMIT = ~arith_uint256() >> 20; // ppcoin: find last block index up to pindex const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) @@ -61,7 +60,7 @@ unsigned int GRC::GetNextTargetRequired(const CBlockIndex* pindexLast) // ppcoin: target change every block // ppcoin: retarget with exponential moving toward target spacing - CBigNum bnNew; + arith_uint256 bnNew; bnNew.SetCompact(pindexPrev->nBits); // Gridcoin - Reset Diff to 1 on 12-19-2014 (R Halford) - Diff sticking at diff --git a/src/gridcoin/staking/kernel.cpp b/src/gridcoin/staking/kernel.cpp index 1eb8e195ab..7f1952140d 100644 --- a/src/gridcoin/staking/kernel.cpp +++ b/src/gridcoin/staking/kernel.cpp @@ -12,6 +12,8 @@ #include "streams.h" #include "util.h" +#include + using namespace std; using namespace GRC; @@ -385,12 +387,28 @@ bool GRC::ReadStakedInput( CTxDB& txdb, const uint256 prevout_hash, CBlockHeader& out_header, - CTransaction& out_txprev) + CTransaction& out_txprev, + CBlockIndex* pindexPrev) { CTxIndex tx_index; // Get transaction index for the previous transaction if (!txdb.ReadTxIndex(prevout_hash, tx_index)) { + if (pindexPrev != nullptr) { + for (CBlockIndex* pindex = pindexPrev; pindex->pprev != nullptr; pindex = pindex->pprev) { + CBlock block; + ReadBlockFromDisk(block, pindex, Params().GetConsensus()); + + for (const auto& tx : block.vtx) { + if (tx.GetHash() == prevout_hash) { + error("Found tx %s in block %s", tx.GetHash().ToString(), pindex->GetBlockHash().ToString()); + out_txprev = tx; + out_header = pindex->GetBlockHeader(); + return true; + } + } + } + } // Previous transaction not in main chain, may occur during initial download return error("%s: tx index not found for input tx %s", __func__, prevout_hash.GetHex()); } @@ -429,7 +447,7 @@ bool GRC::CalculateLegacyV3HashProof( CTransaction input_tx; CBlockHeader input_block; - if (!ReadStakedInput(txdb, prevout.hash, input_block, input_tx)) { + if (!ReadStakedInput(txdb, prevout.hash, input_block, input_tx, nullptr)) { return coinstake.DoS(1, error("Read staked input failed.")); } @@ -443,7 +461,7 @@ bool GRC::CalculateLegacyV3HashProof( << coinstake.nTime << por_nonce; - out_hash_proof = CBigNum(out.GetHash()).getuint256(); + out_hash_proof = out.GetHash(); return true; } @@ -584,7 +602,7 @@ bool GRC::CheckProofOfStakeV8( CBlockHeader header; CTransaction txPrev; - if (!ReadStakedInput(txdb, prevout.hash, header, txPrev)) + if (!ReadStakedInput(txdb, prevout.hash, header, txPrev, pindexPrev)) return tx.DoS(1, error("%s: read staked input failed", __func__)); if (!VerifySignature(txPrev, tx, 0, 0)) @@ -613,13 +631,12 @@ bool GRC::CheckProofOfStakeV8( //Stake refactoring TomasBrod int64_t Weight = CalculateStakeWeightV8(txPrev, prevout.n); - CBigNum bnHashProof(hashProofOfStake); + arith_uint320 bnHashProof = arith_uint320(hashProofOfStake); // Base target - CBigNum bnTarget; - bnTarget.SetCompact(Block.nBits); + arith_uint320 bnTarget = arith_uint256().SetCompact(Block.nBits); // Weighted target - bnTarget *= Weight; + bnTarget *= arith_uint320(Weight); LogPrint(BCLog::LogFlags::VERBOSE, @@ -628,7 +645,7 @@ bool GRC::CheckProofOfStakeV8( " Trg %72s", generated_by_me?" Local,":"", (double)header.nTime, (double)txPrev.nTime, (double)tx.nTime, Block.nBits, (double)Weight, - CBigNum(hashProofOfStake).GetHex(), bnTarget.GetHex() + hashProofOfStake.GetHex(), bnTarget.GetHex() ); // Now check if proof-of-stake hash meets target protocol diff --git a/src/gridcoin/staking/kernel.h b/src/gridcoin/staking/kernel.h index 237ea7b4b7..380355daad 100644 --- a/src/gridcoin/staking/kernel.h +++ b/src/gridcoin/staking/kernel.h @@ -57,7 +57,8 @@ bool ReadStakedInput( CTxDB& txdb, const uint256 prevout_hash, CBlockHeader& out_header, - CTransaction& out_txprev); + CTransaction& out_txprev, + CBlockIndex* pindexPrev = nullptr); //! //! \brief Calculate the provided block's proof hash with the version 3 staking diff --git a/src/gridcoin/staking/reward.cpp b/src/gridcoin/staking/reward.cpp index 887604f57d..1055bed52a 100644 --- a/src/gridcoin/staking/reward.cpp +++ b/src/gridcoin/staking/reward.cpp @@ -4,6 +4,7 @@ #include "amount.h" #include "gridcoin/appcache.h" +#include "gridcoin/protocol.h" #include "gridcoin/staking/reward.h" #include "main.h" @@ -41,7 +42,7 @@ CAmount GRC::GetConstantBlockReward(const CBlockIndex* index) const CAmount MAX_CBR = DEFAULT_CBR * 2; CAmount reward = DEFAULT_CBR; - AppCacheEntry oCBReward = ReadCache(Section::PROTOCOL, "blockreward1"); + AppCacheEntry oCBReward = GetProtocolRegistry().GetProtocolEntryByKeyLegacy("blockreward1"); //TODO: refactor the expire checking to subroutine //Note: time constant is same as GetBeaconPublicKey diff --git a/src/gridcoin/staking/spam.h b/src/gridcoin/staking/spam.h index 41a27eaa1a..84918e8eeb 100644 --- a/src/gridcoin/staking/spam.h +++ b/src/gridcoin/staking/spam.h @@ -201,12 +201,11 @@ class SeenStakes // ...to produce a distribution for values of x with a minimal rate of // collision. // - using limit_t = std::numeric_limits; static const size_t w = sizeof(size_t) * 8; static const size_t M = std::log2(m_proofs_seen.size()); - static const size_t a = (GetRand(limit_t::max()) * 2) + 1; - static const size_t b = GetRand(std::pow(2, w - M) - 1); + static const size_t a = (GetRand() * 2) + 1; + static const size_t b = GetRand(std::pow(2, w - M) - 1); size_t x = 0; diff --git a/src/gridcoin/superblock.cpp b/src/gridcoin/superblock.cpp index 5e557a08c0..4b9e7c61d7 100644 --- a/src/gridcoin/superblock.cpp +++ b/src/gridcoin/superblock.cpp @@ -6,6 +6,7 @@ #include "compat/endian.h" #include "hash.h" #include "main.h" +#include #include "gridcoin/superblock.h" #include "gridcoin/support/xml.h" #include "node/blockstorage.h" @@ -13,7 +14,6 @@ #include "util.h" #include "util/reverse_iterator.h" -#include using namespace GRC; @@ -1083,7 +1083,7 @@ QuorumHash QuorumHash::Hash(const Superblock& superblock) } Md5Sum output; - MD5((const unsigned char*)input.data(), input.size(), output.data()); + GRC__MD5((const unsigned char*)input.data(), input.size(), output.data()); return QuorumHash(output); } diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 73929b9bc5..0f92700364 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -1228,14 +1228,15 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe return accrual; } - Beacon_ptr beacon_ptr = beacon; + Beacon_ptr beacon_ptr; // Walk back the entries in the historical beacon map linked by renewal prev tx hash until the first // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier // than here. - while (beacon_ptr->Renewed()) - { - beacon_ptr = beacons.GetBeaconDB().find(beacon_ptr->m_prev_beacon_hash)->second; + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon); + } catch (std::runtime_error& e) { + std::abort(); } const CBlockIndex* pindex_baseline = GRC::Tally::GetBaseline(); diff --git a/src/gridcoin/upgrade.cpp b/src/gridcoin/upgrade.cpp index 5402f03cdf..f52d08e1bf 100644 --- a/src/gridcoin/upgrade.cpp +++ b/src/gridcoin/upgrade.cpp @@ -3,6 +3,7 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "gridcoin/upgrade.h" +#include #include "util.h" #include "init.h" @@ -16,7 +17,6 @@ #include #include -#include using namespace GRC; @@ -32,12 +32,16 @@ Upgrade::Upgrade() void Upgrade::ScheduledUpdateCheck() { - std::string VersionResponse = ""; + std::string VersionResponse; + std::string change_log; + + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - CheckForLatestUpdate(VersionResponse); + CheckForLatestUpdate(VersionResponse, change_log, upgrade_type); } -bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dialog, bool snapshotrequest) +bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, std::string& change_log, Upgrade::UpgradeType& upgrade_type, + bool ui_dialog, bool snapshotrequest) { // If testnet skip this || If the user changes this to disable while wallet running just drop out of here now. // (Need a way to remove items from scheduler.) @@ -46,8 +50,8 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial Http VersionPull; - std::string GithubResponse = ""; - std::string VersionResponse = ""; + std::string GithubResponse; + std::string VersionResponse; // We receive the response and it's in a json reply UniValue Response(UniValue::VOBJ); @@ -64,15 +68,15 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial if (VersionResponse.empty()) { - LogPrintf("WARNING %s: No Response from GitHub", __func__); + LogPrintf("WARNING: %s: No Response from GitHub", __func__); return false; } - std::string GithubReleaseData = ""; - std::string GithubReleaseTypeData = ""; - std::string GithubReleaseBody = ""; - std::string GithubReleaseType = ""; + std::string GithubReleaseData; + std::string GithubReleaseTypeData; + std::string GithubReleaseBody; + std::string GithubReleaseType; try { @@ -95,14 +99,19 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial } GithubReleaseTypeData = ToLower(GithubReleaseTypeData); - if (GithubReleaseTypeData.find("leisure") != std::string::npos) - GithubReleaseType = _("leisure"); - else if (GithubReleaseTypeData.find("mandatory") != std::string::npos) + if (GithubReleaseTypeData.find("leisure") != std::string::npos) { + GithubReleaseType = _("leisure"); + upgrade_type = Upgrade::UpgradeType::Leisure; + } else if (GithubReleaseTypeData.find("mandatory") != std::string::npos) { GithubReleaseType = _("mandatory"); - - else + // This will be confirmed below by also checking the second position version. If not incremented, then it will + // be set to unknown. + upgrade_type = Upgrade::UpgradeType::Mandatory; + } else { GithubReleaseType = _("unknown"); + upgrade_type = Upgrade::UpgradeType::Unknown; + } // Parse version data std::vector GithubVersion; @@ -113,6 +122,7 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial LocalVersion.push_back(CLIENT_VERSION_MAJOR); LocalVersion.push_back(CLIENT_VERSION_MINOR); LocalVersion.push_back(CLIENT_VERSION_REVISION); + LocalVersion.push_back(CLIENT_VERSION_BUILD); if (GithubVersion.size() != 4) { @@ -123,60 +133,79 @@ bool Upgrade::CheckForLatestUpdate(std::string& client_message_out, bool ui_dial bool NewVersion = false; bool NewMandatory = false; + bool same_version = true; try { // Left to right version numbers. - // 3 numbers to check for production. - for (unsigned int x = 0; x < 3; x++) - { + // 4 numbers to check. + for (unsigned int x = 0; x <= 3; x++) { int github_version = 0; - if (!ParseInt32(GithubVersion[x], &github_version)) - { + if (!ParseInt32(GithubVersion[x], &github_version)) { throw std::invalid_argument("Failed to parse GitHub version from official GitHub project repo."); } - if (github_version > LocalVersion[x]) - { + if (github_version > LocalVersion[x]) { NewVersion = true; - if (x < 2) - { + same_version = false; + + if (x < 2 && upgrade_type == Upgrade::UpgradeType::Mandatory) { NewMandatory = true; + } else { + upgrade_type = Upgrade::UpgradeType::Unknown; } - break; + } else { + same_version &= (github_version == LocalVersion[x]); } } - } - catch (std::exception& ex) - { + } catch (std::exception& ex) { error("%s: Exception occurred checking client version against GitHub version (%s)", __func__, ToString(ex.what())); + upgrade_type = Upgrade::UpgradeType::Unknown; return false; } - if (!NewVersion) return NewVersion; - - // New version was found + // Populate client_message_out regardless of whether new version is found, because we are using this method for + // the version information button in the "About Gridcoin" dialog. client_message_out = _("Local version: ") + strprintf("%d.%d.%d.%d", CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD) + "\r\n"; client_message_out.append(_("GitHub version: ") + GithubReleaseData + "\r\n"); - client_message_out.append(_("This update is ") + GithubReleaseType + "\r\n\r\n"); - // For snapshot requests we will handle things differently after this point - if (snapshotrequest && NewMandatory) - return NewVersion; + if (NewVersion) { + client_message_out.append(_("This update is ") + GithubReleaseType + "\r\n\r\n"); + } else if (same_version) { + client_message_out.append(_("The latest release is ") + GithubReleaseType + "\r\n\r\n"); + client_message_out.append(_("You are running the latest release.") + "\n"); + } else { + client_message_out.append(_("The latest release is ") + GithubReleaseType + "\r\n\r\n"); + + // If not a new version available and the version is not the same, the only thing left is that we are running + // a version greater than the latest release version, so set the upgrade_type to Unsupported, which is used for a + // warning. + upgrade_type = Upgrade::UpgradeType::Unsupported; + client_message_out.append(_("WARNING: You are running a version that is higher than the latest release.") + "\n"); + } + + change_log = GithubReleaseBody; - if (NewMandatory) + if (!NewVersion) return false; + + // For snapshot requests we will only return true if there is a new mandatory version AND the snapshotrequest boolean + // is set true. This is because the snapshot request context is looking for the presence of a new mandatory to block + // the snapshot download before upgrading to the new mandatory if there is one. + if (snapshotrequest && NewMandatory) return true; + + if (NewMandatory) { client_message_out.append(_("WARNING: A mandatory release is available. Please upgrade as soon as possible.") + "\n"); + } - std::string ChangeLog = GithubReleaseBody; - - if (ui_dialog) - uiInterface.UpdateMessageBox(client_message_out, ChangeLog); + if (ui_dialog) { + uiInterface.UpdateMessageBox(client_message_out, static_cast(upgrade_type), change_log); + } - return NewVersion; + return true; } void Upgrade::SnapshotMain() @@ -188,8 +217,10 @@ void Upgrade::SnapshotMain() // Verify a mandatory release is not available before we continue to snapshot download. std::string VersionResponse = ""; + std::string change_log; + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - if (CheckForLatestUpdate(VersionResponse, false, true)) + if (CheckForLatestUpdate(VersionResponse, change_log, upgrade_type, false, true)) { std::cout << this->ResetBlockchainMessages(UpdateAvailable) << std::endl; std::cout << this->ResetBlockchainMessages(GithubResponse) << std::endl; @@ -428,13 +459,10 @@ void Upgrade::VerifySHA256SUM() return; } - unsigned char digest[SHA256_DIGEST_LENGTH]; - - SHA256_CTX ctx; - SHA256_Init(&ctx); + CSHA256 hasher; fs::path fileloc = GetDataDir() / "snapshot.zip"; - unsigned char *buffer[32768]; + uint8_t buffer[32768]; int bytesread = 0; CAutoFile file(fsbridge::fopen(fileloc, "rb"), SER_DISK, CLIENT_VERSION); @@ -453,15 +481,16 @@ void Upgrade::VerifySHA256SUM() unsigned int read_count = 0; while ((bytesread = fread(buffer, 1, sizeof(buffer), file.Get()))) { - SHA256_Update(&ctx, buffer, bytesread); + hasher.Write(buffer, bytesread); ++read_count; DownloadStatus.SetSHA256SUMProgress(read_count * 100 / total_reads); } - SHA256_Final(digest, &ctx); + uint8_t digest[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(digest); - const std::vector digest_vector(digest, digest + SHA256_DIGEST_LENGTH); + const std::vector digest_vector(digest, digest + CSHA256::OUTPUT_SIZE); std::string FileSHA256SUM = HexStr(digest_vector); diff --git a/src/gridcoin/upgrade.h b/src/gridcoin/upgrade.h index daa1efba8f..c4dac3fce9 100644 --- a/src/gridcoin/upgrade.h +++ b/src/gridcoin/upgrade.h @@ -125,6 +125,13 @@ class Upgrade GithubResponse }; + enum UpgradeType { + Unknown, + Leisure, + Mandatory, + Unsupported //! This is used for a running version that is greater than the current official release. + }; + //! //! \brief Scheduler call to CheckForLatestUpdate //! @@ -133,7 +140,8 @@ class Upgrade //! //! \brief Check for latest updates on GitHub. //! - static bool CheckForLatestUpdate(std::string& client_message_out, bool ui_dialog = true, bool snapshotrequest = false); + static bool CheckForLatestUpdate(std::string& client_message_out, std::string& change_log, UpgradeType& upgrade_type, + bool ui_dialog = true, bool snapshotrequest = false); //! //! \brief Function that will be threaded to download snapshot diff --git a/src/gridcoin/voting/builders.cpp b/src/gridcoin/voting/builders.cpp index bbba0bf1d6..529a883028 100644 --- a/src/gridcoin/voting/builders.cpp +++ b/src/gridcoin/voting/builders.cpp @@ -859,7 +859,10 @@ uint256 GRC::SendPollContract(PollBuilder builder) { LOCK2(cs_main, pwalletMain->cs_wallet); - result_pair = SendContract(builder.BuildContractTx(pwalletMain)); + + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + + result_pair = SendContract(builder.BuildContractTx(pwalletMain, contract_version)); } if (!result_pair.second.empty()) { @@ -875,7 +878,10 @@ uint256 GRC::SendVoteContract(VoteBuilder builder) { LOCK2(cs_main, pwalletMain->cs_wallet); - result_pair = SendContract(builder.BuildContractTx(pwalletMain)); + + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + + result_pair = SendContract(builder.BuildContractTx(pwalletMain, contract_version)); } if (!result_pair.second.empty()) { @@ -1014,7 +1020,7 @@ PollBuilder PollBuilder::SetTitle(std::string title) ToString(Poll::MAX_TITLE_SIZE))); } - m_poll->m_title = std::move(title); + m_poll->m_title = title; return std::move(*this); } @@ -1031,7 +1037,7 @@ PollBuilder PollBuilder::SetUrl(std::string url) ToString(Poll::MAX_URL_SIZE))); } - m_poll->m_url = std::move(url); + m_poll->m_url = url; return std::move(*this); } @@ -1044,7 +1050,7 @@ PollBuilder PollBuilder::SetQuestion(std::string question) ToString(Poll::MAX_QUESTION_SIZE))); } - m_poll->m_question = std::move(question); + m_poll->m_question = question; return std::move(*this); } @@ -1053,13 +1059,13 @@ PollBuilder PollBuilder::SetChoices(std::vector labels) { m_poll->m_choices = Poll::ChoiceList(); - return AddChoices(std::move(labels)); + return AddChoices(labels); } PollBuilder PollBuilder::AddChoices(std::vector labels) { for (auto& label : labels) { - *this = AddChoice(std::move(label)); + *this = AddChoice(label); } return std::move(*this); @@ -1090,7 +1096,7 @@ PollBuilder PollBuilder::AddChoice(std::string label) throw VotingError(strprintf(_("Duplicate poll choice: %s"), label)); } - m_poll->m_choices.Add(std::move(label)); + m_poll->m_choices.Add(label); return std::move(*this); } @@ -1099,20 +1105,20 @@ PollBuilder PollBuilder::SetAdditionalFields(std::vector { m_poll->m_additional_fields = Poll::AdditionalFieldList(); - return AddAdditionalFields(std::move(fields)); + return AddAdditionalFields(fields); } PollBuilder PollBuilder::SetAdditionalFields(Poll::AdditionalFieldList fields) { m_poll->m_additional_fields = Poll::AdditionalFieldList(); - return AddAdditionalFields(std::move(fields)); + return AddAdditionalFields(fields); } PollBuilder PollBuilder::AddAdditionalFields(std::vector fields) { for (auto& field : fields) { - *this = AddAdditionalField(std::move(field)); + *this = AddAdditionalField(field); } if (!m_poll->m_additional_fields.WellFormed(m_poll->m_type.Value())) { @@ -1125,7 +1131,7 @@ PollBuilder PollBuilder::AddAdditionalFields(std::vector PollBuilder PollBuilder::AddAdditionalFields(Poll::AdditionalFieldList fields) { for (auto& field : fields) { - *this = AddAdditionalField(std::move(field)); + *this = AddAdditionalField(field); } if (!m_poll->m_additional_fields.WellFormed(m_poll->m_type.Value())) { @@ -1152,31 +1158,31 @@ PollBuilder PollBuilder::AddAdditionalField(Poll::AdditionalField field) ToString(POLL_MAX_ADDITIONAL_FIELDS_SIZE))); } - if (field.m_name.size() > Poll::AdditionalField::MAX_N_OR_V_SIZE) { + if (field.m_name.size() > Poll::AdditionalField::MAX_NAME_SIZE) { throw VotingError(strprintf( _("Poll additional field name \"%s\" exceeds %s characters."), field.m_name, - ToString(Poll::AdditionalField::MAX_N_OR_V_SIZE))); + ToString(Poll::AdditionalField::MAX_NAME_SIZE))); } - if (field.m_value.size() > Poll::AdditionalField::MAX_N_OR_V_SIZE) { + if (field.m_value.size() > Poll::AdditionalField::MAX_VALUE_SIZE) { throw VotingError(strprintf( _("Poll additional field value \"%s\" for field name \"%s\" exceeds %s characters."), field.m_value, field.m_name, - ToString(Poll::AdditionalField::MAX_N_OR_V_SIZE))); + ToString(Poll::AdditionalField::MAX_VALUE_SIZE))); } if (m_poll->m_additional_fields.FieldExists(field.m_name)) { throw VotingError(strprintf(_("Duplicate poll additional field: %s"), field.m_name)); } - m_poll->m_additional_fields.Add(std::move(field)); + m_poll->m_additional_fields.Add(field); return std::move(*this); } -CWalletTx PollBuilder::BuildContractTx(CWallet* const pwallet) +CWalletTx PollBuilder::BuildContractTx(CWallet* const pwallet, const uint32_t& contract_version) { if (!pwallet) { throw VotingError(_("No wallet available.")); @@ -1208,6 +1214,7 @@ CWalletTx PollBuilder::BuildContractTx(CWallet* const pwallet) PollEligibilityClaim claim = claim_builder.BuildClaim(*m_poll); tx.vContracts.emplace_back(MakeContract( + contract_version, ContractAction::ADD, std::move(m_poll_payload_version), std::move(*m_poll), @@ -1346,7 +1353,7 @@ VoteBuilder VoteBuilder::AddResponse(const std::string& label) throw VotingError(strprintf(_("\"%s\" is not a valid poll choice."), label)); } -CWalletTx VoteBuilder::BuildContractTx(CWallet* const pwallet) +CWalletTx VoteBuilder::BuildContractTx(CWallet* const pwallet, const uint32_t& contract_version) { if (!pwallet) { throw VotingError(_("No wallet available.")); @@ -1362,7 +1369,7 @@ CWalletTx VoteBuilder::BuildContractTx(CWallet* const pwallet) claim_builder.BuildClaim(*m_vote, *m_poll); tx.vContracts.emplace_back( - MakeContract(ContractAction::ADD, std::move(*m_vote))); + MakeContract(contract_version, ContractAction::ADD, std::move(*m_vote))); SelectFinalInputs(*pwallet, tx); Vote& vote = tx.vContracts.back().SharePayload().As(); diff --git a/src/gridcoin/voting/builders.h b/src/gridcoin/voting/builders.h index ae5b7cf24f..be5f90d7dc 100644 --- a/src/gridcoin/voting/builders.h +++ b/src/gridcoin/voting/builders.h @@ -220,12 +220,13 @@ class PollBuilder //! \brief Generate a poll contract transaction with the constructed poll. //! //! \param pwallet Points to a wallet instance to generate the claim from. + //! \param contract_version the contract version number to use. //! //! \return A new transaction that contains the poll contract. //! //! \throws VotingError If the constructed poll is malformed. //! - CWalletTx BuildContractTx(CWallet* const pwallet); + CWalletTx BuildContractTx(CWallet* const pwallet, const uint32_t& contract_version); private: std::unique_ptr m_poll; //!< The poll under construction. @@ -337,12 +338,13 @@ class VoteBuilder //! \brief Generate a vote contract transaction with the constructed vote. //! //! \param pwallet Points to a wallet instance to generate the claim from. + //! \param contract_version the contract version number to use. //! //! \return A new transaction that contains the vote contract. //! //! \throws VotingError If the constructed vote is malformed. //! - CWalletTx BuildContractTx(CWallet* const pwallet); + CWalletTx BuildContractTx(CWallet* const pwallet, const uint32_t& contract_version); private: const Poll* m_poll; //!< The poll to create a vote contract for. diff --git a/src/gridcoin/voting/poll.h b/src/gridcoin/voting/poll.h index d05cea18a6..1decaae541 100644 --- a/src/gridcoin/voting/poll.h +++ b/src/gridcoin/voting/poll.h @@ -203,9 +203,15 @@ class Poll { public: //! - //! \brief The maximum length for a poll additional field name or value. + //! \brief The maximum length for a poll additional field name. //! - static constexpr size_t MAX_N_OR_V_SIZE = 100; + static constexpr size_t MAX_NAME_SIZE = 100; + + //! + //! \brief The maximum length for a poll additional field value. This is currently set to align with the + //! maximum Project URL length. + //! + static constexpr size_t MAX_VALUE_SIZE = 500; std::string m_name; std::string m_value; @@ -244,8 +250,8 @@ class Poll template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(LIMITED_STRING(m_name, MAX_N_OR_V_SIZE)); - READWRITE(LIMITED_STRING(m_value, MAX_N_OR_V_SIZE)); + READWRITE(LIMITED_STRING(m_name, MAX_NAME_SIZE)); + READWRITE(LIMITED_STRING(m_value, MAX_VALUE_SIZE)); READWRITE(m_required); } }; // AdditionalField diff --git a/src/gridcoin/voting/registry.cpp b/src/gridcoin/voting/registry.cpp index 6b2d1b8213..5c016d8a2d 100644 --- a/src/gridcoin/voting/registry.cpp +++ b/src/gridcoin/voting/registry.cpp @@ -734,7 +734,15 @@ const PollRegistry::Sequence PollRegistry::Polls() const { LOCK(GetPollRegistry().cs_poll_registry); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-reference" +#endif return Sequence(m_polls); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + } const PollReference* PollRegistry::TryLatestActive() const EXCLUSIVE_LOCKS_REQUIRED(PollRegistry::cs_poll_registry) @@ -1042,6 +1050,9 @@ void PollRegistry::AddVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED( *poll_ref->m_ptitle, poll_ref->Votes().size()); + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } @@ -1064,6 +1075,10 @@ void PollRegistry::AddVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED( return; } poll_ref->LinkVote(ctx.m_tx.GetHash()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } @@ -1071,6 +1086,8 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR { const auto payload = ctx->SharePayloadAs(); + int64_t poll_time = payload->m_poll.m_timestamp; + m_polls.erase(ToLower(payload->m_poll.m_title)); m_polls_by_txid.erase(ctx.m_tx.GetHash()); @@ -1081,6 +1098,10 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR payload->m_poll.m_title, m_polls.size()); + if (fQtActive) { + uiInterface.NewPollReceived(poll_time);; + } + } void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED(cs_main, PollRegistry::cs_poll_registry) @@ -1096,6 +1117,10 @@ void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR ctx.m_tx.GetHash().GetHex(), *poll_ref->m_ptitle, poll_ref->Votes().size()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } return; @@ -1110,6 +1135,10 @@ void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR if (PollReference* poll_ref = TryBy(title)) { poll_ref->UnlinkVote(ctx.m_tx.GetHash()); + + if (fQtActive && !poll_ref->Expired(GetAdjustedTime())) { + uiInterface.NewVoteReceived(poll_ref->Txid()); + } } } diff --git a/src/gridcoin/voting/result.cpp b/src/gridcoin/voting/result.cpp index 1f5c5cb0d5..962cf21303 100644 --- a/src/gridcoin/voting/result.cpp +++ b/src/gridcoin/voting/result.cpp @@ -109,6 +109,11 @@ class VoteCandidate { LOCK(pwalletMain->cs_wallet); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: vote contract tx hash %s ismine status %i", + __func__, + m_tx.GetHash().GetHex(), + pwalletMain->IsMine(m_tx)); + return pwalletMain->IsMine(m_tx); } @@ -864,18 +869,28 @@ class VoteCounter { CTransaction tx; - if (!m_txdb.ReadDiskTx(txid, tx)) { - LogPrint(LogFlags::VOTE, "%s: failed to read vote tx", __func__); - throw InvalidVoteError(); + { + // This lock is taken here to ensure that we wait on the leveldb batch write ("transaction commit") to finish + // in ReorganizeChain (which is essentially the ConnectBlock scope) and ensure that the voting transactions + // which correspond to the new vote signals sent from the contract handlers are actually present in leveldb when + // the below ReadDiskTx is called. + LOCK(cs_tx_val_commit_to_disk); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: cs_tx_val_commit_to_disk locked", __func__); + + if (!m_txdb.ReadDiskTx(txid, tx)) { + LogPrintf("WARN: %s: failed to read vote tx.", __func__); + } + + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: cs_tx_val_commit_to_disk unlocked", __func__); } if (tx.nTime < m_poll.m_timestamp) { - LogPrint(LogFlags::VOTE, "%s: tx earlier than poll", __func__); + LogPrintf("WARN: %s: tx earlier than poll", __func__); throw InvalidVoteError(); } if (m_poll.Expired(tx.nTime)) { - LogPrint(LogFlags::VOTE, "%s: tx exceeds expiration", __func__); + LogPrintf("WARN: %s: tx exceeds expiration", __func__); throw InvalidVoteError(); } @@ -885,7 +900,7 @@ class VoteCounter } if (!contract.WellFormed()) { - LogPrint(LogFlags::VOTE, "%s: skipped bad contract", __func__); + LogPrintf("WARN: %s: skipped bad contract", __func__); continue; } @@ -920,7 +935,14 @@ class VoteCounter throw InvalidVoteError(); } +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" +#endif ProcessLegacyVote(candidate.LegacyVote()); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif } //! @@ -1221,7 +1243,11 @@ void PollResult::TallyVote(VoteDetail detail) if (detail.m_ismine != ISMINE_NO) { m_self_voted = true; - m_self_vote_detail = detail; + + m_self_vote_detail.m_amount += detail.m_amount; + m_self_vote_detail.m_mining_id = detail.m_mining_id; + m_self_vote_detail.m_magnitude = detail.m_magnitude; + m_self_vote_detail.m_ismine = detail.m_ismine; } for (const auto& response_pair : detail.m_responses) { @@ -1231,6 +1257,24 @@ void PollResult::TallyVote(VoteDetail detail) m_responses[response_offset].m_weight += response_weight; m_responses[response_offset].m_votes += 1.0 / detail.m_responses.size(); m_total_weight += response_weight; + + if (detail.m_ismine != ISMINE_NO) { + bool choice_found = false; + + // If the response offset entry already exists, then append to the existing entry... + for (auto& choice : m_self_vote_detail.m_responses) { + if (choice.first == response_offset) { + choice.second += response_weight; + choice_found = true; + break; + } + } + + // Otherwise make a new m_responses entry to represent the response offset and the associated weight. + if (!choice_found) { + m_self_vote_detail.m_responses.push_back(std::make_pair(response_offset, response_weight)); + } + } } m_votes.emplace_back(std::move(detail)); @@ -1252,6 +1296,15 @@ VoteDetail::VoteDetail() : m_amount(0), m_magnitude(Magnitude::Zero()), m_ismine { } +VoteDetail::VoteDetail(const VoteDetail &original_votedetail) + : m_amount(original_votedetail.m_amount) + , m_mining_id(original_votedetail.m_mining_id) + , m_magnitude(original_votedetail.m_magnitude) + , m_ismine(original_votedetail.m_ismine) + , m_responses(original_votedetail.m_responses) +{ +} + bool VoteDetail::Empty() const { return m_amount == 0 && m_magnitude == 0; diff --git a/src/gridcoin/voting/result.h b/src/gridcoin/voting/result.h index 758a7bbcf9..5ca0cda601 100644 --- a/src/gridcoin/voting/result.h +++ b/src/gridcoin/voting/result.h @@ -71,6 +71,13 @@ class PollResult //! VoteDetail(); + //! + //! \brief User copy constructor. + //! + //! \param original_votedetail + //! + VoteDetail(const VoteDetail& original_votedetail); + //! //! \brief Determine whether a vote contributes no weight. //! diff --git a/src/init.cpp b/src/init.cpp index a6f786963d..5cb0bff00c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -19,6 +19,7 @@ #include "scheduler.h" #include "gridcoin/gridcoin.h" #include "gridcoin/upgrade.h" +#include "gridcoin/contract/registry.h" #include "miner.h" #include "node/blockstorage.h" #include @@ -57,8 +58,6 @@ extern constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; std::unique_ptr g_banman; -static std::unique_ptr globalVerifyHandle; - /** * The PID file facilities. */ @@ -89,6 +88,19 @@ static fs::path GetPidFile(const ArgsManager& args) // Shutdown // +#if HAVE_SYSTEM +static void ShutdownNotify(const ArgsManager& args) +{ + std::vector threads; + for (const auto& cmd : args.GetArgs("-shutdownnotify")) { + threads.emplace_back(runCommand, cmd); + } + for (auto& t : threads) { + t.join(); + } +} +#endif + bool ShutdownRequested() { return fRequestShutdown; @@ -128,6 +140,11 @@ void Shutdown(void* parg) if (fFirstThread) { LogPrintf("gridcoinresearch exiting..."); + + #if HAVE_SYSTEM + ShutdownNotify(gArgs); + #endif + fShutdown = true; // Signal to the scheduler to stop. @@ -166,7 +183,6 @@ void Shutdown(void* parg) // This causes issues on daemons where it tries to create a second // lock file. //CTxDB().Close(); - globalVerifyHandle.reset(); ECC_Stop(); UninterruptibleSleep(std::chrono::milliseconds{50}); LogPrintf("Gridcoin exited"); @@ -387,8 +403,11 @@ void SetupServerArgs() " prefixed by datadir location. (default: %s)", GRIDCOIN_CONF_FILENAME, GRIDCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - //TODO: Implement startupnotify option - //argsman.AddArg("-startupnotify=", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-startupnotify=", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-shutdownnotify=", "Execute command immediately before beginning shutdown. The need for shutdown may be urgent," + " so be careful not to delay it long (if the command doesn't require interaction with the" + " server, consider having it fork into the background).", + ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); // Staking @@ -414,6 +433,13 @@ void SetupServerArgs() "if -enablesidestaking is set. If set along with -sidestakeaddresses " "overrides the -sidestake entries.", ArgsManager::ALLOW_ANY | ArgsManager::IMMEDIATE_EFFECT, OptionsCategory::STAKING); + argsman.AddArg("-sidestakedescriptions=string1,string2,...,stringN>", "Sidestake entry description. There can be as many " + "specified as desired. Only six per stake can be sent. " + "If more than six are specified. Six are randomly chosen " + "for each stake. Only active if -enablesidestaking is set. " + "If set along with -sidestakeaddresses overrides the " + "-sidestake entries.", + ArgsManager::ALLOW_ANY | ArgsManager::IMMEDIATE_EFFECT, OptionsCategory::STAKING); argsman.AddArg("-enablestakesplit", "Enable unspent output spitting when staking to optimize staking efficiency " "(default: 0", ArgsManager::ALLOW_ANY | ArgsManager::IMMEDIATE_EFFECT, OptionsCategory::STAKING); @@ -592,11 +618,21 @@ void SetupServerArgs() hidden_args.emplace_back("-daemonwait"); #endif + // Temporary hidden option for block v13 height override to facilitate testing. + hidden_args.emplace_back("-blockv13height"); + // Additional hidden options hidden_args.emplace_back("-devbuild"); hidden_args.emplace_back("-scrapersleep"); hidden_args.emplace_back("-activebeforesb"); - hidden_args.emplace_back("-clearbeaconhistory"); + + // This puts hidden options in the form of -clearhistory, where is the contract types that have a + // registry with a backing db. This is currently beacon, project, protocol, and scraper. + for (const auto& contract_type : GRC::RegistryBookmarks::CONTRACT_TYPES_WITH_REG_DB) { + std::string history_arg = "-clear" + GRC::Contract::Type::ToString(contract_type) + "history"; + + hidden_args.emplace_back(history_arg); + } // -boinckey should now be removed entirely. It is put here to prevent the executable erroring out on // an invalid parameter for old clients that may have left the argument in. @@ -657,6 +693,17 @@ bool InitSanityCheck() return true; } +#if HAVE_SYSTEM +static void StartupNotify(const ArgsManager& args) +{ + std::string cmd = args.GetArg("-startupnotify", ""); + if (!cmd.empty()) { + std::thread t(runCommand, cmd); + t.detach(); // thread runs free + } +} +#endif + /** * Initialize global loggers. * @@ -969,7 +1016,6 @@ bool AppInit2(ThreadHandlerPtr threads) LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); RandomInit(); ECC_Start(); - globalVerifyHandle.reset(new ECCVerifyHandle()); // Sanity check if (!InitSanityCheck()) @@ -1098,15 +1144,22 @@ bool AppInit2(ThreadHandlerPtr threads) } // -tor can override normal proxy, -notor disables Tor entirely - if (gArgs.IsArgSet("-tor") && (fProxy || gArgs.IsArgSet("-tor"))) { - proxyType addrOnion; - if (!gArgs.IsArgSet("-tor")) { - addrOnion = addrProxy; + if (gArgs.IsArgSet("-tor")) { + CService addrOnion; + + // If -tor is specified without any argument, and proxy was specified, then override proxy with tor + // at same address and port. + if (gArgs.GetArg("-tor", "") == "") { + if (fProxy) { + addrOnion = addrProxy; + } } else { - CService addrProxy(LookupNumeric(gArgs.GetArg("-tor", "").c_str(), 9050)); + addrOnion = CService(LookupNumeric(gArgs.GetArg("-tor", "").c_str(), 9050)); + } + + if (!addrOnion.IsValid()) { + return InitError(strprintf(_("Invalid -tor address: '%s'"), gArgs.GetArg("-tor", gArgs.GetArg("-proxy", "")))); } - if (!addrOnion.IsValid()) - return InitError(strprintf(_("Invalid -tor address: '%s'"), gArgs.GetArg("-tor", ""))); SetProxy(NET_TOR, addrOnion); SetReachable(NET_TOR, true); } @@ -1537,6 +1590,10 @@ bool AppInit2(ThreadHandlerPtr threads) GRC::ScheduleBackgroundJobs(scheduler); + #if HAVE_SYSTEM + StartupNotify(gArgs); + #endif + uiInterface.InitMessage(_("Done loading")); g_timer.GetTimes("Done loading", "init"); diff --git a/src/key.cpp b/src/key.cpp index 68922d2244..2afde1763c 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -165,7 +165,7 @@ bool CKey::Check(const unsigned char *vch) { void CKey::MakeNewKey(bool fCompressedIn) { do { - GetStrongRandBytes(keydata.data(), keydata.size()); + GetStrongRandBytes(keydata); } while (!Check(keydata.data())); fValid = true; fCompressed = fCompressedIn; @@ -239,7 +239,7 @@ bool CKey::Sign(const uint256 &hash, std::vector& vchSig, bool gr secp256k1_pubkey pk; ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pk, begin()); assert(ret); - ret = secp256k1_ecdsa_verify(GetVerifyContext(), &sig, hash.begin(), &pk); + ret = secp256k1_ecdsa_verify(secp256k1_context_static, &sig, hash.begin(), &pk); assert(ret); return true; } @@ -250,7 +250,7 @@ bool CKey::VerifyPubKey(const CPubKey& pubkey) const { } unsigned char rnd[8]; std::string str = "Bitcoin key verification\n"; - GetRandBytes(rnd, sizeof(rnd)); + GetRandBytes(rnd); uint256 hash; CHash256().Write(MakeUCharSpan(str)).Write(rnd).Finalize(hash); std::vector vchSig; @@ -274,9 +274,9 @@ bool CKey::SignCompact(const uint256 &hash, std::vector& vchSig) secp256k1_pubkey epk, rpk; ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &epk, begin()); assert(ret); - ret = secp256k1_ecdsa_recover(GetVerifyContext(), &rpk, &rsig, hash.begin()); + ret = secp256k1_ecdsa_recover(secp256k1_context_static, &rpk, &rsig, hash.begin()); assert(ret); - ret = secp256k1_ec_pubkey_cmp(GetVerifyContext(), &epk, &rpk); + ret = secp256k1_ec_pubkey_cmp(secp256k1_context_static, &epk, &rpk); assert(ret == 0); return true; } @@ -372,13 +372,13 @@ bool ECC_InitSanityCheck() { void ECC_Start() { assert(secp256k1_context_sign == nullptr); - secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); assert(ctx != nullptr); { // Pass in a random blinding seed to the secp256k1 context. std::vector> vseed(32); - GetRandBytes(vseed.data(), 32); + GetRandBytes(vseed); bool ret = secp256k1_context_randomize(ctx, vseed.data()); assert(ret); } diff --git a/src/leveldb/CMakeLists.txt b/src/leveldb/CMakeLists.txt index 1cb46256c2..42d67d7f63 100644 --- a/src/leveldb/CMakeLists.txt +++ b/src/leveldb/CMakeLists.txt @@ -69,8 +69,8 @@ else(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") # Disable RTTI. - string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") + #string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") endif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # Test whether -Wthread-safety is available. See diff --git a/src/main.cpp b/src/main.cpp index 2d55fdcd97..8aa9e05018 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "gridcoin/claim.h" #include "gridcoin/mrc.h" #include "gridcoin/contract/contract.h" +#include "gridcoin/contract/registry.h" #include "gridcoin/project.h" #include "gridcoin/quorum.h" #include "gridcoin/researcher.h" @@ -61,6 +62,7 @@ CCriticalSection cs_setpwalletRegistered; set setpwalletRegistered; CCriticalSection cs_main; +CCriticalSection cs_tx_val_commit_to_disk; CTxMemPool mempool; @@ -926,8 +928,8 @@ bool ForceReorganizeToHash(uint256 NewHash) bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned& cnt_dis, CBlockIndex* pcommon) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { set vRereadCPIDs; - GRC::BeaconRegistry& beacons = GRC::GetBeaconRegistry(); - GRC::PollRegistry& polls = GRC::GetPollRegistry(); + + GRC::RegistryBookmarks registries; while(pindexBest != pcommon) { @@ -965,8 +967,9 @@ bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned // for the reverted activations. This is safe to do before the transactional level reverts with beacon // contracts, because any beacon that is activated CANNOT have been a new advertisement in the superblock // itself. It would not be verified. AND if the beacon is a renewal, it would never be in the activation list - // for a superblock. - beacons.Deactivate(pindexBest->GetBlockHash()); + // for a superblock. We call GetBeaconRegistry directly here, because the IHandler class does not have + // a virtual method that corresponds to this call, as it is only relevant to beacons. + GRC::GetBeaconRegistry().Deactivate(pindexBest->GetBlockHash()); GRC::Quorum::PopSuperblock(pindexBest); GRC::Quorum::LoadSuperblockIndex(pindexBest->pprev); @@ -980,7 +983,8 @@ bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned GRC::Quorum::ForgetVote(pindexBest); } - // Delete beacons, polls and votes from contracts in disconnected blocks. + // Delete beacons, scraper entries, protocol entries, projects, polls and votes from contracts + // in disconnected blocks. if (pindexBest->IsContract()) { // Skip coinbase and coinstake transactions: @@ -988,19 +992,14 @@ bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned tx != end; ++tx) { + // This reverts contracts for those contract types which have handlers that properly handle + // contract level reversions. for (const auto& contract : tx->GetContracts()) { - if (contract.m_type == GRC::ContractType::BEACON) - { - const GRC::ContractContext contract_context(contract, *tx, pindexBest); - - beacons.Revert(contract_context); - } - - if (contract.m_type == GRC::ContractType::POLL || contract.m_type == GRC::ContractType::VOTE) { + if (GRC::RegistryBookmarks::IsRegistryRevertCapable(contract.m_type.Value())) { const GRC::ContractContext contract_context(contract, *tx, pindexBest); - polls.Revert(contract_context); + GRC::RegistryBookmarks::GetRegistryWithRevert(contract.m_type.Value()).Revert(contract_context); } } } @@ -1030,11 +1029,14 @@ bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned if (!txdb.TxnCommit()) return error("DisconnectBlocksBatch: TxnCommit failed"); /*fatal*/ - // Record new best height (the common block) in the beacon registry after the series of reverts. - GRC::BeaconRegistry& beacons = GRC::GetBeaconRegistry(); - beacons.SetDBHeight(pindexBest->nHeight); + // Record new best height (the common block) in the registries that have a backing DB. This is important + // to ensure that if the wallet is shutdown, on the next start, the contract replay (if any) is done from + // the correct height. + registries.UpdateRegistryBlockHeights(pindexBest->nHeight); - GRC::ReplayContracts(pindexBest); + // Replaying contracts after a block disconnection is no longer needed, as all contract types that have handlers + // that operate at the tx/contract level have fully implemented reversion. + //GRC::ReplayContracts(pindexBest); // Tally research averages. if(IsV9Enabled_Tally(nBestHeight) && !IsV11Enabled(nBestHeight)) { @@ -1154,84 +1156,96 @@ EXCLUSIVE_LOCKS_REQUIRED(cs_main) return error("%s: TxnBegin failed", __func__); } - if (pindexGenesisBlock == nullptr) { - if (hash != (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)) { - txdb.TxnAbort(); - return error("%s: genesis block hash does not match", __func__); - } - - pindexGenesisBlock = pindex; - } else { - assert(pindex->GetBlockHash()==block.GetHash(true)); - assert(pindex->pprev == pindexBest); + { + // This lock protects the time period between the GridcoinConnectBlock, which also connects validated transaction + // contracts and causes contract handlers to fire, and the committing of the txindex changes to disk. Any contract + // handlers that generate signals whose downstream handlers make use of transaction data on disk via leveldb (txdb) + // on another thread need to take this lock to ensure that the write to leveldb and the access of the transaction data + // by the signal handlers is appropriately serialized. + LOCK(cs_tx_val_commit_to_disk); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: cs_tx_val_commit_to_disk locked", __func__); + + if (pindexGenesisBlock == nullptr) { + if (hash != (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)) { + txdb.TxnAbort(); + return error("%s: genesis block hash does not match", __func__); + } - if (!ConnectBlock(block, txdb, pindex, false)) { - txdb.TxnAbort(); - error("%s: ConnectBlock %s failed, Previous block %s", - __func__, - hash.ToString().c_str(), - pindex->pprev->GetBlockHash().ToString()); - InvalidChainFound(pindex); - return false; + pindexGenesisBlock = pindex; + } else { + assert(pindex->GetBlockHash()==block.GetHash(true)); + assert(pindex->pprev == pindexBest); + + if (!ConnectBlock(block, txdb, pindex, false)) { + txdb.TxnAbort(); + error("%s: ConnectBlock %s failed, Previous block %s", + __func__, + hash.ToString().c_str(), + pindex->pprev->GetBlockHash().ToString()); + InvalidChainFound(pindex); + return false; + } } - } - // Delete redundant memory transactions - for (auto const& tx : block.vtx) { - mempool.remove(tx); - mempool.removeConflicts(tx); - } + // Delete redundant memory transactions + for (auto const& tx : block.vtx) { + mempool.remove(tx); + mempool.removeConflicts(tx); + } - // Remove stale MRCs in the mempool that are not in this new block. Remember the MRCs were initially validated in - // AcceptToMemoryPool. Here we just need to do a staleness check. - std::vector to_be_erased; + // Remove stale MRCs in the mempool that are not in this new block. Remember the MRCs were initially validated in + // AcceptToMemoryPool. Here we just need to do a staleness check. + std::vector to_be_erased; - for (const auto& [_, pool_tx] : mempool.mapTx) { - for (const auto& pool_tx_contract : pool_tx.GetContracts()) { - if (pool_tx_contract.m_type == GRC::ContractType::MRC) { - GRC::MRC pool_tx_mrc = pool_tx_contract.CopyPayloadAs(); + for (const auto& [_, pool_tx] : mempool.mapTx) { + for (const auto& pool_tx_contract : pool_tx.GetContracts()) { + if (pool_tx_contract.m_type == GRC::ContractType::MRC) { + GRC::MRC pool_tx_mrc = pool_tx_contract.CopyPayloadAs(); - if (pool_tx_mrc.m_last_block_hash != hashBestChain) { - to_be_erased.push_back(pool_tx); + if (pool_tx_mrc.m_last_block_hash != hashBestChain) { + to_be_erased.push_back(pool_tx); + } } } } - } - - // TODO: Additional mempool removals for generic transactions based on txns... - // that satisfy lock time requirements, - // that are at least 30m old, - // that have been broadcast at least once min 5m ago, - // that had at least 45s to go in to the last block, - // and are still not in the txdb? (for the wallet itself, not mempool.) - for (const auto& tx : to_be_erased) { - LogPrintf("%s: Erasing stale transaction %s from mempool and wallet.", __func__, tx.GetHash().ToString()); - mempool.remove(tx); - // If this transaction was in this wallet (i.e. erasure successful), then send signal for GUI. - if (pwalletMain->EraseFromWallet(tx.GetHash())) { - pwalletMain->NotifyTransactionChanged(pwalletMain, tx.GetHash(), CT_DELETED); + // TODO: Additional mempool removals for generic transactions based on txns... + // that satisfy lock time requirements, + // that are at least 30m old, + // that have been broadcast at least once min 5m ago, + // that had at least 45s to go in to the last block, + // and are still not in the txdb? (for the wallet itself, not mempool.) + + for (const auto& tx : to_be_erased) { + LogPrintf("%s: Erasing stale transaction %s from mempool and wallet.", __func__, tx.GetHash().ToString()); + mempool.remove(tx); + // If this transaction was in this wallet (i.e. erasure successful), then send signal for GUI. + if (pwalletMain->EraseFromWallet(tx.GetHash())) { + pwalletMain->NotifyTransactionChanged(pwalletMain, tx.GetHash(), CT_DELETED); + } } - } - // Clean up spent outputs in wallet that are now not spent if mempool transactions erased above. This - // is ugly and heavyweight and should be replaced when the upstream wallet code is ported. Unlike the - // repairwallet rpc, this is silent. - if (!to_be_erased.empty()) { - int nMisMatchFound = 0; - CAmount nBalanceInQuestion = 0; + // Clean up spent outputs in wallet that are now not spent if mempool transactions erased above. This + // is ugly and heavyweight and should be replaced when the upstream wallet code is ported. Unlike the + // repairwallet rpc, this is silent. + if (!to_be_erased.empty()) { + int nMisMatchFound = 0; + CAmount nBalanceInQuestion = 0; - pwalletMain->FixSpentCoins(nMisMatchFound, nBalanceInQuestion); - } + pwalletMain->FixSpentCoins(nMisMatchFound, nBalanceInQuestion); + } - if (!txdb.WriteHashBestChain(pindex->GetBlockHash())) { - txdb.TxnAbort(); - return error("%s: WriteHashBestChain failed", __func__); - } + if (!txdb.WriteHashBestChain(pindex->GetBlockHash())) { + txdb.TxnAbort(); + return error("%s: WriteHashBestChain failed", __func__); + } + + // Make sure it's successfully written to disk before changing memory structure + if (!txdb.TxnCommit()) { + return error("%s: TxnCommit failed", __func__); + } - // Make sure it's successfully written to disk before changing memory structure - if (!txdb.TxnCommit()) { - return error("%s: TxnCommit failed", __func__); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: cs_tx_val_commit_to_disk unlocked", __func__); } // Add to current best branch @@ -1674,7 +1688,7 @@ bool LoadBlockIndex(bool fAllowNew) txNew.nTime = 1413033777; txNew.vin.resize(1); txNew.vout.resize(1); - txNew.vin[0].scriptSig = CScript() << 0 << CBigNum(42) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vin[0].scriptSig = CScript() << 0 << CScriptNum(42) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout[0].SetEmpty(); CBlock block; block.vtx.push_back(txNew); @@ -2928,7 +2942,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle) { uint64_t nonce = 0; while (nonce == 0) { - GetRandBytes((unsigned char*)&nonce, sizeof(nonce)); + GetRandBytes({(unsigned char*)&nonce, sizeof(nonce)}); } pto->fPingQueued = false; pto->nPingUsecStart = GetTimeMicros(); diff --git a/src/main.h b/src/main.h index e42753e3d1..5af4e3e47a 100644 --- a/src/main.h +++ b/src/main.h @@ -75,6 +75,7 @@ typedef std::unordered_map BlockMap; extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; +extern CCriticalSection cs_tx_val_commit_to_disk; extern BlockMap mapBlockIndex; extern CBlockIndex* pindexGenesisBlock; extern unsigned int nStakeMinAge; @@ -210,7 +211,7 @@ class CMerkleTx : public CTransaction class CBlockHeader { public: - static const int32_t CURRENT_VERSION = 12; + static const int32_t CURRENT_VERSION = 13; // header int32_t nVersion; diff --git a/src/miner.cpp b/src/miner.cpp index 118a968987..bc0ef2deb7 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -150,8 +150,8 @@ bool TrySignClaim( // This is in anonymous namespace because it is only to be used by miner code here in this file. bool CreateMRCRewards(CBlock &blocknew, std::map>& mrc_map, - std::map& mrc_tx_map, - GRC::Claim claim, CWallet* pwallet) EXCLUSIVE_LOCKS_REQUIRED(cs_main) + std::map& mrc_tx_map, uint32_t& claim_contract_version, + GRC::Claim& claim, CWallet* pwallet) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // For convenience CTransaction& coinstake = blocknew.vtx[1]; @@ -205,7 +205,7 @@ bool CreateMRCRewards(CBlock &blocknew, std::map( - GRC::ContractAction::ADD, - std::move(claim))); + claim_contract_version, + GRC::ContractAction::ADD, + std::move(claim))); return true; } @@ -336,6 +337,10 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, const GRC::ResearcherPtr researcher = GRC::Researcher::Get(); const GRC::CpidOption cpid = researcher->Id().TryCpid(); + // This boolean will be used to ensure that there is only one mandatory sidestake transaction bound into a block. This + // in combination with the transaction level validation for the maximum mandatory allocation perfects that rule. + bool mandatory_sidestake_bound = false; + // Largest block you're willing to create: unsigned int nBlockMaxSize = gArgs.GetArg("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2); // Limit to between 1K and MAX_BLOCK_SIZE-1K for sanity: @@ -474,7 +479,6 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, - 2) * coinstake_output_ser_size; } - uint64_t nBlockTx = 0; int nBlockSigOps = 100; std::make_heap(vecPriority.begin(), vecPriority.end()); @@ -600,6 +604,18 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, } //TryCpid() } // output limit } // contract type is MRC + + // If a mandatory sidestake contract has not already been bound into the block, then set mandatory_sidestake_bound + // to true. The ignore_transaction flag is still false, so this mandatory sidestake contract will be bound into the + // block. Any more mandatory sidestakes in the transaction loop will be ignored because the mandatory_sidestake_bound + // will be set to true in the second and succeeding iterations in the loop. + if (contract.m_type == GRC::ContractType::SIDESTAKE) { + if (!mandatory_sidestake_bound) { + mandatory_sidestake_bound = true; + } else { + ignore_transaction = true; + } + } // contract type is SIDESTAKE } // contracts not empty if (ignore_transaction) continue; @@ -609,7 +625,6 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, block.vtx.push_back(tx); nBlockSize += nTxSize; - ++nBlockTx; nBlockSigOps += nTxSigOps; nFees += nTxFees; @@ -661,7 +676,7 @@ bool CreateCoinStake(CBlock &blocknew, CKey &key, function += ": "; int64_t CoinWeight; - CBigNum StakeKernelHash; + arith_uint256 StakeKernelHash; CTxDB txdb("r"); int64_t StakeWeightSum = 0; double StakeValueSum = 0; @@ -729,11 +744,10 @@ bool CreateCoinStake(CBlock &blocknew, CKey &key, CoinWeight = GRC::CalculateStakeWeightV8(CoinTx, CoinTxN); - StakeKernelHash.setuint256(GRC::CalculateStakeHashV8(block_time, CoinTx, CoinTxN, txnew.nTime, StakeModifier)); + StakeKernelHash = UintToArith256(GRC::CalculateStakeHashV8(block_time, CoinTx, CoinTxN, txnew.nTime, StakeModifier)); - CBigNum StakeTarget; - StakeTarget.SetCompact(blocknew.nBits); - StakeTarget *= CoinWeight; + arith_uint320 StakeTarget = arith_uint256().SetCompact(blocknew.nBits); + StakeTarget *= arith_uint320(CoinWeight); StakeWeightSum += CoinWeight; StakeWeightMin = std::min(StakeWeightMin, CoinWeight); StakeWeightMax = std::max(StakeWeightMax, CoinWeight); @@ -753,7 +767,7 @@ bool CreateCoinStake(CBlock &blocknew, CKey &key, StakeKernelDiff, GRC::GetBlockDifficulty(blocknew.nBits)); - if (StakeKernelHash <= StakeTarget) + if (arith_uint320(StakeKernelHash) <= StakeTarget) { // Found a kernel LogPrintf("CreateCoinStake: Found Kernel"); @@ -829,14 +843,14 @@ bool CreateCoinStake(CBlock &blocknew, CKey &key, } void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStakeSplit, bool &fEnableSideStaking, - SideStakeAlloc &vSideStakeAlloc, int64_t &nMinStakeSplitValue, double &dEfficiency) + int64_t &nMinStakeSplitValue, double &dEfficiency) { // When this function is called, CreateCoinStake and CreateGridcoinReward have already been called // and there will be a single coinstake output (besides the empty one) that has the combined stake + research // reward. This function does the following... // 1. Perform reward payment to specified addresses ("sidestaking") in the following manner... // a. Check if both flags false and if so return with no action. - // b. Limit number of outputs based on bv. 3 for <=9 and 8 for >= 10. + // b. Limit number of outputs based on bv: 3 for <= v9, 8 for v10 & v11, and 10 for >= v12. // c. Pull the nValue from the original output and store locally. (nReward was passed in.) // d. Pop the existing outputs. // e. Validate each address provided for redirection in turn. If valid, create an output of the @@ -869,7 +883,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake // 8 for 10 and above (excluding MRC outputs). The first one must be empty, so that gives 2 and 7 usable ones, // respectively. MRC outputs are excluded here. They are addressed in CreateMRC separately. Unlike in other areas, // the foundation sidestake IS COUNTED in the GetMRCOutputLimit because it is a sidestake, but handled in the - // CreateMRCRewards function and not here. + // CreateMRCRewards function and not here. For block version 12+ nMaxOutputs is 10, which gives 9 usable. unsigned int nMaxOutputs = GetCoinstakeOutputLimit(blocknew.nVersion) - GetMRCOutputLimit(blocknew.nVersion, true); // Set the maximum number of sidestake outputs to two less than the maximum allowable coinstake outputs @@ -881,7 +895,7 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake unsigned int nOutputsUsed = 1; // If the number of sidestaking allocation entries exceeds nMaxSideStakeOutputs, then shuffle the vSideStakeAlloc - // to support sidestaking with more than six entries. This is a super simple solution but has some disadvantages. + // to support sidestaking with more than eight entries. This is a super simple solution but has some disadvantages. // If the person made a mistake and has the entries in the config file add up to more than 100%, then those entries // resulting a cumulative total over 100% will always be excluded, not just randomly excluded, because the cumulative // check is done in the order of the entries in the config file. This is not regarded as a big issue, because @@ -889,9 +903,19 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake // mMaxSideStakeOutput entries, the residual returned to the coinstake will vary when the entries are shuffled, // because the total percentage of the selected entries will be randomized. No attempt to renormalize // the percentages is done. - if (vSideStakeAlloc.size() > nMaxSideStakeOutputs) - { - Shuffle(vSideStakeAlloc.begin(), vSideStakeAlloc.end(), FastRandomContext()); + SideStakeAlloc mandatory_sidestakes + = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::MANDATORY, false); + SideStakeAlloc local_sidestakes + = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::LOCAL, false); + + if (mandatory_sidestakes.size() > GetMandatorySideStakeOutputLimit(blocknew.nVersion)) { + Shuffle(mandatory_sidestakes.begin(), mandatory_sidestakes.end(), FastRandomContext()); + } + + if (local_sidestakes.size() > nMaxSideStakeOutputs + - std::min(GetMandatorySideStakeOutputLimit(blocknew.nVersion), + mandatory_sidestakes.size())) { + Shuffle(local_sidestakes.begin(), local_sidestakes.end(), FastRandomContext()); } // Initialize remaining stake output value to the total value of output for stake, which also includes @@ -911,34 +935,46 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake blocknew.vtx[1].vout.clear(); CScript SideStakeScriptPubKey; - double dSumAllocation = 0.0; - - if (fEnableSideStaking) - { - // Iterate through passed in SideStake vector until either all elements processed, the maximum number of - // sidestake outputs is reached, or accumulated allocation will exceed 100%. - for(auto iterSideStake = vSideStakeAlloc.begin(); - (iterSideStake != vSideStakeAlloc.end()) && (nOutputsUsed <= nMaxSideStakeOutputs); - ++iterSideStake) + GRC::Allocation SumAllocation; + + // Lambda for sidestake allocation. This iterates through the provided sidestake vector until either all elements processed, + // the maximum number of sidestake outputs is reached via the provided output_limit, or accumulated allocation will exceed 100%. + const auto allocate_sidestakes = [&](SideStakeAlloc sidestakes, unsigned int output_limit) { + for (auto iterSideStake = sidestakes.begin(); + (iterSideStake != sidestakes.end()) + && (nOutputsUsed <= output_limit); + ++iterSideStake) { - CBitcoinAddress address(iterSideStake->first); + CBitcoinAddress address(iterSideStake->get()->GetDestination()); + GRC::Allocation allocation = iterSideStake->get()->GetAllocation(); + if (!address.IsValid()) { LogPrintf("WARN: SplitCoinStakeOutput: ignoring sidestake invalid address %s.", - iterSideStake->first.c_str()); + address.ToString()); continue; } // Do not process a distribution that would result in an output less than 1 CENT. This will flow back into // the coinstake below. Prevents dust build-up. - if (nReward * iterSideStake->second < CENT) + // + // This is extremely important for mandatory sidestakes when validating this on a receiving node. + // This allows the validator to retrace the dust elimination for the coinstake mandatory sidestakes, and + // verify that EITHER the residual number of mandatory outputs after dust elimination is less than or equal to the + // maximum, in which case they all must be present and valid, OR, the residual number of outputs is greater than the + // maximum, which means that the maximum number of mandatory outputs MUST be present and valid. + // + // Note that nOutputsUsed is NOT incremented if the output is suppressed by this check. + if (allocation * nReward < CENT) { LogPrintf("WARN: SplitCoinStakeOutput: distribution %f too small to address %s.", - CoinToDouble(nReward * iterSideStake->second), iterSideStake->first.c_str()); + CoinToDouble((allocation * nReward).ToCAmount()), + address.ToString() + ); continue; } - if (dSumAllocation + iterSideStake->second > 1.0) + if (SumAllocation + allocation > 1) { LogPrintf("WARN: SplitCoinStakeOutput: allocation percentage over 100 percent, " "ending sidestake allocations."); @@ -961,11 +997,11 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake int64_t nSideStake = 0; // For allocations ending less than 100% assign using sidestake allocation. - if (dSumAllocation + iterSideStake->second < 1.0) - nSideStake = nReward * iterSideStake->second; + if (SumAllocation + allocation < 1) + nSideStake = (allocation * nReward).ToCAmount(); // We need to handle the final sidestake differently in the case it brings the total allocation up to 100%, // because testing showed in corner cases the output return to the staking address could be off by one Halford. - else if (dSumAllocation + iterSideStake->second == 1.0) + else if (SumAllocation + allocation == 1) // Simply assign the special case final nSideStake the remaining output value minus input value to ensure // a match on the output flowing down. nSideStake = nRemainingStakeOutputValue - nInputValue; @@ -973,18 +1009,24 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake blocknew.vtx[1].vout.push_back(CTxOut(nSideStake, SideStakeScriptPubKey)); LogPrintf("SplitCoinStakeOutput: create sidestake UTXO %i value %f to address %s", - nOutputsUsed, CoinToDouble(nReward * iterSideStake->second), iterSideStake->first.c_str()); - dSumAllocation += iterSideStake->second; + nOutputsUsed, + CoinToDouble((allocation * nReward).ToCAmount()), + address.ToString() + ); + SumAllocation += allocation; nRemainingStakeOutputValue -= nSideStake; nOutputsUsed++; } - // If we get here and dSumAllocation is zero then the enablesidestaking flag was set, but no VALID distribution - // was in the vSideStakeAlloc vector. (Note that this is also in the parsing routine in StakeMiner, so it will show - // up when the wallet is first started, but also needs to be here, to remind the user periodically that something - // is amiss.) - if (dSumAllocation == 0.0) - LogPrintf("WARN: %s: enablesidestaking was set in config but nothing has been allocated for" - " distribution!", __func__); + }; + + if (fEnableSideStaking) { + // Iterate through mandatory SideStake vector until either all elements processed, the maximum number of + // mandatory sidestake outputs is reached, or accumulated allocation will exceed 100%. + allocate_sidestakes(mandatory_sidestakes, GetMandatorySideStakeOutputLimit(blocknew.nVersion)); + + // Iterate through local SideStake vector until either all elements processed, the maximum number of + // sidestake outputs is reached, or accumulated allocation will exceed 100%. + allocate_sidestakes(local_sidestakes, nMaxSideStakeOutputs); } // By this point, if SideStaking was used and 100% was allocated nRemainingStakeOutputValue will be @@ -1050,23 +1092,27 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake // The final state here of the coinstake blocknew.vtx[1].vout is // [empty], // [reward split 1], [reward split 2], ... , [reward split m], - // [sidestake 1], ... , [sidestake n], - // [MRC 1], ..., [MRC p]. + // [mandatory sidestake 1], ... , [mandatory sidestake n] + // [sidestake 1], ... , [sidestake p], + // [MRC 1], ..., [MRC q]. // // Currently according to the output limit rules encoded in CreateMRC and here: // For block version 10-11: - // one empty, m <= 6, m + n <= 7, and p = 0. + // one empty, m <= 7, n = 0, n + p <= 6, m + n + p <= 7 (i.e. empty + m + n + p <= 8), and q = 0, total <= 8.. // // For block version 12: - // one empty, m <= 6, m + n <= 10, and p <= 10. + // one empty, m <= 9, n = 0, n + p <= 8, m + n + p <= 9 (i.e. empty + m + n + p <= 10), and q <= 10, total <= 20. + // (On testnet q <= 3, total <= 13.) + + // For block version 13+: + // one empty, m <= 9, n <= 4, n + p <= 8, m + n + p <= 9 (i.e. empty + m + n + p <= 10), and q <= 10, total <= 20. + // (On testnet q <= 3, total <= 13.) // The total generated GRC is the total of the reward splits - the fees (the original GridcoinReward which is the // research reward + CBR), plus the total of the MRC outputs 2 to p (these outputs already have the fees subtracted) // MRC output 1 is always to the foundation (it is essentially a sidestake) and represents a cut of the MRC fees. } - - unsigned int GetNumberOfStakeOutputs(int64_t &nValue, int64_t &nMinStakeSplitValue, double &dEfficiency) { int64_t nDesiredStakeOutputValue = 0; @@ -1248,110 +1294,6 @@ bool IsMiningAllowed(CWallet *pwallet) return g_miner_status.StakingEnabled(); } -// This function parses the config file for the directives for side staking. It is used -// in StakeMiner for the miner loop and also called by rpc getstakinginfo. -SideStakeAlloc GetSideStakingStatusAndAlloc() -{ - SideStakeAlloc vSideStakeAlloc; - std::vector> raw_vSideStakeAlloc; - double dSumAllocation = 0.0; - - // Parse destinations and allocations. We don't need to worry about any that are rejected other than a warning - // message, because any unallocated rewards will go back into the coinstake output(s). - - // If -sidestakeaddresses and -sidestakeallocations is set in either the config file or the r-w settings file - // and the settings are not empty and they are the same size, this will take precedence over the multiple entry - // -sidestake format. - std::vector addresses; - std::vector allocations; - - ParseString(gArgs.GetArg("-sidestakeaddresses", ""), ',', addresses); - ParseString(gArgs.GetArg("-sidestakeallocations", ""), ',', allocations); - - if (addresses.size() != allocations.size()) - { - LogPrintf("WARN: %s: Malformed new style sidestaking configuration entries. Reverting to original format.", - __func__); - } - - if (addresses.size() && addresses.size() == allocations.size()) - { - for (unsigned int i = 0; i < addresses.size(); ++i) - { - raw_vSideStakeAlloc.push_back(std::make_pair(addresses[i], allocations[i])); - } - } - else if (gArgs.GetArgs("-sidestake").size()) - { - for (auto const& sSubParam : gArgs.GetArgs("-sidestake")) - { - std::vector vSubParam; - - ParseString(sSubParam, ',', vSubParam); - if (vSubParam.size() != 2) - { - LogPrintf("WARN: %s: Incomplete SideStake Allocation specified. Skipping SideStake entry.", __func__); - continue; - } - - raw_vSideStakeAlloc.push_back(std::make_pair(vSubParam[0], vSubParam[1])); - } - } - - for (auto const& entry : raw_vSideStakeAlloc) - { - std::string sAddress; - double dAllocation = 0.0; - - sAddress = entry.first; - - CBitcoinAddress address(sAddress); - if (!address.IsValid()) - { - LogPrintf("WARN: %s: ignoring sidestake invalid address %s.", __func__, sAddress); - continue; - } - - if (!ParseDouble(entry.second, &dAllocation)) - { - LogPrintf("WARN: %s: Invalid allocation %s provided. Skipping allocation.", __func__, entry.second); - continue; - } - - dAllocation /= 100.0; - - if (dAllocation <= 0) - { - LogPrintf("WARN: %s: Negative or zero allocation provided. Skipping allocation.", __func__); - continue; - } - - // The below will stop allocations if someone has made a mistake and the total adds up to more than 100%. - // Note this same check is also done in SplitCoinStakeOutput, but it needs to be done here for two reasons: - // 1. Early alertment in the debug log, rather than when the first kernel is found, and 2. When the UI is - // hooked up, the SideStakeAlloc vector will be filled in by other than reading the config file and will - // skip the above code. - dSumAllocation += dAllocation; - if (dSumAllocation > 1.0) - { - LogPrintf("WARN: %s: allocation percentage over 100 percent, ending sidestake allocations.", __func__); - break; - } - - vSideStakeAlloc.push_back(std::pair(sAddress, dAllocation)); - LogPrint(BCLog::LogFlags::MINER, "INFO: %s: SideStakeAlloc Address %s, Allocation %f", - __func__, sAddress, dAllocation); - } - - // If we get here and dSumAllocation is zero then the enablesidestaking flag was set, but no VALID distribution - // was provided in the config file, so warn in the debug log. - if (!dSumAllocation) - LogPrintf("WARN: %s: enablesidestaking was set in config but nothing has been allocated for" - " distribution!", __func__); - - return vSideStakeAlloc; -} - // This function parses the config file for the directives for stake splitting. It is used // in StakeMiner for the miner loop and also called by rpc getstakinginfo. bool GetStakeSplitStatusAndParams(int64_t& nMinStakeSplitValue, double& dEfficiency, int64_t& nDesiredStakeOutputValue) @@ -1415,12 +1357,10 @@ void StakeMiner(CWallet *pwallet) // nMinStakeSplitValue and dEfficiency are out parameters. bool fEnableStakeSplit = GetStakeSplitStatusAndParams(nMinStakeSplitValue, dEfficiency, nDesiredStakeOutputValue); - bool fEnableSideStaking = gArgs.GetBoolArg("-enablesidestaking"); - - LogPrint(BCLog::LogFlags::MINER, "INFO: %s: fEnableSideStaking = %u", __func__, fEnableSideStaking); - - // vSideStakeAlloc is an out parameter. - if (fEnableSideStaking) vSideStakeAlloc = GetSideStakingStatusAndAlloc(); + // If the vSideStakeAlloc is not empty, then set fEnableSideStaking to true. Note that vSideStakeAlloc will not be empty + // if non-zero allocation mandatory sidestakes are set OR local sidestaking is turned on by the -enablesidestaking config + // option. + bool fEnableSideStaking = (!GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, false).empty()); // wait for next round if (!MilliSleep(nMinerSleep)) return; @@ -1450,10 +1390,13 @@ void StakeMiner(CWallet *pwallet) // * Create a bare block - // This transition code is for mandatory change from V11 to v12 block format (accommodates MRC). - if (!IsV12Enabled(pindexPrev->nHeight + 1)) { - StakeBlock.nVersion = 11; + // This transition code is to handle the v12 to v13 transition. The other transition handling + // for block versions in the stakeminer have been removed, because no blocks of an earlier version + // than v13 can be staked now, since the chain is past the v12 transition height. + if (!IsV13Enabled(pindexPrev->nHeight + 1)) { + StakeBlock.nVersion = 12; } + StakeBlock.nTime = GetAdjustedTime(); StakeBlock.nNonce = 0; StakeBlock.nBits = GRC::GetNextTargetRequired(pindexPrev); @@ -1492,9 +1435,11 @@ void StakeMiner(CWallet *pwallet) LogPrintf("INFO: %s: added Gridcoin reward to coinstake", __func__); + uint32_t claim_contract_version = IsV13Enabled(pindexPrev->nHeight + 1) ? 3 : 2; + // * Add MRC outputs to coinstake. This has to be done before the coinstake splitting/sidestaking, because // Some of the MRC fees go to the miner as part of the reward, and this affects the SplitCoinStakeOutput calculation. - if (!CreateMRCRewards(StakeBlock, mrc_map, mrc_tx_map, claim, pwallet)) continue; + if (!CreateMRCRewards(StakeBlock, mrc_map, mrc_tx_map, claim_contract_version, claim, pwallet)) continue; g_timer.GetTimes(function + "CreateMRC", "miner"); @@ -1503,7 +1448,7 @@ void StakeMiner(CWallet *pwallet) // * If argument is supplied desiring stake output splitting or side staking, then call SplitCoinStakeOutput. if (fEnableStakeSplit || fEnableSideStaking) SplitCoinStakeOutput(StakeBlock, nReward, fEnableStakeSplit, fEnableSideStaking, - vSideStakeAlloc, nMinStakeSplitValue, dEfficiency); + nMinStakeSplitValue, dEfficiency); g_timer.GetTimes(function + "SplitCoinStakeOutput", "miner"); diff --git a/src/miner.h b/src/miner.h index a26259ad18..c713a8de89 100644 --- a/src/miner.h +++ b/src/miner.h @@ -8,11 +8,13 @@ #define BITCOIN_MINER_H #include "main.h" +#include "gridcoin/sidestake.h" + class CWallet; class CWalletTx; -typedef std::vector< std::pair > SideStakeAlloc; +typedef std::vector SideStakeAlloc; extern unsigned int nMinerSleep; @@ -24,10 +26,14 @@ static const int64_t MIN_STAKE_SPLIT_VALUE_GRC = 800; void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStakeSplit, bool &fEnableSideStaking, SideStakeAlloc &vSideStakeAlloc, double &dEfficiency); unsigned int GetNumberOfStakeOutputs(int64_t &nValue, int64_t &nMinStakeSplitValue, double &dEfficiency); -SideStakeAlloc GetSideStakingStatusAndAlloc(); bool GetStakeSplitStatusAndParams(int64_t& nMinStakeSplitValue, double& dEfficiency, int64_t& nDesiredStakeOutputValue); -bool CreateMRCRewards(CBlock &blocknew, std::map>& mrc_map, std::map& mrc_tx_map, GRC::Claim claim, CWallet* pwallet) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool CreateMRCRewards(CBlock &blocknew, + std::map>& mrc_map, + std::map& mrc_tx_map, + uint32_t& claim_contract_version, + GRC::Claim& claim, + CWallet* pwallet) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, std::map>& mrc_map); bool CreateGridcoinReward(CBlock &blocknew, CBlockIndex* pindexPrev, int64_t &nReward, GRC::Claim& claim) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/net.cpp b/src/net.cpp index b7d0b699d6..0f1f87cf0d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -212,7 +212,7 @@ void AdvertiseLocal(CNode *pnode) // If discovery is enabled, sometimes give our peer the address it // tells us that it sees us as in case it has a better idea of our // address than we do. - const int randomNumber = GetRandInt((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3+1 : 1+1); + const int randomNumber = GetRand((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3+1 : 1+1); if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() || randomNumber == 0)) { @@ -486,7 +486,7 @@ void CNode::PushVersion() int64_t nTime = GetAdjustedTime(); CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(LookupNumeric("0.0.0.0", 0))); CAddress addrMe = CAddress(CService(), nLocalServices); - GetRandBytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); + GetRandBytes({(unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)}); LogPrint(BCLog::LogFlags::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%s", PROTOCOL_VERSION, nBestHeight, addrMe.ToString(), addrYou.ToString(), addr.ToString()); @@ -1560,14 +1560,12 @@ void ThreadOpenConnections2(void* parg) // Only connect out to one peer per network group (/16 for IPv4). // Do this here so we don't have to critsect vNodes inside mapAddresses critsect. - int nOutbound = 0; set > setConnected; { LOCK(cs_vNodes); for (auto const& pnode : vNodes) { if (!pnode->fInbound) { setConnected.insert(pnode->addr.GetGroup()); - nOutbound++; } } } diff --git a/src/netaddress.h b/src/netaddress.h index 4831edf300..cbaf02f7a7 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -16,8 +16,6 @@ #include #include -#endif // BITCOIN_NETADDRESS_H - enum Network { NET_UNROUTABLE, @@ -181,3 +179,5 @@ class CService : public CNetAddr } }; +#endif // BITCOIN_NETADDRESS_H + diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index f491b723a8..502686fa9f 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -15,7 +15,10 @@ bool WriteBlockToDisk(const CBlock& block, unsigned int& nFileRet, unsigned int& nBlockPosRet, const CMessageHeader::MessageStartChars& messageStart) + EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + AssertLockHeld(cs_main); + // Open history file to append CAutoFile fileout(AppendBlockFile(nFileRet), SER_DISK, CLIENT_VERSION); if (fileout.IsNull()) diff --git a/src/node/ui_interface.cpp b/src/node/ui_interface.cpp index 2a7d5ec950..297d64997a 100644 --- a/src/node/ui_interface.cpp +++ b/src/node/ui_interface.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. +#include "uint256.h" #include #include @@ -25,6 +26,7 @@ struct UISignals { boost::signals2::signal MRCChanged; boost::signals2::signal BeaconChanged; boost::signals2::signal NewPollReceived; + boost::signals2::signal NewVoteReceived; boost::signals2::signal NotifyScraperEvent; boost::signals2::signal ThreadSafeAskFee; boost::signals2::signal ThreadSafeHandleURI; @@ -32,6 +34,7 @@ struct UISignals { boost::signals2::signal Translate; boost::signals2::signal NotifyBlocksChanged; boost::signals2::signal UpdateMessageBox; + boost::signals2::signal RwSettingsUpdated; }; static UISignals g_ui_signals; @@ -53,6 +56,7 @@ ADD_SIGNALS_IMPL_WRAPPER(AccrualChangedFromStakeOrMRC); ADD_SIGNALS_IMPL_WRAPPER(MRCChanged); ADD_SIGNALS_IMPL_WRAPPER(BeaconChanged); ADD_SIGNALS_IMPL_WRAPPER(NewPollReceived); +ADD_SIGNALS_IMPL_WRAPPER(NewVoteReceived); ADD_SIGNALS_IMPL_WRAPPER(NotifyScraperEvent); ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeAskFee); ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeHandleURI); @@ -60,9 +64,10 @@ ADD_SIGNALS_IMPL_WRAPPER(QueueShutdown); ADD_SIGNALS_IMPL_WRAPPER(Translate); ADD_SIGNALS_IMPL_WRAPPER(NotifyBlocksChanged); ADD_SIGNALS_IMPL_WRAPPER(UpdateMessageBox); +ADD_SIGNALS_IMPL_WRAPPER(RwSettingsUpdated); void CClientUIInterface::ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style); } -void CClientUIInterface::UpdateMessageBox(const std::string& version, const std::string& message) { return g_ui_signals.UpdateMessageBox(version, message); } +void CClientUIInterface::UpdateMessageBox(const std::string& version, const int& update_type, const std::string& message) { return g_ui_signals.UpdateMessageBox(version, update_type, message); } bool CClientUIInterface::ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCaption) { return g_ui_signals.ThreadSafeAskFee(nFeeRequired, strCaption).value_or(false); } bool CClientUIInterface::ThreadSafeAskQuestion(std::string caption, std::string body) { return g_ui_signals.ThreadSafeAskQuestion(caption, body).value_or(false); } void CClientUIInterface::ThreadSafeHandleURI(const std::string& strURI) { return g_ui_signals.ThreadSafeHandleURI(strURI); } @@ -78,9 +83,10 @@ void CClientUIInterface::AccrualChangedFromStakeOrMRC() { return g_ui_signals.Ac void CClientUIInterface::MRCChanged() { return g_ui_signals.MRCChanged(); } void CClientUIInterface::BeaconChanged() { return g_ui_signals.BeaconChanged(); } void CClientUIInterface::NewPollReceived(int64_t poll_time) { return g_ui_signals.NewPollReceived(poll_time); } +void CClientUIInterface::NewVoteReceived(const uint256& poll_txid) { return g_ui_signals.NewVoteReceived(poll_txid); } void CClientUIInterface::NotifyAlertChanged(const uint256 &hash, ChangeType status) { return g_ui_signals.NotifyAlertChanged(hash, status); } void CClientUIInterface::NotifyScraperEvent(const scrapereventtypes& ScraperEventtype, ChangeType status, const std::string& message) { return g_ui_signals.NotifyScraperEvent(ScraperEventtype, status, message); } - +void CClientUIInterface::RwSettingsUpdated() { return g_ui_signals.RwSettingsUpdated(); } bool InitError(const std::string &str) { diff --git a/src/node/ui_interface.h b/src/node/ui_interface.h index 8ae74def0c..6a2aa852be 100644 --- a/src/node/ui_interface.h +++ b/src/node/ui_interface.h @@ -88,7 +88,7 @@ class CClientUIInterface ADD_SIGNALS_DECL_WRAPPER(ThreadSafeMessageBox, void, const std::string& message, const std::string& caption, int style); /** Update notification message box. */ - ADD_SIGNALS_DECL_WRAPPER(UpdateMessageBox, void, const std::string& version, const std::string& message); + ADD_SIGNALS_DECL_WRAPPER(UpdateMessageBox, void, const std::string& version, const int& update_type, const std::string& message); /** Ask the user whether they want to pay a fee or not. */ ADD_SIGNALS_DECL_WRAPPER(ThreadSafeAskFee, bool, int64_t nFeeRequired, const std::string& strCaption); @@ -135,6 +135,12 @@ class CClientUIInterface /** New poll received **/ ADD_SIGNALS_DECL_WRAPPER(NewPollReceived, void, int64_t poll_time); + /** New vote received **/ + ADD_SIGNALS_DECL_WRAPPER(NewVoteReceived, void, const uint256& poll_txid); + + /** Read-write settings file updated **/ + ADD_SIGNALS_DECL_WRAPPER(RwSettingsUpdated, void); + /** * New, updated or cancelled alert. * @note called with lock cs_mapAlerts held. diff --git a/src/noui.cpp b/src/noui.cpp index 033b04df56..188b7f8bba 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -21,7 +21,7 @@ static bool noui_ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCa return true; } -static int noui_UpdateMessageBox(const std::string& version, const std::string& message) +static int noui_UpdateMessageBox(const std::string& version, const int& upgrade_type, const std::string& message) { std::string caption = _("Gridcoin Update Available"); diff --git a/src/obj/build.h.in b/src/obj/build.h.in new file mode 100644 index 0000000000..9cbb8aa9c1 --- /dev/null +++ b/src/obj/build.h.in @@ -0,0 +1,7 @@ +#ifndef GRIDCOIN_BUILD_H +#define GRIDCOIN_BUILD_H + +#cmakedefine BUILD_GIT_TAG "@BUILD_GIT_TAG@" +#cmakedefine BUILD_GIT_COMMIT "@BUILD_GIT_COMMIT@" + +#endif // GRIDCOIN_BUILD_H diff --git a/src/pbkdf2.cpp b/src/pbkdf2.cpp index 8c5b2d9c22..c59a894089 100644 --- a/src/pbkdf2.cpp +++ b/src/pbkdf2.cpp @@ -28,71 +28,6 @@ be32enc(void *pp, uint32_t x) } - -/* Initialize an HMAC-SHA256 operation with the given key. */ -void -HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) -{ - unsigned char pad[64]; - unsigned char khash[32]; - const unsigned char * K = (const unsigned char *)_K; - size_t i; - - /* If Klen > 64, the key is really SHA256(K). */ - if (Klen > 64) { - SHA256_Init(&ctx->ictx); - SHA256_Update(&ctx->ictx, K, Klen); - SHA256_Final(khash, &ctx->ictx); - K = khash; - Klen = 32; - } - - /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ - SHA256_Init(&ctx->ictx); - memset(pad, 0x36, 64); - for (i = 0; i < Klen; i++) - pad[i] ^= K[i]; - SHA256_Update(&ctx->ictx, pad, 64); - - /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ - SHA256_Init(&ctx->octx); - memset(pad, 0x5c, 64); - for (i = 0; i < Klen; i++) - pad[i] ^= K[i]; - SHA256_Update(&ctx->octx, pad, 64); - - /* Clean the stack. */ - memset(khash, 0, 32); -} - -/* Add bytes to the HMAC-SHA256 operation. */ -void -HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void *in, size_t len) -{ - - /* Feed data to the inner SHA256 operation. */ - SHA256_Update(&ctx->ictx, in, len); -} - -/* Finish an HMAC-SHA256 operation. */ -void -HMAC_SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX * ctx) -{ - unsigned char ihash[32]; - - /* Finish the inner SHA256 operation. */ - SHA256_Final(ihash, &ctx->ictx); - - /* Feed the inner hash to the outer SHA256 operation. */ - SHA256_Update(&ctx->octx, ihash, 32); - - /* Finish the outer SHA256 operation. */ - SHA256_Final(digest, &ctx->octx); - - /* Clean the stack. */ - memset(ihash, 0, 32); -} - /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and @@ -102,7 +37,8 @@ void PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) { - HMAC_SHA256_CTX PShctx, hctx; + CHMAC_SHA256 bare(passwd, passwdlen); + CHMAC_SHA256 salted(passwd, passwdlen); size_t i; uint8_t ivec[4]; uint8_t U[32]; @@ -112,8 +48,7 @@ PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t clen; /* Compute HMAC state after processing P and S. */ - HMAC_SHA256_Init(&PShctx, passwd, passwdlen); - HMAC_SHA256_Update(&PShctx, salt, saltlen); + salted.Write(salt, saltlen); /* Iterate through the blocks. */ for (i = 0; i * 32 < dkLen; i++) { @@ -121,18 +56,18 @@ PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, be32enc(ivec, (uint32_t)(i + 1)); /* Compute U_1 = PRF(P, S || INT(i)). */ - memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); - HMAC_SHA256_Update(&hctx, ivec, 4); - HMAC_SHA256_Final(U, &hctx); + CHMAC_SHA256 U_1 = salted; + U_1.Write(ivec, 4); + U_1.Finalize(U); /* T_i = U_1 ... */ memcpy(T, U, 32); for (j = 2; j <= c; j++) { /* Compute U_j. */ - HMAC_SHA256_Init(&hctx, passwd, passwdlen); - HMAC_SHA256_Update(&hctx, U, 32); - HMAC_SHA256_Final(U, &hctx); + CHMAC_SHA256 U_j = bare; + U_j.Write(U, 32); + U_j.Finalize(U); /* ... xor U_j ... */ for (k = 0; k < 32; k++) @@ -145,8 +80,5 @@ PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, clen = 32; memcpy(&buf[i * 32], T, clen); } - - /* Clean PShctx, since we never called _Final on it. */ - memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX)); } diff --git a/src/pbkdf2.h b/src/pbkdf2.h index c585a129c1..905622dab5 100644 --- a/src/pbkdf2.h +++ b/src/pbkdf2.h @@ -3,22 +3,9 @@ #ifndef BITCOIN_PBKDF2_H #define BITCOIN_PBKDF2_H -#include -#include - -typedef struct HMAC_SHA256Context { - SHA256_CTX ictx; - SHA256_CTX octx; -} HMAC_SHA256_CTX; - -void -HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen); +#include -void -HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void *in, size_t len); - -void -HMAC_SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX * ctx); +#include void PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 8ca72b5136..296d640118 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -9,10 +9,16 @@ #include #include -namespace +namespace { + +struct Secp256k1SelfTester { -/* Global secp256k1_context object used for verification. */ -secp256k1_context* secp256k1_context_verify = nullptr; + Secp256k1SelfTester() { + /* Run libsecp256k1 self-test before using the secp256k1_context_static. */ + secp256k1_selftest(); + } +} SECP256K1_SELFTESTER; + } // namespace /** This function is taken from the libsecp256k1 distribution and implements @@ -25,7 +31,7 @@ secp256k1_context* secp256k1_context_verify = nullptr; * strict DER before being passed to this module, and we know it supports all * violations present in the blockchain before that point. */ -int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) { +int ecdsa_signature_parse_der_lax(secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) { size_t rpos, rlen, spos, slen; size_t pos = 0; size_t lenbyte; @@ -33,7 +39,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_ int overflow = 0; /* Hack to initialize sig with a correctly-parsed but invalid signature. */ - secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + secp256k1_ecdsa_signature_parse_compact(secp256k1_context_static, sig, tmpsig); /* Sequence tag byte */ if (pos == inputlen || input[pos] != 0x30) { @@ -156,13 +162,13 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_ } if (!overflow) { - overflow = !secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + overflow = !secp256k1_ecdsa_signature_parse_compact(secp256k1_context_static, sig, tmpsig); } if (overflow) { /* Overwrite the result again with a correctly-parsed but invalid signature if parsing failed. */ memset(tmpsig, 0, 64); - secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + secp256k1_ecdsa_signature_parse_compact(secp256k1_context_static, sig, tmpsig); } return 1; } @@ -172,18 +178,17 @@ bool CPubKey::Verify(const uint256 &hash, const std::vector& vchS return false; secp256k1_pubkey pubkey; secp256k1_ecdsa_signature sig; - assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); - if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, vch, size())) { + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, vch, size())) { return false; } - if (!ecdsa_signature_parse_der_lax(secp256k1_context_verify, &sig, vchSig.data(), vchSig.size())) { + if (!ecdsa_signature_parse_der_lax(&sig, vchSig.data(), vchSig.size())) { return false; } /* libsecp256k1's ECDSA verification requires lower-S signatures, which have * not historically been enforced in Bitcoin, so normalize them first. */ // This however is not the case with Gridcoin. - // secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, &sig, &sig); - return secp256k1_ecdsa_verify(secp256k1_context_verify, &sig, hash.begin(), &pubkey); + // secp256k1_ecdsa_signature_normalize(secp256k1_context_static, &sig, &sig); + return secp256k1_ecdsa_verify(secp256k1_context_static, &sig, hash.begin(), &pubkey); } bool CPubKey::RecoverCompact(const uint256 &hash, const std::vector& vchSig) { @@ -193,16 +198,15 @@ bool CPubKey::RecoverCompact(const uint256 &hash, const std::vector& vchSig) { secp256k1_ecdsa_signature sig; - assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use CPubKey."); - if (!ecdsa_signature_parse_der_lax(secp256k1_context_verify, &sig, vchSig.data(), vchSig.size())) { + if (!ecdsa_signature_parse_der_lax(&sig, vchSig.data(), vchSig.size())) { return false; } - return (!secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, nullptr, &sig)); -} - -/* static */ int ECCVerifyHandle::refcount = 0; - -ECCVerifyHandle::ECCVerifyHandle() -{ - if (refcount == 0) { - assert(secp256k1_context_verify == nullptr); - secp256k1_context_verify = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - assert(secp256k1_context_verify != nullptr); - } - refcount++; -} - -ECCVerifyHandle::~ECCVerifyHandle() -{ - refcount--; - if (refcount == 0) { - assert(secp256k1_context_verify != nullptr); - secp256k1_context_destroy(secp256k1_context_verify); - secp256k1_context_verify = nullptr; - } -} - -const secp256k1_context* GetVerifyContext() { - return secp256k1_context_verify; + return (!secp256k1_ecdsa_signature_normalize(secp256k1_context_static, nullptr, &sig)); } diff --git a/src/pubkey.h b/src/pubkey.h index 8ecf617a79..eb27ae1d73 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -258,21 +258,4 @@ struct CExtPubKey { bool Derive(CExtPubKey& out, unsigned int nChild) const; }; -/** Users of this module must hold an ECCVerifyHandle. The constructor and - * destructor of these are not allowed to run in parallel, though. */ -class ECCVerifyHandle -{ - static int refcount; - -public: - ECCVerifyHandle(); - ~ECCVerifyHandle(); -}; - -typedef struct secp256k1_context_struct secp256k1_context; - -/** Access to the internal secp256k1 context used for verification. Only intended to be used - * by key.cpp. */ -const secp256k1_context* GetVerifyContext(); - #endif // BITCOIN_PUBKEY_H diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt new file mode 100644 index 0000000000..9a763d4e37 --- /dev/null +++ b/src/qt/CMakeLists.txt @@ -0,0 +1,207 @@ +# Translations +# ============ +add_subdirectory(locale) + + +# libgridcoinqt +# ============= + +add_library(gridcoinqt STATIC + aboutdialog.cpp + addressbookpage.cpp + addresstablemodel.cpp + askpassphrasedialog.cpp + bantablemodel.cpp + bitcoinaddressvalidator.cpp + bitcoinamountfield.cpp + bitcoingui.cpp + bitcoinunits.cpp + clicklabel.cpp + clientmodel.cpp + coincontroldialog.cpp + coincontroltreewidget.cpp + consolidateunspentdialog.cpp + consolidateunspentwizard.cpp + consolidateunspentwizardselectdestinationpage.cpp + consolidateunspentwizardselectinputspage.cpp + consolidateunspentwizardsendpage.cpp + csvmodelwriter.cpp + decoration.cpp + diagnosticsdialog.cpp + editaddressdialog.cpp + editsidestakedialog.cpp + favoritespage.cpp + guiutil.cpp + intro.cpp + monitoreddatamapper.cpp + mrcmodel.cpp + mrcrequestpage.cpp + noresult.cpp + notificator.cpp + optionsdialog.cpp + optionsmodel.cpp + overviewpage.cpp + peertablemodel.cpp + qtipcserver.cpp + qvalidatedlineedit.cpp + qvaluecombobox.cpp + receivecoinspage.cpp + researcher/projecttablemodel.cpp + researcher/researchermodel.cpp + researcher/researcherwizard.cpp + researcher/researcherwizardauthpage.cpp + researcher/researcherwizardbeaconpage.cpp + researcher/researcherwizardemailpage.cpp + researcher/researcherwizardinvestorpage.cpp + researcher/researcherwizardmodedetailpage.cpp + researcher/researcherwizardmodepage.cpp + researcher/researcherwizardpoolpage.cpp + researcher/researcherwizardpoolsummarypage.cpp + researcher/researcherwizardprojectspage.cpp + researcher/researcherwizardsummarypage.cpp + rpcconsole.cpp + sendcoinsdialog.cpp + sendcoinsentry.cpp + sidestaketablemodel.cpp + signverifymessagedialog.cpp + trafficgraphwidget.cpp + transactiondesc.cpp + transactiondescdialog.cpp + transactionfilterproxy.cpp + transactionrecord.cpp + transactiontablemodel.cpp + transactionview.cpp + updatedialog.cpp + upgradeqt.cpp + voting/additionalfieldstableview.cpp + voting/additionalfieldstablemodel.cpp + voting/poll_types.cpp + voting/pollcard.cpp + voting/pollcardview.cpp + voting/polldetails.cpp + voting/pollresultchoiceitem.cpp + voting/pollresultdialog.cpp + voting/polltab.cpp + voting/polltablemodel.cpp + voting/pollwizard.cpp + voting/pollwizarddetailspage.cpp + voting/pollwizardprojectpage.cpp + voting/pollwizardsummarypage.cpp + voting/pollwizardtypepage.cpp + voting/votewizard.cpp + voting/votewizardballotpage.cpp + voting/votewizardsummarypage.cpp + voting/votingmodel.cpp + voting/votingpage.cpp + walletmodel.cpp + winshutdownmonitor.cpp + + bitcoin.qrc + bitcoin_locale.qrc +) + +if(WIN32) + target_sources(gridcoinqt PRIVATE res/gridcoinresearch.rc) +elseif(APPLE) + target_sources(gridcoinqt PRIVATE + macdockiconhandler.mm + macnotificationhandler.mm + macos_appnap.mm + ) +endif() + +set_target_properties(gridcoinqt PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + + AUTOUIC_SEARCH_PATHS "${CMAKE_SOURCE_DIR}/src;${CMAKE_SOURCE_DIR}/src/qt/forms" +) + +# These files include 'node/ui_interface.cpp', which AutoUIC tries to process +set_source_files_properties( + bitcoin.cpp + clientmodel.cpp + mrcmodel.cpp + qtipcserver.cpp + researcher/researchermodel.cpp + sidestaketablemodel.cpp + transactiondesc.cpp + transactiontablemodel.cpp + voting/votingmodel.cpp + walletmodel.cpp + PROPERTIES + SKIP_AUTOUIC ON +) + +# Libraries to link +# ================= + +target_link_libraries(gridcoinqt PUBLIC + Qt5::Concurrent + Qt5::Core + Qt5::Gui + Qt5::Network + Qt5::Widgets + gridcoin_util +) + +if(APPLE) + target_link_libraries(gridcoinqt PUBLIC + "-framework Foundation" + "-framework ApplicationServices" + "-framework AppKit" + ) +endif() + +target_compile_definitions(gridcoinqt PUBLIC HAVE_CONFIG_H) + +if(USE_DBUS) + target_link_libraries(gridcoinqt PUBLIC Qt5::DBus) +endif() + +if(ENABLE_UPNP) + if(DEFAULT_UPNP) + target_compile_definitions(gridcoinqt PRIVATE USE_UPNP=1) + else() + target_compile_definitions(gridcoinqt PRIVATE USE_UPNP=0) + endif() +endif() + +add_dependencies(gridcoinqt gridcoinqt_l10n) + + +# Application +# =========== + +add_executable(gridcoinresearch WIN32 MACOSX_BUNDLE bitcoin.cpp) + +target_link_libraries(gridcoinresearch PRIVATE + Qt5::Widgets + gridcoin_util + gridcoinqt +) + +if(UNIX AND NOT APPLE) + include(GNUInstallDirs) + install(TARGETS gridcoinresearch + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + install(DIRECTORY "${CMAKE_SOURCE_DIR}/share/icons" + DESTINATION "${CMAKE_INSTALL_DATADIR}" + ) + install(FILES "${CMAKE_SOURCE_DIR}/contrib/gridcoinresearch.desktop" + DESTINATION "${CMAKE_INSTALL_DATADIR}/applications" + ) + install(FILES "${CMAKE_SOURCE_DIR}/doc/gridcoinresearch.1" + DESTINATION "${CMAKE_INSTALL_MANDIR}" + ) +endif() + + +# Tests +# ===== + +if(ENABLE_TESTS) + add_subdirectory(test) +endif() diff --git a/src/qt/aboutdialog.cpp b/src/qt/aboutdialog.cpp index 332b677763..0f18d289d4 100755 --- a/src/qt/aboutdialog.cpp +++ b/src/qt/aboutdialog.cpp @@ -2,15 +2,44 @@ #include "qt/decoration.h" #include "ui_aboutdialog.h" #include "clientmodel.h" +#include "updatedialog.h" +#include "util.h" AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); - ui->copyrightLabel->setText("Copyright 2009-2022 The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"); + + QString copyrightText = "Copyright 2009-"; + std::variant copyright_year = COPYRIGHT_YEAR; + + try { + copyrightText += QString::number(std::get(copyright_year)); + } catch (const std::bad_variant_access& e) { + try { + copyrightText += std::get(copyright_year); + } catch (const std::bad_variant_access& e) { + copyrightText += "Present"; + } + } + + copyrightText += " The Bitcoin/Peercoin/Black-Coin/Gridcoin developers"; + + ui->copyrightLabel->setText(copyrightText); resize(GRC::ScaleSize(this, width(), height())); + + if (!fTestNet && !gArgs.GetBoolArg("-disableupdatecheck", false)) { + connect(ui->versionInfoButton, &QAbstractButton::pressed, this, [this]() { handlePressVersionInfoButton(); }); + } else if (gArgs.GetBoolArg("-disableupdatecheck", false)) { + ui->versionInfoButton->setDisabled(true); + ui->versionInfoButton->setToolTip(tr("Version information and update check has been disabled " + "by config or startup parameter.")); + } else { + ui->versionInfoButton->setDisabled(true); + ui->versionInfoButton->setToolTip(tr("Version information is not available on testnet.")); + } } void AboutDialog::setModel(ClientModel *model) @@ -30,3 +59,28 @@ void AboutDialog::on_buttonBox_accepted() { close(); } + +void AboutDialog::handlePressVersionInfoButton() +{ + std::string client_message_out; + std::string change_log; + GRC::Upgrade::UpgradeType upgrade_type = GRC::Upgrade::UpgradeType::Unknown; + + + GRC::Upgrade::CheckForLatestUpdate(client_message_out, change_log, upgrade_type, false, false); + + if (client_message_out == std::string {}) { + client_message_out = "No response from GitHub - check network connectivity."; + change_log = " "; + } + + UpdateDialog update_dialog; + + update_dialog.setWindowTitle("Gridcoin Version Information"); + update_dialog.setVersion(QString().fromStdString(client_message_out)); + update_dialog.setUpgradeType(static_cast(upgrade_type)); + update_dialog.setDetails(QString().fromStdString(change_log)); + update_dialog.setModal(false); + + update_dialog.exec(); +} diff --git a/src/qt/aboutdialog.h b/src/qt/aboutdialog.h index 1b1dba84d7..a517b262b0 100644 --- a/src/qt/aboutdialog.h +++ b/src/qt/aboutdialog.h @@ -23,6 +23,7 @@ class AboutDialog : public QDialog private slots: void on_buttonBox_accepted(); + void handlePressVersionInfoButton(); }; #endif // BITCOIN_QT_ABOUTDIALOG_H diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index d9c91c51c3..fcc2edc1b7 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -42,7 +42,7 @@ class AddressBookPage : public QDialog const QString &getReturnValue() const { return returnValue; } public slots: - void done(int retval); + void done(int retval) override; void exportClicked(); void changeFilter(const QString& needle); void resizeTableColumns(const bool& neighbor_pair_adjust = false, const int& index = 0, diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 5061ff236b..f96967e59e 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -84,11 +84,10 @@ void AskPassphraseDialog::accept() oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); newpass2.reserve(MAX_PASSPHRASE_SIZE); - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make this input mlock()'d to begin with. - oldpass.assign(ui->oldPassphraseEdit->text().toStdString().c_str()); - newpass1.assign(ui->newPassphraseEdit->text().toStdString().c_str()); - newpass2.assign(ui->repeatNewPassphraseEdit->text().toStdString().c_str()); + + oldpass.assign(std::string_view{ui->oldPassphraseEdit->text().toStdString()}); + newpass1.assign(std::string_view{ui->newPassphraseEdit->text().toStdString()}); + newpass2.assign(std::string_view{ui->repeatNewPassphraseEdit->text().toStdString()}); secureClearPassFields(); @@ -135,10 +134,20 @@ void AskPassphraseDialog::accept() } break; case UnlockStaking: case Unlock: - if(!model->setWalletLocked(false, oldpass)) - { - QMessageBox::critical(this, tr("Wallet unlock failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); + if(!model->setWalletLocked(false, oldpass)) { + // Check if the passphrase has a null character + if (oldpass.find('\0') == std::string::npos) { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 5.4.6, " + "please try again with only the characters up to — but not including — " + "the first null character. If this is successful, please set a new " + "passphrase to avoid this issue in the future.")); + } } else { @@ -157,8 +166,18 @@ void AskPassphraseDialog::accept() } else { - QMessageBox::critical(this, tr("Wallet encryption failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); + // Check if the old passphrase had a null character + if (oldpass.find('\0') == std::string::npos) { + QMessageBox::critical(this, tr("Passphrase change failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QMessageBox::critical(this, tr("Passphrase change failed"), + tr("The old passphrase entered for the wallet decryption is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 5.4.6, " + "please try again with only the characters up to — but not including — " + "the first null character.")); + } } } else diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 82bd5a8ab1..24028b6be3 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -192,16 +192,16 @@ static void InitMessage(const std::string &message) } } -static void UpdateMessageBox(const std::string& version, const std::string& message) +static void UpdateMessageBox(const std::string& version, const int& update_version, const std::string& message) { std::string caption = _("Gridcoin Update Available"); if (guiref) { - std::string guiaddition = version + _("Click \"Show Details\" to view changes in latest update."); QMetaObject::invokeMethod(guiref, "update", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), - Q_ARG(QString, QString::fromStdString(guiaddition)), + Q_ARG(QString, QString::fromStdString(version)), + Q_ARG(int, update_version), Q_ARG(QString, QString::fromStdString(message))); } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index becaeb4abd..37fa568a96 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -20,6 +20,7 @@ #include "signverifymessagedialog.h" #include "optionsdialog.h" #include "aboutdialog.h" +#include "voting/polltab.h" #include "voting/votingpage.h" #include "clientmodel.h" #include "walletmodel.h" @@ -43,6 +44,8 @@ #include "univalue.h" #include "upgradeqt.h" #include "voting/votingmodel.h" +#include "voting/polltablemodel.h" +#include "updatedialog.h" #ifdef Q_OS_MAC #include "macdockiconhandler.h" @@ -106,7 +109,14 @@ BitcoinGUI::BitcoinGUI(QWidget* parent) , nWeight(0) { QSettings settings; - if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { + + QString window_geometry_key = "MainWindowGeometry"; + + if (GetDataDir() != GetDefaultDataDir()) { + window_geometry_key += "_" + QString().fromStdString(SanitizeString(GetDataDir().string())); + } + + if (!restoreGeometry(settings.value(window_geometry_key).toByteArray())) { // Restore failed (perhaps missing setting), center the window setGeometry(QStyle::alignedRect(Qt::LeftToRight,Qt::AlignCenter,QDesktopWidget().availableGeometry(this).size() * 0.4,QDesktopWidget().availableGeometry(this))); @@ -236,7 +246,14 @@ BitcoinGUI::BitcoinGUI(QWidget* parent) BitcoinGUI::~BitcoinGUI() { QSettings settings; - settings.setValue("MainWindowGeometry", saveGeometry()); + + QString window_geometry_key = "MainWindowGeometry"; + + if (GetDataDir() != GetDefaultDataDir()) { + window_geometry_key += "_" + QString().fromStdString(SanitizeString(GetDataDir().string())); + } + + settings.setValue(window_geometry_key, saveGeometry()); if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); #ifdef Q_OS_MAC @@ -539,7 +556,8 @@ void BitcoinGUI::createMenuBar() file->addAction(verifyMessageAction); file->addSeparator(); - if (!gArgs.GetBoolArg("-testnet", false)) + // Snapshot GUI menu action disabled due to snapshot CDN abuse in 202308. + if (/* !gArgs.GetBoolArg("-testnet", false) */ false) { file->addAction(snapshotAction); } @@ -1142,22 +1160,20 @@ void BitcoinGUI::error(const QString &title, const QString &message, bool modal) } } -void BitcoinGUI::update(const QString &title, const QString& version, const QString &message) +void BitcoinGUI::update(const QString &title, const QString& version, const int& upgrade_type, const QString &message) { - // Create our own message box; A dialog can go here in future for qt if we choose - - updateMessageDialog.reset(new QMessageBox); + updateMessageDialog.reset(new UpdateDialog); updateMessageDialog->setWindowTitle(title); - updateMessageDialog->setText(version); - updateMessageDialog->setDetailedText(message); - updateMessageDialog->setIcon(QMessageBox::Information); - updateMessageDialog->setStandardButtons(QMessageBox::Ok); + updateMessageDialog->setVersion(version); + updateMessageDialog->setUpgradeType(static_cast(upgrade_type)); + updateMessageDialog->setDetails(message); updateMessageDialog->setModal(false); - connect(updateMessageDialog.get(), &QMessageBox::finished, [this](int) { updateMessageDialog.reset(); }); + + connect(updateMessageDialog.get(), &QDialog::finished, this, [this]() { updateMessageDialog.reset(); }); + // Due to slight delay in gui load this could appear behind the gui ui // The only other option available would make the message box stay on top of all applications - QTimer::singleShot(5000, updateMessageDialog.get(), [this]() { updateMessageDialog->show(); }); } @@ -1870,7 +1886,7 @@ void BitcoinGUI::updateBeaconIcon() if (researcherModel->hasPendingBeacon()) { labelBeaconIcon->setToolTip(tr("CPID: %1\n" - "Time left to activate: %2" + "Time left to activate: %2\n" "%3") .arg(researcherModel->formatCpid(), researcherModel->formatTimeToPendingBeaconExpiration(), @@ -1917,11 +1933,25 @@ void BitcoinGUI::handleNewPoll() overviewPage->setCurrentPollTitle(votingModel->getCurrentPollTitle()); } +//! +//! \brief BitcoinGUI::extracted. Helper function to avoid container detach on range loop warning. +//! \param expiring_polls +//! \param notification +//! +void BitcoinGUI::extracted(QStringList& expiring_polls, QString& notification) +{ + for (const auto& expiring_poll : expiring_polls) { + notification += expiring_poll + "\n"; + } +} + void BitcoinGUI::handleExpiredPoll() { - // The only difference between this and handleNewPoll() is no call to the event notifier. + if (!clientModel) { + return; + } - if (!clientModel || !clientModel->getOptionsModel()) { + if (!clientModel->getOptionsModel()) { return; } @@ -1929,6 +1959,30 @@ void BitcoinGUI::handleExpiredPoll() return; } + // Only do if in sync. + if (researcherModel && !researcherModel->outOfSync() && votingPage->getActiveTab()) { + + // First refresh the active poll tab and underlying table + votingPage->getActiveTab()->refresh(); + + if (!clientModel->getOptionsModel()->getDisablePollNotifications()) { + QStringList expiring_polls = votingModel->getExpiringPollsNotNotified(); + + if (!expiring_polls.isEmpty()) { + QString notification = tr("The following poll(s) are about to expire:\n"); + + extracted(expiring_polls, notification); + + notification += tr("Open Gridcoin to vote."); + + notificator->notify( + Notificator::Information, + tr("Poll(s) about to expire"), + notification); + } + } + } + overviewPage->setCurrentPollTitle(votingModel->getCurrentPollTitle()); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index eec17f954f..e1934f8348 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -20,6 +20,7 @@ class WalletModel; class ResearcherModel; class MRCModel; class VotingModel; +class PollTableModel; class TransactionView; class OverviewPage; class FavoritesPage; @@ -31,6 +32,7 @@ class Notificator; class RPCConsole; class DiagnosticsDialog; class ClickLabel; +class UpdateDialog; QT_BEGIN_NAMESPACE class QLabel; @@ -109,7 +111,7 @@ class BitcoinGUI : public QMainWindow TransactionView *transactionView; VotingPage *votingPage; SignVerifyMessageDialog *signVerifyMessageDialog; - std::unique_ptr updateMessageDialog; + std::unique_ptr updateMessageDialog; QLabel *statusbarAlertsLabel; QLabel *labelEncryptionIcon; @@ -204,7 +206,7 @@ public slots: void setEncryptionStatus(int status); /** Notify the user if there is an update available */ - void update(const QString& title, const QString& version, const QString& message); + void update(const QString& title, const QString& version, const int& upgrade_type, const QString& message); /** Notify the user of an error in the network or transaction handling code. */ void error(const QString &title, const QString &message, bool modal); @@ -295,6 +297,7 @@ private slots: QString GetEstimatedStakingFrequency(unsigned int nEstimateTime); void handleNewPoll(); + void extracted(QStringList& expiring_polls, QString& notification); void handleExpiredPoll(); }; diff --git a/src/qt/editsidestakedialog.cpp b/src/qt/editsidestakedialog.cpp new file mode 100644 index 0000000000..18596ac7ca --- /dev/null +++ b/src/qt/editsidestakedialog.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "editsidestakedialog.h" +#include "ui_editsidestakedialog.h" +#include "sidestaketablemodel.h" +#include "guiutil.h" +#include "qt/decoration.h" + +#include + +EditSideStakeDialog::EditSideStakeDialog(Mode mode, QWidget* parent) + : QDialog(parent) + , ui(new Ui::EditSideStakeDialog) + , mode(mode) + , model(nullptr) +{ + ui->setupUi(this); + + resize(GRC::ScaleSize(this, width(), height())); + + GUIUtil::setupAddressWidget(ui->addressLineEdit, this); + + switch (mode) + { + case NewSideStake: + setWindowTitle(tr("New SideStake")); + ui->statusLineEdit->setEnabled(false); + ui->statusLabel->setHidden(true); + ui->statusLineEdit->setHidden(true); + break; + case EditSideStake: + setWindowTitle(tr("Edit SideStake")); + ui->addressLineEdit->setEnabled(false); + ui->statusLabel->setHidden(false); + ui->statusLineEdit->setHidden(false); + ui->statusLineEdit->setEnabled(false); + break; + } + +} + +EditSideStakeDialog::~EditSideStakeDialog() +{ + delete ui; +} + +void EditSideStakeDialog::setModel(SideStakeTableModel* model) +{ + this->model = model; + if (!model) { + return; + } + +} + +void EditSideStakeDialog::loadRow(int row) +{ + m_row = row; + + ui->addressLineEdit->setText(model->index(row, SideStakeTableModel::Address, QModelIndex()).data(Qt::EditRole).toString()); + ui->allocationLineEdit->setText(model->index(row, SideStakeTableModel::Allocation, QModelIndex()).data(Qt::EditRole).toString()); + ui->descriptionLineEdit->setText(model->index(row, SideStakeTableModel::Description, QModelIndex()).data(Qt::EditRole).toString()); + ui->statusLineEdit->setText(model->index(row, SideStakeTableModel::Status, QModelIndex()).data(Qt::EditRole).toString()); +} + +bool EditSideStakeDialog::saveCurrentRow() +{ + if (!model) { + return false; + } + + bool success = true; + + switch (mode) + { + case NewSideStake: + address = model->addRow(ui->addressLineEdit->text(), + ui->allocationLineEdit->text(), + ui->descriptionLineEdit->text()); + + if (address.isEmpty()) { + success = false; + } + + break; + case EditSideStake: + QModelIndex index = model->index(m_row, SideStakeTableModel::Allocation, QModelIndex()); + model->setData(index, ui->allocationLineEdit->text(), Qt::EditRole); + + if (model->getEditStatus() == SideStakeTableModel::OK || model->getEditStatus() == SideStakeTableModel::NO_CHANGES) { + index = model->index(m_row, SideStakeTableModel::Description, QModelIndex()); + model->setData(index, ui->descriptionLineEdit->text(), Qt::EditRole); + + if (model->getEditStatus() == SideStakeTableModel::OK || model->getEditStatus() == SideStakeTableModel::NO_CHANGES) { + break; + } + } + + success = false; + + break; + } + + return success; +} + +void EditSideStakeDialog::accept() +{ + if (!model) { + return; + } + + if (!saveCurrentRow()) + { + switch (model->getEditStatus()) + { + case SideStakeTableModel::OK: + // Failed with unknown reason. Just reject. + break; + case SideStakeTableModel::NO_CHANGES: + // No changes were made during edit operation. Just reject. + break; + case SideStakeTableModel::INVALID_ADDRESS: + QMessageBox::warning(this, windowTitle(), + tr("The entered address \"%1\" is not " + "a valid Gridcoin address.").arg(ui->addressLineEdit->text()), + QMessageBox::Ok, QMessageBox::Ok); + break; + case SideStakeTableModel::DUPLICATE_ADDRESS: + QMessageBox::warning(this, windowTitle(), + tr("The entered address \"%1\" already " + "has a local sidestake entry.").arg(ui->addressLineEdit->text()), + QMessageBox::Ok, QMessageBox::Ok); + break; + case SideStakeTableModel::INVALID_ALLOCATION: + QMessageBox::warning(this, windowTitle(), + tr("The entered allocation is not valid. Check to make sure that the " + "allocation is greater than zero and when added to the other allocations " + "totals less than 100."), + QMessageBox::Ok, QMessageBox::Ok); + break; + case SideStakeTableModel::INVALID_DESCRIPTION: + QMessageBox::warning(this, windowTitle(), + tr("The entered description is not valid. Check to make sure that the " + "description only contains letters, numbers, spaces, periods, or " + "underscores."), + QMessageBox::Ok, QMessageBox::Ok); + } + + return; + } + + QDialog::accept(); +} diff --git a/src/qt/editsidestakedialog.h b/src/qt/editsidestakedialog.h new file mode 100644 index 0000000000..2da58052ad --- /dev/null +++ b/src/qt/editsidestakedialog.h @@ -0,0 +1,50 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_EDITSIDESTAKEDIALOG_H +#define BITCOIN_QT_EDITSIDESTAKEDIALOG_H + +#include + +QT_BEGIN_NAMESPACE +class QDataWidgetMapper; +QT_END_NAMESPACE + +namespace Ui { +class EditSideStakeDialog; +} +class SideStakeTableModel; + +/** Dialog for editing an address and associated information. + */ +class EditSideStakeDialog : public QDialog +{ + Q_OBJECT + +public: + enum Mode { + NewSideStake, + EditSideStake + }; + + explicit EditSideStakeDialog(Mode mode, QWidget* parent = nullptr); + ~EditSideStakeDialog(); + + void setModel(SideStakeTableModel* model); + void loadRow(int row); + +public slots: + void accept(); + +private: + bool saveCurrentRow(); + + Ui::EditSideStakeDialog *ui; + Mode mode; + SideStakeTableModel *model; + int m_row; + + QString address; +}; +#endif // BITCOIN_QT_EDITSIDESTAKEDIALOG_H diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui index c67bdbac35..e3952591f1 100644 --- a/src/qt/forms/aboutdialog.ui +++ b/src/qt/forms/aboutdialog.ui @@ -136,6 +136,43 @@ This product includes software developed by the OpenSSL Project for use in the O + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Version Information + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/qt/forms/editsidestakedialog.ui b/src/qt/forms/editsidestakedialog.ui new file mode 100644 index 0000000000..a2dd71f36b --- /dev/null +++ b/src/qt/forms/editsidestakedialog.ui @@ -0,0 +1,173 @@ + + + EditSideStakeDialog + + + + 0 + 0 + 400 + 300 + + + + Add or Edit SideStake + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Address + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Allocation + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Description + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Status + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + EditSideStakeDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditSideStakeDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index ee3632e12c..c1c7d8038a 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -16,8 +16,8 @@ true - - + + QTabWidget::North @@ -245,83 +245,147 @@ Staking - - - - 10 - 10 - 651 - 135 - - - - - - - This enables or disables staking (the default is enabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. - - - Enable Staking - - - - - - - This enables or disables splitting of stake outputs to optimize staking (default disabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. - - - Enable Stake Splitting - - - - - - - - - Target Efficiency - - - - - - - Valid values are between 75 and 98 percent. Note that a change to this setting will permanently override the config file with an entry in the settings file. - - - - - - - Min Post Split UTXO - - - - - - - Valid values are 800 or greater. Note that a change to this setting will permanently override the config file with an entry in the settings file. - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - + + + + + + + This enables or disables staking (the default is enabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + + + Enable Staking + + + + + + + This enables or disables splitting of stake outputs to optimize staking (default disabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + + + Enable Stake Splitting + + + + + + + + + Target Efficiency + + + + + + + Valid values are between 75 and 98 percent. Note that a change to this setting will permanently override the config file with an entry in the settings file. + + + + + + + Min Post Split UTXO + + + + + + + Valid values are 800 or greater. Note that a change to this setting will permanently override the config file with an entry in the settings file. + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Enable Locally Specified Sidestaking + + + + + + + true + + + + + + + + + New + + + + :/icons/add:/icons/add + + + + + + + false + + + Edit + + + + :/icons/edit:/icons/edit + + + + + + + false + + + Delete + + + + :/icons/remove:/icons/remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + @@ -369,6 +433,37 @@ + + + + + + Hours before poll expiry reminder + + + + + + + Valid values are between 0.25 and 168.0 hours. + + + + + + + Qt::Horizontal + + + + 80 + 20 + + + + + + @@ -503,7 +598,7 @@ - + @@ -585,7 +680,7 @@ QValidatedLineEdit QLineEdit -
qvalidatedlineedit.h
+
qvalidatedlineedit.h
QValueComboBox @@ -593,6 +688,8 @@
qvaluecombobox.h
- + + + diff --git a/src/qt/forms/updatedialog.ui b/src/qt/forms/updatedialog.ui new file mode 100644 index 0000000000..d713ec8b28 --- /dev/null +++ b/src/qt/forms/updatedialog.ui @@ -0,0 +1,98 @@ + + + UpdateDialog + + + + 0 + 0 + 609 + 430 + + + + Dialog + + + + + + + + icon + + + + + + + version + + + + + + + + + false + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + changelog + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + UpdateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UpdateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/forms/voting/pollcard.ui b/src/qt/forms/voting/pollcard.ui index 112505846d..10312b84d6 100644 --- a/src/qt/forms/voting/pollcard.ui +++ b/src/qt/forms/voting/pollcard.ui @@ -201,14 +201,14 @@ - Your Last Vote: + Your Vote(s): - Your Vote Weight: + Your Vote Weight(s): @@ -298,6 +298,13 @@
+ + + + Stale + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 7082b578a5..f43dc85779 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -48,7 +48,7 @@ QString dateTimeStr(qint64 nTime) QString formatPingTime(double dPingTime) { - return (dPingTime == std::numeric_limits::max()/1e6 || dPingTime == 0) ? + return (dPingTime >= static_cast(std::numeric_limits::max()) / 1e6 || dPingTime <= 0) ? QObject::tr("N/A") : QObject::tr("%1 ms").arg(QString::number((int)(dPingTime * 1000), 10)); } diff --git a/src/qt/locale/CMakeLists.txt b/src/qt/locale/CMakeLists.txt new file mode 100644 index 0000000000..696f09dcdf --- /dev/null +++ b/src/qt/locale/CMakeLists.txt @@ -0,0 +1,76 @@ +set(TS_FILES + bitcoin_af_ZA.ts + bitcoin_ar.ts + bitcoin_be_BY.ts + bitcoin_bg.ts + bitcoin_ca.ts + bitcoin_ca@valencia.ts + bitcoin_ca_ES.ts + bitcoin_cs.ts + bitcoin_cy.ts + bitcoin_da.ts + bitcoin_de.ts + bitcoin_el_GR.ts + bitcoin_en.ts + bitcoin_eo.ts + bitcoin_es.ts + bitcoin_es_CL.ts + bitcoin_es_DO.ts + bitcoin_es_MX.ts + bitcoin_es_UY.ts + bitcoin_et.ts + bitcoin_eu_ES.ts + bitcoin_fa.ts + bitcoin_fa_IR.ts + bitcoin_fi.ts + bitcoin_fr.ts + bitcoin_fr_CA.ts + bitcoin_gl.ts + bitcoin_he.ts + bitcoin_hi_IN.ts + bitcoin_hr.ts + bitcoin_hu.ts + bitcoin_id_ID.ts + bitcoin_it.ts + bitcoin_ja.ts + bitcoin_ka.ts + bitcoin_kk_KZ.ts + bitcoin_ko_KR.ts + bitcoin_ky.ts + bitcoin_la.ts + bitcoin_lt.ts + bitcoin_lv_LV.ts + bitcoin_ms_MY.ts + bitcoin_nb.ts + bitcoin_nl.ts + bitcoin_pam.ts + bitcoin_pl.ts + bitcoin_pt_BR.ts + bitcoin_pt_PT.ts + bitcoin_ro_RO.ts + bitcoin_ru.ts + bitcoin_sk.ts + bitcoin_sl_SI.ts + bitcoin_sq.ts + bitcoin_sr.ts + bitcoin_sv.ts + bitcoin_th_TH.ts + bitcoin_tr.ts + bitcoin_uk.ts + bitcoin_ur_PK.ts + bitcoin_vi.ts + bitcoin_zh_CN.ts + bitcoin_zh_TW.ts +) + +set_source_files_properties(${TS_FILES} PROPERTIES + OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}" +) + +if(LUPDATE) + qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +else() + qt_add_translation(QM_FILES ${TS_FILES}) +endif() + +add_custom_target(gridcoinqt_l10n DEPENDS ${QM_FILES}) diff --git a/src/qt/locale/bitcoin_de.ts b/src/qt/locale/bitcoin_de.ts index fb6bd84a62..dcc5012ea8 100644 --- a/src/qt/locale/bitcoin_de.ts +++ b/src/qt/locale/bitcoin_de.ts @@ -12,7 +12,11 @@ Value Wert - + + Required + Erforderlich + + AddressBookPage @@ -63,6 +67,10 @@ These are your Gridcoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you. Das sind Ihre Gridcoin Adressen um Zahlungen zu erhalten. Sie werden vielleicht verschiedene an jeden Sender vergeben, damit Sie im Auge behalten können wer sie bezahlt. + + Double-click to edit label + Doppelklicken um das Label zu bearbeiten + &Delete &Löschen @@ -79,6 +87,11 @@ Export Address Book Data Exportiere Addressbuch Daten + + Comma separated file + Name of CSV file format + Kommaseparierte Datei + Error exporting Fehler berichten @@ -206,6 +219,13 @@ Warnung: Die Feststelltaste ist aktiviert! + + BanTableModel + + IP/Netmask + IP/Netzmaske + + BitcoinGUI @@ -236,6 +256,10 @@ Show the list of addresses for receiving payments Zeige die Liste der Addressen für die Erhaltung von Zahlungen + + &History + &Historie + Browse transaction history Transaktionsverlauf durchsehen @@ -264,6 +288,14 @@ &Web Site &Webseite + + &GRC Chat Room + &GRC Chatraum + + + GRC Chatroom + GRC Chatraum + Gridcoin rewards distributed computing with BOINC Gridcoin belohnt verteiltest Rechnen mit BOINC @@ -308,6 +340,10 @@ &Encrypt Wallet... Wallet &verschlüsseln... + + Encrypt wallet + Wallet verschlüsseln + &Change Passphrase... Passphrase &ändern... @@ -368,6 +404,10 @@ &Help &Hilfe + + Toggle light/dark mode. + Hell-/Dunkelmodus umschalten. + [testnet] [Testnetz] @@ -446,6 +486,14 @@ Incoming transaction Eingehende Transaktion + + Close Confirmation + Beenden bestätigen + + + Exit the Gridcoin wallet? + Die Gridcoinwallet beenden? + URI handling URI Handhabung @@ -514,6 +562,10 @@ %1 times per %2 %1 Mal pro %2 + + none + keine + ClientModel @@ -552,6 +604,10 @@ Change: Wechselgeld: + + Select All + Alles auswählen + Amount Betrag @@ -682,13 +738,45 @@ ConsolidateUnspentWizardSelectInputsPage + + Address + Adresse + Date Datum + + Confirmations + Bestätigungen + + + Confirmed + Bestätigt + + + Quantity + Anzahl + + + Fee + Gebühr + DiagnosticsDialog + + Diagnostics + Diagnose + + + Close + Schließen + + + Passed + Bestanden + Warning Warnung @@ -777,6 +865,10 @@ Welcome Willkommen + + Error + Fehler + %n GB of free space available @@ -805,6 +897,14 @@ Form Formular + + Nothing here yet... + Noch nichts hier... + + + No results available. + Keine Ergebnisse verfügbar. + Loading... Lade... @@ -852,6 +952,14 @@ Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. Minimiert die Anwendung anstatt sie zu beenden wenn das Fenster geschlossen wird. Wenn dies aktiviert ist, müssen Sie das Programm über "Beenden" im Menü schließen. + + Disable Transaction Notifications + Transaktionsbenachrichtigungen deaktivieren + + + Disable Poll Notifications + Abstimmungsbenachrichtigungen deaktivieren + The user interface language can be set here. This setting will take effect after restarting Gridcoin. Die Sprache der GUI kann hier verändert werden. Die Einstellung wird nach einem Neustart übernommen. @@ -876,6 +984,10 @@ Show only a tray icon after minimizing the window. Nur ein Symbol im Infobereich anzeigen, nachdem das Programmfenster minimiert wurde. + + Start minimized + Minimiert starten + &Minimize to the tray instead of the taskbar In den Infobereich anstatt in die Taskleiste &minimieren @@ -976,6 +1088,17 @@ Schwierigkeit: + + PeerTableModel + + Sent + Gesendet + + + Received + Empfangen + + PollCard @@ -1535,7 +1658,11 @@ WARNING: unknown change address WARNUNG: Unbekannte Wechseladdresse - + + Active + Aktiv + + SendCoinsEntry @@ -2018,12 +2145,23 @@ bis + + VotingModel + + Poll not found. + Abstimmung nicht gefunden. + + VotingPage Sort by... Sortieren nach... + + &Active + &Aktiv + WalletModel diff --git a/src/qt/locale/bitcoin_pt_PT.ts b/src/qt/locale/bitcoin_pt_PT.ts index 3737f96cad..d3c4bcc10b 100644 --- a/src/qt/locale/bitcoin_pt_PT.ts +++ b/src/qt/locale/bitcoin_pt_PT.ts @@ -316,11 +316,11 @@ Este produto inclui desenvolvimento de software pelo Projeto OpenSSL para uso em Web Site - Website + Sítio Web &Web Site - &Website + &Site Web &GRC Chat Room @@ -460,11 +460,11 @@ Este produto inclui desenvolvimento de software pelo Projeto OpenSSL para uso em &Reset blockchain data - &Repor dados da blockchain + &Repor dados da cadeia de blocos Remove blockchain data and start chain from zero - Remover dados da blockchain e começar a cadeira do zero + Remover dados da cadeia de blocos e começar a cadeia do zero &Mask values @@ -608,15 +608,15 @@ Endereço: %4 Warning: Canceling after stage 2 will result in sync from 0 or corrupted blockchain files. - Aviso: Cancelar depois do 2º passo irá resultar em sincronizar tudo do "0", ou ficheiros corrompidos no blockchain. + Aviso: Cancelar depois do 2º passo irá resultar em sincronizar tudo do "0", ou ficheiros corrompidos na cadeia de blocos. Do you want to delete blockchain data and sync from zero? - Tem a certeza que quer eliminar os dados da blockchain e começar a sincronização do zero? + Tem a certeza que quer eliminar os dados da cadeia de blocos e começar a sincronização do zero? Warning: After the blockchain data is deleted, the wallet will shutdown and when restarted will begin syncing from zero. Your balance will temporarily show as 0 GRC while syncing. - Aviso: Depois dos dados da blockchain serem eliminados, a carteira irá encerrar e ao reiniciar, irá começar a sincronizar do zero. O seu balanço irá temporariamente aparecer como 0 GRC enquanto sincroniza. + Aviso: Depois dos dados da cadeia de blocos serem eliminados, a carteira irá encerrar e ao reiniciar, irá começar a sincronizar do zero. O seu balanço irá temporariamente aparecer como 0 GRC enquanto sincroniza. Close Confirmation @@ -754,6 +754,12 @@ Projeto(s) excluído(s): %2. CPID: %1 +Time left to activate: %2%3 + CPID: %1 +Tempo restante para ativar: %2%3 + + + CPID: %1 Beacon age: %2 Current beacon expired! %3 @@ -774,11 +780,11 @@ Expira: %3 New Poll - Nova Votação + Nova Sondagem A new poll is available. Open Gridcoin to vote. - Uma nova votação está disponível. Abra o Gridcoin para votar. + Uma nova sondagem está disponível. Abra o Gridcoin para votar. @@ -850,6 +856,10 @@ Expira: %3 Filter Filtro + + Pushing this button after making a input selection either manually or with the filter will present a destination address list where you specify a single address as the destination for the consolidated output. The send (Pay To) entry will be filled in with this address and you can finish the consolidation by pressing the send button. + Ao premir este botão depois de fazer uma seleção de entrada manualmente ou com o filtro, é apresentada uma lista de endereços de destino onde pode especificar um único endereço como destino para a saída consolidada. A entrada de envio (Pagar a) será preenchida com este endereço e pode terminar a consolidação premindo o botão de enviar. + Consolidate Consolidado @@ -930,6 +940,10 @@ Expira: %3 Copy change Copiar modificação + + Flips the filter mode between selecting inputs less than or equal to the provided value (<=) and greater than or equal to the provided value (>=). The filter also automatically limits the number of inputs to %1, in ascending order for <= and descending order for >=. + Alterna o modo de filtro entre a seleção de entradas menores ou iguais ao valor fornecido (<=) e maiores ou iguais ao valor fornecido (>=). O filtro também limita automaticamente o número de entradas a %1, em ordem crescente para <= e decrescente para >=. + Select None Não Selecionar Nenhum @@ -1027,6 +1041,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. WizardPage Página do Assistente + + Step 2: Select the destination address for the consolidation transaction. Note that all of the selected inputs will be consolidated to an output on this address. If there is a very small amount of change (due to uncertainty in the fee calculation), it will also be sent to this address. If you selected inputs only from a particular address on the previous page, then that address will already be selected by default. + Passo 2: Selecionar o endereço de destino para a transação de consolidação. Note que todas as entradas selecionadas serão consolidadas numa saída neste endereço. Se houver um valor muito pequeno de mudança (devido à incerteza no cálculo da taxa), ele também será enviado para este endereço. Se na página anterior tiver selecionado apenas entradas de um determinado endereço, esse endereço já estará selecionado por defeito. + Label Etiqueta @@ -1270,11 +1288,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. One or more tests have generated a warning status. Wallet operation may be degraded. Please see the individual test tooltips for details and recommended action(s). - Um ou mais testes geraram um estado de alerta. Operações com a carteira podem estar corrompidas. Por favor, veja as dicas individuais de teste para detalhes e ações recomendadas. + Um ou mais testes geraram um estado de alerta. Operações com a carteira podem estar corrompidas. Por favor, consulte as dicas individuais de teste para detalhes e ações recomendadas. One or more tests have failed. Proper wallet operation may be significantly degraded or impossible. Please see the individual test tooltips for details and recommended action(s). - Um ou mais testes falharam. Operações adequadas com a carteira, podem estar signifcamente degradadas ou impossiveis. Por favor, veja as dicas indivuais de teste para detalhes e ações recomendadas. + Um ou mais testes falharam. Operações adequadas com a carteira, podem estar significativamente degradadas ou impossíveis. Por favor, consulte as dicas individuais de teste para detalhes e ações recomendadas. All tests passed. Your wallet operation is normal. @@ -1410,6 +1428,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Use a custom data directory: Utilizar uma diretoria de dados personalizada: + + When you click OK, %1 will begin to download and process the full %4 block chain (~%2GB), either from genesis in %3, or the last synchronized block, if this was a preexisting data directory. + Quando clicar em OK, %1 começará a transferir e a processar toda a cadeia de blocos %4 (~%2GB), quer a partir da génese em %3, ou a partir do último bloco sincronizado, se este for um diretório de dados pré-existente. + + + The synchronization is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off. + A sincronização é muito exigente e pode expor problemas de hardware do seu computador que anteriormente tivessem passado despercebidos. Cada vez que executar o %1, ele continuará a transferência onde parou anteriormente. + Error: Specified data directory "%1" cannot be created. Erro: Diretoria de dados especificada "%1" não pode ser criada. @@ -1442,10 +1468,34 @@ Isto significa que uma taxa de pelo menos %2 é necessária. MRCModel + + You must have a mature balance of at least 1 GRC to submit an MRC. + Tem que ter um balanço com maturidade de pelo menos 1 GRC para poder submeter um pedido de MRC. + + + Too soon since your last research rewards payment. + Muito cedo desde o último pagamento de recompensa de pesquisa. + + + The total fee (the minimum fee + fee boost) is greater than the rewards due. + A taxa total (taxa mínima + taxa de boost) é maior que os ganhos da recompensa. + + + Your MRC was successfully submitted earlier but has now become stale without being bound to the just received block by a staker. This may be because your MRC was submitted just before the block was staked and the MRC didn't make it to the staker in time, or your MRC was pushed down in the queue past the pay limit. Please wait for the next block to clear the queue and try again. + O seu MRC foi submetido com sucesso mais cedo, mas agora tornou-se obsoleto sem estar ligado ao bloco recém recebido por um staker. Isto pode acontecerr porque o seu MRC foi submetida pouco antes do bloco ter sido staked e o MRC não chegou a tempo ao staker, ou o seu MRC desceu na fila para além do limite de pagamentos. Por favor, aguarde pelo próximo bloco para limpar a fila de espera e tente novamente. + You have a pending MRC request. Tem uma solicitação de MRC pendente. + + Your MRC was successfully submitted, but other MRCs with higher fees have pushed your MRC down in the queue past the pay limit, and your MRC will be canceled. Wait until the next block is received and the queue clears and try again. Your fee for the canceled MRC will be refunded. + O seu MRC foi submetido com êxito, mas outros MRCs com taxas mais altas, fizeram o seu MRC baixar na fila, ultrapassando o limite de pagamentos, e o seu MRC será cancelado. Aguarde até que o próximo bloco seja recebido e a fila seja limpa e tente novamente. A sua taxa para o MRC cancelado será reembolsada. + + + The MRC queue is full. You can try boosting your fee to put your MRC request in the queue and displace another MRC request. + A fila do MRC está cheia. Pode tentar aumentar a sua taxa para colocar o seu pedido MRC na fila e remover da fila outro pedido de MRC. + The wallet is locked. A carteira está trancada. @@ -1465,6 +1515,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. MRC Fee @ Pay Limit Position in Queue Taxa MRC @ Posição Limite de Pagamento na Fila + + MRC Fee @ Tail of Queue + Taxa MRC @ Na Cauda da Fila + + + Your projected or actual position among MRCs in the memory pool ordered by MRC fee in descending order + A sua posição projetada ou real entre os MRCs na reserva de memória, ordenada por taxa de MRC por ordem decrescente + Number of All MRC Requests in Queue Número de Todos os Pedidos MRC na Fila @@ -1473,6 +1531,50 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The number of MRCs in the memory pool Número de pedidos MRC em espera + + Your Projected MRC Request Position in Queue + A sua posição prevista do seu pedido MRC na fila de espera + + + The MRC fee being paid by the MRC in the last position within the pay limit in the memory pool + A taxa de MRC que está a ser paga pelo MRC na última posição dentro do limite de remuneração na reserva de memória + + + MRC Request Pay Limit per Block + Limite de pagamento por bloco do pedido MRC + + + Your MRC Calculated Minimum Fee + A sua taxa mínima calculada pelo MRC + + + The calculated minimum fee for the MRC. This may not be sufficient to submit the MRC if the queue is already full. In that case, you need to use the MRC fee boost to raise the fee to get your MRC in the queue. + A taxa mínima calculada para o MRC. Este valor pode não ser suficiente para submeter o MRC se a fila de espera já estiver cheia. Nesse caso, é necessário que faça um boost da taxa de MRC para a aumentar, colocando o MRC na fila de espera. + + + The lowest MRC fee being paid of MRCs in the memory pool + A taxa mais baixa de MRC que está a ser paga de MRCs na reserva de memória + + + The maximum number of MRCs that can be paid per block + O número máximo de MRCs que pode ser pago por bloco + + + The highest MRC fee being paid of MRCs in the memory pool + A taxa mais alta de MRC que está a ser paga de MRCs na reserva de memória + + + MRC Fee @ Head of Queue + Taxa MRC @ No Topo da Fila + + + MRC Fee Boost + Aumento da taxa de inscrição no MRC + + + Raise to Minimum For Submit + Subir para o Mínimo para Submeter + Update Atualizar @@ -1501,7 +1603,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A block update must have occurred after wallet start or sync to submit MRCs. Uma atualização do bloco tem de ocorrer depois da carteira iniciar ou sincronizar, para submeter um pedido MRC. - + + You must have a mature balance of at least 1 GRC to submit an MRC. + Tem que ter um balanço com maturidade de pelo menos 1 GRC para poder submeter um pedido de MRC. + + NoResult @@ -1583,9 +1689,33 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Staking A realizar stake + + This enables or disables staking (the default is enabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + Isto ativa ou desativa o staking (por predefinição está ativado). Note que uma alteração a esta definição irá substituir permanentemente o ficheiro de configuração por uma entrada no ficheiro de definições. + Enable Staking - Habilitar Realização de Stake + Ativar Staking + + + This enables or disables splitting of stake outputs to optimize staking (default disabled). Note that a change to this setting will permanently override the config file with an entry in the settings file. + Isso permite ativar ou desativar a divisão das saídas de stake para otimizar a aposta (desativado por padrão). Note que uma alteração nesta configuração irá substituir permanentemente o arquivo de configuração com uma entrada no ficheiro de configurações. + + + Enable Stake Splitting + Ativar Divisão de Stake + + + Target Efficiency + Eficiência do Objetivo + + + Min Post Split UTXO + Mínimo Pós-divisão UTXO + + + Valid values are 800 or greater. Note that a change to this setting will permanently override the config file with an entry in the settings file. + Os valores válidos são 800 ou superior. Note que uma alteração a esta definição substituirá permanentemente o ficheiro de configuração por uma entrada no ficheiro de definições. Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Quit in the menu. @@ -1601,7 +1731,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Disable Poll Notifications - Desabilitar Notificações de Votações + Desabilitar Notificações de Sondagens The user interface language can be set here. This setting will take effect after restarting Gridcoin. @@ -1647,6 +1777,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Disable &update checks Desabilitar verificações de &atualização + + Return change to an input address for contract transactions + Devolver o troco para um endereço de entrada para transações de contrato + + + Connect to the Gridcoin network through a SOCKS5 proxy (e.g. when connecting through Tor). + Ligar à rede Gridcoin através de um proxy SOCKS5 (por exemplo, quando a ligação é feita através do Tor). + &Connect through SOCKS5 proxy: &Ligar através de proxy SOCKS5: @@ -1711,7 +1849,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The supplied proxy address is invalid. O endereço de proxy introduzido é inválido. - + + The supplied target staking efficiency is invalid. + A eficiência de staking do alvo fornecido é inválida. + + + The supplied minimum post stake-split UTXO size is invalid. + O tamanho mínimo de UTXO pós-divisão de stack fornecido é inválido. + + OverviewPage @@ -1806,17 +1952,25 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Researcher Investigador + + Open the Manual Reward Claim (MRC) request page + Abrir a página de pedido de Pedido de Recompensa Manual (MRC) + Status: Estado: + + You are approaching the accrual limit of 16384 GRC. If you have a relatively low balance, you should request payment via MRC so that you do not lose earned rewards. + Está a atingir o limite de acumulação de 16384 GRC. Se tiver um saldo relativamente baixo, deve solicitar o pagamento através de MRC para que não perca as recompensas ganhas. + Recent Transactions Transações Recentes Current Polls - Votações Atuais + Sondagens Atuais Out of Sync @@ -1874,7 +2028,19 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll Type - Tipo de Votação + Tipo de Sonadagem + + + Your Last Vote: + O Seu Último Voto: + + + Your Vote Weight: + O Peso do Seu Voto: + + + Your % of AVW: + A Sua % de AVW Balance @@ -1938,11 +2104,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollResultDialog Poll Details - Detalhes da Votação + Detalhes da Sondagem Poll ID - ID da Votação + ID da Sondagem @@ -1976,7 +2142,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll Type - Tipo de Votação + Tipo de Sondagem Duration @@ -2015,22 +2181,22 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizard Create a Poll - Criar uma Votação + Criar uma Sondagem PollWizardDetailsPage Poll Details - Detalhes da Votação + Detalhes da Sondagem Some fields are locked for the selected poll type. - Alguns campos estão bloqueados para o tipo de votação selecionada. + Alguns campos estão bloqueados para o tipo de sondagem selecionada. Poll Type: - Tipo de Votação: + Tipo de Sondagem: Duration: @@ -2070,7 +2236,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A poll with a yes/no/abstain response type cannot include any additional custom choices. - Uma votação com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. + Uma sondagem com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. Additional Fields: @@ -2078,7 +2244,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Create Poll - Criar Votação + Criar Sondagem Balance @@ -2102,7 +2268,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. This poll will cost %1 plus a transaction fee. Continue? - Esta votação irá custar %1 para além duma taxa adicional. Continuar? + Esta sondagem irá custar %1 para além duma taxa adicional. Continuar? @@ -2144,11 +2310,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizardSummaryPage Poll Created - Votação Criada + Sondagem Criada The poll will activate with the next block. - A votação será ativada no próximo bloco. + A sondagem será ativada no próximo bloco. Copy ID @@ -2159,15 +2325,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PollWizardTypePage Create a Poll - Criar uma Votação + Criar uma Sondagem The Gridcoin community established guidelines for polls with requirements for each type. Please read the wiki for more information: - A comunidade Gridcoin estabeleceu diretrizes para as votações, com requisitos para cada tipo. Por favor leia a wiki para mais informações: + A comunidade Gridcoin estabeleceu diretrizes para as sondagem, com requisitos para cada tipo. Por favor leia a wiki para mais informações: Choose a poll type: - Escolha o tipo de votação: + Escolha o tipo de sondagem: @@ -2195,6 +2361,17 @@ Isto significa que uma taxa de pelo menos %2 é necessária. QObject + + Error: Cannot parse command line arguments. Please check the arguments and ensure they are valid and formatted correctly: + + + Erro: Não é possível analisar os argumentos na linha de comando. Verifique os argumentos e certifique-se de que são válidos e estão formatados corretamente: + + + + Error: Cannot read configuration file. Please check the path and format of the file. + Erro: Não é possível ler o ficheiro de configuração. Verifique o caminho e o formato do ficheiro. + Error: Specified data directory "%1" does not exist. Erro: A diretoria %1 especificada não existe. @@ -2674,7 +2851,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Not attached Não anexado - + + Uses external adapter + Utiliza adaptador externo + + ResearcherWizard @@ -2698,12 +2879,16 @@ Isto significa que uma taxa de pelo menos %2 é necessária. 1. Sign in to your account at the website for a whitelisted BOINC project. - 1. Entre na sua conta num website dum projeto BOINC na lista aprovada. + 1. Entre na sua conta no sítio web dum projeto BOINC que esteja na lista aprovada. 2. Visit the settings page to change your username. Many projects label it as "other account info". 2. Visite a página de configurações para alterar o nome de utilizador. Muitos projetos identificam como "outra informação da conta" + + 3. Change your "name" (real name or nickname) to the following verification code: + 3. Altere o seu "nome" (nome real ou alcunha) para o seguinte código de verificação: + Copy the verification code to the system clipboard Copie o código de verificação para a área de transferência @@ -2714,7 +2899,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. 4. Some projects will not export your statistics by default. If available, enable the privacy setting that gives consent to the project to export your statistics data. Many projects place this setting on the "Preferences for this Project" page and label it as "Do you consent to exporting your data to BOINC statistics aggregation web sites?" - 4. Alguns projetos não irão exportar as suas estatísticas por defeito. Se a opção estiver disponível, deve alterar as definições de privacidade que consentem que um projeto possa exportar os dados das suas estatísticas. Muitos projetos colocam esta definição na página "Preferências para este Projeto" e etiquetam-na como "Pretende consentir a exportação dos seus dados para websites de estatísticas de agregação do BOINC ?" + 4. Alguns projetos não irão exportar as suas estatísticas por defeito. Se a opção estiver disponível, deve alterar as definições de privacidade que consentem que um projeto possa exportar os dados das suas estatísticas. Muitos projetos colocam esta definição na página "Preferências para este Projeto" e etiquetam-na como "Pretende consentir a exportação dos seus dados para sítios web de estatísticas de agregação do BOINC ?" 5. Wait 24 to 48 hours for the verification process to finish (beacon status will change to "active"). @@ -2911,11 +3096,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. In this mode, a pool will take care of staking research rewards for you. Your wallet can still earn standard staking rewards on your balance. You do not need a BOINC account, CPID, or beacon. Please choose a pool and follow the instructions on the website to sign up and connect the pool's account manager to BOINC: - Neste modo, a pool irá tomar conta de realizar o stake para pesquisar recompensas para si. A sua carteira pode ainda ganhar recompensas normais por realizar stake. Não precisa de uma conta BOINC, CPID ou Beacon. Por favor escolha uma pool e siga as instruções no website para se registar e ligar a uma conta duma pool gestora do BOINC. + Neste modo, a pool irá tomar conta de realizar o stake para pesquisar recompensas para si. A sua carteira pode ainda ganhar recompensas normais por realizar stake. Não precisa de uma conta BOINC, CPID ou Beacon. Por favor escolha uma pool e siga as instruções no sítio web para se registar e ligar a uma conta duma pool gestora do BOINC: Website URL - URL do Website + URL do Sítio Web As you sign up, the pool may ask for a payment address to send earnings to. Press the button below to generate an address. @@ -2945,7 +3130,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Pool Receiving Address Endereço de Receção da Pool - + + Error: failed to generate a new address. + Erro: falhou ao gerar um novo endereço. + + ResearcherWizardPoolSummaryPage @@ -3094,6 +3283,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Beacon renewal available. Renovação do beacon disponível. + + Split CPID or mismatched email. + CPID dividido ou eMail não correspondente. + + + Your projects either refer to more than one CPID or your projects' email do not match what you used to configure Gridcoin here. Please ensure all of your projects are attached using the same email address, the email address matches what was configured here, and if you added a project recently, update that project and then all other projects using the update button in the BOINC manager, then restart the client and recheck. + Os seus projetos referem-se a mais do que um CPID ou, o e-mail dos seus projetos não corresponde ao que utilizou para configurar o Gridcoin. Por favor, certifique-se de que todos os seus projetos estão anexados utilizando o mesmo endereço de e-mail, o que corresponde ao que foi configurado aqui, e se adicionou um projeto recentemente, atualize esse projeto de seguida, todos os outros projetos utilizando o botão de atualização no Gestor BOINC. Por último, reinicie o cliente e verifique novamente. + Waiting for magnitude. Aguardando pela magnitude @@ -3137,6 +3334,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reset Repor + + Consolidate Wizard + Assistente de Consolidação + Quantity: Quantidade: @@ -3503,9 +3704,9 @@ Isto significa que uma taxa de pelo menos %2 é necessária. , broadcast through %n node(s) - - - + + , transmitida através de %n nó + , transmitida através de %n nós @@ -3548,6 +3749,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PoS+RR Side Stake Sent PoS+RR Side Stake Enviado + + MRC Payment Received + Pagamento MRC Recebido + + + MRC Payment Sent + Pagamento MRC Enviado + Mined - Superblock Minada - Super Bloco @@ -3768,6 +3977,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. PoS+RR Side Stake Sent PoS+RR Side Stake Enviado + + MRC Payment Received + Pagamento MRC Recebido + + + MRC Payment Sent + Pagamento MRC Enviado + Mined - Superblock Minada - Super Bloco @@ -3782,12 +3999,16 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll - Votação + Sondagem Vote Voto + + Manual Rewards Claim Request + Solicitação Manual de Reivindicação de Recompensas + Message Mensagem @@ -4009,11 +4230,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Poll not found. - Votação não encontrada. + Sondagem não encontrada. Failed to load poll from disk - Falhou o carregamento das votações do disco + Falhou o carregamento das sondagens do disco @@ -4024,7 +4245,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Polls - Votações + Sondagem Search by title @@ -4048,11 +4269,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Create &Poll - Criar &Votação + Criar &Sondagem A new poll is available. Press "Refresh" to load it. - Uma nova votação está disponível. Carregue em "Atualizar" para a mostrar. + Uma nova sondagem está disponível. Carregue em "Atualizar" para a mostrar. &Active @@ -4090,7 +4311,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. A poll with a yes/no/abstain response type cannot include any additional custom choices. - Uma votação com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. + Uma sondagem com uma resposta do tipo sim/não/abster-se, não pode incluir quaisquer escolhas adicionais. Cannot obtain a lock on data directory %s. %s is probably already running and using that directory. @@ -4100,6 +4321,14 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Cannot obtain a lock on data directory %s. %s is probably already running. Não foi possivel cadear a diretoria de dados %s. %s já está provavelmente em execução. + + Check that BOINC is installed and that you have the correct path in the config file if you installed it to a nonstandard location. + Verifique que o BOINC está instalado e que tem o caminho correto no ficheiro de configuração, caso o tenha instalado numa localização não definida por padrão. + + + Error: Clock skew is 5 minutes or greater. Please check your clock settings. + Erro: O sincronização do relógio é de 5 minutos ou mais. Verifique as definições do seu relógio. + Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here. Erro: A transação foi rejeitada. Isso pode acontecer se algumas das na sua moedas na carteira já tiverem sido gastas, se tiver utilizado uma cópia da wallet.dat e as moedas não tiverem sido marcadas como gastas aqui. @@ -4108,21 +4337,153 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds Erro: Esta transação devido à sua quantia, complexidade ou utilização de fundos recebidos recentemente, necessita de uma taxa de transação de pelo menos %s + + For initiatives related to the Gridcoin community not covered by other poll types. + Para iniciativas relacionadas com a Comunidade Gridcoin, não abrangidas por outros tipos de sondagem. + + + For polls about community representation, public relations, and communications. + Para sondagens sobre representação comunitária, relações públicas e comunicações. + + + Please check your network and also check the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + Por favor, verifique a sua rede e também o ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, poderá querer aguardar mais alguns minutos para que as ligações sejam estabelecidas e testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Please ensure that you have followed the process to advertise and verify your beacon. You can use the research wizard (the beacon button on the overview screen). + Certifique-se de que seguiu o processo para publicitar e verificar o seu beacon. Pode utilizar o assistente de pesquisa (o botão beacon no ecrã de resumo). + + + Poll additional field value "%s" for field name "%s" exceeds %s characters. + A sondagem do valor de campo adicional "%s" para o nome de campo "%s" excede os %s caracteres. + + + Proposals related to Gridcoin management like poll requirements and funding. + Propostas relacionadas com a gestão do Gridcoin, como requisitos de sondagem e financiamento. + + + Propose additions or removals of computing projects for research reward eligibility. + Propor adições ou remoções de projetos de computação para a elegibilidade de prémios de investigação. + + + The IP for the port test site is unable to be resolved. This could mean your DNS is not working correctly. The wallet may operate without DNS, but it could be severely degraded, especially if the wallet is new and a database of prior successful connections has not been built up. Please check your computer and ensure name resolution is operating correctly. + Não é possível resolver o IP do site de teste de portas. Isto pode significar que o seu DNS não está a funcionar corretamente. A carteira pode funcionar sem DNS, mas pode ser gravemente degradada, especialmente se a carteira for nova e não tiver sido criada uma base de dados de ligações anteriores bem sucedidas. Verifique o seu computador e certifique-se de que a resolução de nomes está a funcionar corretamente. + + + The connection to the port test site was refused. This could be a transient problem with the port test site, but could also be an issue with your firewall. If you are also failing the connection test, your firewall is most likely blocking network communications from the Gridcoin client. + A ligação ao site de teste de portas foi recusada. Isto pode ser um problema transitório com o site de teste de portas, mas também pode ser um problema com a sua firewall. Se também estiver a falhar o teste de ligação, a sua firewall está provavelmente a bloquear as comunicações de rede do cliente Gridcoin. + + + The network has experienced a low-level error and this probably means your IP address or other network connection parameters are not configured correctly. Please check your network configuration on your computer. + A rede sofreu um erro de baixo nível, o que provavelmente significa que o seu endereço IP ou outros parâmetros de ligação à rede não estão corretamente configurados. Verifique a configuração da rede no seu computador. + + + The network is reporting an unspecified socket error. If you also are failing the connection test, then please check your computer's network configuration. + A rede está a reportar um erro de socket não especificado. Se também falhar o teste de ligação, verifique a configuração de rede do seu computador. + + + The port test site is closed on port. This could be a transient problem with the port test site, but could also be an issue with your firewall. If you are also failing the connection test, your firewall is most likely blocking network communications from the Gridcoin client. + O sítio de teste de portas está fechado na porta. Isto pode ser um problema transitório com o site de teste de portas, mas também pode ser um problema com a sua firewall. Se também estiver a falhar o teste de ligação, a sua firewall está muito provavelmente a bloquear as comunicações de rede do cliente Gridcoin. + + + The wallet has less than five connections to the network and is unable to connect to an NTP server to check your computer clock. This is not necessarily a problem. You can wait a few minutes and try the test again. + A carteira tem menos de cinco ligações à rede e não consegue ligar-se a um servidor NTP para verificar o relógio do seu computador. Isto não é necessariamente um problema. Pode esperar alguns minutos e repetir o teste novamente. + The wallet will now shutdown. Please start your wallet to begin sync from zero A carteira irá encerrar. Por favor, inicie a sua carteira para começar a sincronização do zero + + There is a new leisure version available and you should upgrade as soon as practical. + Existe uma nova versão de lazer disponível e deve ser atualizada assim que possível. + + + There is a new mandatory version available and you should upgrade as soon as possible to ensure your wallet remains in consensus with the network. + Existe uma nova versão obrigatória disponível e deve atualizá-la o mais rapidamente possível para garantir que a sua carteira se mantém em consenso com a rede. + + + Verify (1) that you have BOINC installed correctly, (2) that you have attached at least one whitelisted project, (3) that you advertised your beacon with the same email as you use for your BOINC project(s), and (4) that the CPID on the overview screen matches the CPID when you login to your BOINC project(s) online. + Verifique (1) se o BOINC está instalado corretamente, (2) se adicionou pelo menos um projeto da whitelist, (3) se anunciou seu beacon com o mesmo e-mail que usa para o(s) seu(s) projeto(s) BOINC e (4) se o CPID no ecrã de visão geral corresponde ao CPID quando inicia sessão no(s) seu(s) projeto(s) BOINC on-line. + + + Verify that you have actually completed workunits for the projects you have attached and that you have authorized the export of statistics. Please see https://gridcoin.us/guides/whitelist.htm. + Verifique se as unidades de trabalho dos projetos que adicionou foram efetivamente concluídas e se autorizou a exportação de estatísticas. Consultar https://gridcoin.us/guides/whitelist.htm. + WARNING: A mandatory release is available. Please upgrade as soon as possible. AVISO: Uma versão obrigatória do cliente Gridcoin está disponível. Por favor atualize o mais rapidamente possível. + + Warning: Clock skew is between 3 and 5 minutes. Please check your clock settings. + Aviso: O desvio do relógio situa-se entre 3 e 5 minutos. Verifique as definições do seu relógio. + + + Warning: ETTS is > 90 days. It will take a very long time to receive your research rewards by staking - increase balance or use MRC + Aviso: O ETTS é > 90 dias. Vai demorar muito tempo a receber as suas recompensas de investigação por staking - aumente o saldo ou utilize o MRC + + + Warning: ETTS is infinite. No coins to stake - increase balance or use MRC + Aviso: O ETTS é infinito. Não há moedas para realizar stake - aumente o saldo ou use o MRC + Warning: Ending this process after Stage 2 will result in syncing from 0 or an incomplete/corrupted blockchain. - Aviso: Acabar este processo antes da Fase 2 irão resultar numa sincronização do "0", ou num blockchain incompleto/corrupto. + Aviso: Acabar este processo antes da Fase 2 irá resultar numa sincronização do "0", ou numa cadeia de blocos incompleta/corrupta. + + + You have no balance and will be unable to retrieve your research rewards when solo crunching by staking. You can use MRC to retrieve your rewards, or you should acquire GRC to stake so you can retrieve your research rewards. Please see https://gridcoin.us/guides/boinc-install.htm. + Não tens saldo e não será possível recuperar as tuas recompensas de investigação quando estiveres a fazer crunch a solo por staking. Podes usar MRC para recuperar as tuas recompensas, ou deverás adquirir GRC para fazer stake e poderes recuperar as tuas recompensas de investigação. Consulte https://gridcoin.us/guides/boinc-install.htm. + + + You will not be able to stake because you have less than %1 connection(s). Please check your network and also check the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + Não será possível realizar stake porque tem menos de %1 ligação(ões). Por favor, verifique a sua rede e verifique também o ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode querer esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your balance is low given the current network difficulty to stake in a reasonable period of time to retrieve your research rewards when solo crunching. You should consider acquiring more GRC to stake more often, or else use MRC to retrieve your rewards. + O seu saldo é baixo dada a dificuldade atual da rede em realizar stake num período de tempo razoável para recuperar as suas recompensas de investigação quando faz crunch a solo. Deve considerar a aquisição de mais GRC para realizar stake mais vezes, ou então usar MRC para recuperar as suas recompensas. + + + Your balance is too low given the current network difficulty to stake in a reasonable period of time to retrieve your research rewards when solo crunching. You can use MRC to retrieve your rewards, or you should acquire more GRC to stake more often. + O teu saldo é demasiado baixo tendo em conta a dificuldade atual da rede, para poderes realizar stake num período de tempo razoável para recuperares as tuas recompensas de investigação quando fazes crunch a solo. Podes usar MRC para recuperar as tuas recompensas, ou deverás adquirir mais GRC para realizar stake mais vezes. + + + Your clock in your computer is significantly off from UTC or network time and this may seriously degrade the operation of the wallet, including maintaining connection to the network. You should check your time and time zone settings for your computer. A very common problem is the off by one hour caused by a time zone issue or problems with daylight savings time. + O relógio do seu computador está significativamente desfasado da hora UTC ou da hora da rede, o que pode degradar seriamente o funcionamento da carteira, incluindo a manutenção da ligação à rede. Deve verificar as definições da hora e do fuso horário do seu computador. Um problema muito comum é o desfasamento de uma hora causado por um problema de fuso horário ou por problemas com a hora de Verão. + + + Your difficulty is extremely low and your wallet is almost certainly forked. Please ensure you are running the latest version and try removing the blockchain database and resyncing from genesis using the menu option. (Note this will take 2-4 hours.) + A sua dificuldade é extremamente baixa e a sua carteira está quase de certeza bifurcada. Por favor, certifique-se de que está a correr a versão mais recente e tente remover a base de dados da cadeia de blocos e voltar a sincronizar a partir do génese usando a opção de menu. (Note que isso levará de 2-4 horas). + + + Your difficulty is low but your wallet is still in initial sync. Please recheck it later to see if this passes. + A sua dificuldade é baixa, mas a sua carteira ainda está na sincronização inicial. Volte a verificar mais tarde para ver se isto ficou resolvido. + + + Your difficulty is very low and your wallet is probably forked. Please ensure you are running the latest version and try removing the blockchain database and resyncing from genesis using the menu option. (Note this will take 2-4 hours.) + A sua dificuldade é muito baixa e a sua carteira está provavelmente bifurcada. Por favor, certifique-se de que está a correr a versão mais recente e tente remover a base de dados da cadeia de blocos e volte a sincronizar a partir da génese usando a opção de menu. (Observe que isso levará de 2-4 horas). + + + Your outbound connection count is critically low. Please check your the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + A sua contagem de ligações de saída é criticamente baixa. Por favor, verifique o seu ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode ter de esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your outbound connection count is low. Please check your the config file and ensure your addnode entries are up-to-date. If you recently started the wallet, you may want to wait another few minutes for connections to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/. + A sua contagem de ligações de saída é baixa. Por favor, verifique o seu ficheiro de configuração e certifique-se de que as suas entradas addnode estão atualizadas. Se iniciou recentemente a carteira, pode ter de esperar mais alguns minutos para que as ligações se estabeleçam e depois testar novamente. Consulte https://gridcoin.us/wiki/config-file.html e https://addnodes.cycy.me/. + + + Your wallet is not in sync and has not previously been in sync during this run, please wait for the wallet to sync and retest. If there are other failures preventing the wallet from syncing, please correct those items and retest to see if this test passes. + A sua carteira não está sincronizada e não esteve anteriormente sincronizada durante esta execução, aguarde que a carteira sincronize e volte a testar. Se existirem outras falhas que impeçam a sincronização da carteira, corrija esses itens e volte a testar para ver se este teste é concluído com sucesso. + + + Your wallet is out of sync with the network but was in sync before. If this fails there is likely a severe problem that is preventing the wallet from syncing. If the lack of sync is due to network connection issues, you will see failures on the network connection test(s). If the network connections pass, but your wallet fails this test, and continues to fail this test on repeated attempts with a few minutes in between, this could indicate a more serious issue. In that case you should check the debug log to see if it sheds light on the cause for no sync. + A sua carteira está fora de sincronia com a rede, mas estava sincronizada anteriormente. Se isto falhar, é provável que exista um problema grave que esteja a impedir a sincronização da carteira. Se a falta de sincronização se dever a problemas de ligação à rede, verá falhas no(s) teste(s) de ligação à rede. Se as ligações de rede passarem, mas a sua carteira falhar este teste, e continuar a falhar este teste em tentativas repetidas com alguns minutos de intervalo, isto pode indicar um problema mais sério. Nesse caso, deve verificar o registo de depuração para ver se este esclarece a causa da falha na sincronização. + + + Your wallet is still in initial sync. If this is a sync from the beginning (genesis), the sync process can take from 2 to 4 hours, or longer on a slow computer. If you have synced your wallet before but you just started the wallet up, then wait a few more minutes and retry the diagnostics again. + A sua carteira ainda está na sincronização inicial. Se se tratar de uma sincronização desde o início (génese), o processo de sincronização pode demorar de 2 a 4 horas, ou mais num computador lento. Se já sincronizou a sua carteira antes, mas acabou de a iniciar, aguarde mais alguns minutos e tente executar novamente o diagnóstico. A poll choice cannot be empty. - A escolha duma votação não pode estar vazia. + A escolha duma sondagem não pode estar vazia. Are you sure you want to cancel the snapshot operation? @@ -4138,7 +4499,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. CPID count polls are not supported. - Contagem de votações CPID não são suportadas. + Contagem de sondagens CPID não são suportadas. Cancel snapshot operation? @@ -4166,15 +4527,15 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Duplicate poll additional field: %s - Duplicar campo adicional na votação: %s + Duplicar campo adicional na sondagem: %s Duplicate poll choice: %s - Duplicar escolhas da votação: %s + Duplicar escolhas da sondagem: %s Duplicate response for poll choice: %s - Duplicar respostas para escolha da votação %s + Duplicar respostas para escolha da sondagem %s Entire balance reserved @@ -4190,7 +4551,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Exceeded the number of choices in the poll: %s - Excedidos número de escolhas na votação: %s + Excedidos número de escolhas na sondagem: %s Failed to download snapshot.zip; See debug.log @@ -4200,6 +4561,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Failed to rename bootstrap file to .old for backup purposes. Falhou a alteração de nome por motivos de backups no ficheiro bootstrap para .old. + + Failed: 80 block difficulty is less than + Falhou: a dificuldade de 80 blocos é menor que + Failed: Count = Falhou: Contagem = @@ -4214,7 +4579,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. For opinion or casual polls without any particular requirements. - Para opiniões ou votações casuais sem requisitos especiais. + Para opiniões ou sondagens casuais sem requisitos especiais. Get help for a command @@ -4262,7 +4627,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Magnitude-only polls are not supported. - Magnitude-pools apenas não são suportadas. + Sondagens de Magnitude apenas não são suportadas. Multiple Choice @@ -4314,15 +4679,27 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Participant count polls are not supported. - Contagens de participantes das votações não são suportadas. + Contagens de participantes das sondagens não são suportadas. + + + Passed: 80 block difficulty is + Sucesso: A dificuldade de 80 blocos é + + + Passed: Count = + Sucesso: Contagem = + + + Passed: ETTS = + Sucesso: ETTS = Please enter a poll discussion website URL. - Por favor insira o URL do website para a discussão da votação. + Por favor insira o URL do sítio web para a discussão da sondagem. Please enter a poll title. - Por favor insira o titulo da votação. + Por favor insira o titulo da sondagem. Please enter at least one response. @@ -4330,47 +4707,75 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Please enter at least two poll choices. - Por favor insira pelo menos duas escolhas na votação. + Por favor insira pelo menos duas escolhas na sondagem. + + + Poll additional field name "%s" exceeds %s characters. + O nome do campo adicional da sondagem "%s" excede os %s caracteres. + + + Poll cannot contain more than %s additional fields + A sondagem não pode conter mais de %s campos adicionais Poll cannot contain more than %s choices. - Votação não pode conter mais do que %s escolhas. + A sondagem não pode conter mais do que %s escolhas. Poll choice "%s" exceeds %s characters. - Escolha %s da votação excede os %s carateres. + Escolha %s da sondagem excede os %s carateres. Poll discussion URL cannot exceed %s characters. - URL de discussão da votação não pode exceder os %s carateres. + URL de discussão da sondagem não pode exceder os %s carateres. Poll duration cannot exceed %s days. - Duração da votação não pode exceder os %s dias. + Duração da sondagem não pode exceder os %s dias. Poll duration must be at least %s days. - Duração da votação tem de durar pelo menos %s dias. + Duração da sondagem tem de durar pelo menos %s dias. Poll has already finished. - Votação já terminou. + A sondagem já terminou. Poll only allows a single choice. - Votação permite apenas uma escolha única. + A sondagem permite apenas uma escolha única. Poll question cannot exceed %s characters. - Questão da votação não pode exceder %s carateres. + Questão da sondagem não pode exceder %s carateres. Poll signature failed. See debug.log. - Assinatura da votação falhou. Veja debug.log + Assinatura da sondagem falhou. Ver debug.log Poll title cannot exceed %s characters. - Título da votação não pode exceder %s carateres. + Título da sondagem não pode exceder %s carateres. + + + Poll with that title already exists. Please choose another title. + Já existe uma sondagem com esse título. Por favor, escolha outro título. + + + Project Listing + Listagem de Projetos + + + Propose a change to Gridcoin at the protocol level. + Propor uma alteração ao Gridcon ao nível do protocolo. + + + Propose marketing initiatives like ad campaigns. + Propor iniciativas de marketing como campanhas publicitárias. + + + Protocol Development + Protocolo de Desenvolvimento Quorum Hash @@ -4378,7 +4783,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reindexing blockchain from on disk block data files... - Reindexando blockchain dos ficheiros de dados no disco... + Reindexando cadeia de blocos dos ficheiros de dados de blocos no disco... Replaying contracts... @@ -4386,11 +4791,11 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Reset Blockchain Data: Blockchain data removal failed. - Repor Dados da Blockchain: Remoção de dados da Blockchain falhou. + Repor os Dados da Cadeia de Blocos: Remoção de dados da cadeia de blocos falhou. Reset Blockchain Data: Blockchain data removal was a success - Repor Dados da Blockchain: Remoção de dados da Blockchain realizada com sucesso + Repor os Dados da Cadeia de Blocos: Remoção de dados da cadeia de blocos realizada com sucesso Send command to -server or gridcoinresearchd @@ -4422,12 +4827,24 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Stage (3/4): Cleanup blockchain data - Etapa (3/4): Limpando dados do blockchain + Etapa (3/4): Limpando dados da cadeia de blocos Stage (4/4): Extracting snapshot.zip Etapa (4/4): Extraindo o snapshot.zip + + Survey + Questionário + + + The field is not well-formed. + O campo não está bem formado. + + + The field list is not well-formed. + A lista do campo não está bem formada. + The wallet is now shutting down. Please restart your wallet. A carteira está a encerrar. Por favor reinicie a sua carteira. @@ -4436,17 +4853,61 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The wallet will now shutdown. A carteira irá agora encerrar. + + This wallet is almost certainly forked. + A carteira está quase de certeza bifurcada. + + + This wallet is probably forked. + A carteira está provavelmente bifurcada. + + + Unable to create the PID file '%s': %s + Não foi possível criar o ficheiro PID "%s': %s + + + Unknown poll type. This should never happen. + Tipo de sondagem desconhecida. Isto nunca deverá acontecer. + + + Warning: 45 days < ETTS = + Aviso: 45 dias < ETTS = + + + Warning: 80 block difficulty is less than + Aviso: A dificuldade de 80 blocos é menor que + + + Warning: Cannot connect to NTP server + Aviso: Não foi possível ligar ao servidor NTP + + + Warning: Count = + Aviso: Contagem = + + + Wrong Payload version specified for current block height. + Versão de carga útil incorreta especificada para a altura do bloco atual. + Yes/No/Abstain Sim/Não/Abster-se + + You should check your time and time zone settings for your computer. + Deve verificar as definições de hora e fuso horário do seu computador. + You will need to delete the following. Irá necessitar de eliminar o seguinte. "%s" is not a valid poll choice. - %s não é uma escolha válida da votação. + "%s" não é uma escolha válida da sondagem. + + + appears to be blocked. + parece estar bloqueado. leisure @@ -4468,10 +4929,18 @@ Isto significa que uma taxa de pelo menos %2 é necessária. The %s developers Os %s desenvolvedores + + Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported. + Erro: Argumento não suportado -socks encontrado. Definir a versão SOCKS não é mais possível, são apenas suportadas proxies SOCKS5. + Block Version Versão do Bloco + + Block file load progress + Progresso do carregamento do ficheiro de blocos + Block not in index Bloco em falha no index @@ -4534,7 +5003,7 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Importing blockchain data file(s). - Importando ficheiro(s) de dados da blockchain. + Importando ficheiro(s) de dados da cadeia de blocos. Interest @@ -4552,6 +5021,10 @@ Isto significa que uma taxa de pelo menos %2 é necessária. Is Superblock É um Super Bloco + + Latest Version GitHub data response: + Versão mais recente da resposta de dados do GitHub: + Loading Network Averages... Carregando Médias de Rede... @@ -4638,12 +5111,16 @@ por exemplo: alertnotify=echo %%s | mail -s "Gridcoin Alert" admin@foo.com Due to the failure to delete the blockchain data you will be required to manually delete the data before starting your wallet. - Devido à falha em eliminar os dados da blockchain, irá necessitar de eliminar manualmente os dados antes de começar de iniciar a sua carteira. + Devido à falha na eliminação dos dados da cadeia de blocos, ser-lhe-á solicitado que elimine manualmente os dados antes de iniciar a sua carteira. Failed to download snapshot as mandatory client is available for download. Falhou a transferência do snapshot porque existe uma atualização obrigatória da aplicação. + + Failure to do so will result in undefined behaviour or failure to start wallet. + Se não o fizer, o resultado será um comportamento indefinido ou uma falha no arranque da carteira. + Unable to download a snapshot, as the wallet has detected that a new mandatory version is available for install. The mandatory upgrade must be installed before the snapshot can be downloaded and applied. Não foi possível transferir o snapshot, porque a carteira detetou que uma versão obrigatória mais recente, está disponível para instalar. A atualização obrigatória deve ser instalada antes que o snapshot possa ser transferido e aplicado. @@ -4659,7 +5136,7 @@ Please exit Gridcoin, open the data directory, and delete: Your wallet will re-download the blockchain. Your balance may appear incorrect until the synchronization finishes. - AVISO: A Blockchain pode estar corrompida. + AVISO: A Cadeia de Blocos pode estar corrompida. O Gridcoin detetou entradas incorretas do índice. Isto pode ocorrer devido uma fecho inesperado, falha de energia ou uma atualização da aplicação. @@ -4667,7 +5144,7 @@ Por favor saia do Gridcoin, abra a diretoria de dados e, elimine: - os ficheiros blk****.dat - a diretoria txleveldb -A sua carteira irá transferir novamente a blockchain. O seu balanço poder aparecer incorreto até que a sincronização termine. +A sua carteira irá transferir novamente a cadeia de blocos. O seu balanço poder aparecer incorreto até que a sincronização termine. @@ -4682,6 +5159,10 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Gridcoin version Versão do Gridcoin + + Resetting block chain index to prepare for reindexing... + Repondo o índice da block chain para preparar a reindexação... + Stage (1/4): Downloading snapshot.zip: Etapa (1/4): Transferindo o snapshot.zip: @@ -4692,7 +5173,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Stage (3/4): Cleanup blockchain data: - Etapa (3/4): Limpar dados da blockchain: + Etapa (3/4): Limpar dados da cadeia de blocos: Stage (4/4): Extracting snapshot.zip: @@ -4736,15 +5217,15 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Unknown poll response type. - Tipo de resposta da votação desconhecido. + Tipo de resposta da sondagem desconhecida. Unknown poll type. - Tipo de votação desconhecido. + Tipo de sondagem desconhecida. Unknown poll weight type. - Tipo de peso da votação desconhecido. + Tipo de peso da sondagem desconhecido. None @@ -4752,7 +5233,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. No current polls - Sem votações + Sem sondagens Invalid amount for -paytxfee=<amount>: '%s' @@ -4832,7 +5313,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Importing bootstrap blockchain data file. - Importando ficheiro de dados do bootstrap da blockchain. + Importação do ficheiro de dados da cadeia de blocos bootstrap. Loading addresses... @@ -4876,7 +5357,7 @@ Se o ficheiro não existir, crie-o com permissões de leitura. Vote signature failed. See debug.log. - Assinatura da votação falhou. Veja debug.log. + Assinatura da votação falhou. Ver debug.log. Warning: Disk space is low! diff --git a/src/qt/locale/bitcoin_vi.ts b/src/qt/locale/bitcoin_vi.ts index c68b47437f..454b9c557f 100644 --- a/src/qt/locale/bitcoin_vi.ts +++ b/src/qt/locale/bitcoin_vi.ts @@ -9,7 +9,21 @@ <b>Gridcoin</b> Gridcoin - + + +This is experimental software. + +Distributed under the MIT/X11 software license, see the accompanying file COPYING or https://opensource.org/licenses/mit-license.php. + +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (https://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard. + +Đây là phần mềm thử nghiệm. + +Được phân phối theo giấy phép phần mềm MIT/X11, xem tệp COPYING đi kèm hoặc https://opensource.org/licenses/mit-license.php. + +Sản phẩm này bao gồm phần mềm do Dự án OpenSSL phát triển để sử dụng trong Bộ công cụ OpenSSL (https://www.openssl.org/) và phần mềm mã hóa được viết bởi Eric Young (eay@cryptsoft.com) và phần mềm UPnP được viết bởi Thomas Bernard. + + AdditionalFieldsTableDataModel @@ -33,7 +47,7 @@ &New - Tạo mới + &Tạo mới Copy the currently selected address to the system clipboard @@ -41,7 +55,11 @@ &Copy - Sao chép + &Sao chép + + + Show &QR Code + Hiển thị &mã QR Address Book @@ -51,12 +69,20 @@ &Delete &Xóa + + Copy &Label + Sao chép &nhãn dữ liệu + + + &Edit + &Chỉnh sửa + AddressTableModel Label - Nhãn d? li?u + Nhãn dữ liệu Address @@ -69,53 +95,197 @@ AskPassphraseDialog + + Passphrase Dialog + Hộp thoại cụm mật khẩu + + + Enter passphrase + Nhập cụm mật khẩu + + + New passphrase + Cụm mật khẩu mới + + + Repeat new passphrase + Nhập lại cụm mật khẩu mới + + + Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>. + Nhập cụm mật khẩu mới vào ví. <br/>Vui lòng sử dụng cụm mật khẩu gồm <b>mười ký tự ngẫu nhiên trở lên</b> hoặc <b>tám từ trở lên</b>. + Encrypt wallet Mã hóa ví + + This operation needs your wallet passphrase to unlock the wallet. + Thao tác này cần có cụm mật khẩu ví của bạn để mở khóa ví. + Unlock wallet Mở khóa ví + + Change passphrase + Thay đổi cụm mật khẩu + + + Enter the old and new passphrase to the wallet. + Nhập cụm mật khẩu cũ và cụm mật khẩu mới vào ví + Confirm wallet encryption Xác nhận mã hóa ví + + Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR COINS</b>! + Cảnh báo: Nếu bạn mã hóa ví và làm mất cụm mật khẩu, bạn sẽ <b>MẤT TẤT CẢ SỐ TIỀN CỦA MÌNH</b>! + + + Are you sure you wish to encrypt your wallet? + Bạn có chắc chắn muốn mã hóa ví của mình không? + Wallet encrypted Ví đã được mã hóa + + Gridcoin will close now to finish the encryption process. Remember that encrypting your wallet cannot fully protect your coins from being stolen by malware infecting your computer. + Gridcoin sẽ đóng để hoàn tất quá trình mã hóa. Hãy nhớ rằng việc mã hóa ví của bạn không thể bảo vệ hoàn toàn tiền của bạn khỏi việc bị đánh cắp trong trường hợp máy bạn bị lây nhiễm mã độc (virus). + Wallet encryption failed Mã hóa ví thất bại + + The supplied passphrases do not match. + Cụm mật khẩu được cung cấp không khớp. + Wallet unlock failed Mở khóa ví thất bại - + + The passphrase entered for the wallet decryption was incorrect. + Cụm mật khẩu được nhập để giải mã ví không chính xác. + + + Wallet passphrase was successfully changed. + Cụm mật khẩu ví đã được thay đổi thành công. + + + Warning: The Caps Lock key is on! + Cảnh báo: Đang bật phím Caps Lock! + + BitcoinGUI Wallet + + &Overview + &Tổng quan + + + Show general overview of wallet + Hiển thị tổng quan ví + + + &Send + &Gửi + + + Send coins to a Gridcoin address + Gửi tiền đến một địa chỉ Gridcoin + + + &Receive + &Nhận + + + Show the list of addresses for receiving payments + Hiển thị các địa chỉ dùng để nhận thanh toán + + + &History + &Lịch sử + + + &Web Site + &Trang web + + + &GRC Chat Room + &Phòng chat GRC + GRC Chatroom Phòng chat GRC + + E&xit + T&hoát + Quit application Thoát ứng dụng + + &About Gridcoin + &Về Gridcoin + + + Show information about Gridcoin + Hiển thị thông tin về Gridcoin + + + &Options... + &Tùy chọn... + + + &Show / Hide + &Hiển thị / Ẩn + + + &Encrypt Wallet... + &Mã hóa ví... + Encrypt wallet Mã hóa ví + + &Change Passphrase... + &Thay đổi cụm mật khẩu... + + + &Unlock Wallet... + &Mở khóa ví... + + + &Lock Wallet + &Khóa ví + Lock wallet Khóa ví + + &Debug window + &Cửa sổ gỡ lỗi + + + &File + &Tệp + + + &Community + &Cộng đồng + %n active connection(s) to the Gridcoin network @@ -152,6 +322,10 @@ + + Warning: After the blockchain data is deleted, the wallet will shutdown and when restarted will begin syncing from zero. Your balance will temporarily show as 0 GRC while syncing. + Cảnh báo: Sau khi dữ liệu blockchain của bạn bị xóa, ví sẽ tắt và khi khởi động lại ví sẽ bắt đầu đồng bộ hóa lại từ đầu. Số dư tài khoản của bạn sẽ tạm thời hiển thị là 0 GRC trong lúc thực hiện đồng bộ hóa. + Close Confirmation Đóng xác nhận @@ -174,7 +348,7 @@ day - ngà + ngày hour @@ -206,10 +380,18 @@ Select All Chọn tất cả + + Tree &mode + &Chế độ cây + Amount Số lượng + + &List mode + &Chế độ danh sách + Address Địa chỉ @@ -220,7 +402,14 @@ (no label) - (ch?a có nhãn) + (chưa có nhãn) + + + + ConsolidateUnspentWizardSelectInputsPage + + Fee + Phí @@ -245,11 +434,11 @@ EditAddressDialog &Label - Nhãn dữ liệu + &Nhãn dữ liệu &Address - Địa chỉ + &Địa chỉ @@ -273,6 +462,32 @@ + + OptionsDialog + + &Port: + &Cổng: + + + &Window + &Cửa sổ + + + User Interface &language: + &Ngôn ngữ giao diện người dùng: + + + &Cancel + &Hủy + + + + OverviewPage + + Wallet + + + QObject @@ -312,15 +527,93 @@ + + QRCodeDialog + + QR Code Dialog + Hộp thoại mã QR + + + &Save As... + &Lưu dưới dạng... + + + Error encoding URI into QR Code. + Lỗi mã hóa URI thành mã QR + + + Save QR Code + Lưu mã QR + + + + RPCConsole + + &Information + &Thông tin + + + &Open + &Mở + + + 1 &hour + 1 &giờ + + + 1 &day + 1 &ngày + + + 1 &week + 1 &tuần + + + 1 &year + 1 &năm + + + + ResearcherWizard + + &Start Over + &Bắt đầu lại + + + + ResearcherWizardAuthPage + + &Copy + &Sao chép + + + + ResearcherWizardPoolPage + + &Copy + &Sao chép + + + + ResearcherWizardSummaryPage + + &Projects + &Dự án + + SendCoinsDialog Amount: Số lượng: + + S&end + G&ửi + (no label) - (ch?a có nhãn) + (chưa có nhãn) @@ -345,18 +638,18 @@ Amount - S? l??ng + Số lượng TransactionTableModel Address - ??a ch? + Địa chỉ Amount - S? l??ng + Số lượng Open for %n more block(s) @@ -369,15 +662,65 @@ TransactionView Label - Nhãn d? li?u + Nhãn dữ liệu Address - ??a ch? + Địa chỉ Amount - S? l??ng + Số lượng + + UpgradeQt + + &File + &Tệp + + + + bitcoin-core + + Invalid amount + Số tiền không hợp lệ + + + Warning: Please check that your computer's date and time are correct! If your clock is wrong Gridcoin will not work properly. + Cảnh báo: Vui lòng kiểm tra xem ngày và giờ trên máy tính của bạn có chính xác không! Nếu đồng hồ của bạn sai, Gridcoin sẽ không hoạt động bình thường. + + + Warning: Disk space is low! + Cảnh báo: Dung lượng ổ đĩa thấp! + + + Insufficient funds + Nguồn tiền không đủ + + + Loading block index... + Đang tải chỉ mục khối (block index)... + + + Loading wallet... + Đang tải ví... + + + Cannot write default address + Không thể ghi địa chỉ mặc định + + + Rescanning... + Đang quét lại... + + + Done loading + Đã tải xong + + + Error + Lỗi + + \ No newline at end of file diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 9f2f0c1b71..405ed34b62 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -189,7 +189,11 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, QString& e) EXCLUSIVE_LOCKS_REQUIR CWalletTx wtx; std::string e_str; - std::tie(wtx, e_str) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, m_mrc)); + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + + std::tie(wtx, e_str) = GRC::SendContract(GRC::MakeContract(contract_version, + GRC::ContractAction::ADD, + m_mrc)); if (!e_str.empty()) { m_mrc_error = true; s = m_mrc_status = MRCRequestStatus::SUBMIT_ERROR; @@ -251,8 +255,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) // This is similar to createmrcrequest in many ways, but the state tracking is more complicated. - AssertLockHeld(cs_main); - // Record initial block height during init run. if (!m_init_block_height) { m_init_block_height = pindexBest->nHeight; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 697c489eaf..26bd13b1d3 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -1,4 +1,5 @@ #include "optionsdialog.h" +#include "qevent.h" #include "ui_optionsdialog.h" #include "netbase.h" @@ -8,7 +9,11 @@ #include "qt/decoration.h" #include "init.h" #include "miner.h" +#include "sidestaketablemodel.h" +#include "editsidestakedialog.h" +#include "logging.h" +#include #include #include #include @@ -16,13 +21,18 @@ #include OptionsDialog::OptionsDialog(QWidget* parent) - : QDialog(parent) - , ui(new Ui::OptionsDialog) - , model(nullptr) - , mapper(nullptr) - , fRestartWarningDisplayed_Proxy(false) - , fRestartWarningDisplayed_Lang(false) - , fProxyIpValid(true) + : QDialog(parent) + , ui(new Ui::OptionsDialog) + , model(nullptr) + , mapper(nullptr) + , fRestartWarningDisplayed_Proxy(false) + , fRestartWarningDisplayed_Lang(false) + , fProxyIpValid(true) + , fStakingEfficiencyValid(true) + , fMinStakeSplitValueValid(true) + , fPollExpireNotifyValid(true) + , m_init_column_sizes_set(false) + , m_resize_columns_in_progress(false) { ui->setupUi(this); @@ -44,6 +54,7 @@ OptionsDialog::OptionsDialog(QWidget* parent) ui->proxyIp->installEventFilter(this); ui->stakingEfficiency->installEventFilter(this); ui->minPostSplitOutputValue->installEventFilter(this); + ui->pollExpireNotifyLineEdit->installEventFilter(this); /* Window elements init */ #ifdef Q_OS_MAC @@ -103,17 +114,24 @@ OptionsDialog::OptionsDialog(QWidget* parent) connect(this, &OptionsDialog::proxyIpValid, this, &OptionsDialog::handleProxyIpValid); connect(this, &OptionsDialog::stakingEfficiencyValid, this, &OptionsDialog::handleStakingEfficiencyValid); connect(this, &OptionsDialog::minStakeSplitValueValid, this, &OptionsDialog::handleMinStakeSplitValueValid); + /** setup/change UI elements when poll expiry notification time window is valid/invalid */ + connect(this, &OptionsDialog::pollExpireNotifyValid, this, &OptionsDialog::handlePollExpireNotifyValid); if (fTestNet) ui->disableUpdateCheck->setHidden(true); ui->gridcoinAtStartupMinimised->setHidden(!ui->gridcoinAtStartup->isChecked()); ui->limitTxnDisplayDateEdit->setHidden(!ui->limitTxnDisplayCheckBox->isChecked()); + ui->pollExpireNotifyLabel->setHidden(ui->disablePollNotifications->isChecked()); + ui->pollExpireNotifyLineEdit->setHidden(ui->disablePollNotifications->isChecked()); + connect(ui->gridcoinAtStartup, &QCheckBox::toggled, this, &OptionsDialog::hideStartMinimized); connect(ui->gridcoinAtStartupMinimised, &QCheckBox::toggled, this, &OptionsDialog::hideStartMinimized); connect(ui->limitTxnDisplayCheckBox, &QCheckBox::toggled, this, &OptionsDialog::hideLimitTxnDisplayDate); + connect(ui->disablePollNotifications, &QCheckBox::toggled, this , &OptionsDialog::hidePollExpireNotify); + bool stake_split_enabled = ui->enableStakeSplit->isChecked(); ui->stakingEfficiencyLabel->setHidden(!stake_split_enabled); @@ -140,6 +158,55 @@ void OptionsDialog::setModel(OptionsModel *model) mapper->setModel(model); setMapper(); mapper->toFirst(); + + SideStakeTableModel* sidestake_model = model->getSideStakeTableModel(); + + sidestake_model->refresh(); + + ui->sidestakingTableView->setModel(sidestake_model); + ui->sidestakingTableView->verticalHeader()->hide(); + ui->sidestakingTableView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->sidestakingTableView->setSelectionMode(QAbstractItemView::ExtendedSelection); + ui->sidestakingTableView->setContextMenuPolicy(Qt::CustomContextMenu); + + // Scale column widths by the logical DPI over 96.0 to deal with hires displays. + ui->sidestakingTableView->setColumnWidth(SideStakeTableModel::Address, GRC::ScalePx(this, ADDRESS_COLUMN_WIDTH)); + ui->sidestakingTableView->setColumnWidth(SideStakeTableModel::Allocation, GRC::ScalePx(this, ALLOCATION_COLUMN_WIDTH)); + ui->sidestakingTableView->setColumnWidth(SideStakeTableModel::Description, GRC::ScalePx(this, DESCRIPTION_COLUMN_WIDTH)); + ui->sidestakingTableView->setColumnWidth(SideStakeTableModel::Status, GRC::ScalePx(this, STATUS_COLUMN_WIDTH)); + ui->sidestakingTableView->setShowGrid(true); + + // Set table column sizes vector for sidestake table proportional resize algorithm. + m_table_column_sizes = {GRC::ScalePx(this, ADDRESS_COLUMN_WIDTH), + GRC::ScalePx(this, ALLOCATION_COLUMN_WIDTH), + GRC::ScalePx(this, DESCRIPTION_COLUMN_WIDTH), + GRC::ScalePx(this, STATUS_COLUMN_WIDTH)}; + + ui->sidestakingTableView->sortByColumn(0, Qt::AscendingOrder); + + // Insures initial size of sidestake table and (header) columns are correct as of the context directly + // after tab selection. + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &OptionsDialog::tabWidgetSelectionChanged); + + // Insures that header width remains constant and columns are resized correctly when a column delimiter is + // dragged to resize one column. + connect(ui->sidestakingTableView->horizontalHeader(), &QHeaderView::sectionResized, + this, &OptionsDialog::sidestakeTableSectionResized); + + connect(ui->enableSideStaking, &QCheckBox::toggled, this, &OptionsDialog::hideSideStakeEdit); + connect(ui->enableSideStaking, &QCheckBox::toggled, this, &OptionsDialog::refreshSideStakeTableModel); + + connect(ui->pushButtonNewSideStake, &QPushButton::clicked, this, &OptionsDialog::newSideStakeButton_clicked); + connect(ui->pushButtonEditSideStake, &QPushButton::clicked, this, &OptionsDialog::editSideStakeButton_clicked); + connect(ui->pushButtonDeleteSideStake, &QPushButton::clicked, this, &OptionsDialog::deleteSideStakeButton_clicked); + + connect(ui->sidestakingTableView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &OptionsDialog::sidestakeSelectionChanged); + + ui->sidestakingTableView->installEventFilter(this); + + connect(this, &OptionsDialog::sidestakeAllocationInvalid, this, &OptionsDialog::handleSideStakeAllocationInvalid); + connect(this, &OptionsDialog::sidestakeDescriptionInvalid, this, &OptionsDialog::handleSideStakeDescriptionInvalid); } /* update the display unit, to not use the default ("BTC") */ @@ -176,10 +243,12 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->enableStakeSplit, OptionsModel::EnableStakeSplit); mapper->addMapping(ui->stakingEfficiency, OptionsModel::StakingEfficiency); mapper->addMapping(ui->minPostSplitOutputValue, OptionsModel::MinStakeSplitValue); + mapper->addMapping(ui->enableSideStaking, OptionsModel::EnableSideStaking); /* Window */ mapper->addMapping(ui->disableTransactionNotifications, OptionsModel::DisableTrxNotifications); mapper->addMapping(ui->disablePollNotifications, OptionsModel::DisablePollNotifications); + mapper->addMapping(ui->pollExpireNotifyLineEdit, OptionsModel::PollExpireNotification); #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); @@ -194,7 +263,7 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->styleComboBox, OptionsModel::WalletStylesheet,"currentData"); mapper->addMapping(ui->limitTxnDisplayCheckBox, OptionsModel::LimitTxnDisplay); mapper->addMapping(ui->limitTxnDisplayDateEdit, OptionsModel::LimitTxnDate); - mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses); + mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses); } void OptionsDialog::enableApplyButton() @@ -227,7 +296,8 @@ void OptionsDialog::setSaveButtonState(bool fState) void OptionsDialog::on_okButton_clicked() { - mapper->submit(); + refreshSideStakeTableModel(); + accept(); } @@ -238,15 +308,72 @@ void OptionsDialog::on_cancelButton_clicked() void OptionsDialog::on_applyButton_clicked() { - mapper->submit(); + refreshSideStakeTableModel(); + disableApplyButton(); } +void OptionsDialog::newSideStakeButton_clicked() +{ + if (!model) { + return; + } + + EditSideStakeDialog dialog(EditSideStakeDialog::NewSideStake, this); + + dialog.setModel(model->getSideStakeTableModel()); + + dialog.exec(); +} + +void OptionsDialog::editSideStakeButton_clicked() +{ + if (!model || !ui->sidestakingTableView->selectionModel()) { + return; + } + + QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows(); + + if (indexes.isEmpty()) { + return; + } + + if (indexes.size() > 1) { + QMessageBox::warning(this, tr("Error"), tr("You can only edit one sidestake at a time."), QMessageBox::Ok); + } + + EditSideStakeDialog dialog(EditSideStakeDialog::EditSideStake, this); + + dialog.setModel(model->getSideStakeTableModel()); + dialog.loadRow(indexes.at(0).row()); + dialog.exec(); +} + +void OptionsDialog::deleteSideStakeButton_clicked() +{ + if (!model || !ui->sidestakingTableView->selectionModel()) { + return; + } + + QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows(); + + if (indexes.isEmpty()) { + return; + } + + if (indexes.size() > 1) { + QMessageBox::warning(this, tr("Error"), tr("You can only delete one sidestake at a time."), QMessageBox::Ok); + } + + model->getSideStakeTableModel()->removeRows(indexes.at(0).row(), 1); +} + void OptionsDialog::showRestartWarning_Proxy() { if(!fRestartWarningDisplayed_Proxy) { - QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect after restarting Gridcoin."), QMessageBox::Ok); + QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect" + " after restarting Gridcoin."), QMessageBox::Ok); fRestartWarningDisplayed_Proxy = true; } } @@ -298,6 +425,14 @@ void OptionsDialog::hideLimitTxnDisplayDate() } } +void OptionsDialog::hidePollExpireNotify() +{ + if (model) { + ui->pollExpireNotifyLabel->setHidden(ui->disablePollNotifications->isChecked()); + ui->pollExpireNotifyLineEdit->setHidden(ui->disablePollNotifications->isChecked()); + } +} + void OptionsDialog::hideStakeSplitting() { if (model) @@ -311,6 +446,16 @@ void OptionsDialog::hideStakeSplitting() } } +void OptionsDialog::hideSideStakeEdit() +{ + if (model) { + bool local_side_staking_enabled = ui->enableSideStaking->isChecked(); + + ui->pushButtonNewSideStake->setHidden(!local_side_staking_enabled); + ui->pushButtonEditSideStake->setHidden(!local_side_staking_enabled); + } +} + void OptionsDialog::handleProxyIpValid(QValidatedLineEdit *object, bool fState) { // this is used in a check before re-enabling the save buttons @@ -368,9 +513,50 @@ void OptionsDialog::handleMinStakeSplitValueValid(QValidatedLineEdit *object, bo } } +void OptionsDialog::handlePollExpireNotifyValid(QValidatedLineEdit *object, bool fState) +{ + // this is used in a check before re-enabling the save buttons + fPollExpireNotifyValid = fState; + + if (fPollExpireNotifyValid) { + enableSaveButtons(); + ui->statusLabel->clear(); + } else { + disableSaveButtons(); + object->setValid(fPollExpireNotifyValid); + ui->statusLabel->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel->setText(tr("The supplied time for notification before poll expires must " + "be between 0.25 and 24 hours.")); + } +} + +void OptionsDialog::refreshSideStakeTableModel() +{ + if (!mapper->submit() + && model->getSideStakeTableModel()->getEditStatus() == SideStakeTableModel::INVALID_ALLOCATION) { + emit sidestakeAllocationInvalid(); + } else { + model->getSideStakeTableModel()->refresh(); + } +} + bool OptionsDialog::eventFilter(QObject *object, QEvent *event) { - if (event->type() == QEvent::FocusOut) + bool filter_event = false; + + if (event->type() == QEvent::FocusOut) { + filter_event = true; + } + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + + if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { + filter_event = true; + } + } + + if (filter_event) { if (object == ui->proxyIp) { @@ -423,6 +609,186 @@ bool OptionsDialog::eventFilter(QObject *object, QEvent *event) } } } + + if (object == ui->pollExpireNotifyLineEdit) { + bool ok = false; + double hours = ui->pollExpireNotifyLineEdit->text().toDouble(&ok); + + if (!ok) { + emit pollExpireNotifyValid(ui->pollExpireNotifyLineEdit, false); + } else { + if (hours >= 0.25 && hours <= 24.0 * 7.0) { + emit pollExpireNotifyValid(ui->pollExpireNotifyLineEdit, true); + } else { + emit pollExpireNotifyValid(ui->pollExpireNotifyLineEdit, false); + } + } + } + } + + // This is required to provide immediate feedback on invalid allocation entries on in place editing. + if (object == ui->sidestakingTableView) { + if (model->getSideStakeTableModel()->getEditStatus() == SideStakeTableModel::INVALID_ALLOCATION) { + emit sidestakeAllocationInvalid(); + } + + if (model->getSideStakeTableModel()->getEditStatus() == SideStakeTableModel::INVALID_DESCRIPTION) { + emit sidestakeDescriptionInvalid(); + } + } + + return QDialog::eventFilter(object, event); +} + +void OptionsDialog::sidestakeSelectionChanged() +{ + QTableView *table = ui->sidestakingTableView; + + if (table->selectionModel()->hasSelection()) { + QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows(); + + if (indexes.size() > 1) { + ui->pushButtonEditSideStake->setEnabled(false); + ui->pushButtonDeleteSideStake->setEnabled(false); + } else if (static_cast(indexes.at(0).internalPointer())->IsMandatory()) { + ui->pushButtonEditSideStake->setEnabled(false); + ui->pushButtonDeleteSideStake->setEnabled(false); + } else { + ui->pushButtonEditSideStake->setEnabled(true); + ui->pushButtonDeleteSideStake->setEnabled(true); + } + } +} + +void OptionsDialog::handleSideStakeAllocationInvalid() +{ + model->getSideStakeTableModel()->refresh(); + + QMessageBox::warning(this, windowTitle(), + tr("The entered allocation is not valid and is reverted. Check to make sure " + "that the allocation is greater than or equal to zero and when added to the other " + "allocations totals less than 100."), + QMessageBox::Ok, QMessageBox::Ok); +} + +void OptionsDialog::handleSideStakeDescriptionInvalid() +{ + model->getSideStakeTableModel()->refresh(); + + QMessageBox::warning(this, windowTitle(), + tr("The entered description is not valid. Check to make sure that the " + "description only contains letters, numbers, spaces, periods, or " + "underscores."), + QMessageBox::Ok, QMessageBox::Ok); +} + +void OptionsDialog::updateSideStakeTableView() +{ + ui->sidestakingTableView->update(); +} + +void OptionsDialog::resizeSideStakeTableColumns(const bool& neighbor_pair_adjust, const int& index, + const int& old_size, const int& new_size) +{ + // This prevents unwanted recursion to here from addressBookSectionResized. + m_resize_columns_in_progress = true; + + if (!model) { + m_resize_columns_in_progress = false; + + return; + } + + if (!m_init_column_sizes_set) { + for (int i = 0; i < (int) m_table_column_sizes.size(); ++i) { + ui->sidestakingTableView->horizontalHeader()->resizeSection(i, m_table_column_sizes[i]); + + + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: section size = %i", + __func__, + ui->sidestakingTableView->horizontalHeader()->sectionSize(i)); + } + + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: header width = %i", + __func__, + ui->sidestakingTableView->horizontalHeader()->width() + ); + + m_init_column_sizes_set = true; + m_resize_columns_in_progress = false; + + return; + } + + if (neighbor_pair_adjust) { + if (index != SideStakeTableModel::all_ColumnIndex.size() - 1) { + int new_neighbor_section_size = ui->sidestakingTableView->horizontalHeader()->sectionSize(index + 1) + + old_size - new_size; + + ui->sidestakingTableView->horizontalHeader()->resizeSection( + index + 1, new_neighbor_section_size); + + // This detects and deals with the case where the resize of a column tries to force the neighbor + // to a size below its minimum, in which case we have to reverse out the attempt. + if (ui->sidestakingTableView->horizontalHeader()->sectionSize(index + 1) + != new_neighbor_section_size) { + ui->sidestakingTableView->horizontalHeader()->resizeSection( + index, + ui->sidestakingTableView->horizontalHeader()->sectionSize(index) + + new_neighbor_section_size + - ui->sidestakingTableView->horizontalHeader()->sectionSize(index + 1)); + } + } else { + // Do not allow the last column to be resized because there is no adjoining neighbor to the right + // and we are maintaining the total width fixed to the size of the containing frame. + ui->sidestakingTableView->horizontalHeader()->resizeSection(index, old_size); + } + + m_resize_columns_in_progress = false; + + return; + } + + // This is the proportional resize case when the window is resized. + const int width = ui->sidestakingTableView->horizontalHeader()->width() - 5; + + int orig_header_width = 0; + + for (const auto& iter : SideStakeTableModel::all_ColumnIndex) { + orig_header_width += ui->sidestakingTableView->horizontalHeader()->sectionSize(iter); + } + + if (!width || !orig_header_width) return; + + for (const auto& iter : SideStakeTableModel::all_ColumnIndex) { + int section_size = ui->sidestakingTableView->horizontalHeader()->sectionSize(iter); + + ui->sidestakingTableView->horizontalHeader()->resizeSection( + iter, section_size * width / orig_header_width); + } + + m_resize_columns_in_progress = false; +} + +void OptionsDialog::resizeEvent(QResizeEvent *event) +{ + resizeSideStakeTableColumns(); + + QWidget::resizeEvent(event); +} + +void OptionsDialog::sidestakeTableSectionResized(int index, int old_size, int new_size) +{ + // Avoid implicit recursion between resizeTableColumns and addressBookSectionResized + if (m_resize_columns_in_progress) return; + + resizeSideStakeTableColumns(true, index, old_size, new_size); +} + +void OptionsDialog::tabWidgetSelectionChanged(int index) +{ + // Index = 2 is the sidestaking tab for the current tab order. + if (index == 2) { + resizeSideStakeTableColumns(); } - return QDialog::eventFilter(object, event); } diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 1687c00f72..64a2deaa10 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -22,8 +22,13 @@ class OptionsDialog : public QDialog void setModel(OptionsModel *model); void setMapper(); +public slots: + void resizeSideStakeTableColumns(const bool& neighbor_pair_adjust = false, const int& index = 0, + const int& old_size = 0, const int& new_size = 0); + protected: - bool eventFilter(QObject *object, QEvent *event); + bool eventFilter(QObject *object, QEvent *event) override; + void resizeEvent(QResizeEvent *event) override; private slots: /* enable only apply button */ @@ -40,6 +45,10 @@ private slots: void on_cancelButton_clicked(); void on_applyButton_clicked(); + void newSideStakeButton_clicked(); + void editSideStakeButton_clicked(); + void deleteSideStakeButton_clicked(); + void showRestartWarning_Proxy(); void showRestartWarning_Lang(); void updateDisplayUnit(); @@ -47,14 +56,26 @@ private slots: void hideStartMinimized(); void hideLimitTxnDisplayDate(); void hideStakeSplitting(); + void hidePollExpireNotify(); + void hideSideStakeEdit(); void handleProxyIpValid(QValidatedLineEdit *object, bool fState); void handleStakingEfficiencyValid(QValidatedLineEdit *object, bool fState); void handleMinStakeSplitValueValid(QValidatedLineEdit *object, bool fState); + void handlePollExpireNotifyValid(QValidatedLineEdit *object, bool fState); + void handleSideStakeAllocationInvalid(); + void handleSideStakeDescriptionInvalid(); + + void refreshSideStakeTableModel(); + + void tabWidgetSelectionChanged(int index); signals: void proxyIpValid(QValidatedLineEdit *object, bool fValid); void stakingEfficiencyValid(QValidatedLineEdit *object, bool fValid); void minStakeSplitValueValid(QValidatedLineEdit *object, bool fValid); + void pollExpireNotifyValid(QValidatedLineEdit *object, bool fValid); + void sidestakeAllocationInvalid(); + void sidestakeDescriptionInvalid(); private: Ui::OptionsDialog *ui; @@ -65,6 +86,27 @@ private slots: bool fProxyIpValid; bool fStakingEfficiencyValid; bool fMinStakeSplitValueValid; + bool fPollExpireNotifyValid; + + std::vector m_table_column_sizes; + bool m_init_column_sizes_set; + bool m_resize_columns_in_progress; + + enum SideStakeTableColumnWidths + { + ADDRESS_COLUMN_WIDTH = 200, + ALLOCATION_COLUMN_WIDTH = 60, + DESCRIPTION_COLUMN_WIDTH = 150, + STATUS_COLUMN_WIDTH = 50 + }; + +private slots: + void sidestakeSelectionChanged(); + void updateSideStakeTableView(); + + /** Resize address book table columns based on incoming signal */ + void sidestakeTableSectionResized(int index, int old_size, int new_size); + }; #endif // BITCOIN_QT_OPTIONSDIALOG_H diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index f435c2369f..1d86e919ae 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -57,6 +57,7 @@ void OptionsModel::Init() fLimitTxnDisplay = settings.value("fLimitTxnDisplay", false).toBool(); fMaskValues = settings.value("fMaskValues", false).toBool(); limitTxnDate = settings.value("limitTxnDate", QDate()).toDate(); + pollExpireNotification = settings.value("pollExpireNotification", 8.0).toDouble(); nReserveBalance = settings.value("nReserveBalance").toLongLong(); language = settings.value("language", "").toString(); walletStylesheet = settings.value("walletStylesheet", "dark").toString(); @@ -78,6 +79,8 @@ void OptionsModel::Init() if (settings.contains("dataDir") && dataDir != GUIUtil::getDefaultDataDirectory()) { gArgs.SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(settings.value("dataDir").toString()).string()); } + + m_sidestake_model = new SideStakeTableModel(this); } int OptionsModel::rowCount(const QModelIndex & parent) const @@ -142,6 +145,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return QVariant(fMaskValues); case LimitTxnDate: return QVariant(limitTxnDate); + case PollExpireNotification: + return QVariant(pollExpireNotification); case DisableUpdateCheck: return QVariant(gArgs.GetBoolArg("-disableupdatecheck", false)); case DataDir: @@ -152,6 +157,9 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const case EnableStakeSplit: // This comes from the core and is a read-write setting (see below). return QVariant(gArgs.GetBoolArg("-enablestakesplit")); + case EnableSideStaking: + // This comes from the core and is a read-write setting (see below). + return QVariant(gArgs.GetBoolArg("-enablesidestaking")); case StakingEfficiency: // This comes from the core and is a read-write setting (see below). return QVariant((double) gArgs.GetArg("-stakingefficiency", (int64_t) 90)); @@ -284,6 +292,10 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in limitTxnDate = value.toDate(); settings.setValue("limitTxnDate", limitTxnDate); break; + case PollExpireNotification: + pollExpireNotification = value.toDouble(); + settings.setValue("pollExpireNotification", pollExpireNotification); + break; case DisableUpdateCheck: gArgs.ForceSetArg("-disableupdatecheck", value.toBool() ? "1" : "0"); settings.setValue("fDisableUpdateCheck", value.toBool()); @@ -303,10 +315,15 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case EnableStakeSplit: // This is a core setting stored in the read-write settings file and once set will override the read-only //config file. - //fStakeSplitEnabled = value.toBool(); gArgs.ForceSetArg("-enablestakesplit", value.toBool() ? "1" : "0"); updateRwSetting("enablestakesplit", gArgs.GetBoolArg("-enablestakesplit")); break; + case EnableSideStaking: + // This is a core setting stored in the read-write settings file and once set will override the read-only + //config file. + gArgs.ForceSetArg("-enablesidestaking", value.toBool() ? "1" : "0"); + updateRwSetting("enablesidestaking", gArgs.GetBoolArg("-enablesidestaking")); + break; case StakingEfficiency: // This is a core setting stored in the read-write settings file and once set will override the read-only //config file. @@ -380,6 +397,11 @@ int64_t OptionsModel::getLimitTxnDateTime() return limitTxnDateTime.toMSecsSinceEpoch() / 1000; } +double OptionsModel::getPollExpireNotification() +{ + return pollExpireNotification; +} + bool OptionsModel::getStartAtStartup() { return fStartAtStartup; @@ -449,3 +471,8 @@ QString OptionsModel::getDataDir() { return dataDir; } + +SideStakeTableModel* OptionsModel::getSideStakeTableModel() +{ + return m_sidestake_model; +} diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index f9d94bc834..25dceccda4 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -1,6 +1,7 @@ #ifndef BITCOIN_QT_OPTIONSMODEL_H #define BITCOIN_QT_OPTIONSMODEL_H +#include "sidestaketablemodel.h" #include #include @@ -41,8 +42,10 @@ class OptionsModel : public QAbstractListModel DataDir, // QString EnableStaking, // bool EnableStakeSplit, // bool + EnableSideStaking, // bool StakingEfficiency, // double MinStakeSplitValue, // int + PollExpireNotification, // double ContractChangeToInput, // bool MaskValues, // bool OptionIDRowCount @@ -71,10 +74,13 @@ class OptionsModel : public QAbstractListModel bool getMaskValues(); QDate getLimitTxnDate(); int64_t getLimitTxnDateTime(); + double getPollExpireNotification(); QString getLanguage() { return language; } QString getCurrentStyle(); QString getDataDir(); + SideStakeTableModel* getSideStakeTableModel(); + /* Explicit setters */ void setCurrentStyle(QString theme); void setMaskValues(bool privacy_mode); @@ -87,17 +93,20 @@ class OptionsModel : public QAbstractListModel bool fStartMin; bool fDisableTrxNotifications; bool fDisablePollNotifications; - bool bDisplayAddresses; + bool bDisplayAddresses; bool fMinimizeOnClose; bool fConfirmOnClose; bool fCoinControlFeatures; bool fLimitTxnDisplay; bool fMaskValues; QDate limitTxnDate; + double pollExpireNotification; QString language; QString walletStylesheet; QString dataDir; + SideStakeTableModel* m_sidestake_model; + signals: void displayUnitChanged(int unit); void reserveBalanceChanged(qint64); diff --git a/src/qt/res/stylesheets/dark_stylesheet.qss b/src/qt/res/stylesheets/dark_stylesheet.qss index 4290e39ed7..6dde9b1ddf 100644 --- a/src/qt/res/stylesheets/dark_stylesheet.qss +++ b/src/qt/res/stylesheets/dark_stylesheet.qss @@ -994,6 +994,14 @@ PollCard #invalidLabel { color: rgb(150, 0, 0); } +PollCard #staleLabel { + border: 0.065em solid rgb(240, 0, 0); + border-radius: 0.65em; + padding: 0.1em 0.3em; + color: rgb(255, 255, 255); + background-color: rgb(240, 0, 0); +} + PollCard #remainingLabel, PollResultChoiceItem #percentageLabel, PollResultChoiceItem #weightLabel, diff --git a/src/qt/res/stylesheets/light_stylesheet.qss b/src/qt/res/stylesheets/light_stylesheet.qss index 9d2a6e32b0..a81de84aba 100644 --- a/src/qt/res/stylesheets/light_stylesheet.qss +++ b/src/qt/res/stylesheets/light_stylesheet.qss @@ -969,6 +969,14 @@ PollCard #invalidLabel { color: rgb(150, 0, 0); } +PollCard #staleLabel { + border: 0.065em solid rgb(240, 0, 0); + border-radius: 0.65em; + padding: 0.1em 0.3em; + color: rgb(255, 255, 255); + background-color: rgb(240, 0, 0); +} + PollCard #remainingLabel, PollResultChoiceItem #percentageLabel, PollResultChoiceItem #weightLabel, diff --git a/src/qt/researcher/projecttablemodel.cpp b/src/qt/researcher/projecttablemodel.cpp index 40ff467035..e24247a8d3 100644 --- a/src/qt/researcher/projecttablemodel.cpp +++ b/src/qt/researcher/projecttablemodel.cpp @@ -133,7 +133,7 @@ ProjectTableModel::ProjectTableModel(ResearcherModel *model, const bool extended << tr("Whitelist") << tr("Has GDPR Controls") << tr("Magnitude") - << tr("Avg. Credit") + << tr("Recent Avg. Credit") << tr("CPID"); } @@ -186,7 +186,7 @@ QVariant ProjectTableModel::data(const QModelIndex &index, int role) const case Magnitude: return row->m_magnitude; case RecentAverageCredit: - return row->m_rac; + return QString::number(row->m_rac, 'f', 0); } break; @@ -198,8 +198,12 @@ QVariant ProjectTableModel::data(const QModelIndex &index, int role) const } break; case Whitelisted: - if (row->m_whitelisted) { + if (row->m_whitelisted == ProjectRow::WhiteListStatus::True) { return QIcon(":/icons/round_green_check"); + } else if (row->m_whitelisted == ProjectRow::WhiteListStatus::Greylisted) { + return QIcon(":/icons/warning"); + } else if (row->m_whitelisted == ProjectRow::WhiteListStatus::False) { + return QIcon(":/icons/white_and_red_x"); } break; case GDPRControls: diff --git a/src/qt/researcher/researchermodel.cpp b/src/qt/researcher/researchermodel.cpp index c095505d25..18a4f7bef4 100644 --- a/src/qt/researcher/researchermodel.cpp +++ b/src/qt/researcher/researchermodel.cpp @@ -11,6 +11,7 @@ #include "gridcoin/quorum.h" #include "gridcoin/researcher.h" #include "gridcoin/scraper/scraper.h" +#include "gridcoin/superblock.h" #include "node/ui_interface.h" #include "qt/bitcoinunits.h" @@ -23,6 +24,7 @@ #include extern CWallet* pwalletMain; +extern ConvergedScraperStats ConvergedScraperStatsCache; using namespace GRC; using LogFlags = BCLog::LogFlags; @@ -83,7 +85,8 @@ BeaconStatus MapAdvertiseBeaconError(const BeaconError error) case BeaconError::PENDING: return BeaconStatus::PENDING; case BeaconError::TX_FAILED: return BeaconStatus::ERROR_TX_FAILED; case BeaconError::WALLET_LOCKED: return BeaconStatus::ERROR_WALLET_LOCKED; - } + case BeaconError::ALEADY_IN_MEMPOOL: return BeaconStatus::ALREADY_IN_MEMPOOL; + } assert(false); // Suppress warning } @@ -148,6 +151,8 @@ QString ResearcherModel::mapBeaconStatus(const BeaconStatus status) return tr("Beacon expires soon. Renew immediately."); case BeaconStatus::RENEWAL_POSSIBLE: return tr("Beacon eligible for renewal."); + case BeaconStatus::ALREADY_IN_MEMPOOL: + return tr("Beacon advertisement transaction already in mempool."); case BeaconStatus::UNKNOWN: return tr("Waiting for sync..."); } @@ -179,6 +184,7 @@ QIcon ResearcherModel::mapBeaconStatusIcon(const BeaconStatus status) const case BeaconStatus::PENDING: return make_icon(warning); case BeaconStatus::RENEWAL_NEEDED: return make_icon(danger); case BeaconStatus::RENEWAL_POSSIBLE: return make_icon(warning); + case BeaconStatus::ALREADY_IN_MEMPOOL: return make_icon(warning); case BeaconStatus::UNKNOWN: return make_icon(inactive); } @@ -276,7 +282,17 @@ bool ResearcherModel::hasActiveBeacon() const bool ResearcherModel::hasPendingBeacon() const { - return m_pending_beacon.operator bool(); + if (!m_pending_beacon.operator bool()) { + return false; + } + + // If here, a pending beacon is present. Determine if expired + // while pending. No need to actually clean the pending entry + // up. It will be eventually cleaned by the contract handler via + // the ActivatePending call. + GRC::PendingBeacon pending_beacon(*m_pending_beacon); + + return !pending_beacon.PendingExpired(GetAdjustedTime()); } bool ResearcherModel::hasRenewableBeacon() const @@ -471,6 +487,13 @@ std::vector ResearcherModel::buildProjectTable(bool extended) const // const WhitelistSnapshot whitelist = GetWhitelist().Snapshot(); + std::vector excluded_projects; + + { + LOCK(cs_ConvergedScraperStatsCache); + + excluded_projects = ConvergedScraperStatsCache.Convergence.vExcludedProjects; + } // This is temporary implementation of the suppression of "not attached" for projects that // are whitelisted that require an external adapter, and so will not be attached as a native @@ -505,8 +528,15 @@ std::vector ResearcherModel::buildProjectTable(bool extended) const // that also compares the project URL to establish the relationship // between local projects and whitelisted projects: // - if (const Project* whitelist_project = project.TryWhitelist(whitelist)) { - row.m_whitelisted = true; + if (const ProjectEntry* whitelist_project = project.TryWhitelist(whitelist)) { + if (std::find(excluded_projects.begin(), excluded_projects.end(), whitelist_project->m_name) + != excluded_projects.end()) { + row.m_whitelisted = ProjectRow::WhiteListStatus::Greylisted; + row.m_error = tr("Greylisted"); + } else { + row.m_whitelisted = ProjectRow::WhiteListStatus::True; + } + row.m_name = QString::fromStdString(whitelist_project->DisplayName()).toLower(); for (const auto& explain_mag_project : explain_mag) { @@ -521,7 +551,7 @@ std::vector ResearcherModel::buildProjectTable(bool extended) const rows.emplace(whitelist_project->m_name, std::move(row)); } else { - row.m_whitelisted = false; + row.m_whitelisted = ProjectRow::WhiteListStatus::False; row.m_name = QString::fromStdString(project.m_name).toLower(); row.m_rac = project.m_rac; @@ -542,7 +572,7 @@ std::vector ResearcherModel::buildProjectTable(bool extended) const ProjectRow row; row.m_gdpr_controls = project.HasGDPRControls(); - row.m_whitelisted = true; + row.m_name = QString::fromStdString(project.DisplayName()).toLower(); row.m_magnitude = 0.0; @@ -554,6 +584,14 @@ std::vector ResearcherModel::buildProjectTable(bool extended) const row.m_error = tr("Uses external adapter"); } + if (std::find(excluded_projects.begin(), excluded_projects.end(), project.m_name) + != excluded_projects.end()) { + row.m_whitelisted = ProjectRow::WhiteListStatus::Greylisted; + row.m_error = tr("Greylisted"); + } else { + row.m_whitelisted = ProjectRow::WhiteListStatus::True; + } + for (const auto& explain_mag_project : explain_mag) { if (explain_mag_project.m_name == project.m_name) { row.m_magnitude = explain_mag_project.m_magnitude; diff --git a/src/qt/researcher/researchermodel.h b/src/qt/researcher/researchermodel.h index dd353a9807..81309fc0b1 100644 --- a/src/qt/researcher/researchermodel.h +++ b/src/qt/researcher/researchermodel.h @@ -44,6 +44,7 @@ enum class BeaconStatus PENDING, RENEWAL_NEEDED, RENEWAL_POSSIBLE, + ALREADY_IN_MEMPOOL, UNKNOWN, }; @@ -59,7 +60,14 @@ enum class BeaconStatus class ProjectRow { public: - bool m_whitelisted; + enum WhiteListStatus + { + False, + Greylisted, + True + }; + + WhiteListStatus m_whitelisted; std::optional m_gdpr_controls; QString m_name; QString m_cpid; @@ -93,6 +101,11 @@ class ResearcherModel : public QObject bool hasEligibleProjects() const; bool hasPoolProjects() const; bool hasActiveBeacon() const; + + //! + //! \brief hasPendingBeacon returns true if m_pending_beacon is not null and also not expired while pending. + //! \return boolean + //! bool hasPendingBeacon() const; bool hasRenewableBeacon() const; bool beaconExpired() const; diff --git a/src/qt/sidestaketablemodel.cpp b/src/qt/sidestaketablemodel.cpp new file mode 100644 index 0000000000..018cb0977e --- /dev/null +++ b/src/qt/sidestaketablemodel.cpp @@ -0,0 +1,463 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +static void RwSettingsUpdated(SideStakeTableModel* sidestake_model) +{ + qDebug() << QString("%1").arg(__func__); + QMetaObject::invokeMethod(sidestake_model, "updateSideStakeTableModel", Qt::QueuedConnection); +} + +} // anonymous namespace + +SideStakeLessThan::SideStakeLessThan(int column, Qt::SortOrder order) + : m_column(column) + , m_order(order) +{} + +bool SideStakeLessThan::operator()(const GRC::SideStake& left, const GRC::SideStake& right) const +{ + const GRC::SideStake* pLeft = &left; + const GRC::SideStake* pRight = &right; + + if (m_order == Qt::DescendingOrder) { + std::swap(pLeft, pRight); + } + + // For the purposes of sorting mandatory and local sidestakes in the GUI table, we will shift the local status enum to int + // values that are above the mandatory enum values by OUT_OF_BOUND on the mandatory status enum. + int left_status, right_status; + + if (pLeft->IsMandatory()) { + left_status = static_cast(std::get(pLeft->GetStatus()).Value()); + } else { + // For purposes of comparison, the enum value for local sidestake is shifted by the max entry of the mandatory + // status enum. + left_status = static_cast(std::get(pLeft->GetStatus()).Value()) + + static_cast(GRC::MandatorySideStake::MandatorySideStakeStatus::OUT_OF_BOUND); + } + + if (pRight->IsMandatory()) { + right_status = static_cast(std::get(pRight->GetStatus()).Value()); + } else { + // For purposes of comparison, the enum value for local sidestake is shifted by the max entry of the mandatory + // status enum. + right_status = static_cast(std::get(pRight->GetStatus()).Value()) + + static_cast(GRC::MandatorySideStake::MandatorySideStakeStatus::OUT_OF_BOUND); + } + + switch (static_cast(m_column)) { + case SideStakeTableModel::Address: + return pLeft->GetDestination() < pRight->GetDestination(); + case SideStakeTableModel::Allocation: + return pLeft->GetAllocation() < pRight->GetAllocation(); + case SideStakeTableModel::Description: + return pLeft->GetDescription().compare(pRight->GetDescription()) < 0; + case SideStakeTableModel::Status: + return left_status < right_status; + } // no default case, so the compiler can warn about missing cases + assert(false); +} + +class SideStakeTablePriv +{ +public: + QList m_cached_sidestakes; + int m_sort_column{-1}; + Qt::SortOrder m_sort_order; + + void refreshSideStakes() + { + m_cached_sidestakes.clear(); + + std::vector core_sidestakes + = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, true); + + m_cached_sidestakes.reserve(core_sidestakes.size()); + + for (const auto& entry : core_sidestakes) { + m_cached_sidestakes.append(*entry); + } + + if (m_sort_column >= 0) { + std::stable_sort(m_cached_sidestakes.begin(), m_cached_sidestakes.end(), SideStakeLessThan(m_sort_column, m_sort_order)); + } + } + + int size() + { + return m_cached_sidestakes.size(); + } + + GRC::SideStake* index(int idx) + { + if (idx >= 0 && idx < m_cached_sidestakes.size()) { + return &m_cached_sidestakes[idx]; + } + + return nullptr; + } +}; + +SideStakeTableModel::SideStakeTableModel(OptionsModel* parent) + : QAbstractTableModel(parent) +{ + m_columns << tr("Address") << tr("Allocation") << tr("Description") << tr("Status"); + m_priv.reset(new SideStakeTablePriv()); + + subscribeToCoreSignals(); + + // load initial data + refresh(); +} + +SideStakeTableModel::~SideStakeTableModel() +{ + // Intentionally left empty +} + +int SideStakeTableModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_priv->size(); +} + +int SideStakeTableModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_columns.length(); +} + +QVariant SideStakeTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + + GRC::SideStake* rec = static_cast(index.internalPointer()); + + const auto column = static_cast(index.column()); + if (role == Qt::DisplayRole) { + switch (column) { + case Address: + return QString::fromStdString(CBitcoinAddress(rec->GetDestination()).ToString()); + case Allocation: + return QString().setNum(rec->GetAllocation().ToPercent(), 'f', 2) + QString("\%"); + case Description: + return QString::fromStdString(rec->GetDescription()); + case Status: + return QString::fromStdString(rec->StatusToString()); + } // no default case, so the compiler can warn about missing cases + assert(false); + } else if (role == Qt::EditRole) { + switch (column) { + case Address: + return QString::fromStdString(CBitcoinAddress(rec->GetDestination()).ToString()); + case Allocation: + return QString().setNum(rec->GetAllocation().ToPercent(), 'f', 2); + case Description: + return QString::fromStdString(rec->GetDescription()); + case Status: + return QString::fromStdString(rec->StatusToString()); + } // no default case, so the compiler can warn about missing cases + } else if (role == Qt::TextAlignmentRole) { + switch (column) { + case Address: + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case Allocation: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + case Description: + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case Status: + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + default: + return QVariant(); + } + } + + return QVariant(); +} + +bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) { + return false; + } + + GRC::SideStakeRegistry& registry = GRC::GetSideStakeRegistry(); + + GRC::SideStake* rec = static_cast(index.internalPointer()); + + if (role != Qt::EditRole) { + return false; + } + + m_edit_status = OK; + + switch (index.column()) + { + case Address: + { + // The address of a sidestake entry is not editable. + return false; + } + case Allocation: + { + GRC::Allocation prior_total_allocation; + + // Save the original local sidestake (also in the core). + GRC::SideStake orig_sidestake = *rec; + + if (orig_sidestake.GetAllocation().ToPercent() == value.toDouble()) { + m_edit_status = NO_CHANGES; + return false; + } + + for (const auto& entry : registry.ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, true)) { + CTxDestination destination = entry->GetDestination(); + GRC::Allocation allocation = entry->GetAllocation(); + + if (destination == orig_sidestake.GetDestination()) { + continue; + } + + prior_total_allocation += allocation; + } + + bool parse_ok = false; + double read_allocation = value.toDouble(&parse_ok) / 100.0; + + GRC::Allocation modified_allocation(read_allocation); + + if (!parse_ok || modified_allocation < 0 || prior_total_allocation + modified_allocation > 1) { + m_edit_status = INVALID_ALLOCATION; + + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: m_edit_status = %i", + __func__, + (int) m_edit_status); + + return false; + } + + // Overwrite the existing sidestake entry with the modified allocation + registry.NonContractAdd(GRC::LocalSideStake(orig_sidestake.GetDestination(), + modified_allocation, + orig_sidestake.GetDescription(), + std::get(orig_sidestake.GetStatus()).Value()), + true); + + break; + } + case Description: + { + std::string orig_value = value.toString().toStdString(); + std::string san_value = SanitizeString(orig_value, SAFE_CHARS_CSV); + + if (rec->GetDescription() == orig_value) { + m_edit_status = NO_CHANGES; + return false; + } + + if (san_value != orig_value) { + m_edit_status = INVALID_DESCRIPTION; + return false; + } + + // Save the original local sidestake (also in the core). + GRC::SideStake orig_sidestake = *rec; + + // Overwrite the existing sidestake entry with the modified description + registry.NonContractAdd(GRC::LocalSideStake(orig_sidestake.GetDestination(), + orig_sidestake.GetAllocation(), + san_value, + std::get(orig_sidestake.GetStatus()).Value()), + true); + + break; + } + case Status: + // Status is not editable + return false; + } + + updateSideStakeTableModel(); + + return true; +} + +QVariant SideStakeTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole && section < m_columns.size()) + { + return m_columns[section]; + } + } + return QVariant(); +} + +Qt::ItemFlags SideStakeTableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + GRC::SideStake* rec = static_cast(index.internalPointer()); + + Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + if (!rec->IsMandatory() && (index.column() == Allocation || index.column() == Description)) { + retval |= Qt::ItemIsEditable; + } + + return retval; +} + +QModelIndex SideStakeTableModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + GRC::SideStake* data = m_priv->index(row); + + if (data) + return createIndex(row, column, data); + return QModelIndex(); +} + +QString SideStakeTableModel::addRow(const QString &address, const QString &allocation, const QString description) +{ + GRC::SideStakeRegistry& registry = GRC::GetSideStakeRegistry(); + + CBitcoinAddress sidestake_address; + sidestake_address.SetString(address.toStdString()); + + m_edit_status = OK; + + if (!sidestake_address.IsValid()) { + m_edit_status = INVALID_ADDRESS; + return QString(); + } + + // Check for duplicate local sidestakes. Here we use the actual core sidestake registry rather than the + // UI model. + std::vector core_local_sidestake = registry.Try(sidestake_address.Get(), GRC::SideStake::FilterFlag::LOCAL); + + if (!core_local_sidestake.empty()) { + m_edit_status = DUPLICATE_ADDRESS; + return QString(); + } + + GRC::Allocation prior_total_allocation; + + // Get total allocation of all active/mandatory sidestake entries + for (const auto& entry : registry.ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, true)) { + prior_total_allocation += entry->GetAllocation(); + } + + // The new allocation must be parseable as a double, must be greater than or equal to 0, and + // must result in a total allocation of less than or equal to 100%. + bool parse_ok = false; + double read_allocation = allocation.toDouble(&parse_ok) / 100.0; + + GRC::Allocation sidestake_allocation(read_allocation); + + if (!parse_ok || sidestake_allocation < 0 || prior_total_allocation + sidestake_allocation > 1) { + m_edit_status = INVALID_ALLOCATION; + + LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: m_edit_status = %i", + __func__, + (int) m_edit_status); + + return QString(); + } + + std::string sidestake_description = description.toStdString(); + std::string sanitized_description = SanitizeString(sidestake_description, SAFE_CHARS_CSV); + + if (sanitized_description != sidestake_description) { + m_edit_status = INVALID_DESCRIPTION; + return QString(); + } + + registry.NonContractAdd(GRC::LocalSideStake(sidestake_address.Get(), + sidestake_allocation, + sanitized_description, + GRC::LocalSideStake::LocalSideStakeStatus::ACTIVE)); + + updateSideStakeTableModel(); + + return QString::fromStdString(sidestake_address.ToString()); +} + +bool SideStakeTableModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + GRC::SideStake* rec = m_priv->index(row); + + if (count != 1 || !rec || rec->IsMandatory()) + { + // Can only remove one row at a time, and cannot remove rows not in model. + // Also refuse to remove mandatory sidestakes. + return false; + } + + GRC::GetSideStakeRegistry().NonContractDelete(rec->GetDestination()); + + updateSideStakeTableModel(); + + return true; +} + +SideStakeTableModel::EditStatus SideStakeTableModel::getEditStatus() const +{ + return m_edit_status; +} + +void SideStakeTableModel::refresh() +{ + Q_EMIT layoutAboutToBeChanged(); + m_priv->refreshSideStakes(); + + m_edit_status = OK; + + Q_EMIT layoutChanged(); +} + +void SideStakeTableModel::sort(int column, Qt::SortOrder order) +{ + m_priv->m_sort_column = column; + m_priv->m_sort_order = order; + refresh(); +} + +void SideStakeTableModel::updateSideStakeTableModel() +{ + refresh(); + + emit updateSideStakeTableModelSig(); +} + +void SideStakeTableModel::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.RwSettingsUpdated_connect(boost::bind(RwSettingsUpdated, this)); +} + +void SideStakeTableModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client (currently no-op). +} diff --git a/src/qt/sidestaketablemodel.h b/src/qt/sidestaketablemodel.h new file mode 100644 index 0000000000..2c496f373c --- /dev/null +++ b/src/qt/sidestaketablemodel.h @@ -0,0 +1,103 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_SIDESTAKETABLEMODEL_H +#define BITCOIN_QT_SIDESTAKETABLEMODEL_H + +#include +#include +#include "gridcoin/sidestake.h" + +class OptionsModel; +class SideStakeTablePriv; + +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +class SideStakeLessThan +{ +public: + SideStakeLessThan(int column, Qt::SortOrder order); + + bool operator()(const GRC::SideStake& left, const GRC::SideStake& right) const; + +private: + int m_column; + Qt::SortOrder m_order; +}; + +//! +//! \brief The SideStakeTableModel class represents the core sidestake registry as a model which can be consumed +//! and updated by the GUI. +//! +class SideStakeTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit SideStakeTableModel(OptionsModel* parent = nullptr); + ~SideStakeTableModel(); + + enum ColumnIndex { + Address, + Allocation, + Description, + Status + }; + + static constexpr std::initializer_list all_ColumnIndex = {Address, + Allocation, + Description, + Status}; + + /** Return status of edit/insert operation */ + enum EditStatus { + OK, /**< Everything ok */ + NO_CHANGES, /**< No changes were made during edit operation */ + INVALID_ADDRESS, /**< Unparseable address */ + DUPLICATE_ADDRESS, /**< Address already in sidestake registry */ + INVALID_ALLOCATION, /**< Allocation is invalid (i.e. not parseable or not between 0.0 and 100.0) */ + INVALID_DESCRIPTION /**< Description contains an invalid character */ + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + void sort(int column, Qt::SortOrder order); + /*@}*/ + + /** Add a sidestake to the model. + Returns the added address on success, and an empty string otherwise. + */ + QString addRow(const QString &address, const QString &allocation, const QString description); + + EditStatus getEditStatus() const; + +public Q_SLOTS: + void refresh(); + +private: + QStringList m_columns; + std::unique_ptr m_priv; + EditStatus m_edit_status; + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); + +signals: + + void updateSideStakeTableModelSig(); + +public slots: + void updateSideStakeTableModel(); +}; + +#endif // BITCOIN_QT_SIDESTAKETABLEMODEL_H diff --git a/src/qt/test/CMakeLists.txt b/src/qt/test/CMakeLists.txt new file mode 100644 index 0000000000..4717e74597 --- /dev/null +++ b/src/qt/test/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(test_gridcoin-qt + test_main.cpp + uritests.cpp +) + +set_target_properties(test_gridcoin-qt PROPERTIES + AUTOMOC ON +) + +target_link_libraries(test_gridcoin-qt PRIVATE + Qt5::Test + Qt5::Widgets + gridcoin_util + gridcoinqt +) + +add_test(NAME gridcoin_qt_tests COMMAND test_gridcoin-qt) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 8747cc6664..1fc5af5a19 100755 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,6 +1,7 @@ #include "transactionrecord.h" #include "wallet/wallet.h" #include "base58.h" +#include /* Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction(const CWalletTx &wtx, bool datetime_limit_flag, const int64_t &datetime_limit) @@ -361,6 +362,54 @@ QList TransactionRecord::decomposeTransaction(const CWallet * return parts; } +QString TransactionRecord::TypeToString() const +{ + return TypeToString(type); +} + +QString TransactionRecord::TypeToString(const Type& type, const bool& translated) +{ + if (translated) { + switch(type) { + case Other: return QObject::tr("Other"); + case Generated: return QObject::tr("Mined"); + case SendToAddress: return QObject::tr("Sent to Address"); + case SendToOther: return QObject::tr("Sent to Other"); + case RecvWithAddress: return QObject::tr("Received with Address"); + case RecvFromOther: return QObject::tr("Received from Other"); + case SendToSelf: return QObject::tr("Self"); + case BeaconAdvertisement: return QObject::tr("Beacon Advertisements"); + case Poll: return QObject::tr("Polls"); + case Vote: return QObject::tr("Votes"); + case Message: return QObject::tr("Messages"); + case MRC: return QObject::tr("MRCs"); + } + + assert(false); // Suppress warning + } else { + switch(type) { + case Other: return "Other"; + case Generated: return "Mined"; + case SendToAddress: return "Sent to Address"; + case SendToOther: return "Sent to Other"; + case RecvWithAddress: return "Received with Address"; + case RecvFromOther: return "Received from Other"; + case SendToSelf: return "Self"; + case BeaconAdvertisement: return "Beacon Advertisements"; + case Poll: return "Polls"; + case Vote: return "Votes"; + case Message: return "Messages"; + case MRC: return "MRCs"; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return QString{}; +} + void TransactionRecord::updateStatus(const CWalletTx &wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 9ce027b73c..4bd8c075d7 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -87,6 +87,18 @@ class TransactionRecord MRC }; + static constexpr std::initializer_list TYPES {Other, + Generated, + SendToAddress, + RecvWithAddress, + RecvFromOther, + SendToSelf, + BeaconAdvertisement, + Poll, + Vote, + Message, + MRC}; + /** Number of confirmation recommended for accepting a transaction */ static const int RecommendedNumConfirmations = 10; @@ -114,6 +126,9 @@ class TransactionRecord static bool showTransaction(const CWalletTx &wtx, bool datetime_limit_flag = false, const int64_t &datetime_limit = 0); static QList decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx); + QString TypeToString() const; + static QString TypeToString(const Type& type, const bool& translated = true); + /** @name Immutable transaction attributes @{*/ uint256 hash; diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 7a58dff1ee..48b26bdbaf 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -216,7 +216,7 @@ class TransactionTablePriv // // If a status update is needed (blocks came in since last check), // update the status of this transaction from the wallet. Otherwise, - // simply re-use the cached status. + // simply reuse the cached status. TRY_LOCK(cs_main, lockMain); if(lockMain) { diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index b080d75696..7457374500 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -81,14 +81,15 @@ TransactionView::TransactionView(QWidget *parent) filterFrameLayout->addWidget(dateWidget); typeWidget = new QComboBox(this); + + // Add catch-all typeWidget->addItem(tr("All Types"), TransactionFilterProxy::ALL_TYPES); - typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) | - TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther)); - typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) | - TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); - typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); - typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); - typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); + + // Add types from TransactionRecord Type enum. + for (const auto& iter : TransactionRecord::TYPES) { + typeWidget->addItem(TransactionRecord::TypeToString(iter), TransactionFilterProxy::TYPE(iter)); + } + filterFrameLayout->addWidget(typeWidget); filterFrameLayout->addStretch(); diff --git a/src/qt/updatedialog.cpp b/src/qt/updatedialog.cpp new file mode 100644 index 0000000000..c5e55649d5 --- /dev/null +++ b/src/qt/updatedialog.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "updatedialog.h" +#include "qicon.h" +#include "qstyle.h" +#include "qt/decoration.h" + +#include "ui_updatedialog.h" + +UpdateDialog::UpdateDialog(QWidget* parent) + : QDialog(parent) + , ui(new Ui::UpdateDialog) +{ + ui->setupUi(this); + + resize(GRC::ScaleSize(this, width(), height())); +} + +UpdateDialog::~UpdateDialog() +{ + delete ui; +} + +void UpdateDialog::setVersion(QString version) +{ + ui->versionData->setText(version); +} + +void UpdateDialog::setDetails(QString message) +{ + ui->versionDetails->setText(message); +} + +void UpdateDialog::setUpgradeType(GRC::Upgrade::UpgradeType upgrade_type) +{ + QIcon info_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + QIcon warning_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); + QIcon unknown_icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); + + switch (upgrade_type) { + case GRC::Upgrade::UpgradeType::Mandatory: + [[fallthrough]]; + case GRC::Upgrade::UpgradeType::Unsupported: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, warning_icon, 48)); + break; + case GRC::Upgrade::UpgradeType::Leisure: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, info_icon, 48)); + break; + case GRC::Upgrade::Unknown: + ui->infoIcon->setPixmap(GRC::ScaleIcon(this, unknown_icon, 48)); + break; + } +} diff --git a/src/qt/updatedialog.h b/src/qt/updatedialog.h new file mode 100644 index 0000000000..7987b706f7 --- /dev/null +++ b/src/qt/updatedialog.h @@ -0,0 +1,32 @@ +// Copyright (c) 2014-2024 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_UPDATEDIALOG_H +#define BITCOIN_QT_UPDATEDIALOG_H + +#include "gridcoin/upgrade.h" +#include + +namespace Ui { +class UpdateDialog; +} + +class UpdateDialog : public QDialog +{ + Q_OBJECT + +public: + explicit UpdateDialog(QWidget* parent = nullptr); + ~UpdateDialog(); + + void setVersion(QString version); + void setDetails(QString message); + void setUpgradeType(GRC::Upgrade::UpgradeType upgrade_type); + +private: + Ui::UpdateDialog *ui; + +}; + +#endif // BITCOIN_QT_UPDATEDIALOG_H diff --git a/src/qt/upgradeqt.cpp b/src/qt/upgradeqt.cpp index 6bbfd9519d..9499d5a032 100644 --- a/src/qt/upgradeqt.cpp +++ b/src/qt/upgradeqt.cpp @@ -41,8 +41,10 @@ bool UpgradeQt::SnapshotMain(QApplication& SnapshotApp) // Verify a mandatory release is not available before we continue to snapshot download. std::string VersionResponse = ""; + std::string change_log; + Upgrade::UpgradeType upgrade_type {Upgrade::UpgradeType::Unknown}; - if (UpgradeMain.CheckForLatestUpdate(VersionResponse, false, true)) + if (UpgradeMain.CheckForLatestUpdate(VersionResponse, change_log, upgrade_type, false, true)) { ErrorMsg(UpgradeMain.ResetBlockchainMessages(Upgrade::UpdateAvailable), UpgradeMain.ResetBlockchainMessages(Upgrade::GithubResponse) + "\r\n" + VersionResponse); diff --git a/src/qt/voting/additionalfieldstablemodel.cpp b/src/qt/voting/additionalfieldstablemodel.cpp index a8f8148fa6..85f99cb639 100644 --- a/src/qt/voting/additionalfieldstablemodel.cpp +++ b/src/qt/voting/additionalfieldstablemodel.cpp @@ -177,7 +177,7 @@ void AdditionalFieldsTableModel::refresh() ->reload(additional_fields); } -Qt::SortOrder AdditionalFieldsTableModel::sort(int column) +Qt::SortOrder AdditionalFieldsTableModel::custom_sort(int column) { if (sortColumn() == column) { QSortFilterProxyModel::sort(column, static_cast(!sortOrder())); diff --git a/src/qt/voting/additionalfieldstablemodel.h b/src/qt/voting/additionalfieldstablemodel.h index 596db7ef53..40cd3703dd 100644 --- a/src/qt/voting/additionalfieldstablemodel.h +++ b/src/qt/voting/additionalfieldstablemodel.h @@ -42,7 +42,7 @@ class AdditionalFieldsTableModel : public QSortFilterProxyModel public slots: void refresh(); - Qt::SortOrder sort(int column); + Qt::SortOrder custom_sort(int column); private: const PollItem* m_poll_item; diff --git a/src/qt/voting/additionalfieldstableview.cpp b/src/qt/voting/additionalfieldstableview.cpp index f184a9cca1..114b967e01 100644 --- a/src/qt/voting/additionalfieldstableview.cpp +++ b/src/qt/voting/additionalfieldstableview.cpp @@ -20,6 +20,11 @@ AdditionalFieldsTableView::~AdditionalFieldsTableView() void AdditionalFieldsTableView::resizeEvent(QResizeEvent* event) { int height = horizontalHeader()->height(); + + if (!model()) { + return; + } + for (int i = 0; i < model()->rowCount(); ++i) { height += rowHeight(i); diff --git a/src/qt/voting/pollcard.cpp b/src/qt/voting/pollcard.cpp index b08df8c9e6..1071192d00 100644 --- a/src/qt/voting/pollcard.cpp +++ b/src/qt/voting/pollcard.cpp @@ -39,6 +39,7 @@ PollCard::PollCard(const PollItem& poll_item, QWidget* parent) ui->myPercentAVWLabel->setText("N/A"); } else { QString choices_str; + QString weights_str; int64_t my_total_weight = 0; @@ -49,11 +50,17 @@ PollCard::PollCard(const PollItem& poll_item, QWidget* parent) choices_str = QString(poll_item.m_choices[choice.first].m_label); } + if (!weights_str.isEmpty()) { + weights_str += ", " + QString::number(choice.second / COIN); + } else { + weights_str = QString::number(choice.second / COIN); + } + my_total_weight += choice.second / COIN; } ui->myLastVoteAnswerLabel->setText(choices_str); - ui->myVoteWeightLabel->setText(QString::number(my_total_weight)); + ui->myVoteWeightLabel->setText(weights_str); if (poll_item.m_active_weight) ui->myPercentAVWLabel->setText(QString::number((double) my_total_weight / (double) poll_item.m_active_weight * (double) 100.0, 'f', 4) + '\%'); @@ -83,6 +90,12 @@ PollCard::PollCard(const PollItem& poll_item, QWidget* parent) ui->invalidLabel->show(); } + if (poll_item.m_stale) { + ui->staleLabel->show(); + } else { + ui->staleLabel->hide(); + } + ui->topAnswerLabel->setText(poll_item.m_top_answer); if (!poll_item.m_finished) { diff --git a/src/qt/voting/pollcardview.cpp b/src/qt/voting/pollcardview.cpp index 6c1e7798b3..c720183851 100644 --- a/src/qt/voting/pollcardview.cpp +++ b/src/qt/voting/pollcardview.cpp @@ -33,7 +33,7 @@ PollCardView::~PollCardView() void PollCardView::setModel(PollTableModel* model) { - m_model = model; + m_polltable_model = model; if (!model) { return; @@ -41,7 +41,7 @@ void PollCardView::setModel(PollTableModel* model) connect(model, &PollTableModel::layoutChanged, this, &PollCardView::redraw); - if (!m_refresh_timer && m_model->includesActivePolls()) { + if (!m_refresh_timer && m_polltable_model->includesActivePolls()) { m_refresh_timer.reset(new QTimer(this)); m_refresh_timer->setTimerType(Qt::VeryCoarseTimer); @@ -76,15 +76,15 @@ void PollCardView::redraw() // sorting and filtering. Hook up model events for these operations. clear(); - if (!m_model) { + if (!m_polltable_model) { return; } const QDateTime now = QDateTime::currentDateTimeUtc(); const QModelIndex dummy_parent; - for (int i = 0; i < m_model->rowCount(dummy_parent); ++i) { - if (const PollItem* poll_item = m_model->rowItem(i)) { + for (int i = 0; i < m_polltable_model->rowCount(dummy_parent); ++i) { + if (const PollItem* poll_item = m_polltable_model->rowItem(i)) { PollCard* card = new PollCard(*poll_item, this); card->updateRemainingTime(now); card->updateIcons(m_theme); diff --git a/src/qt/voting/pollcardview.h b/src/qt/voting/pollcardview.h index d85efa6503..58da557f7f 100644 --- a/src/qt/voting/pollcardview.h +++ b/src/qt/voting/pollcardview.h @@ -43,7 +43,7 @@ public slots: private: Ui::PollCardView* ui; - PollTableModel* m_model; + PollTableModel* m_polltable_model; std::unique_ptr m_refresh_timer; QString m_theme; diff --git a/src/qt/voting/polltab.cpp b/src/qt/voting/polltab.cpp index d08d9b1e26..46f09e9708 100644 --- a/src/qt/voting/polltab.cpp +++ b/src/qt/voting/polltab.cpp @@ -134,7 +134,7 @@ private slots: PollTab::PollTab(QWidget* parent) : QWidget(parent) , ui(new Ui::PollTab) - , m_model(new PollTableModel(this)) + , m_polltable_model(new PollTableModel(this)) , m_no_result(new NoResult(this)) , m_loading(new LoadingBar(this)) { @@ -152,7 +152,12 @@ PollTab::PollTab(QWidget* parent) connect(ui->cards, &PollCardView::detailsRequested, this, &PollTab::showDetailsRowDialog); connect(ui->table, &QAbstractItemView::doubleClicked, this, &PollTab::showPreferredDialog); connect(ui->table, &QWidget::customContextMenuRequested, this, &PollTab::showTableContextMenu); - connect(m_model.get(), &PollTableModel::layoutChanged, this, &PollTab::finishRefresh); + connect(m_polltable_model.get(), &PollTableModel::layoutChanged, this, &PollTab::finishRefresh); + + // Forward the polltable model signal to the Poll Tab signal to avoid having to directly include the PollTableModel + // in the voting page. + connect(m_polltable_model.get(), &PollTableModel::newVoteReceivedAndPollMarkedDirty, + this, &PollTab::newVoteReceivedAndPollMarkedDirty); } PollTab::~PollTab() @@ -163,15 +168,15 @@ PollTab::~PollTab() void PollTab::setVotingModel(VotingModel* model) { m_voting_model = model; - m_model->setModel(model); + m_polltable_model->setModel(model); - ui->cards->setModel(m_model.get()); - ui->table->setModel(m_model.get()); + ui->cards->setModel(m_polltable_model.get()); + ui->table->setModel(m_polltable_model.get()); } void PollTab::setPollFilterFlags(PollFilterFlag flags) { - m_model->setPollFilterFlags(flags); + m_polltable_model->setPollFilterFlags(flags); } void PollTab::changeViewMode(const ViewId view_id) @@ -181,26 +186,26 @@ void PollTab::changeViewMode(const ViewId view_id) void PollTab::refresh() { - if (m_model->empty()) { + if (m_polltable_model->empty()) { m_no_result->showDefaultLoadingTitle(); m_no_result->contentWidgetAs()->setText(WaitMessage()); } m_loading->start(); - m_model->refresh(); + m_polltable_model->refresh(); } void PollTab::filter(const QString& needle) { if (needle != m_last_filter) { - m_model->changeTitleFilter(needle); + m_polltable_model->changeTitleFilter(needle); m_last_filter = needle; } } void PollTab::sort(const int column) { - const Qt::SortOrder order = m_model->sort(column); + const Qt::SortOrder order = m_polltable_model->custom_sort(column); ui->table->horizontalHeader()->setSortIndicator(column, order); } @@ -215,7 +220,7 @@ const PollItem* PollTab::selectedTableItem() const return nullptr; } - return m_model->rowItem( + return m_polltable_model->rowItem( ui->table->selectionModel()->selectedIndexes().first().row()); } @@ -228,10 +233,10 @@ void PollTab::resizeEvent(QResizeEvent* event) void PollTab::finishRefresh() { m_loading->finish(); - ui->stack->setVisible(!m_model->empty()); - m_no_result->setVisible(m_model->empty()); + ui->stack->setVisible(!m_polltable_model->empty()); + m_no_result->setVisible(m_polltable_model->empty()); - if (m_model->empty()) { + if (m_polltable_model->empty()) { m_no_result->showDefaultNoResultTitle(); m_no_result->contentWidgetAs()->setText(FullRefreshMessage()); } @@ -239,7 +244,7 @@ void PollTab::finishRefresh() void PollTab::showVoteRowDialog(int row) { - if (const PollItem* const poll_item = m_model->rowItem(row)) { + if (const PollItem* const poll_item = m_polltable_model->rowItem(row)) { showVoteDialog(*poll_item); } } @@ -251,7 +256,7 @@ void PollTab::showVoteDialog(const PollItem& poll_item) void PollTab::showDetailsRowDialog(int row) { - if (const PollItem* const poll_item = m_model->rowItem(row)) { + if (const PollItem* const poll_item = m_polltable_model->rowItem(row)) { showDetailsDialog(*poll_item); } } @@ -263,7 +268,7 @@ void PollTab::showDetailsDialog(const PollItem& poll_item) void PollTab::showPreferredDialog(const QModelIndex& index) { - if (const PollItem* const poll_item = m_model->rowItem(index.row())) { + if (const PollItem* const poll_item = m_polltable_model->rowItem(index.row())) { if (poll_item->m_finished) { showDetailsDialog(*poll_item); } else { diff --git a/src/qt/voting/polltab.h b/src/qt/voting/polltab.h index 93e68252b4..0034f26682 100644 --- a/src/qt/voting/polltab.h +++ b/src/qt/voting/polltab.h @@ -50,6 +50,9 @@ class PollTab : public QWidget void setVotingModel(VotingModel* voting_model); void setPollFilterFlags(GRC::PollFilterFlag flags); +signals: + void newVoteReceivedAndPollMarkedDirty(); + public slots: void changeViewMode(const ViewId view_id); void refresh(); @@ -60,7 +63,7 @@ public slots: private: Ui::PollTab* ui; VotingModel* m_voting_model; - std::unique_ptr m_model; + std::unique_ptr m_polltable_model; std::unique_ptr m_no_result; std::unique_ptr m_loading; QString m_last_filter; diff --git a/src/qt/voting/polltablemodel.cpp b/src/qt/voting/polltablemodel.cpp index ab423e3e99..880dcf71f1 100644 --- a/src/qt/voting/polltablemodel.cpp +++ b/src/qt/voting/polltablemodel.cpp @@ -5,6 +5,9 @@ #include "qt/guiutil.h" #include "qt/voting/polltablemodel.h" #include "qt/voting/votingmodel.h" +#include "logging.h" +#include "util.h" +#include "util/threadnames.h" #include #include @@ -12,171 +15,179 @@ using namespace GRC; -namespace { -class PollTableDataModel : public QAbstractTableModel +PollTableDataModel::PollTableDataModel() { -public: - PollTableDataModel() - { - qRegisterMetaType>(); - qRegisterMetaType(); - - m_columns - << tr("Title") - << tr("Poll Type") - << tr("Duration") - << tr("Expiration") - << tr("Weight Type") - << tr("Votes") - << tr("Total Weight") - << tr("% of Active Vote Weight") - << tr("Validated") - << tr("Top Answer"); - } + qRegisterMetaType>(); + qRegisterMetaType(); + + m_columns + << tr("Title") + << tr("Poll Type") + << tr("Duration") + << tr("Expiration") + << tr("Weight Type") + << tr("Votes") + << tr("Total Weight") + << tr("% of Active Vote Weight") + << tr("Validated") + << tr("Top Answer") + << tr("Stale Results"); +} - int rowCount(const QModelIndex &parent) const override - { - if (parent.isValid()) { - return 0; - } - return m_rows.size(); +int PollTableDataModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; } + return m_rows.size(); +} - int columnCount(const QModelIndex &parent) const override - { - if (parent.isValid()) { - return 0; - } - return m_columns.size(); +int PollTableDataModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; } + return m_columns.size(); +} - QVariant data(const QModelIndex &index, int role) const override - { - if (!index.isValid()) { - return QVariant(); - } - - const PollItem* row = static_cast(index.internalPointer()); - - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case PollTableModel::Title: - return row->m_title; - case PollTableModel::PollType: - if (row->m_version >= 3) { - return row->m_type_str; - } else { - return QString{}; - } - case PollTableModel::Duration: - return row->m_duration; - case PollTableModel::Expiration: - return GUIUtil::dateTimeStr(row->m_expiration); - case PollTableModel::WeightType: - return row->m_weight_type_str; - case PollTableModel::TotalVotes: - return row->m_total_votes; - case PollTableModel::TotalWeight: - return QString::number(row->m_total_weight); - case PollTableModel::VotePercentAVW: - return QString::number(row->m_vote_percent_AVW, 'f', 4); - case PollTableModel::Validated: - return row->m_validated; - case PollTableModel::TopAnswer: - return row->m_top_answer; - } // no default case, so the compiler can warn about missing cases - assert(false); - - case Qt::TextAlignmentRole: - switch (index.column()) { - case PollTableModel::Duration: - // Pass-through case - case PollTableModel::TotalVotes: - // Pass-through case - case PollTableModel::TotalWeight: - // Pass-through case - case PollTableModel::VotePercentAVW: - // Pass-through case - case PollTableModel::Validated: - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } - break; - - case PollTableModel::SortRole: - switch (index.column()) { - case PollTableModel::Title: - return row->m_title; - case PollTableModel::PollType: - return row->m_type_str; - case PollTableModel::Duration: - return row->m_duration; - case PollTableModel::Expiration: - return row->m_expiration; - case PollTableModel::WeightType: - return row->m_weight_type_str; - case PollTableModel::TotalVotes: - return row->m_total_votes; - case PollTableModel::TotalWeight: - return QVariant::fromValue(row->m_total_weight); - case PollTableModel::VotePercentAVW: - return QVariant::fromValue(row->m_vote_percent_AVW); - case PollTableModel::Validated: - return row->m_validated; - case PollTableModel::TopAnswer: - return row->m_top_answer; - } // no default case, so the compiler can warn about missing cases - assert(false); - } - +QVariant PollTableDataModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { return QVariant(); } - QVariant headerData(int section, Qt::Orientation orientation, int role) const override - { - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole && section < m_columns.size()) { - return m_columns[section]; - } - } + const PollItem* row = static_cast(index.internalPointer()); - return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case PollTableModel::Title: + return row->m_title; + case PollTableModel::PollType: + if (row->m_version >= 3) { + return row->m_type_str; + } else { + return QString{}; + } + case PollTableModel::Duration: + return row->m_duration; + case PollTableModel::Expiration: + return GUIUtil::dateTimeStr(row->m_expiration); + case PollTableModel::WeightType: + return row->m_weight_type_str; + case PollTableModel::TotalVotes: + return row->m_total_votes; + case PollTableModel::TotalWeight: + return QString::number(row->m_total_weight); + case PollTableModel::VotePercentAVW: + return QString::number(row->m_vote_percent_AVW, 'f', 4); + case PollTableModel::Validated: + return row->m_validated; + case PollTableModel::TopAnswer: + return row->m_top_answer; + case PollTableModel::StaleResults: + return row->m_stale; + } // no default case, so the compiler can warn about missing cases + assert(false); + + case Qt::TextAlignmentRole: + switch (index.column()) { + case PollTableModel::Duration: + // Pass-through case + case PollTableModel::TotalVotes: + // Pass-through case + case PollTableModel::TotalWeight: + // Pass-through case + case PollTableModel::VotePercentAVW: + // Pass-through case + case PollTableModel::Validated: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + } + break; + + case PollTableModel::SortRole: + switch (index.column()) { + case PollTableModel::Title: + return row->m_title; + case PollTableModel::PollType: + return row->m_type_str; + case PollTableModel::Duration: + return row->m_duration; + case PollTableModel::Expiration: + return row->m_expiration; + case PollTableModel::WeightType: + return row->m_weight_type_str; + case PollTableModel::TotalVotes: + return row->m_total_votes; + case PollTableModel::TotalWeight: + return QVariant::fromValue(row->m_total_weight); + case PollTableModel::VotePercentAVW: + return QVariant::fromValue(row->m_vote_percent_AVW); + case PollTableModel::Validated: + return row->m_validated; + case PollTableModel::TopAnswer: + return row->m_top_answer; + case PollTableModel::StaleResults: + return row->m_stale; + } // no default case, so the compiler can warn about missing cases + assert(false); } - QModelIndex index(int row, int column, const QModelIndex &parent) const override - { - Q_UNUSED(parent); + return QVariant(); +} - if (row > static_cast(m_rows.size())) { - return QModelIndex(); +QVariant PollTableDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole && section < m_columns.size()) { + return m_columns[section]; } + } - void* data = static_cast(const_cast(&m_rows[row])); + return QVariant(); +} + +QModelIndex PollTableDataModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); - return createIndex(row, column, data); + if (row > static_cast(m_rows.size())) { + return QModelIndex(); } - Qt::ItemFlags flags(const QModelIndex &index) const override - { - if (!index.isValid()) { - return Qt::NoItemFlags; - } + void* data = static_cast(const_cast(&m_rows[row])); + + return createIndex(row, column, data); +} - return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); +Qt::ItemFlags PollTableDataModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; } - void reload(std::vector rows) - { - emit layoutAboutToBeChanged(); - m_rows = std::move(rows); - emit layoutChanged(); + return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + +void PollTableDataModel::reload(std::vector rows) +{ + emit layoutAboutToBeChanged(); + m_rows = std::move(rows); + emit layoutChanged(); +} + +void PollTableDataModel::handlePollStaleFlag(QString poll_txid_string) +{ + emit layoutAboutToBeChanged(); + + for (auto& iter : m_rows) { + if (iter.m_id == poll_txid_string) { + iter.m_stale = true; + } } -private: - QStringList m_columns; - std::vector m_rows; -}; // PollTableDataModel -} // Anonymous namespace + emit layoutChanged(); +} // ----------------------------------------------------------------------------- // Class: PollTableModel @@ -202,7 +213,11 @@ PollTableModel::~PollTableModel() void PollTableModel::setModel(VotingModel* model) { - m_model = model; + m_voting_model = model; + + // Connect poll stale handler to newVoteReceived signal from voting model, which propagates + // from the core. + connect(m_voting_model, &VotingModel::newVoteReceived, this, &PollTableModel::handlePollStaleFlag); } void PollTableModel::setPollFilterFlags(PollFilterFlag flags) @@ -240,18 +255,36 @@ const PollItem* PollTableModel::rowItem(int row) const void PollTableModel::refresh() { - if (!m_model || !m_refresh_mutex.tryLock()) { + if (!m_voting_model || !m_refresh_mutex.tryLock()) { + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: m_refresh_mutex is already taken, so tryLock failed", + __func__); + return; + } else { + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: m_refresh_mutex trylock succeeded.", + __func__); } QtConcurrent::run([this]() { + RenameThread("PollTableModel_refresh"); + util::ThreadSetInternalName("PollTableModel_refresh"); + static_cast(m_data_model.get()) - ->reload(m_model->buildPollTable(m_filter_flags)); + ->reload(m_voting_model->buildPollTable(m_filter_flags)); m_refresh_mutex.unlock(); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: m_refresh_mutex lock released.", + __func__); }); } +void PollTableModel::handlePollStaleFlag(QString poll_txid_string) +{ + m_data_model->handlePollStaleFlag(poll_txid_string); + + emit newVoteReceivedAndPollMarkedDirty(); +} + void PollTableModel::changeTitleFilter(const QString& pattern) { emit layoutAboutToBeChanged(); @@ -259,7 +292,7 @@ void PollTableModel::changeTitleFilter(const QString& pattern) emit layoutChanged(); } -Qt::SortOrder PollTableModel::sort(int column) +Qt::SortOrder PollTableModel::custom_sort(int column) { if (sortColumn() == column) { QSortFilterProxyModel::sort(column, static_cast(!sortOrder())); diff --git a/src/qt/voting/polltablemodel.h b/src/qt/voting/polltablemodel.h index 0923c97a3d..eecf65f5f4 100644 --- a/src/qt/voting/polltablemodel.h +++ b/src/qt/voting/polltablemodel.h @@ -5,6 +5,7 @@ #ifndef GRIDCOIN_QT_VOTING_POLLTABLEMODEL_H #define GRIDCOIN_QT_VOTING_POLLTABLEMODEL_H +#include "uint256.h" #include "gridcoin/voting/filter.h" #include @@ -14,6 +15,26 @@ class PollItem; class VotingModel; +class PollTableDataModel : public QAbstractTableModel +{ +public: + PollTableDataModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void reload(std::vector rows); + void handlePollStaleFlag(QString poll_txid_string); + +private: + QStringList m_columns; + std::vector m_rows; + +}; + class PollTableModel : public QSortFilterProxyModel { Q_OBJECT @@ -31,6 +52,7 @@ class PollTableModel : public QSortFilterProxyModel VotePercentAVW, Validated, TopAnswer, + StaleResults }; enum Roles @@ -50,14 +72,19 @@ class PollTableModel : public QSortFilterProxyModel QString columnName(int offset) const; const PollItem* rowItem(int row) const; +signals: + void newVoteReceivedAndPollMarkedDirty(); + public slots: void refresh(); void changeTitleFilter(const QString& pattern); - Qt::SortOrder sort(int column); + Qt::SortOrder custom_sort(int column); + + void handlePollStaleFlag(QString poll_txid_string); private: - VotingModel* m_model; - std::unique_ptr m_data_model; + VotingModel* m_voting_model; + std::unique_ptr m_data_model; GRC::PollFilterFlag m_filter_flags; QMutex m_refresh_mutex; }; diff --git a/src/qt/voting/pollwizarddetailspage.cpp b/src/qt/voting/pollwizarddetailspage.cpp index 140e66835e..1e75926e76 100644 --- a/src/qt/voting/pollwizarddetailspage.cpp +++ b/src/qt/voting/pollwizarddetailspage.cpp @@ -3,6 +3,7 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "main.h" +#include "qitemdelegate.h" #include "qt/bitcoinunits.h" #include "qt/decoration.h" #include "qt/forms/voting/ui_pollwizarddetailspage.h" @@ -54,6 +55,37 @@ class ChoicesListDelegate : public QStyledItemDelegate } }; // ChoicesListDelegate +//! +//! \brief Applies custom behavior to additional field items in the poll editor. +//! +class AdditionalFieldDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + AdditionalFieldDelegate(QObject* parent = nullptr) : QItemDelegate(parent) + { + } + + QWidget* createEditor( + QWidget* parent, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + QWidget* editor = QItemDelegate::createEditor(parent, option, index); + + if (QLineEdit* line_edit = qobject_cast(editor)) { + if (index.column() == AdditionalFieldsTableModel::Name) { + line_edit->setMaxLength(VotingModel::maxPollAdditionalFieldNameLength()); + } else if (index.column() == AdditionalFieldsTableModel::Value) { + line_edit->setMaxLength(VotingModel::maxPollAdditionalFieldValueLength()); + } + } + + return editor; + } +}; // AdditionalFieldDelegate + //! //! \brief Provides for QWizardPage::registerField() without a real widget. //! @@ -167,6 +199,7 @@ PollWizardDetailsPage::PollWizardDetailsPage(QWidget* parent) ui->responseTypeList->addItem(tr("Multiple Choice")); ChoicesListDelegate* choices_delegate = new ChoicesListDelegate(this); + AdditionalFieldDelegate* additonal_field_delegate = new AdditionalFieldDelegate(this); ui->choicesList->setModel(m_choices_model.get()); ui->choicesList->setItemDelegate(choices_delegate); @@ -174,6 +207,12 @@ PollWizardDetailsPage::PollWizardDetailsPage(QWidget* parent) ui->editChoiceButton->hide(); ui->removeChoiceButton->hide(); + ui->additionalFieldsTableView->setItemDelegate(additonal_field_delegate); + + ui->titleField->setMaxLength(m_voting_model->maxPollTitleLength()); + ui->questionField->setMaxLength(m_voting_model->maxPollQuestionLength()); + ui->urlField->setMaxLength(m_voting_model->maxPollUrlLength()); + connect( ui->responseTypeList, QOverload::of(&QComboBox::currentIndexChanged), this, [=](int index) { @@ -261,12 +300,7 @@ void PollWizardDetailsPage::initializePage() // Only populate poll additional field entries if version >= 3. bool v3_enabled = false; - - { - AssertLockHeld(cs_main); - - v3_enabled = IsPollV3Enabled(nBestHeight); - } + v3_enabled = IsPollV3Enabled(nBestHeight); if (v3_enabled) { poll_item.m_additional_field_entries.push_back( diff --git a/src/qt/voting/pollwizardprojectpage.cpp b/src/qt/voting/pollwizardprojectpage.cpp index f825155be5..1b509d3218 100644 --- a/src/qt/voting/pollwizardprojectpage.cpp +++ b/src/qt/voting/pollwizardprojectpage.cpp @@ -31,6 +31,9 @@ PollWizardProjectPage::PollWizardProjectPage(QWidget* parent) ui->removeWidget->hide(); ui->addRemoveStateLineEdit->hide(); + ui->projectNameField->setMaxLength(m_voting_model->maxPollProjectNameLength()); + ui->projectUrlField->setMaxLength(m_voting_model->maxPollProjectUrlLength()); + QStringListModel* project_names_model = new QStringListModel(this); QStringListModel* project_urls_model = new QStringListModel(this); diff --git a/src/qt/voting/votingmodel.cpp b/src/qt/voting/votingmodel.cpp index bc4ba9ff84..dc55983262 100644 --- a/src/qt/voting/votingmodel.cpp +++ b/src/qt/voting/votingmodel.cpp @@ -15,6 +15,7 @@ #include "gridcoin/voting/payloads.h" #include "logging.h" #include "main.h" +#include "optionsmodel.h" #include "qt/clientmodel.h" #include "qt/voting/votingmodel.h" #include "qt/walletmodel.h" @@ -23,7 +24,6 @@ #include - using namespace GRC; using LogFlags = BCLog::LogFlags; @@ -36,10 +36,19 @@ namespace { //! void NewPollReceived(VotingModel* model, int64_t poll_time) { - LogPrint(LogFlags::QT, "GUI: received NewPollReceived() core signal"); + LogPrint(LogFlags::QT, "INFO: %s: received NewPollReceived() core signal", __func__); QMetaObject::invokeMethod(model, "handleNewPoll", Qt::QueuedConnection, - Q_ARG(int64_t, poll_time)); + Q_ARG(int64_t, poll_time)); +} + +void NewVoteReceived(VotingModel* model, uint256 poll_txid) +{ + LogPrint(LogFlags::QT, "INFO: %s: received NewVoteReceived() core signal", __func__); + + // Ugly but uint256 is not registered as a Metatype. + QMetaObject::invokeMethod(model, "handleNewVote", Qt::QueuedConnection, + Q_ARG(QString, QString().fromStdString(poll_txid.ToString()))); } std::optional BuildPollItem(const PollRegistry::Sequence::Iterator& iter) @@ -116,6 +125,9 @@ std::optional BuildPollItem(const PollRegistry::Sequence::Iterator& it item.m_top_answer = QString::fromStdString(result->WinnerLabel()).replace("_", " "); } + // Mark stale flag false since we just rebuilt the item. + item.m_stale = false; + g_timer.GetTimes(std::string{"End "} + std::string{__func__}, "buildPollTable"); return item; } @@ -134,6 +146,7 @@ VotingModel::VotingModel( , m_options_model(options_model) , m_wallet_model(wallet_model) , m_last_poll_time(0) + , m_pollitems() { subscribeToCoreSignals(); @@ -204,6 +217,42 @@ int VotingModel::maxPollChoiceLabelLength() return Poll::Choice::MAX_LABEL_SIZE; } +int VotingModel::maxPollAdditionalFieldNameLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Poll::AdditionalField::MAX_NAME_SIZE; +} + +int VotingModel::maxPollAdditionalFieldValueLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Poll::AdditionalField::MAX_VALUE_SIZE; +} + +int VotingModel::maxPollProjectNameLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Project::MAX_NAME_SIZE; +} + +int VotingModel::maxPollProjectUrlLength() +{ + // Not strictly accurate: the protocol limits the max length in bytes, but + // Qt limits field lengths in UTF-8 characters which may be represented by + // more than one byte. + // + return Project::MAX_URL_SIZE; +} + OptionsModel& VotingModel::getOptionsModel() { return m_options_model; @@ -236,10 +285,31 @@ QStringList VotingModel::getActiveProjectUrls() const } return Urls; +} + +QStringList VotingModel::getExpiringPollsNotNotified() +{ + QStringList expiring_polls; + + QDateTime now = QDateTime::fromMSecsSinceEpoch(GetAdjustedTime() * 1000); + + qint64 poll_expire_warning = static_cast(m_options_model.getPollExpireNotification() * 3600.0 * 1000.0); + + // Populate the list and mark the poll items included in the list m_expire_notified true. + for (auto& poll : m_pollitems) { + if (!poll.second.m_finished + && now.msecsTo(poll.second.m_expiration) <= poll_expire_warning + && !poll.second.m_expire_notified + && !poll.second.m_self_voted) { + expiring_polls << poll.second.m_title; + poll.second.m_expire_notified = true; + } + } + return expiring_polls; } -std::vector VotingModel::buildPollTable(const PollFilterFlag flags) const +std::vector VotingModel::buildPollTable(const PollFilterFlag flags) { g_timer.InitTimer(__func__, LogInstance().WillLogCategory(BCLog::LogFlags::VOTE)); g_timer.GetTimes(std::string{"Begin "} + std::string{__func__}, __func__); @@ -254,6 +324,29 @@ std::vector VotingModel::buildPollTable(const PollFilterFlag flags) co for (unsigned int i = 0; i < 3; ++i) { for (const auto& iter : WITH_LOCK(m_registry.cs_poll_registry, return m_registry.Polls().Where(flags))) { + // First check to see if the poll item already exists, and if so is it stale (i.e. a new vote has + // been received for that poll). If it is stale, it will need rebuilding. If not, we insert the cached + // poll item into the results and move on. + + bool pollitem_needs_rebuild = true; + bool pollitem_expire_notified = false; + auto pollitems_iter = m_pollitems.find(iter->Ref().Txid()); + + // Note that the NewVoteReceived core signal will also be fired during reorgs where votes are reverted, + // i.e. unreceived. This will cause the stale flag to be set on polls during reorg where votes have been + // removed during reorg, which is what is desired. + if (pollitems_iter != m_pollitems.end()) { + if (!pollitems_iter->second.m_stale) { + // Not stale... the cache entry is good. Insert into items to return and go to the next one. + items.push_back(pollitems_iter->second); + pollitem_needs_rebuild = false; + } else { + // Retain state for expire notification in the case of a stale poll item that needs to be + // refreshed. + pollitem_expire_notified = pollitems_iter->second.m_expire_notified; + } + } + // Note that we are implementing a coarse-grained fork/rollback detector here. // We do this because we have eliminated the cs_main lock to free up the GUI. // Instead we have reversed the locking scheme and have the contract actions (add/delete) @@ -268,13 +361,20 @@ std::vector VotingModel::buildPollTable(const PollFilterFlag flags) co // Transactions that have not been rolled back by a reorg can be safely accessed for reading // by another thread as we are doing here. - try { - if (std::optional item = BuildPollItem(iter)) { - items.push_back(std::move(*item)); + if (pollitem_needs_rebuild) { + try { + if (std::optional item = BuildPollItem(iter)) { + // This will replace any stale existing entry in the cache with the freshly built item. + // It will also correctly add a new entry for a new item. The state of the pending expiry + // notification is retained from the stale entry to the refreshed one. + item->m_expire_notified = pollitem_expire_notified; + m_pollitems[iter->Ref().Txid()] = *item; + items.push_back(std::move(*item)); + } + } catch (InvalidDuetoReorgFork& e) { + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: Invalidated due to reorg/fork. Starting over.", + __func__); } - } catch (InvalidDuetoReorgFork& e) { - LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: Invalidated due to reorg/fork. Starting over.", - __func__); } // This must be AFTER BuildPollItem. If a reorg occurred during reg traversal that could invalidate @@ -440,6 +540,7 @@ VotingResult VotingModel::sendVote( void VotingModel::subscribeToCoreSignals() { uiInterface.NewPollReceived_connect(std::bind(NewPollReceived, this, std::placeholders::_1)); + uiInterface.NewVoteReceived_connect(std::bind(NewVoteReceived, this, std::placeholders::_1)); } void VotingModel::unsubscribeFromCoreSignals() @@ -457,6 +558,22 @@ void VotingModel::handleNewPoll(int64_t poll_time) emit newPollReceived(); } +void VotingModel::handleNewVote(QString poll_txid_string) +{ + uint256 poll_txid; + + poll_txid.SetHex(poll_txid_string.toStdString()); + + auto pollitems_iter = m_pollitems.find(poll_txid); + + if (pollitems_iter != m_pollitems.end()) { + // Set stale flag on poll item associated with vote. + pollitems_iter->second.m_stale = true; + } + + emit newVoteReceived(poll_txid_string); +} + // ----------------------------------------------------------------------------- // Class: AdditionalFieldEntry // ----------------------------------------------------------------------------- diff --git a/src/qt/voting/votingmodel.h b/src/qt/voting/votingmodel.h index 1afee6e127..435eef240a 100644 --- a/src/qt/voting/votingmodel.h +++ b/src/qt/voting/votingmodel.h @@ -86,6 +86,9 @@ class PollItem std::vector m_choices; bool m_self_voted; GRC::PollResult::VoteDetail m_self_vote_detail; + + bool m_stale = true; + bool m_expire_notified = false; }; //! @@ -127,12 +130,30 @@ class VotingModel : public QObject static int maxPollUrlLength(); static int maxPollQuestionLength(); static int maxPollChoiceLabelLength(); + static int maxPollAdditionalFieldNameLength(); + static int maxPollAdditionalFieldValueLength(); + static int maxPollProjectNameLength(); + static int maxPollProjectUrlLength(); OptionsModel& getOptionsModel(); QString getCurrentPollTitle() const; QStringList getActiveProjectNames() const; QStringList getActiveProjectUrls() const; - std::vector buildPollTable(const GRC::PollFilterFlag flags) const; + + //! + //! \brief getExpiringPollsNotNotified. This method populates a QStringList with + //! the polls in the pollitems cache that are within the m_poll_expire_warning window + //! and which have not previously been notified to the user. Since this method is + //! to be used to have the GUI immediately provide notification to the user, it also + //! marks each of the polls in the QStringList m_expire_notified = true so that they + //! will not appear again on this list (unless the wallet is restarted). This accomplishes + //! a single shot notification for each poll that is about to expire. + //! + //! \return QStringList of polls that are about to expire (within m_poll_expire_warning of + //! expiration), and which have not previously been included on the list (i.e. notified). + //! + QStringList getExpiringPollsNotNotified(); + std::vector buildPollTable(const GRC::PollFilterFlag flags); CAmount estimatePollFee() const; @@ -153,6 +174,7 @@ class VotingModel : public QObject signals: void newPollReceived(); + void newVoteReceived(QString poll_txid_string); private: GRC::PollRegistry& m_registry; @@ -161,11 +183,22 @@ class VotingModel : public QObject WalletModel& m_wallet_model; int64_t m_last_poll_time; + //! + //! \brief m_pollitems. A cache of poll items associated with the polls in the registry. + //! Each entry in the cache has a stale flag which is set when vote activity occurs, and is + //! defaulted to true (in construction) when the item is rebuilt by BuildPollItem inBuildPollTable, + //! then changed to false when BuildPollItem completes. When a vote is received (or "un" received + //! in a reorg situation), the NewVoteReceived signal from the core will cause the stale flag + //! in the appropriate corresponding poll item in this cache to be changed back to true. + //! + std::map m_pollitems; + void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); private slots: void handleNewPoll(int64_t poll_time); + void handleNewVote(QString poll_txid_string); }; // VotingModel #endif // GRIDCOIN_QT_VOTING_VOTINGMODEL_H diff --git a/src/qt/voting/votingpage.cpp b/src/qt/voting/votingpage.cpp index a592ee2576..dc2e22403e 100644 --- a/src/qt/voting/votingpage.cpp +++ b/src/qt/voting/votingpage.cpp @@ -129,8 +129,17 @@ void VotingPage::setVotingModel(VotingModel* model) return; } + // Now that PollItem caching is available, automatically refresh current poll tab on receipt of new poll or vote. connect(model, &VotingModel::newPollReceived, [this]() { ui->pollReceivedLabel->show(); + getActiveTab()->refresh(); + ui->pollReceivedLabel->hide(); + }); + + // Using the newVoteReceivedAndPollMarkedDirty instead of newVoteReceived insures the poll staleness flag in the appropriate + // poll item has been marked dirty before the refresh is called. + connect(getActiveTab(), &PollTab::newVoteReceivedAndPollMarkedDirty, [this]() { + getActiveTab()->refresh(); }); } @@ -149,6 +158,10 @@ PollTab& VotingPage::currentTab() return *qobject_cast(ui->tabWidget->currentWidget()); } +PollTab* VotingPage::getActiveTab() +{ + return m_tabs[0]; +} void VotingPage::updateIcons(const QString& theme) { m_filter_action->setIcon(QIcon(":/icons/" + theme + "_search")); diff --git a/src/qt/voting/votingpage.h b/src/qt/voting/votingpage.h index 93e7ce4b65..9ac6ab8b5e 100644 --- a/src/qt/voting/votingpage.h +++ b/src/qt/voting/votingpage.h @@ -35,6 +35,7 @@ class VotingPage : public QWidget void setOptionsModel(OptionsModel* model); PollTab& currentTab(); + PollTab* getActiveTab(); private: Ui::VotingPage* ui; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 7cb909d00b..6c25237a13 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -107,7 +107,10 @@ class WalletModel : public QObject // Copy operator and constructor transfer the context UnlockContext(const UnlockContext& obj) { CopyFrom(obj); } - UnlockContext& operator=(const UnlockContext& rhs) { CopyFrom(rhs); return *this; } + + // Commented out as we don't use the below form and it triggers an infinite recursion + // warning. + // UnlockContext& operator=(const UnlockContext& rhs) { CopyFrom(rhs); return *this; } private: WalletModel *wallet; bool valid; diff --git a/src/qt/winshutdownmonitor.cpp b/src/qt/winshutdownmonitor.cpp index bcbcef6cc5..41c6011876 100755 --- a/src/qt/winshutdownmonitor.cpp +++ b/src/qt/winshutdownmonitor.cpp @@ -44,7 +44,7 @@ bool WinShutdownMonitor::nativeEventFilter(const QByteArray &eventType, void *pM void WinShutdownMonitor::registerShutdownBlockReason(const QString& strReason, const HWND& mainWinId) { typedef BOOL (WINAPI *PSHUTDOWNBRCREATE)(HWND, LPCWSTR); - PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)GetProcAddress(GetModuleHandleA("User32.dll"), "ShutdownBlockReasonCreate"); + PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)(void*)GetProcAddress(GetModuleHandleA("User32.dll"), "ShutdownBlockReasonCreate"); if (shutdownBRCreate == nullptr) { qWarning() << "registerShutdownBlockReason: GetProcAddress for ShutdownBlockReasonCreate failed"; return; diff --git a/src/random.cpp b/src/random.cpp index 2ebc5bd86f..368fa1f9bc 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -16,9 +16,11 @@ #include // for LogPrintf() #include #include +#include #include // for Mutex #include // for GetTimeMicros() +#include #include #include @@ -31,10 +33,8 @@ #include #include #endif -#if defined(HAVE_GETENTROPY) || (defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)) -#include -#endif #if defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX) +#include #include #endif #ifdef HAVE_SYSCTL_ARND @@ -304,16 +304,14 @@ void GetOSRand(unsigned char *ent32) RandFailure(); } } -#elif defined(HAVE_GETENTROPY) && defined(__OpenBSD__) - /* On OpenBSD this can return up to 256 bytes of entropy, will return an - * error if more are requested. - * The call cannot return less than the requested number of bytes. - getentropy is explicitly limited to openbsd here, as a similar (but not - the same) function may exist on other platforms via glibc. +#elif defined(__OpenBSD__) + /* OpenBSD. From the arc4random(3) man page: + "Use of these functions is encouraged for almost all random number + consumption because the other interfaces are deficient in either + quality, portability, standardization, or availability." + The function call is always successful. */ - if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { - RandFailure(); - } + arc4random_buf(ent32, NUM_OS_RANDOM_BYTES); // Silence a compiler warning about unused function. (void)GetDevURandom; #elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX) @@ -581,27 +579,22 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept } } -void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } -void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } +void GetRandBytes(Span bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST); } +void GetStrongRandBytes(Span bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW); } void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } bool g_mock_deterministic_tests{false}; -uint64_t GetRand(uint64_t nMax) noexcept +uint64_t GetRandInternal(uint64_t nMax) noexcept { return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); } -int GetRandInt(int nMax) noexcept -{ - return GetRand(nMax); -} - uint256 GetRandHash() noexcept { uint256 hash; - GetRandBytes((unsigned char*)&hash, sizeof(hash)); + GetRandBytes(hash); return hash; } @@ -714,3 +707,9 @@ void RandomInit() ReportHardwareRand(); } + +std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval) +{ + double unscaled = -std::log1p(GetRand(uint64_t{1} << 48) * -0.0000000000000035527136788 /* -1/2^48 */); + return now + std::chrono::duration_cast(unscaled * average_interval + 0.5us); +} diff --git a/src/random.h b/src/random.h index 95362b421b..066ad1e68f 100644 --- a/src/random.h +++ b/src/random.h @@ -8,9 +8,10 @@ #include #include +#include #include -#include // For std::chrono::microseconds +#include #include #include @@ -66,9 +67,19 @@ * * Thread-safe. */ -void GetRandBytes(unsigned char* buf, int num) noexcept; +void GetRandBytes(Span bytes) noexcept; /** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */ -uint64_t GetRand(uint64_t nMax) noexcept; +uint64_t GetRandInternal(uint64_t nMax) noexcept; +/** Generate a uniform random integer of type T in the range [0..nMax) + * nMax defaults to std::numeric_limits::max() + * Precondition: nMax > 0, T is an integral type, no larger than uint64_t + */ +template +T GetRand(T nMax=std::numeric_limits::max()) noexcept { + static_assert(std::is_integral(), "T must be integral"); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "GetRand only supports up to uint64_t"); + return T(GetRandInternal(nMax)); +} /** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ template D GetRandomDuration(typename std::common_type::type max) noexcept @@ -82,6 +93,18 @@ D GetRandomDuration(typename std::common_type::type max) noexcept }; constexpr auto GetRandMicros = GetRandomDuration; constexpr auto GetRandMillis = GetRandomDuration; + +/** + * Return a timestamp in the future sampled from an exponential distribution + * (https://en.wikipedia.org/wiki/Exponential_distribution). This distribution + * is memoryless and should be used for repeated network events (e.g. sending a + * certain type of message) to minimize leaking information to observers. + * + * The probability of an event occurring before time x is 1 - e^-(x/a) where a + * is the average interval between events. + * */ +std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); + int GetRandInt(int nMax) noexcept; uint256 GetRandHash() noexcept; @@ -93,7 +116,7 @@ uint256 GetRandHash() noexcept; * * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num) noexcept; +void GetStrongRandBytes(Span bytes) noexcept; /** * Gather entropy from various expensive sources, and feed them to the PRNG state. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 15893e49ec..8c3964a9a4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -5,6 +5,9 @@ #include "chainparams.h" #include "blockchain.h" +#include "gridcoin/protocol.h" +#include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "node/blockstorage.h" #include #include "gridcoin/mrc.h" @@ -1443,6 +1446,10 @@ UniValue advertisebeacon(const UniValue& params, bool fHelp) throw JSONRPCError( RPC_WALLET_UNLOCK_NEEDED, "Wallet locked. Unlock it fully to send a beacon transaction"); + case GRC::BeaconError::ALEADY_IN_MEMPOOL: + throw JSONRPCError( + RPC_INVALID_REQUEST, + "Beacon transaction for this CPID is already in the mempool"); } throw JSONRPCError(RPC_INTERNAL_ERROR, "Unexpected error occurred"); @@ -1505,6 +1512,10 @@ UniValue revokebeacon(const UniValue& params, bool fHelp) throw JSONRPCError( RPC_WALLET_UNLOCK_NEEDED, "Wallet locked. Unlock it fully to send a beacon transaction"); + case GRC::BeaconError::ALEADY_IN_MEMPOOL: + throw JSONRPCError( + RPC_INVALID_REQUEST, + "Beacon transaction for this CPID is already in the mempool"); } throw JSONRPCError(RPC_INTERNAL_ERROR, "Unexpected error occurred"); @@ -1554,8 +1565,9 @@ UniValue beaconreport(const UniValue& params, bool fHelp) entry.pushKV("address", beacon_pair.second->GetAddress().ToString()); entry.pushKV("timestamp", beacon_pair.second->m_timestamp); entry.pushKV("hash", beacon_pair.second->m_hash.GetHex()); - entry.pushKV("prev_beacon_hash", beacon_pair.second->m_prev_beacon_hash.GetHex()); + entry.pushKV("prev_beacon_hash", beacon_pair.second->m_previous_hash.GetHex()); entry.pushKV("status", beacon_pair.second->m_status.Raw()); + entry.pushKV("status_text", beacon_pair.second->StatusToString()); results.push_back(entry); } @@ -1669,8 +1681,14 @@ UniValue pendingbeaconreport(const UniValue& params, bool fHelp) { UniValue entry(UniValue::VOBJ); + CBitcoinAddress address; + const CKeyID& key_id = pending_beacon_pair.first; + + address.Set(key_id); + entry.pushKV("cpid", pending_beacon_pair.second->m_cpid.ToString()); - entry.pushKV("address", pending_beacon_pair.first.ToString()); + entry.pushKV("key_id", pending_beacon_pair.first.ToString()); + entry.pushKV("address", address.ToString()); entry.pushKV("timestamp", pending_beacon_pair.second->m_timestamp); results.push_back(entry); @@ -1732,7 +1750,7 @@ UniValue beaconstatus(const UniValue& params, bool fHelp) active.push_back(entry); } - for (auto beacon_ptr : beacons.FindPending(*cpid)) { + for (const auto& beacon_ptr : beacons.FindPending(*cpid)) { UniValue entry(UniValue::VOBJ); entry.pushKV("cpid", cpid->ToString()); entry.pushKV("active", false); @@ -1756,6 +1774,203 @@ UniValue beaconstatus(const UniValue& params, bool fHelp) return res; } +UniValue beaconaudit(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 2) + throw runtime_error( + "beaconaudit [errors only] [cpid]\n" + "\n" + "[errors only] -> Boolean to provide errors only. Defaults to true.\n" + "[cpid] -> Optional parameter of cpid. Defaults to current cpid. * means all active CPIDs.\n" + "\n" + "Conducts consistency audit for beacon contracts and beacon chain for given CPID.\n" + "This is currently limited to looking at multiple renewals for the same CPID in\n" + "the same block and reporting inconsistencies between the normal contract order\n" + "and the historical beacon entries (beacon chainlet) for the CPID.\n"); + + bool errors_only = true; + bool global = false; + + GRC::MiningId mining_id; + + if (params.size() > 0) { + errors_only = params[0].get_bool(); + } + + if (params.size() > 1) { + if (params[1].get_str() == "*") { + global = true; + } else { + mining_id = GRC::MiningId::Parse(params[1].get_str()); + } + } else { + mining_id = GRC::Researcher::Get()->Id(); + } + + if (!global && !mining_id.Valid()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid CPID."); + } + + const GRC::CpidOption cpid = mining_id.TryCpid(); + + if (!global && !cpid) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "No beacon for investor."); + } + + // Only allow auditing when at or above block V11 threshold. + if (!IsV11Enabled(pindexBest->nHeight)) { + throw JSONRPCError(RPC_INVALID_REQUEST, "This function cannot be called when the wallet height is below the block V11" + "threshold."); + } + + // Find Fern starting block. + CBlockIndex* block_index = GRC::BlockFinder::FindByHeight(Params().GetConsensus().BlockV11Height); + + std::set cpids; + + typedef std::tuple BeaconContext; + + std::multimap beacon_contracts; + + // This form of block index traversal starts at the first V11 block and continues to pIndexBest (inclusive). + while (block_index) { + CBlock block; + + if (!ReadBlockFromDisk(block, block_index, Params().GetConsensus())) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Unable to read block from disk. Your blockchain files are corrupted."); + } + + std::set cpids_in_block; + std::multimap beacon_contracts_in_block; + + for (unsigned int i = 0; i < block.vtx.size(); ++i) { + + for (const auto& tx_contract: block.vtx[i].GetContracts()) { + if (tx_contract.m_type != GRC::ContractType::BEACON) continue; + + GRC::BeaconPayload beacon_payload = tx_contract.CopyPayloadAs(); + + // If not global (all cpids) and payload cpid does not match input parameter (specified cpid), continue. + if (!global && beacon_payload.m_cpid != *cpid) continue; + + cpids_in_block.insert(beacon_payload.m_cpid); + + BeaconContext beacon_context {tx_contract, block.vtx[i], i, block_index}; + + beacon_contracts_in_block.insert({ beacon_payload.m_cpid, beacon_context }); + } + } + + for (const auto& cpid_in_block : cpids_in_block) { + if (beacon_contracts_in_block.count(cpid_in_block) > 1) { + cpids.insert(cpid_in_block); + + auto beacons_to_insert = beacon_contracts_in_block.equal_range(cpid_in_block); + + for (auto iter = beacons_to_insert.first; iter != beacons_to_insert.second; ++iter) { + beacon_contracts.insert({ iter->first, iter->second }); + } + } + } + + // If we are at pIndexBest (i.e. no pnext), then break. + if (block_index->pnext) { + block_index = block_index->pnext; + } else { + break; + } + } + + LogPrintf("INFO: %s: number of cpids = %u, number of beacon contracts = %u", + __func__, + cpids.size(), + beacon_contracts.size()); + + UniValue res(UniValue::VOBJ); + UniValue beacons_to_output(UniValue::VARR); + + GRC::BeaconRegistry& beacon_registry = GRC::GetBeaconRegistry(); + + for (const auto& cpid_to_output : cpids) { + UniValue beacon_contracts_output(UniValue::VARR); + auto beacon_contracts_to_output = beacon_contracts.equal_range(cpid_to_output); + + uint256 prev_block_hash, prev_renewal_hash, prev_renewal_hash_report; + + for (auto beacon_contract_to_output = beacon_contracts_to_output.first; + beacon_contract_to_output != beacon_contracts_to_output.second; + ++beacon_contract_to_output) { + bool prev_hash_mismatch_error = false; + bool no_historical_entry_error = false; + + size_t i = std::distance(beacon_contracts_to_output.first, beacon_contract_to_output); + + UniValue beacon_contract_output(UniValue::VOBJ); + + uint256 beacon_hash = get<1>(beacon_contract_to_output->second).GetHash(); + + GRC::Contract::Action action = get<0>(beacon_contract_to_output->second).m_action; + + const GRC::BeaconPayload& beacon_payload = get<0>(beacon_contract_to_output->second).CopyPayloadAs(); + + GRC::BeaconOption historical_beacon_entry = + beacon_registry.FindHistorical(get<1>(beacon_contract_to_output->second).GetHash()); + + if (historical_beacon_entry) { + if (action == GRC::ContractAction::ADD && historical_beacon_entry->m_status == GRC::BeaconStatusForStorage::RENEWAL) { + if (i && prev_block_hash == get<3>(beacon_contract_to_output->second)->GetBlockHash() + && prev_renewal_hash != historical_beacon_entry->m_previous_hash) { + prev_hash_mismatch_error = true; + } + + prev_renewal_hash_report = prev_renewal_hash; + prev_renewal_hash = beacon_hash; + prev_block_hash = get<3>(beacon_contract_to_output->second)->GetBlockHash(); + } + } else { + no_historical_entry_error = true; + } + + if (!errors_only || prev_hash_mismatch_error || no_historical_entry_error) { + beacon_contract_output.pushKV("height", get<3>(beacon_contract_to_output->second)->nHeight); + beacon_contract_output.pushKV("vtx_index", (uint64_t) get<2>(beacon_contract_to_output->second)); + beacon_contract_output.pushKV("txid", beacon_hash.ToString()); + beacon_contract_output.pushKV("tx_time", (int64_t) get<1>(beacon_contract_to_output->second).nTime); + beacon_contract_output.pushKV("tx_time_string", FormatISO8601DateTime(get<1>(beacon_contract_to_output->second).nTime)); + beacon_contract_output.pushKV("action", action.ToString()); + + beacon_contract_output.pushKV("same_block_renewal_prev_hash_mismatch", prev_hash_mismatch_error); + + if (prev_hash_mismatch_error) { + beacon_contract_output.pushKV("previous_renewal_hash_via_contract_traversal", + prev_renewal_hash_report.ToString()); + beacon_contract_output.pushKV("previous_renewal_hash_by_historical_beacon_entry", + historical_beacon_entry->m_previous_hash.ToString()); + } + + if (!no_historical_entry_error) { + beacon_contract_output.pushKV("status", historical_beacon_entry->StatusToString()); + } else { + beacon_contract_output.pushKV("status", "no historical entry"); + } + + beacon_contracts_output.push_back(beacon_contract_output); + } + } + + UniValue beacon(UniValue::VOBJ); + + if (!beacon_contracts_output.empty()) { + beacon.pushKV("cpid", cpid_to_output.ToString()); + beacon.pushKV("contracts", beacon_contracts_output); + beacons_to_output.push_back(beacon); + } + } + + res.pushKV("cpids_with_more_than_one_beacon_contract_in_block", beacons_to_output); + + return res; +} UniValue explainmagnitude(const UniValue& params, bool fHelp) { @@ -2003,11 +2218,16 @@ UniValue superblocks(const UniValue& params, bool fHelp) UniValue addkey(const UniValue& params, bool fHelp) { bool project_v2_enabled = false; + bool block_v13_enabled = false; + uint32_t contract_version = 0; { LOCK(cs_main); project_v2_enabled = IsProjectV2Enabled(nBestHeight); + + block_v13_enabled = IsV13Enabled(nBestHeight); + contract_version = block_v13_enabled ? 3 : 2; } GRC::ContractAction action = GRC::ContractAction::UNKNOWN; @@ -2037,10 +2257,11 @@ UniValue addkey(const UniValue& params, bool fHelp) param_count_max = 5; } - if (type == GRC::ContractType::PROJECT && action == GRC::ContractAction::REMOVE) { + if ((type == GRC::ContractType::PROJECT || type == GRC::ContractType::SCRAPER) + && action == GRC::ContractAction::REMOVE) { required_param_count = 3; - // This is for compatibility with scripts for project administration that may put something in the + // This is for compatibility with scripts for project and scraper administration that may put something in the // fourth parameter because it was originally required even though ignored. The same principal applies // to v2, where the last two parameters for a remove can be supplied, but they will be ignored. if (project_v2_enabled) { @@ -2048,6 +2269,17 @@ UniValue addkey(const UniValue& params, bool fHelp) } } + // For add a mandatory sidestake, the 4th parameter is the allocation and the description (5th parameter) is optional. + if (type == GRC::ContractType::SIDESTAKE) { + if (action == GRC::ContractAction::ADD) { + required_param_count = 4; + param_count_max = 5; + } else { + required_param_count = 3; + param_count_max = 3; + } + } + if (fHelp || params.size() < required_param_count || params.size() > param_count_max) { std::string error_string; @@ -2082,8 +2314,11 @@ UniValue addkey(const UniValue& params, bool fHelp) "Error: Please enter the wallet passphrase with walletpassphrase first."); } - if (type == GRC::ContractType::UNKNOWN) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown contract type."); + if (!(type == GRC::ContractType::PROJECT + || type == GRC::ContractType::SCRAPER + || type == GRC::ContractType::PROTOCOL + || type == GRC::ContractType::SIDESTAKE)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid contract type for addkey."); } if (action == GRC::ContractAction::UNKNOWN) { @@ -2094,50 +2329,203 @@ UniValue addkey(const UniValue& params, bool fHelp) switch (type.Value()) { case GRC::ContractType::PROJECT: + { if (action == GRC::ContractAction::ADD) { - if (project_v2_enabled) { + bool gdpr_export_control = false; + + if (block_v13_enabled) { + // We must do our own conversion to boolean here, because the 5th parameter can either be + // a boolean for project or a string for sidestake, which means the client.cpp entry cannot contain a + // unicode type specifier for the 5th parameter. + if (ToLower(params[4].get_str()) == "true") { + gdpr_export_control = true; + } else if (ToLower(params[4].get_str()) != "false") { + // Neither true or false - throw an exception. + throw JSONRPCError(RPC_INVALID_PARAMETER, "GDPR export parameter invalid. Must be true or false."); + } + contract = GRC::MakeContract( + contract_version, action, + uint32_t{3}, // Contract payload version number, 3 params[2].get_str(), // Name params[3].get_str(), // URL - int64_t{0}, // Default zero timestamp - uint32_t{2}, // Contract version number, 2 - params[4].getBool()); // GDPR stats export protection enforced boolean + gdpr_export_control); // GDPR stats export protection enforced boolean + + } else if (project_v2_enabled) { + // We must do our own conversion to boolean here, because the 5th parameter can either be + // a boolean for project or a string for sidestake, which means the client.cpp entry cannot contain a + // unicode type specifier for the 5th parameter. + if (ToLower(params[4].get_str()) == "true") { + gdpr_export_control = true; + } else if (ToLower(params[4].get_str()) != "false") { + // Neither true or false - throw an exception. + throw JSONRPCError(RPC_INVALID_PARAMETER, "GDPR export parameter invalid. Must be true or false."); + } - } else { contract = GRC::MakeContract( + contract_version, action, + uint32_t{2}, // Contract payload version number, 2 params[2].get_str(), // Name params[3].get_str(), // URL - int64_t{0}, // Default zero timestamp - uint32_t{1}); // Contract version number, 1 + gdpr_export_control); // GDPR stats export protection enforced boolean + + } else { + contract = GRC::MakeContract( + contract_version, + action, + params[2].get_str(), // Name + params[3].get_str()); // URL } } else if (action == GRC::ContractAction::REMOVE) { - if (project_v2_enabled) { + if (block_v13_enabled) { contract = GRC::MakeContract( + contract_version, action, + uint32_t{3}, // Contract payload version number, 3 params[2].get_str(), // Name std::string{}, // URL ignored - int64_t{0}, // Default zero timestamp - uint32_t{2}); // Contract version number, 2 + false); // GDPR status irrelevant - } else { + } else if (project_v2_enabled) { contract = GRC::MakeContract( + contract_version, action, + uint32_t{2}, // Contract payload version number, 2 params[2].get_str(), // Name std::string{}, // URL ignored - int64_t{0}, // Default zero timestamp - uint32_t{1}); // Contract version number, 1 + false); // GDPR status irrelevant + + } else { + contract = GRC::MakeContract( + contract_version, + action, + params[2].get_str(), // Name + std::string{}); // URL ignored + } + } + break; + } + case GRC::ContractType::SCRAPER: + { + std::string status_string = ToLower(params[3].get_str()); + GRC::ScraperEntryStatus status = GRC::ScraperEntryStatus::UNKNOWN; + + CBitcoinAddress scraper_address; + if (!scraper_address.SetString(params[2].get_str())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address specified for the scraper is invalid."); + } + + if (block_v13_enabled) { // Contract version will be 3. + CKeyID key_id; + + scraper_address.GetKeyID(key_id); + + if (action == GRC::ContractAction::ADD) { + if (status_string == "false") { + status = GRC::ScraperEntryStatus::NOT_AUTHORIZED; + } else if (status_string == "true") { + status = GRC::ScraperEntryStatus::AUTHORIZED; + } else if (status_string == "explorer") { + status = GRC::ScraperEntryStatus::EXPLORER; + } else { + JSONRPCError(RPC_INVALID_PARAMETER, "Status specified for the scraper is invalid."); + } + } else if (action == GRC::ContractAction::REMOVE) { + status = GRC::ScraperEntryStatus::DELETED; } + + contract = GRC::MakeContract( + contract_version, + action, + uint32_t {2}, // Contract payload version number + key_id, + status); + + } else { // Block v13 not enabled. (Contract version will be 2.) + if (action == GRC::ContractAction::ADD && !(status_string == "false" || status_string == "true")) { + JSONRPCError(RPC_INVALID_PARAMETER, "Status specified for the scraper is invalid."); + } else if (action == GRC::ContractAction::REMOVE) { + status_string = "false"; + } + + // This form of ScraperEntryPayload generation matches the payload constructor that uses the Parse + // function to convert legacy arguments into a native scraper entry. + contract = GRC::MakeContract( + contract_version, + action, + scraper_address.ToString(), + status_string); } break; - default: + } + case GRC::ContractType::PROTOCOL: + // There will be no legacy payload contracts past version 2. This will need to be changed before the + // block v13 mandatory (which also means contract v3). contract = GRC::MakeLegacyContract( type.Value(), action, params[2].get_str(), // key params[3].get_str()); // value break; + case GRC::ContractType::SIDESTAKE: + { + if (block_v13_enabled) { + CBitcoinAddress sidestake_address; + if (!sidestake_address.SetString(params[2].get_str())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address specified for the sidestake is invalid."); + } + + std::string description; + if (params.size() > 4) { + description = params[4].get_str(); + } + + // We have to do our own conversion here because the 4th parameter type specifier cannot be set other + // than string in the client.cpp file. + double allocation = 0.0; + if (params.size() > 3 && !ParseDouble(params[3].get_str(), &allocation)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid allocation specified."); + } + + allocation /= 100.0; + + if (allocation > 1.0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Allocation specified is greater than 100.0%."); + } + + contract = GRC::MakeContract( + contract_version, // Contract version number (3+) + action, // Contract action + uint32_t {1}, // Contract payload version number + sidestake_address.Get(), // Sidestake destination + allocation, // Sidestake allocation + description, // Sidestake description + GRC::MandatorySideStake::MandatorySideStakeStatus::MANDATORY // sidestake status + ); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Sidestake contracts are not valid for block version less than v13."); + } + + break; + } + case GRC::ContractType::BEACON: + [[fallthrough]]; + case GRC::ContractType::CLAIM: + [[fallthrough]]; + case GRC::ContractType::MESSAGE: + [[fallthrough]]; + case GRC::ContractType::MRC: + [[fallthrough]]; + case GRC::ContractType::POLL: + [[fallthrough]]; + case GRC::ContractType::VOTE: + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid contract type for addkey."); + case GRC::ContractType::UNKNOWN: + [[fallthrough]]; + case GRC::ContractType::OUT_OF_BOUND: + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid contract type."); } if (!contract.RequiresMasterKey()) { @@ -2209,94 +2597,106 @@ UniValue debug(const UniValue& params, bool fHelp) return res; } -UniValue getlistof(const UniValue& params, bool fHelp) +UniValue listprojects(const UniValue& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() != 0) throw runtime_error( - "getlistof \n" - "\n" - " -> key of requested data\n" + "listprojects\n" "\n" - "Displays data associated to a specified key type\n"); + "Displays information about whitelisted projects.\n"); UniValue res(UniValue::VOBJ); - std::string sType = params[0].get_str(); + for (const auto& project : GRC::GetWhitelist().Snapshot().Sorted()) { + UniValue entry(UniValue::VOBJ); - res.pushKV("Key Type", sType); + entry.pushKV("version", (int)project.m_version); + entry.pushKV("display_name", project.DisplayName()); + entry.pushKV("url", project.m_url); + entry.pushKV("base_url", project.BaseUrl()); + entry.pushKV("display_url", project.DisplayUrl()); + entry.pushKV("stats_url", project.StatsUrl()); - LOCK(cs_main); + if (project.HasGDPRControls()) { + entry.pushKV("gdpr_controls", *project.HasGDPRControls()); + } - UniValue entries(UniValue::VOBJ); - for(const auto& entry : ReadSortedCacheSection(StringToSection(sType))) - { - const auto& key = entry.first; - const auto& value = entry.second; + entry.pushKV("time", DateTimeStrFormat(project.m_timestamp)); - UniValue obj(UniValue::VOBJ); - obj.pushKV("value", value.value); - obj.pushKV("timestamp", value.timestamp); - entries.pushKV(key, obj); + res.pushKV(project.m_name, entry); } - res.pushKV("entries", entries); return res; } -UniValue listdata(const UniValue& params, bool fHelp) +UniValue listscrapers(const UniValue& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() != 0) throw runtime_error( - "listdata \n" - "\n" - " -> key in cache\n" + "listscrapers\n" "\n" - "Displays data associated to a key stored in cache\n"); + "Displays information about scrapers recognized by the network.\n"); UniValue res(UniValue::VOBJ); + UniValue scraper_entries(UniValue::VARR); + + for (const auto& scraper : GRC::GetScraperRegistry().Scrapers()) { + UniValue entry(UniValue::VOBJ); + + CBitcoinAddress address(scraper.first); - std::string sType = params[0].get_str(); + entry.pushKV("scraper_address", address.ToString()); + entry.pushKV("current_scraper_entry_tx_hash", scraper.second->m_hash.ToString()); + if (scraper.second->m_previous_hash.IsNull()) { + entry.pushKV("previous_scraper_entry_tx_hash", "null"); + } else { + entry.pushKV("previous_scraper_entry_tx_hash", scraper.second->m_previous_hash.ToString()); + } - res.pushKV("Key Type", sType); + entry.pushKV("scraper_entry_timestamp", scraper.second->m_timestamp); + entry.pushKV("scraper_entry_time", DateTimeStrFormat(scraper.second->m_timestamp)); + entry.pushKV("scraper_entry_status", scraper.second->StatusToString()); - LOCK(cs_main); + scraper_entries.push_back(entry); + } - Section section = StringToSection(sType); - for(const auto& item : ReadCacheSection(section)) - res.pushKV(item.first, item.second.value); + res.pushKV("current_scraper_entries", scraper_entries); return res; } -UniValue listprojects(const UniValue& params, bool fHelp) +UniValue listprotocolentries(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( - "listprojects\n" + "listprotocolentries\n" "\n" - "Displays information about whitelisted projects.\n"); + "Displays the protocol entries on the network.\n"); UniValue res(UniValue::VOBJ); + UniValue scraper_entries(UniValue::VARR); - for (const auto& project : GRC::GetWhitelist().Snapshot().Sorted()) { + for (const auto& protocol : GRC::GetProtocolRegistry().ProtocolEntries()) { UniValue entry(UniValue::VOBJ); - entry.pushKV("version", (int)project.m_version); - entry.pushKV("display_name", project.DisplayName()); - entry.pushKV("url", project.m_url); - entry.pushKV("base_url", project.BaseUrl()); - entry.pushKV("display_url", project.DisplayUrl()); - entry.pushKV("stats_url", project.StatsUrl()); - - if (project.HasGDPRControls()) { - entry.pushKV("gdpr_controls", *project.HasGDPRControls()); + entry.pushKV("protocol_entry_key", protocol.first); + entry.pushKV("protocol_entry_value", protocol.second->m_value); + entry.pushKV("current_protocol_entry_tx_hash", protocol.second->m_hash.ToString()); + if (protocol.second->m_previous_hash.IsNull()) { + entry.pushKV("previous_protocol_entry_tx_hash", "null"); + } else { + entry.pushKV("previous_protocol_entry_tx_hash", protocol.second->m_previous_hash.ToString()); } - entry.pushKV("time", DateTimeStrFormat(project.m_timestamp)); + entry.pushKV("protocol_entry_timestamp", protocol.second->m_timestamp); + entry.pushKV("protocol_entry_time", DateTimeStrFormat(protocol.second->m_timestamp)); + entry.pushKV("protocol_entry_status", protocol.second->StatusToString()); - res.pushKV(project.m_name, entry); + scraper_entries.push_back(entry); } + res.pushKV("current_protocol_entries", scraper_entries); + return res; } @@ -2634,11 +3034,11 @@ UniValue SuperblockReport(int lookback, bool displaycontract, std::string cpid) if (cpid_parsed) { - c.pushKV("Magnitude", superblock.m_cpids.MagnitudeOf(*cpid_parsed).Floating()); + c.pushKV("magnitude", superblock.m_cpids.MagnitudeOf(*cpid_parsed).Floating()); } if (displaycontract) - c.pushKV("Contract Contents", SuperblockToJson(superblock)); + c.pushKV("contract_contents", SuperblockToJson(superblock)); results.push_back(c); @@ -2997,7 +3397,11 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { CWalletTx wtx; std::string error; - std::tie(wtx, error) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, mrc)); + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + + std::tie(wtx, error) = GRC::SendContract(GRC::MakeContract(contract_version, + GRC::ContractAction::ADD, + mrc)); if (!error.empty()) { throw runtime_error(error); } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index f1b8b3ed74..9724c04aa9 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -201,9 +201,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "superblocks" , 1 }, // Developer - { "addkey" , 4 }, { "auditsnapshotaccrual" , 1 }, { "auditsnapshotaccruals" , 0 }, + { "beaconaudit" , 0 }, { "convergencereport" , 0 }, { "debug" , 0 }, { "dumpcontracts" , 2 }, @@ -352,8 +352,8 @@ int CommandLineRPC(int argc, char *argv[]) const UniValue reply = CallRPC(strMethod, params); // Parse reply - const UniValue& result = find_value(reply, "result"); - const UniValue& error = find_value(reply, "error"); + UniValue result = find_value(reply, "result"); + UniValue error = find_value(reply, "error"); if (!error.isNull()) { diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 7c82924d32..be24b0b100 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -98,8 +98,6 @@ UniValue getstakinginfo(const UniValue& params, bool fHelp) bool fEnableSideStaking = gArgs.GetBoolArg("-enablesidestaking"); - if (fEnableSideStaking) vSideStakeAlloc = GetSideStakingStatusAndAlloc(); - stakesplitting.pushKV("stake-splitting-enabled", fEnableStakeSplit); if (fEnableStakeSplit) { @@ -110,19 +108,23 @@ UniValue getstakinginfo(const UniValue& params, bool fHelp) } obj.pushKV("stake-splitting", stakesplitting); - sidestaking.pushKV("side-staking-enabled", fEnableSideStaking); - if (fEnableSideStaking) + // This is what the miner sees... + vSideStakeAlloc = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::ALL, false); + + sidestaking.pushKV("local_side_staking_enabled", fEnableSideStaking); + + // Note that if local_side_staking_enabled is true, then local sidestakes will be applicable and shown. Mandatory + // sidestakes are always included. + for (const auto& alloc : vSideStakeAlloc) { - for (const auto& alloc : vSideStakeAlloc) - { - sidestakingalloc.pushKV("address", alloc.first); - sidestakingalloc.pushKV("allocation-pct", alloc.second * 100); + sidestakingalloc.pushKV("address", CBitcoinAddress(alloc->GetDestination()).ToString()); + sidestakingalloc.pushKV("allocation_pct", alloc->GetAllocation().ToPercent()); + sidestakingalloc.pushKV("status", alloc->StatusToString()); - vsidestakingalloc.push_back(sidestakingalloc); - } - sidestaking.pushKV("side-staking-allocations", vsidestakingalloc); + vsidestakingalloc.push_back(sidestakingalloc); } - obj.pushKV("side-staking", sidestaking); + sidestaking.pushKV("side_staking_allocations", vsidestakingalloc); + obj.pushKV("side_staking", sidestaking); obj.pushKV("difficulty", diff); obj.pushKV("errors", GetWarnings("statusbar")); @@ -298,47 +300,29 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) GRC::Beacon_ptr beacon_ptr = beacon_try; - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: active beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s", - __func__, - beacon_ptr->m_timestamp, - beacon_ptr->m_hash.GetHex(), - beacon_ptr->m_prev_beacon_hash.GetHex()); + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); UniValue beacon_chain(UniValue::VARR); - UniValue beacon_chain_entry(UniValue::VOBJ); - - beacon_chain_entry.pushKV("ctx_hash", beacon_ptr->m_hash.GetHex()); - beacon_chain_entry.pushKV("timestamp", beacon_ptr->m_timestamp); - beacon_chain.push_back(beacon_chain_entry); - - // This walks back the entries in the historical beacon map linked by renewal prev tx hash until the first - // beacon in the renewal chain is found (the original advertisement). The accrual starts no earlier than here. - uint64_t renewals = 0; - // The renewals <= 100 is simply to prevent an infinite loop if there is a problem with the beacon chain in the registry. This - // was an issue in post Fern beacon db work, but has been resolved and not encountered since. Still makes sense to leave the - // limit in, which represents 41 years worth of beacon chain at the 150 day standard auto-renewal cycle. - while (beacon_ptr->Renewed() && renewals <= 100) - { - auto iter = beacons.GetBeaconDB().find(beacon_ptr->m_prev_beacon_hash); - beacon_ptr = iter->second; + try { + beacon_ptr = beacons.GetBeaconChainletRoot(beacon_ptr, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + throw JSONRPCError(RPC_INTERNAL_ERROR, e.what()); + } - LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: renewal %u beacon: timestamp = %" PRId64 ", ctx_hash = %s," - " prev_beacon_ctx_hash = %s.", - __func__, - renewals, - beacon_ptr->m_timestamp, - beacon_ptr->m_hash.GetHex(), - beacon_ptr->m_prev_beacon_hash.GetHex()); + for (const auto& iter : *beacon_chain_out_ptr) { + UniValue beacon_chain_entry(UniValue::VOBJ); - beacon_chain_entry.pushKV("ctx_hash", beacon_ptr->m_hash.GetHex()); - beacon_chain_entry.pushKV("timestamp", beacon_ptr->m_timestamp); + beacon_chain_entry.pushKV("ctx_hash", iter.first.GetHex()); + beacon_chain_entry.pushKV("timestamp", iter.second); beacon_chain.push_back(beacon_chain_entry); - - ++renewals; } + int64_t renewals = beacon_chain_out_ptr->size() - 1; + bool retry_from_baseline = false; // Up to two passes. The first is from the start of the current beacon chain for the CPID, the second from the Fern baseline. diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index e6a023bd05..65180d6139 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -52,7 +52,7 @@ UniValue addnode(const UniValue& params, bool fHelp) CAddress addr; CNode* pnode= ConnectNode(addr, strNode.c_str()); if(!pnode) - throw JSONRPCError(-23, "Error: Node connection failed"); + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node connection failed"); UniValue result(UniValue::VOBJ); result.pushKV("result", "ok"); return result; @@ -67,13 +67,13 @@ UniValue addnode(const UniValue& params, bool fHelp) if (strCommand == "add") { if (it != vAddedNodes.end()) - throw JSONRPCError(-23, "Error: Node already added"); + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added"); vAddedNodes.push_back(strNode); } else if(strCommand == "remove") { if (it == vAddedNodes.end()) - throw JSONRPCError(-24, "Error: Node has not been added."); + throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node has not been added."); vAddedNodes.erase(it); } @@ -95,7 +95,7 @@ UniValue getnodeaddresses(const UniValue& params, bool fHelp) count = params[0].get_int(); if (count <= 0) - throw JSONRPCError(-8, "Address count out of range"); + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range"); // returns a shuffled list of CAddress std::vector vAddr = addrman.GetAddr(); @@ -145,7 +145,7 @@ UniValue getaddednodeinfo(const UniValue& params, bool fHelp) break; } if (laddedNodes.size() == 0) - throw JSONRPCError(-24, "Error: Node has not been added."); + throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node has not been added."); } if (!fDns) diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 1fed71021a..6b8a752a1d 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -40,45 +40,74 @@ enum HTTPStatusCode HTTP_INTERNAL_SERVER_ERROR = 500, }; -// Bitcoin RPC error codes +//! Gridcoin RPC error codes enum RPCErrorCode { - // Standard JSON-RPC 2.0 errors + //! Standard JSON-RPC 2.0 errors + // RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400). + // It should not be used for application-layer errors. RPC_INVALID_REQUEST = -32600, + // RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404). + // It should not be used for application-layer errors. RPC_METHOD_NOT_FOUND = -32601, RPC_INVALID_PARAMS = -32602, + // RPC_INTERNAL_ERROR should only be used for genuine errors in gridcoind + // (for example datadir corruption). RPC_INTERNAL_ERROR = -32603, RPC_PARSE_ERROR = -32700, - // General application defined errors - RPC_MISC_ERROR = -1, // std::exception thrown in command handling - RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter - RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key - RPC_OUT_OF_MEMORY = -7, // Ran out of memory during operation - RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter - RPC_DATABASE_ERROR = -20, // Database error - RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format - RPC_DEPRECATED = -23, // Use for deprecated commands - - // P2P client errors - RPC_CLIENT_NOT_CONNECTED = -9, // Gridcoin is not connected - RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, // Still downloading initial blocks - RPC_CLIENT_NODE_ALREADY_ADDED = -23, // Node is already added - RPC_CLIENT_NODE_NOT_ADDED = -24, // Node has not been added before - RPC_CLIENT_NODE_NOT_CONNECTED = -29, // Node to disconnect not found in connected nodes - RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, // Invalid IP/Subnet - RPC_CLIENT_P2P_DISABLED = -31, // No valid connection manager instance found - - // Wallet errors - RPC_WALLET_ERROR = -4, // Unspecified problem with wallet (key not found etc.) - RPC_WALLET_INSUFFICIENT_FUNDS = -6, // Not enough funds in wallet or account - RPC_WALLET_INVALID_ACCOUNT_NAME = -11, // Invalid account name - RPC_WALLET_KEYPOOL_RAN_OUT = -12, // Keypool ran out, call keypoolrefill first - RPC_WALLET_UNLOCK_NEEDED = -13, // Enter the wallet passphrase with walletpassphrase first - RPC_WALLET_PASSPHRASE_INCORRECT = -14, // The wallet passphrase entered was incorrect - RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet - RPC_WALLET_ALREADY_UNLOCKED = -17, // Wallet is already unlocked + //! General application defined errors + RPC_MISC_ERROR = -1, //!< std::exception thrown in command handling + RPC_TYPE_ERROR = -3, //!< Unexpected type was passed as parameter + RPC_INVALID_ADDRESS_OR_KEY = -5, //!< Invalid address or key + RPC_OUT_OF_MEMORY = -7, //!< Ran out of memory during operation + RPC_INVALID_PARAMETER = -8, //!< Invalid, missing or duplicate parameter + RPC_DATABASE_ERROR = -20, //!< Database error + RPC_DESERIALIZATION_ERROR = -22, //!< Error parsing or validating structure in raw format + RPC_VERIFY_ERROR = -25, //!< General error during transaction or block submission + RPC_VERIFY_REJECTED = -26, //!< Transaction or block was rejected by network rules + RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain + RPC_IN_WARMUP = -28, //!< Client still warming up + RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated + + //! Aliases for backward compatibility + RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR, + RPC_TRANSACTION_REJECTED = RPC_VERIFY_REJECTED, + RPC_TRANSACTION_ALREADY_IN_CHAIN= RPC_VERIFY_ALREADY_IN_CHAIN, + + //! P2P client errors + RPC_CLIENT_NOT_CONNECTED = -9, //!< Gridcoin is not connected + RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, //!< Still downloading initial blocks + RPC_CLIENT_NODE_ALREADY_ADDED = -23, //!< Node is already added + RPC_CLIENT_NODE_NOT_ADDED = -24, //!< Node has not been added before + RPC_CLIENT_NODE_NOT_CONNECTED = -29, //!< Node to disconnect not found in connected nodes + RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //!< Invalid IP/Subnet + RPC_CLIENT_P2P_DISABLED = -31, //!< No valid connection manager instance found + RPC_CLIENT_NODE_CAPACITY_REACHED= -34, //!< Max number of outbound or block-relay connections already open + + //! Chain errors + RPC_CLIENT_MEMPOOL_DISABLED = -33, //!< No mempool instance found + + //! Wallet errors + RPC_WALLET_ERROR = -4, //!< Unspecified problem with wallet (key not found etc.) + RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Not enough funds in wallet or account + RPC_WALLET_INVALID_LABEL_NAME = -11, //!< Invalid label name + RPC_WALLET_KEYPOOL_RAN_OUT = -12, //!< Keypool ran out, call keypoolrefill first + RPC_WALLET_UNLOCK_NEEDED = -13, //!< Enter the wallet passphrase with walletpassphrase first + RPC_WALLET_PASSPHRASE_INCORRECT = -14, //!< The wallet passphrase entered was incorrect + RPC_WALLET_WRONG_ENC_STATE = -15, //!< Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + RPC_WALLET_ENCRYPTION_FAILED = -16, //!< Failed to encrypt the wallet + RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked + RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified + RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded) + RPC_WALLET_ALREADY_LOADED = -35, //!< This same wallet is already loaded + RPC_WALLET_ALREADY_EXISTS = -36, //!< There is already a wallet with the same name + + //! Backwards compatible aliases + RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME, + + //! Unused reserved codes, kept around for backwards compatibility. Do not reuse. + RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode }; // diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c47e0debc7..ef570f9da7 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -11,6 +11,7 @@ #include "gridcoin/contract/contract.h" #include "gridcoin/mrc.h" #include "gridcoin/project.h" +#include "gridcoin/sidestake.h" #include "gridcoin/staking/difficulty.h" #include "gridcoin/superblock.h" #include "gridcoin/support/block_finder.h" @@ -245,6 +246,20 @@ UniValue VotePayloadToJson(const GRC::ContractPayload& payload) return out; } +UniValue SideStakePayloadToJson (const GRC::ContractPayload& payload) +{ + const auto& sidestake = payload.As(); + + UniValue out(UniValue::VOBJ); + + out.pushKV("address", CBitcoinAddress(sidestake.m_entry.m_destination).ToString()); + out.pushKV("allocation", sidestake.m_entry.m_allocation.ToPercent()); + out.pushKV("description", sidestake.m_entry.m_description); + out.pushKV("status", sidestake.m_entry.StatusToString()); + + return out; +} + UniValue LegacyVotePayloadToJson(const GRC::ContractPayload& payload) { const auto& vote = payload.As(); @@ -295,6 +310,9 @@ UniValue ContractToJson(const GRC::Contract& contract) case GRC::ContractType::MRC: out.pushKV("body", MRCToJson(contract.CopyPayloadAs())); break; + case GRC::ContractType::SIDESTAKE: + out.pushKV("body", SideStakePayloadToJson(contract.SharePayload())); + break; default: out.pushKV("body", LegacyContractPayloadToJson(contract.SharePayload())); break; @@ -1510,14 +1528,14 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); - const UniValue& txid_v = find_value(o, "txid"); + UniValue txid_v = find_value(o, "txid"); if (!txid_v.isStr()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing txid key"); string txid = txid_v.get_str(); if (!IsHex(txid)) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); - const UniValue& vout_v = find_value(o, "vout"); + UniValue vout_v = find_value(o, "vout"); if (!vout_v.isNum()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); int nOutput = vout_v.get_int(); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 8392ad0fe2..5bf4a69b26 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -374,19 +374,20 @@ static const CRPCCommand vRPCCommands[] = { "auditsnapshotaccrual", &auditsnapshotaccrual, cat_developer }, { "auditsnapshotaccruals", &auditsnapshotaccruals, cat_developer }, { "addkey", &addkey, cat_developer }, + { "beaconaudit", &beaconaudit, cat_developer }, { "changesettings", &changesettings, cat_developer }, { "currentcontractaverage", ¤tcontractaverage, cat_developer }, { "debug", &debug, cat_developer }, { "dumpcontracts", &dumpcontracts, cat_developer }, { "exportstats1", &rpc_exportstats, cat_developer }, { "getblockstats", &rpc_getblockstats, cat_developer }, - { "getlistof", &getlistof, cat_developer }, { "getrecentblocks", &rpc_getrecentblocks, cat_developer }, { "inspectaccrualsnapshot", &inspectaccrualsnapshot, cat_developer }, { "listalerts", &listalerts, cat_developer }, - { "listdata", &listdata, cat_developer }, { "listprojects", &listprojects, cat_developer }, + { "listprotocolentries", &listprotocolentries, cat_developer }, { "listresearcheraccounts", &listresearcheraccounts, cat_developer }, + { "listscrapers", &listscrapers, cat_developer }, { "listsettings", &listsettings, cat_developer }, { "logging", &logging, cat_developer }, { "network", &network, cat_developer }, @@ -593,7 +594,7 @@ void StartRPCThreads() (gArgs.GetArg("-rpcuser", "") == gArgs.GetArg("-rpcpassword", "")))) { unsigned char rand_pwd[32]; - GetRandBytes(rand_pwd, sizeof(rand_pwd)); + GetRandBytes({rand_pwd, sizeof(rand_pwd)}); string strWhatAmI = "To use gridcoind"; if (gArgs.IsArgSet("-server")) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); diff --git a/src/rpc/server.h b/src/rpc/server.h index 4bde65f262..6aa1c4850f 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -188,16 +188,17 @@ extern UniValue superblocks(const UniValue& params, bool fHelp); extern UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp); extern UniValue auditsnapshotaccruals(const UniValue& params, bool fHelp); extern UniValue addkey(const UniValue& params, bool fHelp); +extern UniValue beaconaudit(const UniValue& params, bool fHelp); extern UniValue currentcontractaverage(const UniValue& params, bool fHelp); extern UniValue debug(const UniValue& params, bool fHelp); extern UniValue dumpcontracts(const UniValue& params, bool fHelp); extern UniValue rpc_getblockstats(const UniValue& params, bool fHelp); -extern UniValue getlistof(const UniValue& params, bool fHelp); extern UniValue inspectaccrualsnapshot(const UniValue& params, bool fHelp); extern UniValue listalerts(const UniValue& params, bool fHelp); -extern UniValue listdata(const UniValue& params, bool fHelp); extern UniValue listprojects(const UniValue& params, bool fHelp); +extern UniValue listprotocolentries(const UniValue& params, bool fHelp); extern UniValue listresearcheraccounts(const UniValue& params, bool fHelp); +extern UniValue listscrapers(const UniValue& params, bool fHelp); extern UniValue listsettings(const UniValue& params, bool fHelp); extern UniValue logging(const UniValue& params, bool fHelp); extern UniValue network(const UniValue& params, bool fHelp); diff --git a/src/rpc/voting.cpp b/src/rpc/voting.cpp index a593c75ad0..68acf379e4 100644 --- a/src/rpc/voting.cpp +++ b/src/rpc/voting.cpp @@ -303,7 +303,10 @@ UniValue SubmitVote(const Poll& poll, VoteBuilder builder) LOCK2(cs_main, pwalletMain->cs_wallet); // Note that a lock on cs_poll_registry does NOT need to be taken here. // This lock will be taken by the contract handler. - result_pair = SendContract(builder.BuildContractTx(pwalletMain)); + + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3: 2; + + result_pair = SendContract(builder.BuildContractTx(pwalletMain, contract_version)); } if (!result_pair.second.empty()) { @@ -507,7 +510,10 @@ UniValue addpoll(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); // Note that a lock on cs_poll_registry does NOT need to be taken here. // This lock will be taken by the contract handler. - result_pair = SendContract(builder.BuildContractTx(pwalletMain)); + + uint32_t contract_version = IsV13Enabled(nBestHeight) ? 3 : 2; + + result_pair = SendContract(builder.BuildContractTx(pwalletMain, contract_version)); } if (!result_pair.second.empty()) { @@ -563,9 +569,17 @@ UniValue getpollresults(const UniValue& params, bool fHelp) // We only need to lock the registry to retrieve the reference. If there is a reorg during the PollResultToJson, it will // throw. - if (const PollReference* ref = WITH_LOCK(GetPollRegistry().cs_poll_registry, return TryPollByTitleOrId(title_or_id))) { + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" +#endif + if (const PollReference* ref = WITH_LOCK(GetPollRegistry().cs_poll_registry, return TryPollByTitleOrId(title_or_id))) { return PollResultToJson(*ref); } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif throw JSONRPCError(RPC_MISC_ERROR, "No matching poll found"); } @@ -716,9 +730,16 @@ UniValue votedetails(const UniValue& params, bool fHelp) // We only need to lock the registry to retrieve the reference. If there is a reorg during the PollResultToJson, it will // throw. - if (const PollReference* ref = WITH_LOCK(GetPollRegistry().cs_poll_registry, return TryPollByTitleOrId(title_or_id))) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" +#endif + if (const PollReference* ref = WITH_LOCK(GetPollRegistry().cs_poll_registry, return TryPollByTitleOrId(title_or_id))) { return VoteDetailsToJson(*ref); } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif throw JSONRPCError(RPC_MISC_ERROR, "No matching poll found"); } diff --git a/src/script.cpp b/src/script.cpp index 81218332b5..e81af2bfcf 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -3,12 +3,9 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -using namespace std; - #include "script.h" #include #include "keystore.h" -#include "bignum.h" #include "key.h" #include "main.h" #include "random.h" @@ -18,6 +15,8 @@ using namespace std; #include +using namespace std; + CScriptID::CScriptID(const CScript& in) : BaseHash(Hash160(in)) {} //CScriptID::CScriptID(const ScriptHash& in) : BaseHash(static_cast(in)) {} @@ -26,20 +25,10 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc static const valtype vchFalse(0); static const valtype vchZero(0); static const valtype vchTrue(1, 1); -static const CBigNum bnZero(0); -static const CBigNum bnOne(1); -static const CBigNum bnFalse(0); -static const CBigNum bnTrue(1); -static const size_t nMaxNumSize = 4; - - -CBigNum CastToBigNum(const valtype& vch) -{ - if (vch.size() > nMaxNumSize) - throw runtime_error("CastToBigNum() : overflow"); - // Get rid of extra leading zeros - return CBigNum(CBigNum(vch).getvch()); -} +static const CScriptNum bnZero(0); +static const CScriptNum bnOne(1); +static const CScriptNum bnFalse(0); +static const CScriptNum bnTrue(1); bool CastToBool(const valtype& vch) { @@ -332,7 +321,6 @@ static bool IsCanonicalSignature(const valtype &vchSig) { bool EvalScript(vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType) { - CAutoBN_CTX pctx; CScript::const_iterator pc = script.begin(); CScript::const_iterator pend = script.end(); CScript::const_iterator pbegincodehash = script.begin(); @@ -405,7 +393,7 @@ bool EvalScript(vector >& stack, const CScript& script, co case OP_16: { // ( -- value) - CBigNum bn((int)opcode - (int)(OP_1 - 1)); + CScriptNum bn((int)opcode - (int)(OP_1 - 1)); stack.push_back(bn.getvch()); } break; @@ -581,7 +569,7 @@ bool EvalScript(vector >& stack, const CScript& script, co case OP_DEPTH: { // -- stacksize - CBigNum bn(stack.size()); + CScriptNum bn(stack.size()); stack.push_back(bn.getvch()); } break; @@ -631,7 +619,7 @@ bool EvalScript(vector >& stack, const CScript& script, co // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) if (stack.size() < 2) return false; - int n = CastToBigNum(stacktop(-1)).getint(); + int n = CScriptNum(stacktop(-1), false).getint(); popstack(stack); if (n < 0 || n >= (int)stack.size()) return false; @@ -673,124 +661,16 @@ bool EvalScript(vector >& stack, const CScript& script, co } break; - - // - // Splice ops - // - case OP_CAT: - { - // (x1 x2 -- out) - if (stack.size() < 2) - return false; - valtype& vch1 = stacktop(-2); - valtype& vch2 = stacktop(-1); - vch1.insert(vch1.end(), vch2.begin(), vch2.end()); - popstack(stack); - if (stacktop(-1).size() > MAX_SCRIPT_ELEMENT_SIZE) - return false; - } - break; - - case OP_SUBSTR: - { - // (in begin size -- out) - if (stack.size() < 3) - return false; - valtype& vch = stacktop(-3); - int nBegin = CastToBigNum(stacktop(-2)).getint(); - int nEnd = nBegin + CastToBigNum(stacktop(-1)).getint(); - if (nBegin < 0 || nEnd < nBegin) - return false; - if (nBegin > (int)vch.size()) - nBegin = vch.size(); - if (nEnd > (int)vch.size()) - nEnd = vch.size(); - vch.erase(vch.begin() + nEnd, vch.end()); - vch.erase(vch.begin(), vch.begin() + nBegin); - popstack(stack); - popstack(stack); - } - break; - - case OP_LEFT: - case OP_RIGHT: - { - // (in size -- out) - if (stack.size() < 2) - return false; - valtype& vch = stacktop(-2); - int nSize = CastToBigNum(stacktop(-1)).getint(); - if (nSize < 0) - return false; - if (nSize > (int)vch.size()) - nSize = vch.size(); - if (opcode == OP_LEFT) - vch.erase(vch.begin() + nSize, vch.end()); - else - vch.erase(vch.begin(), vch.end() - nSize); - popstack(stack); - } - break; - case OP_SIZE: { // (in -- in size) if (stack.size() < 1) return false; - CBigNum bn(stacktop(-1).size()); + CScriptNum bn(stacktop(-1).size()); stack.push_back(bn.getvch()); } break; - - // - // Bitwise logic - // - case OP_INVERT: - { - // (in - out) - if (stack.size() < 1) - return false; - valtype& vch = stacktop(-1); - for (unsigned int i = 0; i < vch.size(); i++) - vch[i] = ~vch[i]; - } - break; - - // - // WARNING: These disabled opcodes exhibit unexpected behavior - // when used on signed integers due to a bug in MakeSameSize() - // [see definition of MakeSameSize() above]. - // - case OP_AND: - case OP_OR: - case OP_XOR: - { - // (x1 x2 - out) - if (stack.size() < 2) - return false; - valtype& vch1 = stacktop(-2); - valtype& vch2 = stacktop(-1); - MakeSameSize(vch1, vch2); // <-- NOT SAFE FOR SIGNED VALUES - if (opcode == OP_AND) - { - for (unsigned int i = 0; i < vch1.size(); i++) - vch1[i] &= vch2[i]; - } - else if (opcode == OP_OR) - { - for (unsigned int i = 0; i < vch1.size(); i++) - vch1[i] |= vch2[i]; - } - else if (opcode == OP_XOR) - { - for (unsigned int i = 0; i < vch1.size(); i++) - vch1[i] ^= vch2[i]; - } - popstack(stack); - } - break; - case OP_EQUAL: case OP_EQUALVERIFY: //case OP_NOTEQUAL: // use OP_NUMNOTEQUAL @@ -825,8 +705,6 @@ bool EvalScript(vector >& stack, const CScript& script, co // case OP_1ADD: case OP_1SUB: - case OP_2MUL: - case OP_2DIV: case OP_NEGATE: case OP_ABS: case OP_NOT: @@ -835,13 +713,11 @@ bool EvalScript(vector >& stack, const CScript& script, co // (in -- out) if (stack.size() < 1) return false; - CBigNum bn = CastToBigNum(stacktop(-1)); + CScriptNum bn(stacktop(-1), false); switch (opcode) { case OP_1ADD: bn += bnOne; break; case OP_1SUB: bn -= bnOne; break; - case OP_2MUL: bn <<= 1; break; - case OP_2DIV: bn >>= 1; break; case OP_NEGATE: bn = -bn; break; case OP_ABS: if (bn < bnZero) bn = -bn; break; case OP_NOT: bn = (bn == bnZero); break; @@ -855,11 +731,6 @@ bool EvalScript(vector >& stack, const CScript& script, co case OP_ADD: case OP_SUB: - case OP_MUL: - case OP_DIV: - case OP_MOD: - case OP_LSHIFT: - case OP_RSHIFT: case OP_BOOLAND: case OP_BOOLOR: case OP_NUMEQUAL: @@ -875,9 +746,9 @@ bool EvalScript(vector >& stack, const CScript& script, co // (x1 x2 -- out) if (stack.size() < 2) return false; - CBigNum bn1 = CastToBigNum(stacktop(-2)); - CBigNum bn2 = CastToBigNum(stacktop(-1)); - CBigNum bn; + CScriptNum bn1(stacktop(-2), false); + CScriptNum bn2(stacktop(-1), false); + CScriptNum bn(0); switch (opcode) { case OP_ADD: @@ -888,33 +759,6 @@ bool EvalScript(vector >& stack, const CScript& script, co bn = bn1 - bn2; break; - case OP_MUL: - if (!BN_mul(&bn, &bn1, &bn2, pctx)) - return false; - break; - - case OP_DIV: - if (!BN_div(&bn, nullptr, &bn1, &bn2, pctx)) - return false; - break; - - case OP_MOD: - if (!BN_mod(&bn, &bn1, &bn2, pctx)) - return false; - break; - - case OP_LSHIFT: - if (bn2 < bnZero || bn2 > CBigNum(2048)) - return false; - bn = bn1 << bn2.getulong(); - break; - - case OP_RSHIFT: - if (bn2 < bnZero || bn2 > CBigNum(2048)) - return false; - bn = bn1 >> bn2.getulong(); - break; - case OP_BOOLAND: bn = (bn1 != bnZero && bn2 != bnZero); break; case OP_BOOLOR: bn = (bn1 != bnZero || bn2 != bnZero); break; case OP_NUMEQUAL: bn = (bn1 == bn2); break; @@ -947,9 +791,9 @@ bool EvalScript(vector >& stack, const CScript& script, co // (x min max -- out) if (stack.size() < 3) return false; - CBigNum bn1 = CastToBigNum(stacktop(-3)); - CBigNum bn2 = CastToBigNum(stacktop(-2)); - CBigNum bn3 = CastToBigNum(stacktop(-1)); + CScriptNum bn1(stacktop(-3), false); + CScriptNum bn2(stacktop(-2), false); + CScriptNum bn3(stacktop(-1), false); bool fValue = (bn2 <= bn1 && bn1 < bn3); popstack(stack); popstack(stack); @@ -1042,7 +886,7 @@ bool EvalScript(vector >& stack, const CScript& script, co if ((int)stack.size() < i) return false; - int nKeysCount = CastToBigNum(stacktop(-i)).getint(); + int nKeysCount = CScriptNum(stacktop(-i), false).getint(); if (nKeysCount < 0 || nKeysCount > 20) return false; nOpCount += nKeysCount; @@ -1053,7 +897,7 @@ bool EvalScript(vector >& stack, const CScript& script, co if ((int)stack.size() < i) return false; - int nSigsCount = CastToBigNum(stacktop(-i)).getint(); + int nSigsCount = CScriptNum(stacktop(-i), false).getint(); if (nSigsCount < 0 || nSigsCount > nKeysCount) return false; int isig = ++i; @@ -1258,7 +1102,7 @@ class CSignatureCache { // Evict a random entry. Random because that helps // foil would-be DoS attackers who might try to pre-generate - // and re-use a set of valid signatures just-slightly-greater + // and reuse a set of valid signatures just-slightly-greater // than our cache size. uint256 randomHash = GetRandHash(); std::vector unused; diff --git a/src/script.h b/src/script.h index 51e55abd7e..c65169c25c 100644 --- a/src/script.h +++ b/src/script.h @@ -15,9 +15,9 @@ #include "keystore.h" -#include "bignum.h" #include "prevector.h" #include +#include "serialize.h" #include "wallet/ismine.h" typedef std::vector valtype; @@ -31,7 +31,14 @@ class CScriptID : public BaseHash CScriptID() : BaseHash() {} explicit CScriptID(const CScript& in); explicit CScriptID(const uint160& in) : BaseHash(in) {} -// explicit CScriptID(const ScriptHash& in); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_hash); + } }; static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes @@ -86,6 +93,13 @@ class CNoDestination { friend bool operator==(const CNoDestination &a, const CNoDestination &b) { return true; } friend bool operator!=(const CNoDestination &a, const CNoDestination &b) { return false; } friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + {} + }; /** A txout script template with a specific destination. It is either: @@ -246,11 +260,193 @@ enum opcodetype const char* GetOpName(opcodetype opcode); +class scriptnum_error : public std::runtime_error +{ +public: + explicit scriptnum_error(const std::string& str) : std::runtime_error(str) {} +}; + +class CScriptNum +{ +/** + * Numeric opcodes (OP_1ADD, etc) are restricted to operating on 4-byte integers. + * The semantics are subtle, though: operands must be in the range [-2^31 +1...2^31 -1], + * but results may overflow (and are valid as long as they are not used in a subsequent + * numeric operation). CScriptNum enforces those semantics by storing results as + * an int64 and allowing out-of-range values to be returned as a vector of bytes but + * throwing an exception if arithmetic is done or the result is interpreted as an integer. + */ +public: + + explicit CScriptNum(const int64_t& n) + { + m_value = n; + } + + static const size_t nDefaultMaxNumSize = 4; + + explicit CScriptNum(const std::vector& vch, bool fRequireMinimal, + const size_t nMaxNumSize = nDefaultMaxNumSize) + { + if (vch.size() > nMaxNumSize) { + throw scriptnum_error("script number overflow"); + } + if (fRequireMinimal && vch.size() > 0) { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if ((vch.back() & 0x7f) == 0) { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if (vch.size() <= 1 || (vch[vch.size() - 2] & 0x80) == 0) { + throw scriptnum_error("non-minimally encoded script number"); + } + } + } + m_value = set_vch(vch); + } + + inline bool operator==(const int64_t& rhs) const { return m_value == rhs; } + inline bool operator!=(const int64_t& rhs) const { return m_value != rhs; } + inline bool operator<=(const int64_t& rhs) const { return m_value <= rhs; } + inline bool operator< (const int64_t& rhs) const { return m_value < rhs; } + inline bool operator>=(const int64_t& rhs) const { return m_value >= rhs; } + inline bool operator> (const int64_t& rhs) const { return m_value > rhs; } + + inline bool operator==(const CScriptNum& rhs) const { return operator==(rhs.m_value); } + inline bool operator!=(const CScriptNum& rhs) const { return operator!=(rhs.m_value); } + inline bool operator<=(const CScriptNum& rhs) const { return operator<=(rhs.m_value); } + inline bool operator< (const CScriptNum& rhs) const { return operator< (rhs.m_value); } + inline bool operator>=(const CScriptNum& rhs) const { return operator>=(rhs.m_value); } + inline bool operator> (const CScriptNum& rhs) const { return operator> (rhs.m_value); } + + inline CScriptNum operator+( const int64_t& rhs) const { return CScriptNum(m_value + rhs);} + inline CScriptNum operator-( const int64_t& rhs) const { return CScriptNum(m_value - rhs);} + inline CScriptNum operator+( const CScriptNum& rhs) const { return operator+(rhs.m_value); } + inline CScriptNum operator-( const CScriptNum& rhs) const { return operator-(rhs.m_value); } + + inline CScriptNum& operator+=( const CScriptNum& rhs) { return operator+=(rhs.m_value); } + inline CScriptNum& operator-=( const CScriptNum& rhs) { return operator-=(rhs.m_value); } + + inline CScriptNum operator&( const int64_t& rhs) const { return CScriptNum(m_value & rhs);} + inline CScriptNum operator&( const CScriptNum& rhs) const { return operator&(rhs.m_value); } + + inline CScriptNum& operator&=( const CScriptNum& rhs) { return operator&=(rhs.m_value); } + + inline CScriptNum operator-() const + { + assert(m_value != std::numeric_limits::min()); + return CScriptNum(-m_value); + } + + inline CScriptNum& operator=( const int64_t& rhs) + { + m_value = rhs; + return *this; + } + + inline CScriptNum& operator+=( const int64_t& rhs) + { + assert(rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits::max() - rhs) || + (rhs < 0 && m_value >= std::numeric_limits::min() - rhs)); + m_value += rhs; + return *this; + } + + inline CScriptNum& operator-=( const int64_t& rhs) + { + assert(rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits::min() + rhs) || + (rhs < 0 && m_value <= std::numeric_limits::max() + rhs)); + m_value -= rhs; + return *this; + } + + inline CScriptNum& operator&=( const int64_t& rhs) + { + m_value &= rhs; + return *this; + } + + int getint() const + { + if (m_value > std::numeric_limits::max()) + return std::numeric_limits::max(); + else if (m_value < std::numeric_limits::min()) + return std::numeric_limits::min(); + return m_value; + } + + int64_t GetInt64() const { return m_value; } + + std::vector getvch() const + { + return serialize(m_value); + } + + static std::vector serialize(const int64_t& value) + { + if(value == 0) + return std::vector(); + + std::vector result; + const bool neg = value < 0; + uint64_t absvalue = neg ? ~static_cast(value) + 1 : static_cast(value); + + while(absvalue) + { + result.push_back(absvalue & 0xff); + absvalue >>= 8; + } + +// - If the most significant byte is >= 0x80 and the value is positive, push a +// new zero-byte to make the significant byte < 0x80 again. + +// - If the most significant byte is >= 0x80 and the value is negative, push a +// new 0x80 byte that will be popped off when converting to an integral. + +// - If the most significant byte is < 0x80 and the value is negative, add +// 0x80 to it, since it will be subtracted and interpreted as a negative when +// converting to an integral. + + if (result.back() & 0x80) + result.push_back(neg ? 0x80 : 0); + else if (neg) + result.back() |= 0x80; + + return result; + } + +private: + static int64_t set_vch(const std::vector& vch) + { + if (vch.empty()) + return 0; + + int64_t result = 0; + for (size_t i = 0; i != vch.size(); ++i) + result |= static_cast(vch[i]) << 8*i; + + // If the input vector's most significant byte is 0x80, remove it from + // the result's msb and return a negative. + if (vch.back() & 0x80) + return -((int64_t)(result & ~(0x80ULL << (8 * (vch.size() - 1))))); + + return result; + } + + int64_t m_value; +}; inline std::string ValueString(const std::vector& vch) { if (vch.size() <= 4) - return strprintf("%d", CBigNum(vch).getint()); + return strprintf("%d", CScriptNum(vch, false).getint()); else return HexStr(vch); } @@ -269,7 +465,7 @@ class CScript : public CScriptBase } else { - CBigNum bn(n); + CScriptNum bn(n); *this << bn.getvch(); } return *this; @@ -283,7 +479,7 @@ class CScript : public CScriptBase } else { - CBigNum bn(n); + CScriptNum bn(n); *this << bn.getvch(); } return *this; @@ -334,7 +530,7 @@ class CScript : public CScriptBase explicit CScript(opcodetype b) { operator<<(b); } explicit CScript(const uint256& b) { operator<<(b); } - explicit CScript(const CBigNum& b) { operator<<(b); } + explicit CScript(const CScriptNum& b) { operator<<(b); } explicit CScript(const std::vector& b) { operator<<(b); } @@ -378,7 +574,7 @@ class CScript : public CScriptBase return (*this) << vchKey; } - CScript& operator<<(const CBigNum& b) + CScript& operator<<(const CScriptNum& b) { *this << b.getvch(); return *this; diff --git a/src/secp256k1/.cirrus.yml b/src/secp256k1/.cirrus.yml index a2e7f36d1f..0b904a4e38 100644 --- a/src/secp256k1/.cirrus.yml +++ b/src/secp256k1/.cirrus.yml @@ -1,6 +1,9 @@ env: + ### cirrus config + CIRRUS_CLONE_DEPTH: 1 ### compiler options HOST: + WRAPPER_CMD: # Specific warnings can be disabled with -Wno-error=foo. # -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual. WERROR_CFLAGS: -Werror -pedantic-errors @@ -22,21 +25,27 @@ env: SECP256K1_TEST_ITERS: BENCH: yes SECP256K1_BENCH_ITERS: 2 - CTIMETEST: yes + CTIMETESTS: yes # Compile and run the tests EXAMPLES: yes +# https://cirrus-ci.org/pricing/#compute-credits +credits_snippet: &CREDITS + # Don't use any credits for now. + use_compute_credits: false + cat_logs_snippet: &CAT_LOGS always: cat_tests_log_script: - cat tests.log || true + cat_noverify_tests_log_script: + - cat noverify_tests.log || true cat_exhaustive_tests_log_script: - cat exhaustive_tests.log || true - cat_valgrind_ctime_test_log_script: - - cat valgrind_ctime_test.log || true + cat_ctime_tests_log_script: + - cat ctime_tests.log || true cat_bench_log_script: - cat bench.log || true - on_failure: cat_config_log_script: - cat config.log || true cat_test_env_script: @@ -47,10 +56,8 @@ cat_logs_snippet: &CAT_LOGS merge_base_script_snippet: &MERGE_BASE merge_base_script: - if [ "$CIRRUS_PR" = "" ]; then exit 0; fi - - git fetch $CIRRUS_REPO_CLONE_URL $CIRRUS_BASE_BRANCH - - git config --global user.email "ci@ci.ci" - - git config --global user.name "ci" - - git merge FETCH_HEAD # Merge base to detect silent merge conflicts + - git fetch --depth=1 $CIRRUS_REPO_CLONE_URL "pull/${CIRRUS_PR}/merge" + - git checkout FETCH_HEAD # Use merged changes to detect silent merge conflicts linux_container_snippet: &LINUX_CONTAINER container: @@ -69,13 +76,15 @@ task: - env: {WIDEMUL: int64, RECOVERY: yes} - env: {WIDEMUL: int64, ECDH: yes, SCHNORRSIG: yes} - env: {WIDEMUL: int128} + - env: {WIDEMUL: int128_struct} - env: {WIDEMUL: int128, RECOVERY: yes, SCHNORRSIG: yes} - env: {WIDEMUL: int128, ECDH: yes, SCHNORRSIG: yes} - env: {WIDEMUL: int128, ASM: x86_64} - env: { RECOVERY: yes, SCHNORRSIG: yes} - - env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETEST: no, BENCH: no} + - env: {CTIMETESTS: no, RECOVERY: yes, ECDH: yes, SCHNORRSIG: yes, CPPFLAGS: -DVERIFY} + - env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETESTS: no, BENCH: no} - env: {CPPFLAGS: -DDETERMINISTIC} - - env: {CFLAGS: -O0, CTIMETEST: no} + - env: {CFLAGS: -O0, CTIMETESTS: no} - env: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 } - env: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 } matrix: @@ -107,65 +116,32 @@ task: << : *CAT_LOGS task: - name: "x86_64: macOS Catalina" + name: "arm64: macOS Ventura" macos_instance: - image: catalina-base + image: ghcr.io/cirruslabs/macos-ventura-base:latest env: HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 - # Cirrus gives us a fixed number of 12 virtual CPUs. Not that we even have that many jobs at the moment... - MAKEFLAGS: -j13 + # Cirrus gives us a fixed number of 4 virtual CPUs. Not that we even have that many jobs at the moment... + MAKEFLAGS: -j5 matrix: << : *ENV_MATRIX + env: + ASM: no + WITH_VALGRIND: no + CTIMETESTS: no matrix: - env: - CC: gcc-9 + CC: gcc - env: CC: clang - # Update Command Line Tools - # Uncomment this if the Command Line Tools on the CirrusCI macOS image are too old to brew valgrind. - # See https://apple.stackexchange.com/a/195963 for the implementation. - ## update_clt_script: - ## - system_profiler SPSoftwareDataType - ## - touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress - ## - |- - ## PROD=$(softwareupdate -l | grep "*.*Command Line" | tail -n 1 | awk -F"*" '{print $2}' | sed -e 's/^ *//' | sed 's/Label: //g' | tr -d '\n') - ## # For debugging - ## - softwareupdate -l && echo "PROD: $PROD" - ## - softwareupdate -i "$PROD" --verbose - ## - rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress - ## - brew_valgrind_pre_script: - # Retry a few times because this tends to fail randomly. - - for i in {1..5}; do brew update && break || sleep 15; done - - brew config - - brew tap LouisBrunner/valgrind - # Fetch valgrind source but don't build it yet. - - brew fetch --HEAD LouisBrunner/valgrind/valgrind - brew_valgrind_cache: - # This is $(brew --cellar valgrind) but command substition does not work here. - folder: /usr/local/Cellar/valgrind - # Rebuild cache if ... - fingerprint_script: - # ... macOS version changes: - - sw_vers - # ... brew changes: - - brew config - # ... valgrind changes: - - git -C "$(brew --cache)/valgrind--git" rev-parse HEAD - populate_script: - # If there's no hit in the cache, build and install valgrind. - - brew install --HEAD LouisBrunner/valgrind/valgrind - brew_valgrind_post_script: - # If we have restored valgrind from the cache, tell brew to create symlink to the PATH. - # If we haven't restored from cached (and just run brew install), this is a no-op. - - brew link valgrind brew_script: - - brew install automake libtool gcc@9 + - brew install automake libtool gcc << : *MERGE_BASE test_script: - ./ci/cirrus.sh << : *CAT_LOGS + << : *CREDITS task: name: "s390x (big-endian): Linux (Debian stable, QEMU)" @@ -178,7 +154,7 @@ task: ECDH: yes RECOVERY: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no << : *MERGE_BASE test_script: # https://sourceware.org/bugzilla/show_bug.cgi?id=27008 @@ -197,7 +173,7 @@ task: ECDH: yes RECOVERY: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no matrix: - env: {} - env: {EXPERIMENTAL: yes, ASM: arm} @@ -217,7 +193,7 @@ task: ECDH: yes RECOVERY: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no << : *MERGE_BASE test_script: - ./ci/cirrus.sh @@ -234,24 +210,70 @@ task: ECDH: yes RECOVERY: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no << : *MERGE_BASE test_script: - ./ci/cirrus.sh << : *CAT_LOGS task: - name: "x86_64 (mingw32-w64): Windows (Debian stable, Wine)" << : *LINUX_CONTAINER env: - WRAPPER_CMD: wine64-stable - SECP256K1_TEST_ITERS: 16 - HOST: x86_64-w64-mingw32 + WRAPPER_CMD: wine + WITH_VALGRIND: no + ECDH: yes + RECOVERY: yes + SCHNORRSIG: yes + CTIMETESTS: no + matrix: + - name: "x86_64 (mingw32-w64): Windows (Debian stable, Wine)" + env: + HOST: x86_64-w64-mingw32 + - name: "i686 (mingw32-w64): Windows (Debian stable, Wine)" + env: + HOST: i686-w64-mingw32 + << : *MERGE_BASE + test_script: + - ./ci/cirrus.sh + << : *CAT_LOGS + +task: + << : *LINUX_CONTAINER + env: + WRAPPER_CMD: wine + WERROR_CFLAGS: -WX WITH_VALGRIND: no ECDH: yes RECOVERY: yes + EXPERIMENTAL: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no + # Use a MinGW-w64 host to tell ./configure we're building for Windows. + # This will detect some MinGW-w64 tools but then make will need only + # the MSVC tools CC, AR and NM as specified below. + HOST: x86_64-w64-mingw32 + CC: /opt/msvc/bin/x64/cl + AR: /opt/msvc/bin/x64/lib + NM: /opt/msvc/bin/x64/dumpbin -symbols -headers + # Set non-essential options that affect the CLI messages here. + # (They depend on the user's taste, so we don't want to set them automatically in configure.ac.) + CFLAGS: -nologo -diagnostics:caret + LDFLAGS: -Xlinker -Xlinker -Xlinker -nologo + matrix: + - name: "x86_64 (MSVC): Windows (Debian stable, Wine)" + - name: "x86_64 (MSVC): Windows (Debian stable, Wine, int128_struct)" + env: + WIDEMUL: int128_struct + - name: "x86_64 (MSVC): Windows (Debian stable, Wine, int128_struct with __(u)mulh)" + env: + WIDEMUL: int128_struct + CPPFLAGS: -DSECP256K1_MSVC_MULH_TEST_OVERRIDE + - name: "i686 (MSVC): Windows (Debian stable, Wine)" + env: + HOST: i686-w64-mingw32 + CC: /opt/msvc/bin/x86/cl + AR: /opt/msvc/bin/x86/lib + NM: /opt/msvc/bin/x86/dumpbin -symbols -headers << : *MERGE_BASE test_script: - ./ci/cirrus.sh @@ -264,7 +286,7 @@ task: ECDH: yes RECOVERY: yes SCHNORRSIG: yes - CTIMETEST: no + CTIMETESTS: no matrix: - name: "Valgrind (memcheck)" container: @@ -301,14 +323,40 @@ task: - ./ci/cirrus.sh << : *CAT_LOGS +# Memory sanitizers task: - name: "C++ -fpermissive" << : *LINUX_CONTAINER + name: "MSan" env: - # ./configure correctly errors out when given CC=g++. - # We hack around this by passing CC=g++ only to make. - CC: gcc - MAKEFLAGS: -j4 CC=g++ CFLAGS=-fpermissive\ -g + ECDH: yes + RECOVERY: yes + SCHNORRSIG: yes + CTIMETESTS: yes + CC: clang + SECP256K1_TEST_ITERS: 32 + ASM: no + WITH_VALGRIND: no + container: + memory: 2G + matrix: + - env: + CFLAGS: "-fsanitize=memory -g" + - env: + ECMULTGENPRECISION: 2 + ECMULTWINDOW: 2 + CFLAGS: "-fsanitize=memory -g -O3" + << : *MERGE_BASE + test_script: + - ./ci/cirrus.sh + << : *CAT_LOGS + +task: + name: "C++ -fpermissive (entire project)" + << : *LINUX_CONTAINER + env: + CC: g++ + CFLAGS: -fpermissive -g + CPPFLAGS: -DSECP256K1_CPLUSPLUS_TEST_OVERRIDE WERROR_CFLAGS: ECDH: yes RECOVERY: yes @@ -318,9 +366,44 @@ task: - ./ci/cirrus.sh << : *CAT_LOGS +task: + name: "C++ (public headers)" + << : *LINUX_CONTAINER + test_script: + - g++ -Werror include/*.h + - clang -Werror -x c++-header include/*.h + - /opt/msvc/bin/x64/cl.exe -c -WX -TP include/*.h + task: name: "sage prover" << : *LINUX_CONTAINER test_script: - cd sage - sage prove_group_implementations.sage + +task: + name: "x86_64: Windows (VS 2022)" + windows_container: + image: cirrusci/windowsservercore:visualstudio2022 + cpu: 4 + memory: 3840MB + env: + PATH: '%CIRRUS_WORKING_DIR%\build\src\RelWithDebInfo;%PATH%' + x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' + # Ignore MSBuild warning MSB8029. + # See: https://learn.microsoft.com/en-us/visualstudio/msbuild/errors/msb8029?view=vs-2022 + IgnoreWarnIntDirInTempDetected: 'true' + merge_script: + - PowerShell -NoLogo -Command if ($env:CIRRUS_PR -ne $null) { git fetch $env:CIRRUS_REPO_CLONE_URL pull/$env:CIRRUS_PR/merge; git reset --hard FETCH_HEAD; } + configure_script: + - '%x64_NATIVE_TOOLS%' + - cmake -G "Visual Studio 17 2022" -A x64 -S . -B build -DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DSECP256K1_BUILD_EXAMPLES=ON + build_script: + - '%x64_NATIVE_TOOLS%' + - cmake --build build --config RelWithDebInfo -- -property:UseMultiToolTask=true;CL_MPcount=5 + check_script: + - '%x64_NATIVE_TOOLS%' + - ctest --test-dir build -j 5 + - build\src\RelWithDebInfo\bench_ecmult.exe + - build\src\RelWithDebInfo\bench_internal.exe + - build\src\RelWithDebInfo\bench.exe diff --git a/src/secp256k1/.gitignore b/src/secp256k1/.gitignore index d88627d72e..bc7e499de7 100644 --- a/src/secp256k1/.gitignore +++ b/src/secp256k1/.gitignore @@ -1,11 +1,12 @@ bench bench_ecmult bench_internal +noverify_tests tests exhaustive_tests precompute_ecmult_gen precompute_ecmult -valgrind_ctime_test +ctime_tests ecdh_example ecdsa_example schnorr_example @@ -13,9 +14,9 @@ schnorr_example *.so *.a *.csv -!.gitignore *.log *.trs +*.sage.py Makefile configure @@ -34,8 +35,6 @@ libtool *.lo *.o *~ -*.log -*.trs coverage/ coverage.html @@ -44,8 +43,6 @@ coverage.*.html *.gcno *.gcov -src/libsecp256k1-config.h -src/libsecp256k1-config.h.in build-aux/ar-lib build-aux/config.guess build-aux/config.sub @@ -60,5 +57,7 @@ build-aux/m4/ltversion.m4 build-aux/missing build-aux/compile build-aux/test-driver -src/stamp-h1 libsecp256k1.pc + +# Default CMake build directory. +/build diff --git a/src/secp256k1/CHANGELOG.md b/src/secp256k1/CHANGELOG.md new file mode 100644 index 0000000000..7f43843641 --- /dev/null +++ b/src/secp256k1/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.0] - 2023-03-08 + +#### Added + - Added experimental support for CMake builds. Traditional GNU Autotools builds (`./configure` and `make`) remain fully supported. + - Usage examples: Added a recommended method for securely clearing sensitive data, e.g., secret keys, from memory. + - Tests: Added a new test binary `noverify_tests`. This binary runs the tests without some additional checks present in the ordinary `tests` binary and is thereby closer to production binaries. The `noverify_tests` binary is automatically run as part of the `make check` target. + +#### Fixed + - Fixed declarations of API variables for MSVC (`__declspec(dllimport)`). This fixes MSVC builds of programs which link against a libsecp256k1 DLL dynamically and use API variables (and not only API functions). Unfortunately, the MSVC linker now will emit warning `LNK4217` when trying to link against libsecp256k1 statically. Pass `/ignore:4217` to the linker to suppress this warning. + +#### Changed + - Forbade cloning or destroying `secp256k1_context_static`. Create a new context instead of cloning the static context. (If this change breaks your code, your code is probably wrong.) + - Forbade randomizing (copies of) `secp256k1_context_static`. Randomizing a copy of `secp256k1_context_static` did not have any effect and did not provide defense-in-depth protection against side-channel attacks. Create a new context if you want to benefit from randomization. + +#### Removed + - Removed the configuration header `src/libsecp256k1-config.h`. We recommend passing flags to `./configure` or `cmake` to set configuration options (see `./configure --help` or `cmake -LH`). If you cannot or do not want to use one of the supported build systems, pass configuration flags such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG` manually to the compiler (see the file `configure.ac` for supported flags). + +#### ABI Compatibility + +Due to changes in the API regarding `secp256k1_context_static` described above, the ABI is *not* compatible with previous versions. + +## [0.2.0] - 2022-12-12 + +#### Added + - Added usage examples for common use cases in a new `examples/` directory. + - Added `secp256k1_selftest`, to be used in conjunction with `secp256k1_context_static`. + - Added support for 128-bit wide multiplication on MSVC for x86_64 and arm64, giving roughly a 20% speedup on those platforms. + +#### Changed + - Enabled modules `schnorrsig`, `extrakeys` and `ecdh` by default in `./configure`. + - The `secp256k1_nonce_function_rfc6979` nonce function, used by default by `secp256k1_ecdsa_sign`, now reduces the message hash modulo the group order to match the specification. This only affects improper use of ECDSA signing API. + +#### Deprecated + - Deprecated context flags `SECP256K1_CONTEXT_VERIFY` and `SECP256K1_CONTEXT_SIGN`. Use `SECP256K1_CONTEXT_NONE` instead. + - Renamed `secp256k1_context_no_precomp` to `secp256k1_context_static`. + - Module `schnorrsig`: renamed `secp256k1_schnorrsig_sign` to `secp256k1_schnorrsig_sign32`. + +#### ABI Compatibility + +Since this is the first release, we do not compare application binary interfaces. +However, there are earlier unreleased versions of libsecp256k1 that are *not* ABI compatible with this version. + +## [0.1.0] - 2013-03-05 to 2021-12-25 + +This version was in fact never released. +The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6). +Therefore, this version number does not uniquely identify a set of source files. + +[unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/bitcoin-core/secp256k1/compare/423b6d19d373f1224fd671a982584d7e7900bc93..v0.2.0 +[0.1.0]: https://github.com/bitcoin-core/secp256k1/commit/423b6d19d373f1224fd671a982584d7e7900bc93 diff --git a/src/secp256k1/CMakeLists.txt b/src/secp256k1/CMakeLists.txt new file mode 100644 index 0000000000..5c8aad6fcc --- /dev/null +++ b/src/secp256k1/CMakeLists.txt @@ -0,0 +1,302 @@ +cmake_minimum_required(VERSION 3.1) + +if(CMAKE_VERSION VERSION_GREATER 3.14) + # MSVC runtime library flags are selected by the CMAKE_MSVC_RUNTIME_LIBRARY abstraction. + cmake_policy(SET CMP0091 NEW) + # MSVC warning flags are not in CMAKE__FLAGS by default. + cmake_policy(SET CMP0092 NEW) +endif() + +# The package (a.k.a. release) version is based on semantic versioning 2.0.0 of +# the API. All changes in experimental modules are treated as +# backwards-compatible and therefore at most increase the minor version. +project(libsecp256k1 VERSION 0.3.0 LANGUAGES C) + +# The library version is based on libtool versioning of the ABI. The set of +# rules for updating the version can be found here: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +# All changes in experimental modules are treated as if they don't affect the +# interface and therefore only increase the revision. +set(${PROJECT_NAME}_LIB_VERSION_CURRENT 2) +set(${PROJECT_NAME}_LIB_VERSION_REVISION 0) +set(${PROJECT_NAME}_LIB_VERSION_AGE 0) + +set(CMAKE_C_STANDARD 90) +set(CMAKE_C_EXTENSIONS OFF) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +# We do not use CMake's BUILD_SHARED_LIBS option. +option(SECP256K1_BUILD_SHARED "Build shared library." ON) +option(SECP256K1_BUILD_STATIC "Build static library." ON) +if(NOT SECP256K1_BUILD_SHARED AND NOT SECP256K1_BUILD_STATIC) + message(FATAL_ERROR "At least one of SECP256K1_BUILD_SHARED and SECP256K1_BUILD_STATIC must be enabled.") +endif() + +option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON) +if(SECP256K1_ENABLE_MODULE_ECDH) + add_definitions(-DENABLE_MODULE_ECDH=1) +endif() + +option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF) +if(SECP256K1_ENABLE_MODULE_RECOVERY) + add_definitions(-DENABLE_MODULE_RECOVERY=1) +endif() + +option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) +option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) + set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON) + add_definitions(-DENABLE_MODULE_SCHNORRSIG=1) +endif() +if(SECP256K1_ENABLE_MODULE_EXTRAKEYS) + add_definitions(-DENABLE_MODULE_EXTRAKEYS=1) +endif() + +option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) +if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS) + add_definitions(-DUSE_EXTERNAL_DEFAULT_CALLBACKS=1) +endif() + +set(SECP256K1_ECMULT_WINDOW_SIZE "AUTO" CACHE STRING "Window size for ecmult precomputation for verification, specified as integer in range [2..24]. \"AUTO\" is a reasonable setting for desktop machines (currently 15). [default=AUTO]") +set_property(CACHE SECP256K1_ECMULT_WINDOW_SIZE PROPERTY STRINGS "AUTO" 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24) +include(CheckStringOptionValue) +check_string_option_value(SECP256K1_ECMULT_WINDOW_SIZE) +if(SECP256K1_ECMULT_WINDOW_SIZE STREQUAL "AUTO") + set(SECP256K1_ECMULT_WINDOW_SIZE 15) +endif() +add_definitions(-DECMULT_WINDOW_SIZE=${SECP256K1_ECMULT_WINDOW_SIZE}) + +set(SECP256K1_ECMULT_GEN_PREC_BITS "AUTO" CACHE STRING "Precision bits to tune the precomputed table size for signing, specified as integer 2, 4 or 8. \"AUTO\" is a reasonable setting for desktop machines (currently 4). [default=AUTO]") +set_property(CACHE SECP256K1_ECMULT_GEN_PREC_BITS PROPERTY STRINGS "AUTO" 2 4 8) +check_string_option_value(SECP256K1_ECMULT_GEN_PREC_BITS) +if(SECP256K1_ECMULT_GEN_PREC_BITS STREQUAL "AUTO") + set(SECP256K1_ECMULT_GEN_PREC_BITS 4) +endif() +add_definitions(-DECMULT_GEN_PREC_BITS=${SECP256K1_ECMULT_GEN_PREC_BITS}) + +set(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY "OFF" CACHE STRING "Test-only override of the (autodetected by the C code) \"widemul\" setting. Legal values are: \"OFF\", \"int128_struct\", \"int128\" or \"int64\". [default=OFF]") +set_property(CACHE SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY PROPERTY STRINGS "OFF" "int128_struct" "int128" "int64") +check_string_option_value(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY) +if(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY) + string(TOUPPER "${SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY}" widemul_upper_value) + add_definitions(-DUSE_FORCE_WIDEMUL_${widemul_upper_value}=1) +endif() +mark_as_advanced(FORCE SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY) + +set(SECP256K1_ASM "AUTO" CACHE STRING "Assembly optimizations to use: \"AUTO\", \"OFF\", \"x86_64\" or \"arm\" (experimental). [default=AUTO]") +set_property(CACHE SECP256K1_ASM PROPERTY STRINGS "AUTO" "OFF" "x86_64" "arm") +check_string_option_value(SECP256K1_ASM) +if(SECP256K1_ASM STREQUAL "arm") + enable_language(ASM) + add_definitions(-DUSE_EXTERNAL_ASM=1) +elseif(SECP256K1_ASM) + include(Check64bitAssembly) + check_64bit_assembly() + if(HAS_64BIT_ASM) + set(SECP256K1_ASM "x86_64") + add_definitions(-DUSE_ASM_X86_64=1) + elseif(SECP256K1_ASM STREQUAL "AUTO") + set(SECP256K1_ASM "OFF") + else() + message(FATAL_ERROR "x86_64 assembly optimization requested but not available.") + endif() +endif() + +option(SECP256K1_EXPERIMENTAL "Allow experimental configuration options." OFF) +if(NOT SECP256K1_EXPERIMENTAL) + if(SECP256K1_ASM STREQUAL "arm") + message(FATAL_ERROR "ARM assembly optimization is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.") + endif() +endif() + +set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]") +set_property(CACHE SECP256K1_VALGRIND PROPERTY STRINGS "AUTO" "OFF" "ON") +check_string_option_value(SECP256K1_VALGRIND) +if(SECP256K1_VALGRIND) + find_package(Valgrind MODULE) + if(Valgrind_FOUND) + set(SECP256K1_VALGRIND ON) + include_directories(${Valgrind_INCLUDE_DIR}) + add_definitions(-DVALGRIND) + elseif(SECP256K1_VALGRIND STREQUAL "AUTO") + set(SECP256K1_VALGRIND OFF) + else() + message(FATAL_ERROR "Valgrind support requested but valgrind/memcheck.h header not available.") + endif() +endif() + +option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." ON) +option(SECP256K1_BUILD_TESTS "Build tests." ON) +option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." ON) +option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." ${SECP256K1_VALGRIND}) +option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) + +# Redefine configuration flags. +# We leave assertions on, because they are only used in the examples, and we want them always on there. +if(MSVC) + string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") +else() + string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") + # Prefer -O2 optimization level. (-O3 is CMake's default for Release for many compilers.) + string(REGEX REPLACE "-O3[ \t\r\n]*" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") +endif() + +# Define custom "Coverage" build type. +set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_RELWITHDEBINFO} -O0 -DCOVERAGE=1 --coverage -Wno-unused-parameter" CACHE STRING + "Flags used by the C compiler during \"Coverage\" builds." + FORCE +) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} --coverage" CACHE STRING + "Flags used for linking binaries during \"Coverage\" builds." + FORCE +) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} --coverage" CACHE STRING + "Flags used by the shared libraries linker during \"Coverage\" builds." + FORCE +) +mark_as_advanced( + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE +) + +if(CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES "RelWithDebInfo" "Release" "Debug" "MinSizeRel" "Coverage") +endif() + +get_property(cached_cmake_build_type CACHE CMAKE_BUILD_TYPE PROPERTY TYPE) +if(cached_cmake_build_type) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY + STRINGS "RelWithDebInfo" "Release" "Debug" "MinSizeRel" "Coverage" + ) +endif() + +set(default_build_type "RelWithDebInfo") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to \"${default_build_type}\" as none was specified") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) +endif() + +include(TryAddCompileOption) +if(MSVC) + try_add_compile_option(/W2) + try_add_compile_option(/wd4146) +else() + try_add_compile_option(-pedantic) + try_add_compile_option(-Wall) + try_add_compile_option(-Wcast-align) + try_add_compile_option(-Wcast-align=strict) + try_add_compile_option(-Wconditional-uninitialized) + try_add_compile_option(-Wextra) + try_add_compile_option(-Wnested-externs) + try_add_compile_option(-Wno-long-long) + try_add_compile_option(-Wno-overlength-strings) + try_add_compile_option(-Wno-unused-function) + try_add_compile_option(-Wreserved-identifier) + try_add_compile_option(-Wshadow) + try_add_compile_option(-Wstrict-prototypes) + try_add_compile_option(-Wundef) +endif() + +if(CMAKE_VERSION VERSION_GREATER 3.2) + # Honor visibility properties for all target types. + # See: https://cmake.org/cmake/help/latest/policy/CMP0063.html + cmake_policy(SET CMP0063 NEW) +endif() +set(CMAKE_C_VISIBILITY_PRESET hidden) + +# Ask CTest to create a "check" target (e.g., make check) as alias for the "test" target. +# CTEST_TEST_TARGET_ALIAS is not documented but supposed to be user-facing. +# See: https://gitlab.kitware.com/cmake/cmake/-/commit/816c9d1aa1f2b42d40c81a991b68c96eb12b6d2 +set(CTEST_TEST_TARGET_ALIAS check) +include(CTest) +# We do not use CTest's BUILD_TESTING because a single toggle for all tests is too coarse for our needs. +mark_as_advanced(BUILD_TESTING) +if(SECP256K1_BUILD_BENCHMARK OR SECP256K1_BUILD_TESTS OR SECP256K1_BUILD_EXHAUSTIVE_TESTS OR SECP256K1_BUILD_CTIME_TESTS OR SECP256K1_BUILD_EXAMPLES) + enable_testing() +endif() + +add_subdirectory(src) +if(SECP256K1_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +message("\n") +message("secp256k1 configure summary") +message("===========================") +message("Build artifacts:") +message(" shared library ...................... ${SECP256K1_BUILD_SHARED}") +message(" static library ...................... ${SECP256K1_BUILD_STATIC}") +message("Optional modules:") +message(" ECDH ................................ ${SECP256K1_ENABLE_MODULE_ECDH}") +message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOVERY}") +message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") +message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") +message("Parameters:") +message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") +message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}") +message("Optional features:") +message(" assembly optimization ............... ${SECP256K1_ASM}") +message(" external callbacks .................. ${SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS}") +if(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY) + message(" wide multiplication (test-only) ..... ${SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY}") +endif() +message("Optional binaries:") +message(" benchmark ........................... ${SECP256K1_BUILD_BENCHMARK}") +message(" noverify_tests ...................... ${SECP256K1_BUILD_TESTS}") +set(tests_status "${SECP256K1_BUILD_TESTS}") +if(CMAKE_BUILD_TYPE STREQUAL "Coverage") + set(tests_status OFF) +endif() +message(" tests ............................... ${tests_status}") +message(" exhaustive tests .................... ${SECP256K1_BUILD_EXHAUSTIVE_TESTS}") +message(" ctime_tests ......................... ${SECP256K1_BUILD_CTIME_TESTS}") +message(" examples ............................ ${SECP256K1_BUILD_EXAMPLES}") +message("") +if(CMAKE_CROSSCOMPILING) + set(cross_status "TRUE, for ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}") +else() + set(cross_status "FALSE") +endif() +message("Cross compiling ....................... ${cross_status}") +message("Valgrind .............................. ${SECP256K1_VALGRIND}") +get_directory_property(definitions COMPILE_DEFINITIONS) +string(REPLACE ";" " " definitions "${definitions}") +message("Preprocessor defined macros ........... ${definitions}") +message("C compiler ............................ ${CMAKE_C_COMPILER}") +message("CFLAGS ................................ ${CMAKE_C_FLAGS}") +get_directory_property(compile_options COMPILE_OPTIONS) +string(REPLACE ";" " " compile_options "${compile_options}") +message("Compile options ....................... " ${compile_options}) +if(DEFINED CMAKE_BUILD_TYPE) + message("Build type:") + message(" - CMAKE_BUILD_TYPE ................... ${CMAKE_BUILD_TYPE}") + string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) + message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_${build_type}}") + message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_${build_type}}") + message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_${build_type}}") +else() + message("Available configurations .............. ${CMAKE_CONFIGURATION_TYPES}") + message("RelWithDebInfo configuration:") + message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_RELWITHDEBINFO}") + message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}") + message("Debug configuration:") + message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_DEBUG}") + message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_DEBUG}") + message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") +endif() +message("\n") +if(SECP256K1_EXPERIMENTAL) + message( + " ******\n" + " WARNING: experimental build\n" + " Experimental features do not have stable APIs or properties, and may not be safe for production use.\n" + " ******\n" + ) +endif() diff --git a/src/secp256k1/Makefile.am b/src/secp256k1/Makefile.am index 51c5960301..e3fdf4da27 100644 --- a/src/secp256k1/Makefile.am +++ b/src/secp256k1/Makefile.am @@ -47,7 +47,14 @@ noinst_HEADERS += src/modinv64_impl.h noinst_HEADERS += src/precomputed_ecmult.h noinst_HEADERS += src/precomputed_ecmult_gen.h noinst_HEADERS += src/assumptions.h +noinst_HEADERS += src/checkmem.h noinst_HEADERS += src/util.h +noinst_HEADERS += src/int128.h +noinst_HEADERS += src/int128_impl.h +noinst_HEADERS += src/int128_native.h +noinst_HEADERS += src/int128_native_impl.h +noinst_HEADERS += src/int128_struct.h +noinst_HEADERS += src/int128_struct_impl.h noinst_HEADERS += src/scratch.h noinst_HEADERS += src/scratch_impl.h noinst_HEADERS += src/selftest.h @@ -58,17 +65,18 @@ noinst_HEADERS += src/hash_impl.h noinst_HEADERS += src/field.h noinst_HEADERS += src/field_impl.h noinst_HEADERS += src/bench.h -noinst_HEADERS += src/basic-config.h noinst_HEADERS += contrib/lax_der_parsing.h noinst_HEADERS += contrib/lax_der_parsing.c noinst_HEADERS += contrib/lax_der_privatekey_parsing.h noinst_HEADERS += contrib/lax_der_privatekey_parsing.c -noinst_HEADERS += examples/random.h +noinst_HEADERS += examples/examples_util.h PRECOMPUTED_LIB = libsecp256k1_precomputed.la noinst_LTLIBRARIES = $(PRECOMPUTED_LIB) libsecp256k1_precomputed_la_SOURCES = src/precomputed_ecmult.c src/precomputed_ecmult_gen.c -libsecp256k1_precomputed_la_CPPFLAGS = $(SECP_INCLUDES) +# We need `-I$(top_srcdir)/src` in VPATH builds if libsecp256k1_precomputed_la_SOURCES have been recreated in the build tree. +# This helps users and packagers who insist on recreating the precomputed files (e.g., Gentoo). +libsecp256k1_precomputed_la_CPPFLAGS = -I$(top_srcdir)/src $(SECP_CONFIG_DEFINES) if USE_EXTERNAL_ASM COMMON_LIB = libsecp256k1_common.la @@ -87,55 +95,58 @@ endif endif libsecp256k1_la_SOURCES = src/secp256k1.c -libsecp256k1_la_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES) -libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) +libsecp256k1_la_CPPFLAGS = $(SECP_CONFIG_DEFINES) +libsecp256k1_la_LIBADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) libsecp256k1_la_LDFLAGS = -no-undefined -version-info $(LIB_VERSION_CURRENT):$(LIB_VERSION_REVISION):$(LIB_VERSION_AGE) -if VALGRIND_ENABLED -libsecp256k1_la_CPPFLAGS += -DVALGRIND -endif - noinst_PROGRAMS = if USE_BENCHMARK noinst_PROGRAMS += bench bench_internal bench_ecmult bench_SOURCES = src/bench.c -bench_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) +bench_LDADD = libsecp256k1.la +bench_CPPFLAGS = $(SECP_CONFIG_DEFINES) bench_internal_SOURCES = src/bench_internal.c -bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) -bench_internal_CPPFLAGS = $(SECP_INCLUDES) +bench_internal_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) +bench_internal_CPPFLAGS = $(SECP_CONFIG_DEFINES) bench_ecmult_SOURCES = src/bench_ecmult.c -bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) -bench_ecmult_CPPFLAGS = $(SECP_INCLUDES) +bench_ecmult_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) +bench_ecmult_CPPFLAGS = $(SECP_CONFIG_DEFINES) endif TESTS = if USE_TESTS +TESTS += noverify_tests +noinst_PROGRAMS += noverify_tests +noverify_tests_SOURCES = src/tests.c +noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) +noverify_tests_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) +noverify_tests_LDFLAGS = -static +if !ENABLE_COVERAGE +TESTS += tests noinst_PROGRAMS += tests -tests_SOURCES = src/tests.c -tests_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES) -if VALGRIND_ENABLED -tests_CPPFLAGS += -DVALGRIND -noinst_PROGRAMS += valgrind_ctime_test -valgrind_ctime_test_SOURCES = src/valgrind_ctime_test.c -valgrind_ctime_test_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +tests_SOURCES = $(noverify_tests_SOURCES) +tests_CPPFLAGS = $(noverify_tests_CPPFLAGS) -DVERIFY +tests_LDADD = $(noverify_tests_LDADD) +tests_LDFLAGS = $(noverify_tests_LDFLAGS) endif -if !ENABLE_COVERAGE -tests_CPPFLAGS += -DVERIFY endif -tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) -tests_LDFLAGS = -static -TESTS += tests + +if USE_CTIME_TESTS +noinst_PROGRAMS += ctime_tests +ctime_tests_SOURCES = src/ctime_tests.c +ctime_tests_LDADD = libsecp256k1.la +ctime_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) endif if USE_EXHAUSTIVE_TESTS noinst_PROGRAMS += exhaustive_tests exhaustive_tests_SOURCES = src/tests_exhaustive.c -exhaustive_tests_CPPFLAGS = $(SECP_INCLUDES) +exhaustive_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) if !ENABLE_COVERAGE exhaustive_tests_CPPFLAGS += -DVERIFY endif # Note: do not include $(PRECOMPUTED_LIB) in exhaustive_tests (it uses runtime-generated tables). -exhaustive_tests_LDADD = $(SECP_LIBS) $(COMMON_LIB) +exhaustive_tests_LDADD = $(COMMON_LIB) exhaustive_tests_LDFLAGS = -static TESTS += exhaustive_tests endif @@ -179,12 +190,12 @@ EXTRA_PROGRAMS = precompute_ecmult precompute_ecmult_gen CLEANFILES = $(EXTRA_PROGRAMS) precompute_ecmult_SOURCES = src/precompute_ecmult.c -precompute_ecmult_CPPFLAGS = $(SECP_INCLUDES) -precompute_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) +precompute_ecmult_CPPFLAGS = $(SECP_CONFIG_DEFINES) +precompute_ecmult_LDADD = $(COMMON_LIB) precompute_ecmult_gen_SOURCES = src/precompute_ecmult_gen.c -precompute_ecmult_gen_CPPFLAGS = $(SECP_INCLUDES) -precompute_ecmult_gen_LDADD = $(SECP_LIBS) $(COMMON_LIB) +precompute_ecmult_gen_CPPFLAGS = $(SECP_CONFIG_DEFINES) +precompute_ecmult_gen_LDADD = $(COMMON_LIB) # See Automake manual, Section "Errors with distclean". # We don't list any dependencies for the prebuilt files here because @@ -211,7 +222,15 @@ maintainer-clean-local: clean-precomp clean-precomp: rm -f $(PRECOMP) -EXTRA_DIST = autogen.sh SECURITY.md +EXTRA_DIST = autogen.sh CHANGELOG.md SECURITY.md +EXTRA_DIST += doc/release-process.md doc/safegcd_implementation.md +EXTRA_DIST += examples/EXAMPLES_COPYING +EXTRA_DIST += sage/gen_exhaustive_groups.sage +EXTRA_DIST += sage/gen_split_lambda_constants.sage +EXTRA_DIST += sage/group_prover.sage +EXTRA_DIST += sage/prove_group_implementations.sage +EXTRA_DIST += sage/secp256k1_params.sage +EXTRA_DIST += sage/weierstrass_prover.sage if ENABLE_MODULE_ECDH include src/modules/ecdh/Makefile.am.include diff --git a/src/secp256k1/README.md b/src/secp256k1/README.md index f5db915e83..19dabe8505 100644 --- a/src/secp256k1/README.md +++ b/src/secp256k1/README.md @@ -2,6 +2,8 @@ libsecp256k1 ============ [![Build Status](https://api.cirrus-ci.com/github/bitcoin-core/secp256k1.svg?branch=master)](https://cirrus-ci.com/github/bitcoin-core/secp256k1) +![Dependencies: None](https://img.shields.io/badge/dependencies-none-success) +[![irc.libera.chat #secp256k1](https://img.shields.io/badge/irc.libera.chat-%23secp256k1-success)](https://web.libera.chat/#secp256k1) Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1. @@ -15,6 +17,7 @@ Features: * Derandomized ECDSA (via RFC6979 or with a caller provided function.) * Very efficient implementation. * Suitable for embedded systems. +* No runtime dependencies. * Optional module for public key recovery. * Optional module for ECDH key exchange. * Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). @@ -57,10 +60,8 @@ Implementation details * Optional runtime blinding which attempts to frustrate differential power analysis. * The precomputed tables add and eventually subtract points for which no known scalar (secret key) is known, preventing even an attacker with control over the secret key used to control the data internally. -Build steps ------------ - -libsecp256k1 is built using autotools: +Building with Autotools +----------------------- $ ./autogen.sh $ ./configure @@ -70,13 +71,51 @@ libsecp256k1 is built using autotools: To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags. +Building with CMake (experimental) +---------------------------------- + +To maintain a pristine source tree, CMake encourages to perform an out-of-source build by using a separate dedicated build tree. + +### Building on POSIX systems + + $ mkdir build && cd build + $ cmake .. + $ make + $ make check # run the test suite + $ sudo make install # optional + +To compile optional modules (such as Schnorr signatures), you need to run `cmake` with additional flags (such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON`). Run `cmake .. -LH` to see the full list of available flags. + +### Cross compiling + +To alleviate issues with cross compiling, preconfigured toolchain files are available in the `cmake` directory. +For example, to cross compile for Windows: + + $ cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-w64-mingw32.toolchain.cmake + +To cross compile for Android with [NDK](https://developer.android.com/ndk/guides/cmake) (using NDK's toolchain file, and assuming the `ANDROID_NDK_ROOT` environment variable has been set): + + $ cmake .. -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=28 + +### Building on Windows + +To build on Windows with Visual Studio, a proper [generator](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#visual-studio-generators) must be specified for a new build tree. + +The following example assumes using of Visual Studio 2022 and CMake v3.21+. + +In "Developer Command Prompt for VS 2022": + + >cmake -G "Visual Studio 17 2022" -A x64 -S . -B build + >cmake --build build --config RelWithDebInfo + Usage examples ----------- - Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`. +Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`. * [ECDSA example](examples/ecdsa.c) * [Schnorr signatures example](examples/schnorr.c) * [Deriving a shared secret (ECDH) example](examples/ecdh.c) - To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`. + +To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`. Test coverage ----------- diff --git a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 b/src/secp256k1/build-aux/m4/bitcoin_secp.m4 index 9cb54de098..624f5e956e 100644 --- a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 +++ b/src/secp256k1/build-aux/m4/bitcoin_secp.m4 @@ -10,6 +10,7 @@ AC_MSG_RESULT([$has_64bit_asm]) ]) AC_DEFUN([SECP_VALGRIND_CHECK],[ +AC_MSG_CHECKING([for valgrind support]) if test x"$has_valgrind" != x"yes"; then CPPFLAGS_TEMP="$CPPFLAGS" CPPFLAGS="$VALGRIND_CPPFLAGS $CPPFLAGS" @@ -19,8 +20,9 @@ if test x"$has_valgrind" != x"yes"; then #if defined(NVALGRIND) # error "Valgrind does not support this platform." #endif - ]])], [has_valgrind=yes; AC_DEFINE(HAVE_VALGRIND,1,[Define this symbol if valgrind is installed, and it supports the host platform])]) + ]])], [has_valgrind=yes]) fi +AC_MSG_RESULT($has_valgrind) ]) dnl SECP_TRY_APPEND_CFLAGS(flags, VAR) diff --git a/src/secp256k1/ci/cirrus.sh b/src/secp256k1/ci/cirrus.sh index b85f012d3f..8495c39203 100755 --- a/src/secp256k1/ci/cirrus.sh +++ b/src/secp256k1/ci/cirrus.sh @@ -1,14 +1,58 @@ #!/bin/sh -set -e -set -x +set -eux export LC_ALL=C +# Print relevant CI environment to allow reproducing the job outside of CI. +print_environment() { + # Turn off -x because it messes up the output + set +x + # There are many ways to print variable names and their content. This one + # does not rely on bash. + for var in WERROR_CFLAGS MAKEFLAGS BUILD \ + ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ + EXPERIMENTAL ECDH RECOVERY SCHNORRSIG \ + SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ + EXAMPLES \ + HOST WRAPPER_CMD \ + CC CFLAGS CPPFLAGS AR NM + do + eval "isset=\${$var+x}" + if [ -n "$isset" ]; then + eval "val=\${$var}" + # shellcheck disable=SC2154 + printf '%s="%s" ' "$var" "$val" + fi + done + echo "$0" + set -x +} +print_environment + +# Start persistent wineserver if necessary. +# This speeds up jobs with many invocations of wine (e.g., ./configure with MSVC) tremendously. +case "$WRAPPER_CMD" in + *wine*) + # Make sure to shutdown wineserver whenever we exit. + trap "wineserver -k || true" EXIT INT HUP + # This is apparently only reliable when we run a dummy command such as "hh.exe" afterwards. + wineserver -p && wine hh.exe + ;; +esac + env >> test_env.log -$CC -v || true -valgrind --version || true +if [ -n "${CC+x}" ]; then + # The MSVC compiler "cl" doesn't understand "-v" + $CC -v || true +fi +if [ "$WITH_VALGRIND" = "yes" ]; then + valgrind --version +fi +if [ -n "$WRAPPER_CMD" ]; then + $WRAPPER_CMD --version +fi ./autogen.sh @@ -20,6 +64,7 @@ valgrind --version || true --enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \ --enable-module-schnorrsig="$SCHNORRSIG" \ --enable-examples="$EXAMPLES" \ + --enable-ctime-tests="$CTIMETESTS" \ --with-valgrind="$WITH_VALGRIND" \ --host="$HOST" $EXTRAFLAGS @@ -36,14 +81,15 @@ export LOG_COMPILER="$WRAPPER_CMD" make "$BUILD" +# Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool +EXEC='./libtool --mode=execute' +if [ -n "$WRAPPER_CMD" ] +then + EXEC="$EXEC $WRAPPER_CMD" +fi + if [ "$BENCH" = "yes" ] then - # Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool - EXEC='./libtool --mode=execute' - if [ -n "$WRAPPER_CMD" ] - then - EXEC="$EXEC $WRAPPER_CMD" - fi { $EXEC ./bench_ecmult $EXEC ./bench_internal @@ -51,9 +97,13 @@ then } >> bench.log 2>&1 fi -if [ "$CTIMETEST" = "yes" ] +if [ "$CTIMETESTS" = "yes" ] then - ./libtool --mode=execute valgrind --error-exitcode=42 ./valgrind_ctime_test > valgrind_ctime_test.log 2>&1 + if [ "$WITH_VALGRIND" = "yes" ]; then + ./libtool --mode=execute valgrind --error-exitcode=42 ./ctime_tests > ctime_tests.log 2>&1 + else + $EXEC ./ctime_tests > ctime_tests.log 2>&1 + fi fi # Rebuild precomputed files (if not cross-compiling). diff --git a/src/secp256k1/ci/linux-debian.Dockerfile b/src/secp256k1/ci/linux-debian.Dockerfile index 5cccbb5565..a83a4e36db 100644 --- a/src/secp256k1/ci/linux-debian.Dockerfile +++ b/src/secp256k1/ci/linux-debian.Dockerfile @@ -1,15 +1,14 @@ FROM debian:stable -RUN dpkg --add-architecture i386 -RUN dpkg --add-architecture s390x -RUN dpkg --add-architecture armhf -RUN dpkg --add-architecture arm64 -RUN dpkg --add-architecture ppc64el -RUN apt-get update +RUN dpkg --add-architecture i386 && \ + dpkg --add-architecture s390x && \ + dpkg --add-architecture armhf && \ + dpkg --add-architecture arm64 && \ + dpkg --add-architecture ppc64el # dkpg-dev: to make pkg-config work in cross-builds # llvm: for llvm-symbolizer, which is used by clang's UBSan for symbolized stack traces -RUN apt-get install --no-install-recommends --no-upgrade -y \ +RUN apt-get update && apt-get install --no-install-recommends -y \ git ca-certificates \ make automake libtool pkg-config dpkg-dev valgrind qemu-user \ gcc clang llvm libc6-dbg \ @@ -19,8 +18,20 @@ RUN apt-get install --no-install-recommends --no-upgrade -y \ gcc-arm-linux-gnueabihf libc6-dev-armhf-cross libc6-dbg:armhf \ gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 \ gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \ - wine gcc-mingw-w64-x86-64 \ + gcc-mingw-w64-x86-64-win32 wine64 wine \ + gcc-mingw-w64-i686-win32 wine32 \ sagemath -# Run a dummy command in wine to make it set up configuration -RUN wine64-stable xcopy || true +WORKDIR /root +# The "wine" package provides a convience wrapper that we need +RUN apt-get update && apt-get install --no-install-recommends -y \ + git ca-certificates wine64 wine python3-simplejson python3-six msitools winbind procps && \ + git clone https://github.com/mstorsjo/msvc-wine && \ + mkdir /opt/msvc && \ + python3 msvc-wine/vsdownload.py --accept-license --dest /opt/msvc Microsoft.VisualStudio.Workload.VCTools && \ + msvc-wine/install.sh /opt/msvc + +# Initialize the wine environment. Wait until the wineserver process has +# exited before closing the session, to avoid corrupting the wine prefix. +RUN wine64 wineboot --init && \ + while (ps -A | grep wineserver) > /dev/null; do sleep 1; done diff --git a/src/secp256k1/cmake/Check64bitAssembly.cmake b/src/secp256k1/cmake/Check64bitAssembly.cmake new file mode 100644 index 0000000000..3f65887765 --- /dev/null +++ b/src/secp256k1/cmake/Check64bitAssembly.cmake @@ -0,0 +1,14 @@ +include(CheckCSourceCompiles) + +function(check_64bit_assembly) + check_c_source_compiles(" + #include + + int main() + { + uint64_t a = 11, tmp; + __asm__ __volatile__(\"movq $0x100000000,%1; mulq %%rsi\" : \"+a\"(a) : \"S\"(tmp) : \"cc\", \"%rdx\"); + } + " HAS_64BIT_ASM) + set(HAS_64BIT_ASM ${HAS_64BIT_ASM} PARENT_SCOPE) +endfunction() diff --git a/src/secp256k1/cmake/CheckStringOptionValue.cmake b/src/secp256k1/cmake/CheckStringOptionValue.cmake new file mode 100644 index 0000000000..bc4d7b5749 --- /dev/null +++ b/src/secp256k1/cmake/CheckStringOptionValue.cmake @@ -0,0 +1,12 @@ +function(check_string_option_value option) + get_property(expected_values CACHE ${option} PROPERTY STRINGS) + if(expected_values) + foreach(value IN LISTS expected_values) + if(value STREQUAL "${${option}}") + return() + endif() + endforeach() + message(FATAL_ERROR "${option} value is \"${${option}}\", but must be one of ${expected_values}.") + endif() + message(AUTHOR_WARNING "The STRINGS property must be set before invoking `check_string_option_value' function.") +endfunction() diff --git a/src/secp256k1/cmake/FindValgrind.cmake b/src/secp256k1/cmake/FindValgrind.cmake new file mode 100644 index 0000000000..f6c1f58649 --- /dev/null +++ b/src/secp256k1/cmake/FindValgrind.cmake @@ -0,0 +1,41 @@ +if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + find_program(BREW_COMMAND brew) + execute_process( + COMMAND ${BREW_COMMAND} --prefix valgrind + OUTPUT_VARIABLE valgrind_brew_prefix + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + +set(hints_paths) +if(valgrind_brew_prefix) + set(hints_paths ${valgrind_brew_prefix}/include) +endif() + +find_path(Valgrind_INCLUDE_DIR + NAMES valgrind/memcheck.h + HINTS ${hints_paths} +) + +if(Valgrind_INCLUDE_DIR) + include(CheckCSourceCompiles) + set(CMAKE_REQUIRED_INCLUDES ${Valgrind_INCLUDE_DIR}) + check_c_source_compiles(" + #include + #if defined(NVALGRIND) + # error \"Valgrind does not support this platform.\" + #endif + + int main() {} + " Valgrind_WORKS) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Valgrind + REQUIRED_VARS Valgrind_INCLUDE_DIR Valgrind_WORKS +) + +mark_as_advanced( + Valgrind_INCLUDE_DIR +) diff --git a/src/secp256k1/cmake/TryAddCompileOption.cmake b/src/secp256k1/cmake/TryAddCompileOption.cmake new file mode 100644 index 0000000000..f53c252c2d --- /dev/null +++ b/src/secp256k1/cmake/TryAddCompileOption.cmake @@ -0,0 +1,23 @@ +include(CheckCCompilerFlag) + +function(try_add_compile_option option) + string(MAKE_C_IDENTIFIER ${option} result) + string(TOUPPER ${result} result) + set(result "C_SUPPORTS${result}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + if(NOT MSVC) + set(CMAKE_REQUIRED_FLAGS "-Werror") + endif() + check_c_compiler_flag(${option} ${result}) + if(${result}) + get_property(compile_options + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + PROPERTY COMPILE_OPTIONS + ) + list(APPEND compile_options "${option}") + set_property( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + PROPERTY COMPILE_OPTIONS "${compile_options}" + ) + endif() +endfunction() diff --git a/src/secp256k1/cmake/arm-linux-gnueabihf.toolchain.cmake b/src/secp256k1/cmake/arm-linux-gnueabihf.toolchain.cmake new file mode 100644 index 0000000000..0d91912b6d --- /dev/null +++ b/src/secp256k1/cmake/arm-linux-gnueabihf.toolchain.cmake @@ -0,0 +1,3 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR arm) +set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) diff --git a/src/secp256k1/cmake/config.cmake.in b/src/secp256k1/cmake/config.cmake.in new file mode 100644 index 0000000000..46b180ab19 --- /dev/null +++ b/src/secp256k1/cmake/config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/src/secp256k1/cmake/x86_64-w64-mingw32.toolchain.cmake b/src/secp256k1/cmake/x86_64-w64-mingw32.toolchain.cmake new file mode 100644 index 0000000000..96119b72d1 --- /dev/null +++ b/src/secp256k1/cmake/x86_64-w64-mingw32.toolchain.cmake @@ -0,0 +1,3 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) diff --git a/src/secp256k1/configure.ac b/src/secp256k1/configure.ac index 2db59a8ff3..a46a0a7be3 100644 --- a/src/secp256k1/configure.ac +++ b/src/secp256k1/configure.ac @@ -4,27 +4,24 @@ AC_PREREQ([2.60]) # the API. All changes in experimental modules are treated as # backwards-compatible and therefore at most increase the minor version. define(_PKG_VERSION_MAJOR, 0) -define(_PKG_VERSION_MINOR, 1) -define(_PKG_VERSION_BUILD, 0) -define(_PKG_VERSION_IS_RELEASE, false) +define(_PKG_VERSION_MINOR, 3) +define(_PKG_VERSION_PATCH, 0) +define(_PKG_VERSION_IS_RELEASE, true) # The library version is based on libtool versioning of the ABI. The set of # rules for updating the version can be found here: # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html # All changes in experimental modules are treated as if they don't affect the # interface and therefore only increase the revision. -define(_LIB_VERSION_CURRENT, 0) +define(_LIB_VERSION_CURRENT, 2) define(_LIB_VERSION_REVISION, 0) define(_LIB_VERSION_AGE, 0) -AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_BUILD)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-pre]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) +AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([build-aux/m4]) AC_CANONICAL_HOST -AH_TOP([#ifndef LIBSECP256K1_CONFIG_H]) -AH_TOP([#define LIBSECP256K1_CONFIG_H]) -AH_BOTTOM([#endif /*LIBSECP256K1_CONFIG_H*/]) # Require Automake 1.11.2 for AM_PROG_AR AM_INIT_AUTOMAKE([1.11.2 foreign subdir-objects]) @@ -33,12 +30,14 @@ AM_INIT_AUTOMAKE([1.11.2 foreign subdir-objects]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_PROG_CC -if test x"$ac_cv_prog_cc_c89" = x"no"; then - AC_MSG_ERROR([c89 compiler support required]) -fi AM_PROG_AS AM_PROG_AR +# Clear some cache variables as a workaround for a bug that appears due to a bad +# interaction between AM_PROG_AR and LT_INIT when combining MSVC's archiver lib.exe. +# https://debbugs.gnu.org/cgi/bugreport.cgi?bug=54421 +AS_UNSET(ac_cv_prog_AR) +AS_UNSET(ac_cv_prog_ac_ct_AR) LT_INIT([win32-dll]) build_windows=no @@ -87,23 +86,42 @@ esac # # TODO We should analogously not touch CPPFLAGS and LDFLAGS but currently there are no issues. AC_DEFUN([SECP_TRY_APPEND_DEFAULT_CFLAGS], [ - # Try to append -Werror=unknown-warning-option to CFLAGS temporarily. Otherwise clang will - # not error out if it gets unknown warning flags and the checks here will always succeed - # no matter if clang knows the flag or not. - SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS="$CFLAGS" - SECP_TRY_APPEND_CFLAGS([-Werror=unknown-warning-option], CFLAGS) - - SECP_TRY_APPEND_CFLAGS([-std=c89 -pedantic -Wno-long-long -Wnested-externs -Wshadow -Wstrict-prototypes -Wundef], $1) # GCC >= 3.0, -Wlong-long is implied by -pedantic. - SECP_TRY_APPEND_CFLAGS([-Wno-overlength-strings], $1) # GCC >= 4.2, -Woverlength-strings is implied by -pedantic. - SECP_TRY_APPEND_CFLAGS([-Wall], $1) # GCC >= 2.95 and probably many other compilers - SECP_TRY_APPEND_CFLAGS([-Wno-unused-function], $1) # GCC >= 3.0, -Wunused-function is implied by -Wall. - SECP_TRY_APPEND_CFLAGS([-Wextra], $1) # GCC >= 3.4, this is the newer name of -W, which we don't use because older GCCs will warn about unused functions. - SECP_TRY_APPEND_CFLAGS([-Wcast-align], $1) # GCC >= 2.95 - SECP_TRY_APPEND_CFLAGS([-Wcast-align=strict], $1) # GCC >= 8.0 - SECP_TRY_APPEND_CFLAGS([-Wconditional-uninitialized], $1) # Clang >= 3.0 only - SECP_TRY_APPEND_CFLAGS([-fvisibility=hidden], $1) # GCC >= 4.0 - - CFLAGS="$SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS" + # GCC and compatible (incl. clang) + if test "x$GCC" = "xyes"; then + # Try to append -Werror=unknown-warning-option to CFLAGS temporarily. Otherwise clang will + # not error out if it gets unknown warning flags and the checks here will always succeed + # no matter if clang knows the flag or not. + SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS="$CFLAGS" + SECP_TRY_APPEND_CFLAGS([-Werror=unknown-warning-option], CFLAGS) + + SECP_TRY_APPEND_CFLAGS([-std=c89 -pedantic -Wno-long-long -Wnested-externs -Wshadow -Wstrict-prototypes -Wundef], $1) # GCC >= 3.0, -Wlong-long is implied by -pedantic. + SECP_TRY_APPEND_CFLAGS([-Wno-overlength-strings], $1) # GCC >= 4.2, -Woverlength-strings is implied by -pedantic. + SECP_TRY_APPEND_CFLAGS([-Wall], $1) # GCC >= 2.95 and probably many other compilers + SECP_TRY_APPEND_CFLAGS([-Wno-unused-function], $1) # GCC >= 3.0, -Wunused-function is implied by -Wall. + SECP_TRY_APPEND_CFLAGS([-Wextra], $1) # GCC >= 3.4, this is the newer name of -W, which we don't use because older GCCs will warn about unused functions. + SECP_TRY_APPEND_CFLAGS([-Wcast-align], $1) # GCC >= 2.95 + SECP_TRY_APPEND_CFLAGS([-Wcast-align=strict], $1) # GCC >= 8.0 + SECP_TRY_APPEND_CFLAGS([-Wconditional-uninitialized], $1) # Clang >= 3.0 only + SECP_TRY_APPEND_CFLAGS([-Wreserved-identifier], $1) # Clang >= 13.0 only + SECP_TRY_APPEND_CFLAGS([-fvisibility=hidden], $1) # GCC >= 4.0 + + CFLAGS="$SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS" + fi + + # MSVC + # Assume MSVC if we're building for Windows but not with GCC or compatible; + # libtool makes the same assumption internally. + # Note that "/opt" and "-opt" are equivalent for MSVC; we use "-opt" because "/opt" looks like a path. + if test x"$GCC" != x"yes" && test x"$build_windows" = x"yes"; then + SECP_TRY_APPEND_CFLAGS([-W2 -wd4146], $1) # Moderate warning level, disable warning C4146 "unary minus operator applied to unsigned type, result still unsigned" + SECP_TRY_APPEND_CFLAGS([-external:anglebrackets -external:W0], $1) # Suppress warnings from #include <...> files + # We pass -ignore:4217 to the MSVC linker to suppress warning 4217 when + # importing variables from a statically linked secp256k1. + # (See the libtool manual, section "Windows DLLs" for background.) + # Unfortunately, libtool tries to be too clever and strips "-Xlinker arg" + # into "arg", so this will be " -Xlinker -ignore:4217" after stripping. + LDFLAGS="-Xlinker -Xlinker -Xlinker -ignore:4217 $LDFLAGS" + fi ]) SECP_TRY_APPEND_DEFAULT_CFLAGS(SECP_CFLAGS) @@ -128,6 +146,10 @@ AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), [], [SECP_SET_DEFAULT([enable_tests], [yes], [yes])]) +AC_ARG_ENABLE(ctime_tests, + AS_HELP_STRING([--enable-ctime-tests],[compile constant-time tests [default=yes if valgrind enabled]]), [], + [SECP_SET_DEFAULT([enable_ctime_tests], [auto], [auto])]) + AC_ARG_ENABLE(experimental, AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]), [], [SECP_SET_DEFAULT([enable_experimental], [no], [yes])]) @@ -141,27 +163,31 @@ AC_ARG_ENABLE(examples, [SECP_SET_DEFAULT([enable_examples], [no], [yes])]) AC_ARG_ENABLE(module_ecdh, - AS_HELP_STRING([--enable-module-ecdh],[enable ECDH module [default=no]]), [], - [SECP_SET_DEFAULT([enable_module_ecdh], [no], [yes])]) + AS_HELP_STRING([--enable-module-ecdh],[enable ECDH module [default=yes]]), [], + [SECP_SET_DEFAULT([enable_module_ecdh], [yes], [yes])]) AC_ARG_ENABLE(module_recovery, AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]), [], [SECP_SET_DEFAULT([enable_module_recovery], [no], [yes])]) AC_ARG_ENABLE(module_extrakeys, - AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module [default=no]]), [], - [SECP_SET_DEFAULT([enable_module_extrakeys], [no], [yes])]) + AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module [default=yes]]), [], + [SECP_SET_DEFAULT([enable_module_extrakeys], [yes], [yes])]) AC_ARG_ENABLE(module_schnorrsig, - AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=no]]), [], - [SECP_SET_DEFAULT([enable_module_schnorrsig], [no], [yes])]) + AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=yes]]), [], + [SECP_SET_DEFAULT([enable_module_schnorrsig], [yes], [yes])]) AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) # Test-only override of the (autodetected by the C code) "widemul" setting. -# Legal values are int64 (for [u]int64_t), int128 (for [unsigned] __int128), and auto (the default). +# Legal values are: +# * int64 (for [u]int64_t), +# * int128 (for [unsigned] __int128), +# * int128_struct (for int128 implemented as a structure), +# * and auto (the default). AC_ARG_WITH([test-override-wide-multiply], [] ,[set_widemul=$withval], [set_widemul=auto]) AC_ARG_WITH([asm], [AS_HELP_STRING([--with-asm=x86_64|arm|no|auto], @@ -207,10 +233,13 @@ else enable_valgrind=yes fi fi -AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"]) + +if test x"$enable_ctime_tests" = x"auto"; then + enable_ctime_tests=$enable_valgrind +fi if test x"$enable_coverage" = x"yes"; then - AC_DEFINE(COVERAGE, 1, [Define this symbol to compile out all VERIFY code]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DCOVERAGE=1" SECP_CFLAGS="-O0 --coverage $SECP_CFLAGS" LDFLAGS="--coverage $LDFLAGS" else @@ -252,7 +281,7 @@ enable_external_asm=no case $set_asm in x86_64) - AC_DEFINE(USE_ASM_X86_64, 1, [Define this symbol to enable x86_64 assembly optimizations]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_ASM_X86_64=1" ;; arm) enable_external_asm=yes @@ -265,17 +294,20 @@ no) esac if test x"$enable_external_asm" = x"yes"; then - AC_DEFINE(USE_EXTERNAL_ASM, 1, [Define this symbol if an external (non-inline) assembly implementation is used]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_ASM=1" fi # Select wide multiplication implementation case $set_widemul in +int128_struct) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_FORCE_WIDEMUL_INT128_STRUCT=1" + ;; int128) - AC_DEFINE(USE_FORCE_WIDEMUL_INT128, 1, [Define this symbol to force the use of the (unsigned) __int128 based wide multiplication implementation]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_FORCE_WIDEMUL_INT128=1" ;; int64) - AC_DEFINE(USE_FORCE_WIDEMUL_INT64, 1, [Define this symbol to force the use of the (u)int64_t based wide multiplication implementation]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_FORCE_WIDEMUL_INT64=1" ;; auto) ;; @@ -302,7 +334,7 @@ case $set_ecmult_window in # not in range AC_MSG_ERROR($error_window_size) fi - AC_DEFINE_UNQUOTED(ECMULT_WINDOW_SIZE, $set_ecmult_window, [Set window size for ecmult precomputation]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DECMULT_WINDOW_SIZE=$set_ecmult_window" ;; esac @@ -315,7 +347,7 @@ fi case $set_ecmult_gen_precision in 2|4|8) - AC_DEFINE_UNQUOTED(ECMULT_GEN_PREC_BITS, $set_ecmult_gen_precision, [Set ecmult gen precision bits]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DECMULT_GEN_PREC_BITS=$set_ecmult_gen_precision" ;; *) AC_MSG_ERROR(['ecmult gen precision not 2, 4, 8 or "auto"']) @@ -323,10 +355,12 @@ case $set_ecmult_gen_precision in esac if test x"$enable_valgrind" = x"yes"; then - SECP_INCLUDES="$SECP_INCLUDES $VALGRIND_CPPFLAGS" + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES $VALGRIND_CPPFLAGS -DVALGRIND" fi -# Add -Werror and similar flags passed from the outside (for testing, e.g., in CI) +# Add -Werror and similar flags passed from the outside (for testing, e.g., in CI). +# We don't want to set the user variable CFLAGS in CI because this would disable +# autoconf's logic for setting default CFLAGS, which we would like to test in CI. SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" ### @@ -334,26 +368,26 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS" ### if test x"$enable_module_ecdh" = x"yes"; then - AC_DEFINE(ENABLE_MODULE_ECDH, 1, [Define this symbol to enable the ECDH module]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1" fi if test x"$enable_module_recovery" = x"yes"; then - AC_DEFINE(ENABLE_MODULE_RECOVERY, 1, [Define this symbol to enable the ECDSA pubkey recovery module]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_RECOVERY=1" fi if test x"$enable_module_schnorrsig" = x"yes"; then - AC_DEFINE(ENABLE_MODULE_SCHNORRSIG, 1, [Define this symbol to enable the schnorrsig module]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SCHNORRSIG=1" enable_module_extrakeys=yes fi # Test if extrakeys is set after the schnorrsig module to allow the schnorrsig # module to set enable_module_extrakeys=yes if test x"$enable_module_extrakeys" = x"yes"; then - AC_DEFINE(ENABLE_MODULE_EXTRAKEYS, 1, [Define this symbol to enable the extrakeys module]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_EXTRAKEYS=1" fi if test x"$enable_external_default_callbacks" = x"yes"; then - AC_DEFINE(USE_EXTERNAL_DEFAULT_CALLBACKS, 1, [Define this symbol if an external implementation of the default callbacks is used]) + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1" fi ### @@ -375,15 +409,12 @@ fi ### Generate output ### -AC_CONFIG_HEADERS([src/libsecp256k1-config.h]) AC_CONFIG_FILES([Makefile libsecp256k1.pc]) -AC_SUBST(SECP_INCLUDES) -AC_SUBST(SECP_LIBS) -AC_SUBST(SECP_TEST_LIBS) -AC_SUBST(SECP_TEST_INCLUDES) AC_SUBST(SECP_CFLAGS) +AC_SUBST(SECP_CONFIG_DEFINES) AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"]) AM_CONDITIONAL([USE_TESTS], [test x"$enable_tests" != x"no"]) +AM_CONDITIONAL([USE_CTIME_TESTS], [test x"$enable_ctime_tests" = x"yes"]) AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$enable_exhaustive_tests" != x"no"]) AM_CONDITIONAL([USE_EXAMPLES], [test x"$enable_examples" != x"no"]) AM_CONDITIONAL([USE_BENCHMARK], [test x"$enable_benchmark" = x"yes"]) @@ -405,6 +436,7 @@ echo "Build Options:" echo " with external callbacks = $enable_external_default_callbacks" echo " with benchmarks = $enable_benchmark" echo " with tests = $enable_tests" +echo " with ctime tests = $enable_ctime_tests" echo " with coverage = $enable_coverage" echo " with examples = $enable_examples" echo " module ecdh = $enable_module_ecdh" diff --git a/src/secp256k1/contrib/lax_der_privatekey_parsing.h b/src/secp256k1/contrib/lax_der_privatekey_parsing.h index 1a8ad8ae0c..3749e418fe 100644 --- a/src/secp256k1/contrib/lax_der_privatekey_parsing.h +++ b/src/secp256k1/contrib/lax_der_privatekey_parsing.h @@ -43,8 +43,7 @@ extern "C" { /** Export a private key in DER format. * * Returns: 1 if the private key was valid. - * Args: ctx: pointer to a context object, initialized for signing (cannot - * be NULL) + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: privkey: pointer to an array for storing the private key in BER. * Should have space for 279 bytes, and cannot be NULL. * privkeylen: Pointer to an int where the length of the private key in diff --git a/src/secp256k1/doc/CHANGELOG.md b/src/secp256k1/doc/CHANGELOG.md deleted file mode 100644 index 3c4c2e4583..0000000000 --- a/src/secp256k1/doc/CHANGELOG.md +++ /dev/null @@ -1,12 +0,0 @@ -# Changelog - -This file is currently only a template for future use. - -Each change falls into one of the following categories: Added, Changed, Deprecated, Removed, Fixed or Security. - -## [Unreleased] - -## [MAJOR.MINOR.PATCH] - YYYY-MM-DD - -### Added/Changed/Deprecated/Removed/Fixed/Security -- [Title with link to Pull Request](https://link-to-pr) diff --git a/src/secp256k1/doc/release-process.md b/src/secp256k1/doc/release-process.md index a35b8a9db3..b522f89657 100644 --- a/src/secp256k1/doc/release-process.md +++ b/src/secp256k1/doc/release-process.md @@ -1,14 +1,52 @@ # Release Process -1. Open PR to master that - 1. adds release notes to `doc/CHANGELOG.md` and - 2. if this is **not** a patch release, updates `_PKG_VERSION_{MAJOR,MINOR}` and `_LIB_VERSIONS_*` in `configure.ac` -2. After the PR is merged, - * if this is **not** a patch release, create a release branch with name `MAJOR.MINOR`. - Make sure that the branch contains the right commits. - Create commit on the release branch that sets `_PKG_VERSION_IS_RELEASE` in `configure.ac` to `true`. - * if this **is** a patch release, open a pull request with the bugfixes to the `MAJOR.MINOR` branch. - Also include the release note commit bump `_PKG_VERSION_BUILD` and `_LIB_VERSIONS_*` in `configure.ac`. -4. Tag the commit with `git tag -s vMAJOR.MINOR.PATCH`. -5. Push branch and tag with `git push origin --tags`. -6. Create a new GitHub release with a link to the corresponding entry in `doc/CHANGELOG.md`. +This document outlines the process for releasing versions of the form `$MAJOR.$MINOR.$PATCH`. + +We distinguish between two types of releases: *regular* and *maintenance* releases. +Regular releases are releases of a new major or minor version as well as patches of the most recent release. +Maintenance releases, on the other hand, are required for patches of older releases. + +You should coordinate with the other maintainers on the release date, if possible. +This date will be part of the release entry in [CHANGELOG.md](../CHANGELOG.md) and it should match the dates of the remaining steps in the release process (including the date of the tag and the GitHub release). +It is best if the maintainers are present during the release, so they can help ensure that the process is followed correctly and, in the case of a regular release, they are aware that they should not modify the master branch between merging the PR in step 1 and the PR in step 3. + +This process also assumes that there will be no minor releases for old major releases. + +## Regular release + +1. Open a PR to the master branch with a commit (using message `"release: prepare for $MAJOR.$MINOR.$PATCH"`, for example) that + * finalizes the release notes in [CHANGELOG.md](../CHANGELOG.md) (make sure to include an entry for `### ABI Compatibility`) and + * updates `_PKG_VERSION_*`, `_LIB_VERSION_*`, and sets `_PKG_VERSION_IS_RELEASE` to `true` in `configure.ac`. +2. After the PR is merged, tag the commit and push it: + ``` + RELEASE_COMMIT= + git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH" $RELEASE_COMMIT + git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH + ``` +3. Open a PR to the master branch with a commit (using message `"release cleanup: bump version after $MAJOR.$MINOR.$PATCH"`, for example) that sets `_PKG_VERSION_IS_RELEASE` to `false` and `_PKG_VERSION_PATCH` to `$PATCH + 1` and increases `_LIB_VERSION_REVISION`. If other maintainers are not present to approve the PR, it can be merged without ACKs. +4. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). + +## Maintenance release + +Note that bugfixes only need to be backported to releases for which no compatible release without the bug exists. + +1. If `$PATCH = 1`, create maintenance branch `$MAJOR.$MINOR`: + ``` + git checkout -b $MAJOR.$MINOR v$MAJOR.$MINOR.0 + git push git@github.com:bitcoin-core/secp256k1.git $MAJOR.$MINOR + ``` +2. Open a pull request to the `$MAJOR.$MINOR` branch that + * includes the bugfixes, + * finalizes the release notes, + * bumps `_PKG_VERSION_PATCH` and `_LIB_VERSION_REVISION` in `configure.ac` (with commit message `"release: update PKG_ and LIB_VERSION for $MAJOR.$MINOR.$PATCH"`, for example). +3. After the PRs are merged, update the release branch and tag the commit: + ``` + git checkout $MAJOR.$MINOR && git pull + git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH" + ``` +4. Push tag: + ``` + git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH + ``` +5. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md). +6. Open PR to the master branch that includes a commit (with commit message `"release notes: add $MAJOR.$MINOR.$PATCH"`, for example) that adds release notes to [CHANGELOG.md](../CHANGELOG.md). diff --git a/src/secp256k1/doc/safegcd_implementation.md b/src/secp256k1/doc/safegcd_implementation.md index 063aa8efae..5dbbb7bbd2 100644 --- a/src/secp256k1/doc/safegcd_implementation.md +++ b/src/secp256k1/doc/safegcd_implementation.md @@ -1,7 +1,7 @@ # The safegcd implementation in libsecp256k1 explained -This document explains the modular inverse implementation in the `src/modinv*.h` files. It is based -on the paper +This document explains the modular inverse and Jacobi symbol implementations in the `src/modinv*.h` files. +It is based on the paper ["Fast constant-time gcd computation and modular inversion"](https://gcd.cr.yp.to/papers.html#safegcd) by Daniel J. Bernstein and Bo-Yin Yang. The references below are for the Date: 2019.04.13 version. @@ -410,7 +410,7 @@ sufficient even. Given that every loop iteration performs *N* divsteps, it will To deal with the branches in `divsteps_n_matrix` we will replace them with constant-time bitwise operations (and hope the C compiler isn't smart enough to turn them back into branches; see -`valgrind_ctime_test.c` for automated tests that this isn't the case). To do so, observe that a +`ctime_tests.c` for automated tests that this isn't the case). To do so, observe that a divstep can be written instead as (compare to the inner loop of `gcd` in section 1). ```python @@ -769,3 +769,51 @@ def modinv_var(M, Mi, x): d, e = update_de(d, e, t, M, Mi) return normalize(f, d, Mi) ``` + +## 8. From GCDs to Jacobi symbol + +We can also use a similar approach to calculate Jacobi symbol *(x | M)* by keeping track of an +extra variable *j*, for which at every step *(x | M) = j (g | f)*. As we update *f* and *g*, we +make corresponding updates to *j* using +[properties of the Jacobi symbol](https://en.wikipedia.org/wiki/Jacobi_symbol#Properties): +* *((g/2) | f)* is either *(g | f)* or *-(g | f)*, depending on the value of *f mod 8* (negating if it's *3* or *5*). +* *(f | g)* is either *(g | f)* or *-(g | f)*, depending on *f mod 4* and *g mod 4* (negating if both are *3*). + +These updates depend only on the values of *f* and *g* modulo *4* or *8*, and can thus be applied +very quickly, as long as we keep track of a few additional bits of *f* and *g*. Overall, this +calculation is slightly simpler than the one for the modular inverse because we no longer need to +keep track of *d* and *e*. + +However, one difficulty of this approach is that the Jacobi symbol *(a | n)* is only defined for +positive odd integers *n*, whereas in the original safegcd algorithm, *f, g* can take negative +values. We resolve this by using the following modified steps: + +```python + # Before + if delta > 0 and g & 1: + delta, f, g = 1 - delta, g, (g - f) // 2 + + # After + if delta > 0 and g & 1: + delta, f, g = 1 - delta, g, (g + f) // 2 +``` + +The algorithm is still correct, since the changed divstep, called a "posdivstep" (see section 8.4 +and E.5 in the paper) preserves *gcd(f, g)*. However, there's no proof that the modified algorithm +will converge. The justification for posdivsteps is completely empirical: in practice, it appears +that the vast majority of nonzero inputs converge to *f=g=gcd(f0, g0)* in a +number of steps proportional to their logarithm. + +Note that: +- We require inputs to satisfy *gcd(x, M) = 1*, as otherwise *f=1* is not reached. +- We require inputs *x &neq; 0*, because applying posdivstep with *g=0* has no effect. +- We need to update the termination condition from *g=0* to *f=1*. + +We account for the possibility of nonconvergence by only performing a bounded number of +posdivsteps, and then falling back to square-root based Jacobi calculation if a solution has not +yet been found. + +The optimizations in sections 3-7 above are described in the context of the original divsteps, but +in the C implementation we also adapt most of them (not including "avoiding modulus operations", +since it's not necessary to track *d, e*, and "constant-time operation", since we never calculate +Jacobi symbols for secret data) to the posdivsteps version. diff --git a/src/secp256k1/examples/CMakeLists.txt b/src/secp256k1/examples/CMakeLists.txt new file mode 100644 index 0000000000..0884b645e0 --- /dev/null +++ b/src/secp256k1/examples/CMakeLists.txt @@ -0,0 +1,34 @@ +add_library(example INTERFACE) +target_include_directories(example INTERFACE + ${PROJECT_SOURCE_DIR}/include +) +target_compile_options(example INTERFACE + $<$:/wd4005> +) +target_link_libraries(example INTERFACE + $<$:bcrypt> +) +if(SECP256K1_BUILD_SHARED) + target_link_libraries(example INTERFACE secp256k1) +elseif(SECP256K1_BUILD_STATIC) + target_link_libraries(example INTERFACE secp256k1_static) + if(MSVC) + target_link_options(example INTERFACE /IGNORE:4217) + endif() +endif() + +add_executable(ecdsa_example ecdsa.c) +target_link_libraries(ecdsa_example example) +add_test(ecdsa_example ecdsa_example) + +if(SECP256K1_ENABLE_MODULE_ECDH) + add_executable(ecdh_example ecdh.c) + target_link_libraries(ecdh_example example) + add_test(ecdh_example ecdh_example) +endif() + +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) + add_executable(schnorr_example schnorr.c) + target_link_libraries(schnorr_example example) + add_test(schnorr_example schnorr_example) +endif() diff --git a/src/secp256k1/examples/ecdh.c b/src/secp256k1/examples/ecdh.c index d7e8add361..4b7b7d6154 100644 --- a/src/secp256k1/examples/ecdh.c +++ b/src/secp256k1/examples/ecdh.c @@ -14,8 +14,7 @@ #include #include -#include "random.h" - +#include "examples_util.h" int main(void) { unsigned char seckey1[32]; @@ -30,12 +29,8 @@ int main(void) { secp256k1_pubkey pubkey1; secp256k1_pubkey pubkey2; - /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` - * needs a context object initialized for signing, which is why we create - * a context with the SECP256K1_CONTEXT_SIGN flag. - * (The docs for `secp256k1_ecdh` don't require any special context, just - * some initialized context) */ - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + /* Before we can call actual API functions, we need to create a "context". */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); if (!fill_random(randomize, sizeof(randomize))) { printf("Failed to generate randomness\n"); return 1; @@ -116,12 +111,12 @@ int main(void) { * example through "out of bounds" array access (see Heartbleed), Or the OS * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. * - * TODO: Prevent these writes from being optimized out, as any good compiler + * Here we are preventing these writes from being optimized out, as any good compiler * will remove any writes that aren't used. */ - memset(seckey1, 0, sizeof(seckey1)); - memset(seckey2, 0, sizeof(seckey2)); - memset(shared_secret1, 0, sizeof(shared_secret1)); - memset(shared_secret2, 0, sizeof(shared_secret2)); + secure_erase(seckey1, sizeof(seckey1)); + secure_erase(seckey2, sizeof(seckey2)); + secure_erase(shared_secret1, sizeof(shared_secret1)); + secure_erase(shared_secret2, sizeof(shared_secret2)); return 0; } diff --git a/src/secp256k1/examples/ecdsa.c b/src/secp256k1/examples/ecdsa.c index 434c856ba0..d1d2b0e365 100644 --- a/src/secp256k1/examples/ecdsa.c +++ b/src/secp256k1/examples/ecdsa.c @@ -13,9 +13,7 @@ #include -#include "random.h" - - +#include "examples_util.h" int main(void) { /* Instead of signing the message directly, we must sign a 32-byte hash. @@ -34,16 +32,12 @@ int main(void) { unsigned char compressed_pubkey[33]; unsigned char serialized_signature[64]; size_t len; - int is_signature_valid; + int is_signature_valid, is_signature_valid2; int return_val; secp256k1_pubkey pubkey; secp256k1_ecdsa_signature sig; - /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` needs - * a context object initialized for signing and `secp256k1_ecdsa_verify` needs - * a context initialized for verification, which is why we create a context - * for both signing and verification with the SECP256K1_CONTEXT_SIGN and - * SECP256K1_CONTEXT_VERIFY flags. */ - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + /* Before we can call actual API functions, we need to create a "context". */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); if (!fill_random(randomize, sizeof(randomize))) { printf("Failed to generate randomness\n"); return 1; @@ -120,18 +114,26 @@ int main(void) { printf("Signature: "); print_hex(serialized_signature, sizeof(serialized_signature)); - /* This will clear everything from the context and free the memory */ secp256k1_context_destroy(ctx); + /* Bonus example: if all we need is signature verification (and no key + generation or signing), we don't need to use a context created via + secp256k1_context_create(). We can simply use the static (i.e., global) + context secp256k1_context_static. See its description in + include/secp256k1.h for details. */ + is_signature_valid2 = secp256k1_ecdsa_verify(secp256k1_context_static, + &sig, msg_hash, &pubkey); + assert(is_signature_valid2 == is_signature_valid); + /* It's best practice to try to clear secrets from memory after using them. * This is done because some bugs can allow an attacker to leak memory, for * example through "out of bounds" array access (see Heartbleed), Or the OS * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. * - * TODO: Prevent these writes from being optimized out, as any good compiler + * Here we are preventing these writes from being optimized out, as any good compiler * will remove any writes that aren't used. */ - memset(seckey, 0, sizeof(seckey)); + secure_erase(seckey, sizeof(seckey)); return 0; } diff --git a/src/secp256k1/examples/random.h b/src/secp256k1/examples/examples_util.h similarity index 69% rename from src/secp256k1/examples/random.h rename to src/secp256k1/examples/examples_util.h index 439226f09f..a52b1fa115 100644 --- a/src/secp256k1/examples/random.h +++ b/src/secp256k1/examples/examples_util.h @@ -71,3 +71,32 @@ static void print_hex(unsigned char* data, size_t size) { } printf("\n"); } + +#if defined(_MSC_VER) +// For SecureZeroMemory +#include +#endif +/* Cleanses memory to prevent leaking sensitive info. Won't be optimized out. */ +static SECP256K1_INLINE void secure_erase(void *ptr, size_t len) { +#if defined(_MSC_VER) + /* SecureZeroMemory is guaranteed not to be optimized out by MSVC. */ + SecureZeroMemory(ptr, len); +#elif defined(__GNUC__) + /* We use a memory barrier that scares the compiler away from optimizing out the memset. + * + * Quoting Adam Langley in commit ad1907fe73334d6c696c8539646c21b11178f20f + * in BoringSSL (ISC License): + * As best as we can tell, this is sufficient to break any optimisations that + * might try to eliminate "superfluous" memsets. + * This method used in memzero_explicit() the Linux kernel, too. Its advantage is that it is + * pretty efficient, because the compiler can still implement the memset() efficently, + * just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by + * Yang et al. (USENIX Security 2017) for more background. + */ + memset(ptr, 0, len); + __asm__ __volatile__("" : : "r"(ptr) : "memory"); +#else + void *(*volatile const volatile_memset)(void *, int, size_t) = memset; + volatile_memset(ptr, 0, len); +#endif +} diff --git a/src/secp256k1/examples/schnorr.c b/src/secp256k1/examples/schnorr.c index 82eb07d5d7..4c0dd1c1a9 100644 --- a/src/secp256k1/examples/schnorr.c +++ b/src/secp256k1/examples/schnorr.c @@ -15,7 +15,7 @@ #include #include -#include "random.h" +#include "examples_util.h" int main(void) { unsigned char msg[12] = "Hello World!"; @@ -26,16 +26,12 @@ int main(void) { unsigned char auxiliary_rand[32]; unsigned char serialized_pubkey[32]; unsigned char signature[64]; - int is_signature_valid; + int is_signature_valid, is_signature_valid2; int return_val; secp256k1_xonly_pubkey pubkey; secp256k1_keypair keypair; - /* The specification in secp256k1_extrakeys.h states that `secp256k1_keypair_create` - * needs a context object initialized for signing. And in secp256k1_schnorrsig.h - * they state that `secp256k1_schnorrsig_verify` needs a context initialized for - * verification, which is why we create a context for both signing and verification - * with the SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY flags. */ - secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + /* Before we can call actual API functions, we need to create a "context". */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); if (!fill_random(randomize, sizeof(randomize))) { printf("Failed to generate randomness\n"); return 1; @@ -139,14 +135,22 @@ int main(void) { /* This will clear everything from the context and free the memory */ secp256k1_context_destroy(ctx); + /* Bonus example: if all we need is signature verification (and no key + generation or signing), we don't need to use a context created via + secp256k1_context_create(). We can simply use the static (i.e., global) + context secp256k1_context_static. See its description in + include/secp256k1.h for details. */ + is_signature_valid2 = secp256k1_schnorrsig_verify(secp256k1_context_static, + signature, msg_hash, 32, &pubkey); + assert(is_signature_valid2 == is_signature_valid); + /* It's best practice to try to clear secrets from memory after using them. * This is done because some bugs can allow an attacker to leak memory, for * example through "out of bounds" array access (see Heartbleed), Or the OS * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. * - * TODO: Prevent these writes from being optimized out, as any good compiler + * Here we are preventing these writes from being optimized out, as any good compiler * will remove any writes that aren't used. */ - memset(seckey, 0, sizeof(seckey)); - + secure_erase(seckey, sizeof(seckey)); return 0; } diff --git a/src/secp256k1/include/secp256k1.h b/src/secp256k1/include/secp256k1.h index dddab346ae..325f35eb04 100644 --- a/src/secp256k1/include/secp256k1.h +++ b/src/secp256k1/include/secp256k1.h @@ -7,7 +7,7 @@ extern "C" { #include -/* Unless explicitly stated all pointer arguments must not be NULL. +/** Unless explicitly stated all pointer arguments must not be NULL. * * The following rules specify the order of arguments in API calls: * @@ -24,15 +24,19 @@ extern "C" { * 5. Opaque data pointers follow the function pointer they are to be passed to. */ -/** Opaque data structure that holds context information (precomputed tables etc.). +/** Opaque data structure that holds context information * - * The purpose of context structures is to cache large precomputed data tables - * that are expensive to construct, and also to maintain the randomization data - * for blinding. + * The primary purpose of context objects is to store randomization data for + * enhanced protection against side-channel leakage. This protection is only + * effective if the context is randomized after its creation. See + * secp256k1_context_create for creation of contexts and + * secp256k1_context_randomize for randomization. * - * Do not create a new context object for each operation, as construction is - * far slower than all other API calls (~100 times slower than an ECDSA - * verification). + * A secondary purpose of context objects is to store pointers to callback + * functions that the library will call when certain error states arise. See + * secp256k1_context_set_error_callback as well as + * secp256k1_context_set_illegal_callback for details. Future library versions + * may use context objects for additional purposes. * * A constructed context can safely be used from multiple threads * simultaneously, but API calls that take a non-const pointer to a context @@ -45,7 +49,7 @@ extern "C" { */ typedef struct secp256k1_context_struct secp256k1_context; -/** Opaque data structure that holds rewriteable "scratch space" +/** Opaque data structure that holds rewritable "scratch space" * * The purpose of this structure is to replace dynamic memory allocations, * because we target architectures where this may not be available. It is @@ -130,7 +134,7 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_INLINE inline # endif -/** When this header is used at build-time the SECP256K1_BUILD define needs to be set +/* When this header is used at build-time the SECP256K1_BUILD define needs to be set * to correctly setup export attributes and nullness checks. This is normally done * by secp256k1.c but to guard against this header being included before secp256k1.c * has had a chance to set the define (e.g. via test harnesses that just includes @@ -141,27 +145,34 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_NO_BUILD #endif -/** At secp256k1 build-time DLL_EXPORT is defined when building objects destined - * for a shared library, but not for those intended for static libraries. - */ - -#ifndef SECP256K1_API -# if defined(_WIN32) -# if defined(SECP256K1_BUILD) && defined(DLL_EXPORT) -# define SECP256K1_API __declspec(dllexport) -# else -# define SECP256K1_API +/* Symbol visibility. See libtool manual, section "Windows DLLs". */ +#if defined(_WIN32) && !defined(__GNUC__) +# ifdef SECP256K1_BUILD +# ifdef DLL_EXPORT +# define SECP256K1_API __declspec (dllexport) +# define SECP256K1_API_VAR extern __declspec (dllexport) # endif -# elif defined(__GNUC__) && (__GNUC__ >= 4) && defined(SECP256K1_BUILD) -# define SECP256K1_API __attribute__ ((visibility ("default"))) +# elif defined _MSC_VER +# define SECP256K1_API +# define SECP256K1_API_VAR extern __declspec (dllimport) +# elif defined DLL_EXPORT +# define SECP256K1_API __declspec (dllimport) +# define SECP256K1_API_VAR extern __declspec (dllimport) +# endif +#endif +#ifndef SECP256K1_API +# if defined(__GNUC__) && (__GNUC__ >= 4) && defined(SECP256K1_BUILD) +# define SECP256K1_API __attribute__ ((visibility ("default"))) +# define SECP256K1_API_VAR extern __attribute__ ((visibility ("default"))) # else # define SECP256K1_API +# define SECP256K1_API_VAR extern # endif #endif -/**Warning attributes - * NONNULL is not used if SECP256K1_BUILD is set to avoid the compiler optimizing out - * some paranoid null checks. */ +/* Warning attributes + * NONNULL is not used if SECP256K1_BUILD is set to avoid the compiler optimizing out + * some paranoid null checks. */ # if defined(__GNUC__) && SECP256K1_GNUC_PREREQ(3, 4) # define SECP256K1_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) # else @@ -173,7 +184,7 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_ARG_NONNULL(_x) # endif -/** Attribute for marking functions, types, and variables as deprecated */ +/* Attribute for marking functions, types, and variables as deprecated */ #if !defined(SECP256K1_BUILD) && defined(__has_attribute) # if __has_attribute(__deprecated__) # define SECP256K1_DEPRECATED(_msg) __attribute__ ((__deprecated__(_msg))) @@ -184,22 +195,26 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_DEPRECATED(_msg) #endif -/** All flags' lower 8 bits indicate what they're for. Do not use directly. */ +/* All flags' lower 8 bits indicate what they're for. Do not use directly. */ #define SECP256K1_FLAGS_TYPE_MASK ((1 << 8) - 1) #define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0) #define SECP256K1_FLAGS_TYPE_COMPRESSION (1 << 1) -/** The higher bits contain the actual data. Do not use directly. */ +/* The higher bits contain the actual data. Do not use directly. */ #define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY (1 << 8) #define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9) #define SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY (1 << 10) #define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8) -/** Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and +/** Context flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and * secp256k1_context_preallocated_create. */ +#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT) + +/** Deprecated context flags. These flags are treated equivalent to SECP256K1_CONTEXT_NONE. */ #define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) #define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) + +/* Testing flag. Do not use. */ #define SECP256K1_CONTEXT_DECLASSIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY) -#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT) /** Flag to pass to secp256k1_ec_pubkey_serialize. */ #define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) @@ -212,23 +227,66 @@ typedef int (*secp256k1_nonce_function)( #define SECP256K1_TAG_PUBKEY_HYBRID_EVEN 0x06 #define SECP256K1_TAG_PUBKEY_HYBRID_ODD 0x07 -/** A simple secp256k1 context object with no precomputed tables. These are useful for - * type serialization/parsing functions which require a context object to maintain - * API consistency, but currently do not require expensive precomputations or dynamic - * allocations. +/** A built-in constant secp256k1 context object with static storage duration, to be + * used in conjunction with secp256k1_selftest. + * + * This context object offers *only limited functionality* , i.e., it cannot be used + * for API functions that perform computations involving secret keys, e.g., signing + * and public key generation. If this restriction applies to a specific API function, + * it is mentioned in its documentation. See secp256k1_context_create if you need a + * full context object that supports all functionality offered by the library. + * + * It is highly recommended to call secp256k1_selftest before using this context. + */ +SECP256K1_API_VAR const secp256k1_context *secp256k1_context_static; + +/** Deprecated alias for secp256k1_context_static. */ +SECP256K1_API_VAR const secp256k1_context *secp256k1_context_no_precomp +SECP256K1_DEPRECATED("Use secp256k1_context_static instead"); + +/** Perform basic self tests (to be used in conjunction with secp256k1_context_static) + * + * This function performs self tests that detect some serious usage errors and + * similar conditions, e.g., when the library is compiled for the wrong endianness. + * This is a last resort measure to be used in production. The performed tests are + * very rudimentary and are not intended as a replacement for running the test + * binaries. + * + * It is highly recommended to call this before using secp256k1_context_static. + * It is not necessary to call this function before using a context created with + * secp256k1_context_create (or secp256k1_context_preallocated_create), which will + * take care of performing the self tests. + * + * If the tests fail, this function will call the default error handler to abort the + * program (see secp256k1_context_set_error_callback). */ -SECP256K1_API extern const secp256k1_context *secp256k1_context_no_precomp; +SECP256K1_API void secp256k1_selftest(void); + /** Create a secp256k1 context object (in dynamically allocated memory). * * This function uses malloc to allocate memory. It is guaranteed that malloc is * called at most once for every call of this function. If you need to avoid dynamic - * memory allocation entirely, see the functions in secp256k1_preallocated.h. + * memory allocation entirely, see secp256k1_context_static and the functions in + * secp256k1_preallocated.h. * * Returns: a newly created context object. - * In: flags: which parts of the context to initialize. + * In: flags: Always set to SECP256K1_CONTEXT_NONE (see below). + * + * The only valid non-deprecated flag in recent library versions is + * SECP256K1_CONTEXT_NONE, which will create a context sufficient for all functionality + * offered by the library. All other (deprecated) flags will be treated as equivalent + * to the SECP256K1_CONTEXT_NONE flag. Though the flags parameter primarily exists for + * historical reasons, future versions of the library may introduce new flags. + * + * If the context is intended to be used for API functions that perform computations + * involving secret keys, e.g., signing and public key generation, then it is highly + * recommended to call secp256k1_context_randomize on the context before calling + * those API functions. This will provide enhanced protection against side-channel + * leakage, see secp256k1_context_randomize for details. * - * See also secp256k1_context_randomize. + * Do not create a new context object for each operation, as construction and + * randomization can take non-negligible time. */ SECP256K1_API secp256k1_context* secp256k1_context_create( unsigned int flags @@ -240,8 +298,11 @@ SECP256K1_API secp256k1_context* secp256k1_context_create( * called at most once for every call of this function. If you need to avoid dynamic * memory allocation entirely, see the functions in secp256k1_preallocated.h. * + * Cloning secp256k1_context_static is not possible, and should not be emulated by + * the caller (e.g., using memcpy). Create a new context instead. + * * Returns: a newly created context object. - * Args: ctx: an existing context to copy + * Args: ctx: an existing context to copy (not secp256k1_context_static) */ SECP256K1_API secp256k1_context* secp256k1_context_clone( const secp256k1_context* ctx @@ -259,6 +320,7 @@ SECP256K1_API secp256k1_context* secp256k1_context_clone( * * Args: ctx: an existing context to destroy, constructed using * secp256k1_context_create or secp256k1_context_clone + * (i.e., not secp256k1_context_static). */ SECP256K1_API void secp256k1_context_destroy( secp256k1_context* ctx @@ -308,7 +370,10 @@ SECP256K1_API void secp256k1_context_set_illegal_callback( ) SECP256K1_ARG_NONNULL(1); /** Set a callback function to be called when an internal consistency check - * fails. The default is crashing. + * fails. + * + * The default callback writes an error message to stderr and calls abort + * to abort the program. * * This can only trigger in case of a hardware failure, miscompilation, * memory corruption, serious bug in the library, or other error would can @@ -426,8 +491,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp( * encoding is invalid. R and S with value 0 are allowed in the encoding. * * After the call, sig will always be initialized. If parsing failed or R or - * S are zero, the resulting sig value is guaranteed to fail validation for any - * message and public key. + * S are zero, the resulting sig value is guaranteed to fail verification for + * any message and public key. */ SECP256K1_API int secp256k1_ecdsa_signature_parse_compact( const secp256k1_context* ctx, @@ -447,7 +512,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_parse_compact( * encoded numbers are out of range. * * After the call, sig will always be initialized. If parsing failed or the - * encoded numbers are out of range, signature validation with it is + * encoded numbers are out of range, signature verification with it is * guaranteed to fail for every message and public key. */ SECP256K1_API int secp256k1_ecdsa_signature_parse_der( @@ -494,7 +559,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( * * Returns: 1: correct signature * 0: incorrect or unparseable signature - * Args: ctx: a secp256k1 context object, initialized for verification. + * Args: ctx: a secp256k1 context object. * In: sig: the signature being verified. * msghash32: the 32-byte message hash being verified. * The verifier must make sure to apply a cryptographic @@ -511,7 +576,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( * * If you need to accept ECDSA signatures from sources that do not obey this * rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to - * validation, but be aware that doing so results in malleable signatures. + * verification, but be aware that doing so results in malleable signatures. * * For details, see the comments for that function. */ @@ -573,16 +638,16 @@ SECP256K1_API int secp256k1_ecdsa_signature_normalize( * If a data pointer is passed, it is assumed to be a pointer to 32 bytes of * extra entropy. */ -SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979; +SECP256K1_API_VAR const secp256k1_nonce_function secp256k1_nonce_function_rfc6979; /** A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979). */ -SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_default; +SECP256K1_API_VAR const secp256k1_nonce_function secp256k1_nonce_function_default; /** Create an ECDSA signature. * * Returns: 1: signature created * 0: the nonce generation function failed, or the secret key was invalid. - * Args: ctx: pointer to a context object, initialized for signing. + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: sig: pointer to an array where the signature will be placed. * In: msghash32: the 32-byte message hash being signed. * seckey: pointer to a 32-byte secret key. @@ -626,7 +691,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_verify( * * Returns: 1: secret was valid, public key stores. * 0: secret was invalid, try again. - * Args: ctx: pointer to a context object, initialized for signing. + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: pubkey: pointer to the created public key. * In: seckey: pointer to a 32-byte secret key. */ @@ -705,7 +770,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add( * Returns: 0 if the arguments are invalid or the resulting public key would be * invalid (only when the tweak is the negation of the corresponding * secret key). 1 otherwise. - * Args: ctx: pointer to a context object initialized for validation. + * Args: ctx: pointer to a context object. * In/Out: pubkey: pointer to a public key object. pubkey will be set to an * invalid value if this function returns 0. * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according to @@ -750,7 +815,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul( /** Tweak a public key by multiplying it by a tweak value. * * Returns: 0 if the arguments are invalid. 1 otherwise. - * Args: ctx: pointer to a context object initialized for validation. + * Args: ctx: pointer to a context object. * In/Out: pubkey: pointer to a public key object. pubkey will be set to an * invalid value if this function returns 0. * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according to @@ -764,30 +829,37 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_mul( const unsigned char *tweak32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); -/** Updates the context randomization to protect against side-channel leakage. - * Returns: 1: randomization successfully updated or nothing to randomize +/** Randomizes the context to provide enhanced protection against side-channel leakage. + * + * Returns: 1: randomization successful * 0: error - * Args: ctx: pointer to a context object. - * In: seed32: pointer to a 32-byte random seed (NULL resets to initial state) + * Args: ctx: pointer to a context object (not secp256k1_context_static). + * In: seed32: pointer to a 32-byte random seed (NULL resets to initial state). * - * While secp256k1 code is written to be constant-time no matter what secret - * values are, it's possible that a future compiler may output code which isn't, + * While secp256k1 code is written and tested to be constant-time no matter what + * secret values are, it is possible that a compiler may output code which is not, * and also that the CPU may not emit the same radio frequencies or draw the same - * amount power for all values. - * - * This function provides a seed which is combined into the blinding value: that - * blinding value is added before each multiplication (and removed afterwards) so - * that it does not affect function results, but shields against attacks which - * rely on any input-dependent behaviour. - * - * This function has currently an effect only on contexts initialized for signing - * because randomization is currently used only for signing. However, this is not - * guaranteed and may change in the future. It is safe to call this function on - * contexts not initialized for signing; then it will have no effect and return 1. - * - * You should call this after secp256k1_context_create or - * secp256k1_context_clone (and secp256k1_context_preallocated_create or - * secp256k1_context_clone, resp.), and you may call this repeatedly afterwards. + * amount of power for all values. Randomization of the context shields against + * side-channel observations which aim to exploit secret-dependent behaviour in + * certain computations which involve secret keys. + * + * It is highly recommended to call this function on contexts returned from + * secp256k1_context_create or secp256k1_context_clone (or from the corresponding + * functions in secp256k1_preallocated.h) before using these contexts to call API + * functions that perform computations involving secret keys, e.g., signing and + * public key generation. It is possible to call this function more than once on + * the same context, and doing so before every few computations involving secret + * keys is recommended as a defense-in-depth measure. Randomization of the static + * context secp256k1_context_static is not supported. + * + * Currently, the random seed is mainly used for blinding multiplications of a + * secret scalar with the elliptic curve base point. Multiplications of this + * kind are performed by exactly those API functions which are documented to + * require a context that is not secp256k1_context_static. As a rule of thumb, + * these are all functions which take a secret key (or a keypair) as an input. + * A notable exception to that rule is the ECDH module, which relies on a different + * kind of elliptic curve point multiplication and thus does not benefit from + * enhanced protection against side-channel leakage currently. */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize( secp256k1_context* ctx, diff --git a/src/secp256k1/include/secp256k1_ecdh.h b/src/secp256k1/include/secp256k1_ecdh.h index c8577984b1..625061b282 100644 --- a/src/secp256k1/include/secp256k1_ecdh.h +++ b/src/secp256k1/include/secp256k1_ecdh.h @@ -27,11 +27,11 @@ typedef int (*secp256k1_ecdh_hash_function)( /** An implementation of SHA256 hash function that applies to compressed public key. * Populates the output parameter with 32 bytes. */ -SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256; +SECP256K1_API_VAR const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256; /** A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256). * Populates the output parameter with 32 bytes. */ -SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default; +SECP256K1_API_VAR const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default; /** Compute an EC Diffie-Hellman secret in constant time * diff --git a/src/secp256k1/include/secp256k1_extrakeys.h b/src/secp256k1/include/secp256k1_extrakeys.h index 09cbeaaa80..3591bc0012 100644 --- a/src/secp256k1/include/secp256k1_extrakeys.h +++ b/src/secp256k1/include/secp256k1_extrakeys.h @@ -108,7 +108,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubke * invalid (only when the tweak is the negation of the corresponding * secret key). 1 otherwise. * - * Args: ctx: pointer to a context object initialized for verification. + * Args: ctx: pointer to a context object. * Out: output_pubkey: pointer to a public key to store the result. Will be set * to an invalid value if this function returns 0. * In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to. @@ -137,7 +137,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add( * * Returns: 0 if the arguments are invalid or the tweaked pubkey is not the * result of tweaking the internal_pubkey with tweak32. 1 otherwise. - * Args: ctx: pointer to a context object initialized for verification. + * Args: ctx: pointer to a context object. * In: tweaked_pubkey32: pointer to a serialized xonly_pubkey. * tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization * is passed in as tweaked_pubkey32). This must match the @@ -159,7 +159,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_ * * Returns: 1: secret was valid, keypair is ready to use * 0: secret was invalid, try again with a different secret - * Args: ctx: pointer to a context object, initialized for signing. + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: keypair: pointer to the created keypair. * In: seckey: pointer to a 32-byte secret key. */ @@ -228,7 +228,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_pub( * invalid (only when the tweak is the negation of the keypair's * secret key). 1 otherwise. * - * Args: ctx: pointer to a context object initialized for verification. + * Args: ctx: pointer to a context object. * In/Out: keypair: pointer to a keypair to apply the tweak to. Will be set to * an invalid value if this function returns 0. * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according diff --git a/src/secp256k1/include/secp256k1_preallocated.h b/src/secp256k1/include/secp256k1_preallocated.h index d2d9014f02..ffa96dd339 100644 --- a/src/secp256k1/include/secp256k1_preallocated.h +++ b/src/secp256k1/include/secp256k1_preallocated.h @@ -58,6 +58,8 @@ SECP256K1_API size_t secp256k1_context_preallocated_size( * bytes, as detailed above. * flags: which parts of the context to initialize. * + * See secp256k1_context_create (in secp256k1.h) for further details. + * * See also secp256k1_context_randomize (in secp256k1.h) * and secp256k1_context_preallocated_destroy. */ @@ -86,8 +88,11 @@ SECP256K1_API size_t secp256k1_context_preallocated_clone_size( * the lifetime of this context object, see the description of * secp256k1_context_preallocated_create for details. * + * Cloning secp256k1_context_static is not possible, and should not be emulated by + * the caller (e.g., using memcpy). Create a new context instead. + * * Returns: a newly created context object. - * Args: ctx: an existing context to copy. + * Args: ctx: an existing context to copy (not secp256k1_context_static). * In: prealloc: a pointer to a rewritable contiguous block of memory of * size at least secp256k1_context_preallocated_size(flags) * bytes, as detailed above. @@ -115,7 +120,8 @@ SECP256K1_API secp256k1_context* secp256k1_context_preallocated_clone( * * Args: ctx: an existing context to destroy, constructed using * secp256k1_context_preallocated_create or - * secp256k1_context_preallocated_clone. + * secp256k1_context_preallocated_clone + * (i.e., not secp256k1_context_static). */ SECP256K1_API void secp256k1_context_preallocated_destroy( secp256k1_context* ctx diff --git a/src/secp256k1/include/secp256k1_recovery.h b/src/secp256k1/include/secp256k1_recovery.h index 0e2847db96..824c604025 100644 --- a/src/secp256k1/include/secp256k1_recovery.h +++ b/src/secp256k1/include/secp256k1_recovery.h @@ -72,7 +72,7 @@ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_serialize_compact( * * Returns: 1: signature created * 0: the nonce generation function failed, or the secret key was invalid. - * Args: ctx: pointer to a context object, initialized for signing. + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: sig: pointer to an array where the signature will be placed. * In: msghash32: the 32-byte message hash being signed. * seckey: pointer to a 32-byte secret key. @@ -94,7 +94,7 @@ SECP256K1_API int secp256k1_ecdsa_sign_recoverable( * * Returns: 1: public key successfully recovered (which guarantees a correct signature). * 0: otherwise. - * Args: ctx: pointer to a context object, initialized for verification. + * Args: ctx: pointer to a context object. * Out: pubkey: pointer to the recovered public key. * In: sig: pointer to initialized signature that supports pubkey recovery. * msghash32: the 32-byte message hash assumed to be signed. diff --git a/src/secp256k1/include/secp256k1_schnorrsig.h b/src/secp256k1/include/secp256k1_schnorrsig.h index 5fedcb07b0..4cd2d98256 100644 --- a/src/secp256k1/include/secp256k1_schnorrsig.h +++ b/src/secp256k1/include/secp256k1_schnorrsig.h @@ -61,7 +61,7 @@ typedef int (*secp256k1_nonce_function_hardened)( * Therefore, to create BIP-340 compliant signatures, algo must be set to * "BIP0340/nonce" and algolen to 13. */ -SECP256K1_API extern const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340; +SECP256K1_API_VAR const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340; /** Data structure that contains additional arguments for schnorrsig_sign_custom. * @@ -106,7 +106,7 @@ typedef struct { * signatures from being valid in multiple contexts by accident. * * Returns 1 on success, 0 on failure. - * Args: ctx: pointer to a context object, initialized for signing. + * Args: ctx: pointer to a context object (not secp256k1_context_static). * Out: sig64: pointer to a 64-byte array to store the serialized signature. * In: msg32: the 32-byte message being signed. * keypair: pointer to an initialized keypair. @@ -161,7 +161,7 @@ SECP256K1_API int secp256k1_schnorrsig_sign_custom( * * Returns: 1: correct signature * 0: incorrect signature - * Args: ctx: a secp256k1 context object, initialized for verification. + * Args: ctx: a secp256k1 context object. * In: sig64: pointer to the 64-byte signature to verify. * msg: the message being verified. Can only be NULL if msglen is 0. * msglen: length of the message diff --git a/src/secp256k1/libsecp256k1.pc.in b/src/secp256k1/libsecp256k1.pc.in index 694e98eef5..0fb6f48a6c 100644 --- a/src/secp256k1/libsecp256k1.pc.in +++ b/src/secp256k1/libsecp256k1.pc.in @@ -9,5 +9,4 @@ URL: https://github.com/bitcoin-core/secp256k1 Version: @PACKAGE_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lsecp256k1 -Libs.private: @SECP_LIBS@ diff --git a/src/secp256k1/sage/gen_exhaustive_groups.sage b/src/secp256k1/sage/gen_exhaustive_groups.sage index 01d15dcdea..070bc1285f 100644 --- a/src/secp256k1/sage/gen_exhaustive_groups.sage +++ b/src/secp256k1/sage/gen_exhaustive_groups.sage @@ -1,124 +1,156 @@ load("secp256k1_params.sage") +MAX_ORDER = 1000 + +# Set of (curve) orders we have encountered so far. orders_done = set() -results = {} -first = True + +# Map from (subgroup) orders to [b, int(gen.x), int(gen.y), gen, lambda] for those subgroups. +solutions = {} + +# Iterate over curves of the form y^2 = x^3 + B. for b in range(1, P): - # There are only 6 curves (up to isomorphism) of the form y^2=x^3+B. Stop once we have tried all. + # There are only 6 curves (up to isomorphism) of the form y^2 = x^3 + B. Stop once we have tried all. if len(orders_done) == 6: break E = EllipticCurve(F, [0, b]) print("Analyzing curve y^2 = x^3 + %i" % b) n = E.order() + # Skip curves with an order we've already tried if n in orders_done: print("- Isomorphic to earlier curve") + print() continue orders_done.add(n) + # Skip curves isomorphic to the real secp256k1 if n.is_pseudoprime(): - print(" - Isomorphic to secp256k1") + assert E.is_isomorphic(C) + print("- Isomorphic to secp256k1") + print() continue - print("- Finding subgroups") - - # Find what prime subgroups exist - for f, _ in n.factor(): - print("- Analyzing subgroup of order %i" % f) - # Skip subgroups of order >1000 - if f < 4 or f > 1000: - print(" - Bad size") - continue - - # Iterate over X coordinates until we find one that is on the curve, has order f, - # and for which curve isomorphism exists that maps it to X coordinate 1. - for x in range(1, P): - # Skip X coordinates not on the curve, and construct the full point otherwise. - if not E.is_x_coord(x): - continue - G = E.lift_x(F(x)) + print("- Finding prime subgroups") - print(" - Analyzing (multiples of) point with X=%i" % x) + # Map from group_order to a set of independent generators for that order. + curve_gens = {} - # Skip points whose order is not a multiple of f. Project the point to have - # order f otherwise. - if (G.order() % f): - print(" - Bad order") + for g in E.gens(): + # Find what prime subgroups of group generated by g exist. + g_order = g.order() + for f, _ in g.order().factor(): + # Skip subgroups that have bad size. + if f < 4: + print(f" - Subgroup of size {f}: too small") + continue + if f > MAX_ORDER: + print(f" - Subgroup of size {f}: too large") continue - G = G * (G.order() // f) + + # Construct a generator for that subgroup. + gen = g * (g_order // f) + assert(gen.order() == f) + + # Add to set the minimal multiple of gen. + curve_gens.setdefault(f, set()).add(min([j*gen for j in range(1, f)])) + print(f" - Subgroup of size {f}: ok") + + for f in sorted(curve_gens.keys()): + print(f"- Constructing group of order {f}") + cbrts = sorted([int(c) for c in Integers(f)(1).nth_root(3, all=true) if c != 1]) + gens = list(curve_gens[f]) + sol_count = 0 + no_endo_count = 0 + + # Consider all non-zero linear combinations of the independent generators. + for j in range(1, f**len(gens)): + gen = sum(gens[k] * ((j // f**k) % f) for k in range(len(gens))) + assert not gen.is_zero() + assert (f*gen).is_zero() # Find lambda for endomorphism. Skip if none can be found. lam = None - for l in Integers(f)(1).nth_root(3, all=True): - if int(l)*G == E(BETA*G[0], G[1]): - lam = int(l) + for l in cbrts: + if l*gen == E(BETA*gen[0], gen[1]): + lam = l break + if lam is None: - print(" - No endomorphism for this subgroup") - break - - # Now look for an isomorphism of the curve that gives this point an X - # coordinate equal to 1. - # If (x,y) is on y^2 = x^3 + b, then (a^2*x, a^3*y) is on y^2 = x^3 + a^6*b. - # So look for m=a^2=1/x. - m = F(1)/G[0] - if not m.is_square(): - print(" - No curve isomorphism maps it to a point with X=1") - continue - a = m.sqrt() - rb = a^6*b - RE = EllipticCurve(F, [0, rb]) - - # Use as generator twice the image of G under the above isormorphism. - # This means that generator*(1/2 mod f) will have X coordinate 1. - RG = RE(1, a^3*G[1]) * 2 - # And even Y coordinate. - if int(RG[1]) % 2: - RG = -RG - assert(RG.order() == f) - assert(lam*RG == RE(BETA*RG[0], RG[1])) - - # We have found curve RE:y^2=x^3+rb with generator RG of order f. Remember it - results[f] = {"b": rb, "G": RG, "lambda": lam} - print(" - Found solution") - break - - print("") - -print("") -print("") -print("/* To be put in src/group_impl.h: */") + no_endo_count += 1 + else: + sol_count += 1 + solutions.setdefault(f, []).append((b, int(gen[0]), int(gen[1]), gen, lam)) + + print(f" - Found {sol_count} generators (plus {no_endo_count} without endomorphism)") + + print() + +def output_generator(g, name): + print(f"#define {name} SECP256K1_GE_CONST(\\") + print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4))) + print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8))) + print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4))) + print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x\\" % tuple((int(g[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8))) + print(")") + +def output_b(b): + print(f"#define SECP256K1_B {int(b)}") + +print() +print("To be put in src/group_impl.h:") +print() +print("/* Begin of section generated by sage/gen_exhaustive_groups.sage. */") +for f in sorted(solutions.keys()): + # Use as generator/2 the one with lowest b, and lowest (x, y) generator (interpreted as non-negative integers). + b, _, _, HALF_G, lam = min(solutions[f]) + output_generator(2 * HALF_G, f"SECP256K1_G_ORDER_{f}") +print("/** Generator for secp256k1, value 'g' defined in") +print(" * \"Standards for Efficient Cryptography\" (SEC2) 2.7.1.") +print(" */") +output_generator(G, "SECP256K1_G") +print("/* These exhaustive group test orders and generators are chosen such that:") +print(" * - The field size is equal to that of secp256k1, so field code is the same.") +print(" * - The curve equation is of the form y^2=x^3+B for some small constant B.") +print(" * - The subgroup has a generator 2*P, where P.x is as small as possible.") +print(f" * - The subgroup has size less than {MAX_ORDER} to permit exhaustive testing.") +print(" * - The subgroup admits an endomorphism of the form lambda*(x,y) == (beta*x,y).") +print(" */") +print("#if defined(EXHAUSTIVE_TEST_ORDER)") first = True -for f in sorted(results.keys()): - b = results[f]["b"] - G = results[f]["G"] - print("# %s EXHAUSTIVE_TEST_ORDER == %i" % ("if" if first else "elif", f)) +for f in sorted(solutions.keys()): + b, _, _, _, lam = min(solutions[f]) + print(f"# {'if' if first else 'elif'} EXHAUSTIVE_TEST_ORDER == {f}") first = False - print("static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST(") - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4))) - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8))) - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(G[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4))) - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(G[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8))) - print(");") - print("static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(") - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x," % tuple((int(b) >> (32 * (7 - i))) & 0xffffffff for i in range(4))) - print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x" % tuple((int(b) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8))) - print(");") + print() + print(f"static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G_ORDER_{f};") + output_b(b) + print() print("# else") print("# error No known generator for the specified exhaustive test group order.") print("# endif") +print("#else") +print() +print("static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G;") +output_b(7) +print() +print("#endif") +print("/* End of section generated by sage/gen_exhaustive_groups.sage. */") + -print("") -print("") -print("/* To be put in src/scalar_impl.h: */") +print() +print() +print("To be put in src/scalar_impl.h:") +print() +print("/* Begin of section generated by sage/gen_exhaustive_groups.sage. */") first = True -for f in sorted(results.keys()): - lam = results[f]["lambda"] +for f in sorted(solutions.keys()): + _, _, _, _, lam = min(solutions[f]) print("# %s EXHAUSTIVE_TEST_ORDER == %i" % ("if" if first else "elif", f)) first = False print("# define EXHAUSTIVE_TEST_LAMBDA %i" % lam) print("# else") print("# error No known lambda for the specified exhaustive test group order.") print("# endif") -print("") +print("/* End of section generated by sage/gen_exhaustive_groups.sage. */") diff --git a/src/secp256k1/sage/prove_group_implementations.sage b/src/secp256k1/sage/prove_group_implementations.sage index 652bd87f11..23799be52b 100644 --- a/src/secp256k1/sage/prove_group_implementations.sage +++ b/src/secp256k1/sage/prove_group_implementations.sage @@ -148,7 +148,7 @@ def formula_secp256k1_gej_add_ge(branch, a, b): zeroes = {} nonzeroes = {} a_infinity = False - if (branch & 4) != 0: + if (branch & 2) != 0: nonzeroes.update({a.Infinity : 'a_infinite'}) a_infinity = True else: @@ -167,15 +167,11 @@ def formula_secp256k1_gej_add_ge(branch, a, b): m_alt = -u2 tt = u1 * m_alt rr = rr + tt - degenerate = (branch & 3) == 3 - if (branch & 1) != 0: + degenerate = (branch & 1) != 0 + if degenerate: zeroes.update({m : 'm_zero'}) else: nonzeroes.update({m : 'm_nonzero'}) - if (branch & 2) != 0: - zeroes.update({rr : 'rr_zero'}) - else: - nonzeroes.update({rr : 'rr_nonzero'}) rr_alt = s1 rr_alt = rr_alt * 2 m_alt = m_alt + u1 @@ -190,13 +186,6 @@ def formula_secp256k1_gej_add_ge(branch, a, b): n = m t = rr_alt^2 rz = a.Z * m_alt - infinity = False - if (branch & 8) != 0: - if not a_infinity: - infinity = True - zeroes.update({rz : 'r.z=0'}) - else: - nonzeroes.update({rz : 'r.z!=0'}) t = t + q rx = t t = t * 2 @@ -209,8 +198,11 @@ def formula_secp256k1_gej_add_ge(branch, a, b): rx = b.X ry = b.Y rz = 1 - if infinity: + if (branch & 4) != 0: + zeroes.update({rz : 'r.z = 0'}) return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), point_at_infinity()) + else: + nonzeroes.update({rz : 'r.z != 0'}) return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), jacobianpoint(rx, ry, rz)) def formula_secp256k1_gej_add_ge_old(branch, a, b): @@ -280,14 +272,14 @@ if __name__ == "__main__": success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var) success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var) success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var) - success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge) + success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 8, formula_secp256k1_gej_add_ge) success = success & (not check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old)) if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive": success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43) success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43) success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43) - success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43) + success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 8, formula_secp256k1_gej_add_ge, 43) success = success & (not check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43)) sys.exit(int(not success)) diff --git a/src/secp256k1/src/CMakeLists.txt b/src/secp256k1/src/CMakeLists.txt new file mode 100644 index 0000000000..26272d0950 --- /dev/null +++ b/src/secp256k1/src/CMakeLists.txt @@ -0,0 +1,151 @@ +# Must be included before CMAKE_INSTALL_INCLUDEDIR is used. +include(GNUInstallDirs) +set(${PROJECT_NAME}_installables "") + +if(SECP256K1_ASM STREQUAL "arm") + add_library(common OBJECT + asm/field_10x26_arm.s + ) + set(common_obj "$") +else() + set(common_obj "") +endif() + +add_library(precomputed OBJECT + precomputed_ecmult.c + precomputed_ecmult_gen.c +) +set(internal_obj "$" "${common_obj}") + +add_library(secp256k1 SHARED EXCLUDE_FROM_ALL + secp256k1.c + ${internal_obj} +) +target_include_directories(secp256k1 INTERFACE + $ +) +target_compile_definitions(secp256k1 PRIVATE + $<$:DLL_EXPORT> +) +set_target_properties(secp256k1 PROPERTIES + VERSION "${${PROJECT_NAME}_LIB_VERSION_CURRENT}.${${PROJECT_NAME}_LIB_VERSION_AGE}.${${PROJECT_NAME}_LIB_VERSION_REVISION}" + SOVERSION "${${PROJECT_NAME}_LIB_VERSION_CURRENT}" +) +if(SECP256K1_BUILD_SHARED) + get_target_property(use_pic secp256k1 POSITION_INDEPENDENT_CODE) + set_target_properties(precomputed PROPERTIES POSITION_INDEPENDENT_CODE ${use_pic}) + set_target_properties(secp256k1 PROPERTIES EXCLUDE_FROM_ALL FALSE) + list(APPEND ${PROJECT_NAME}_installables secp256k1) +endif() + +add_library(secp256k1_static STATIC EXCLUDE_FROM_ALL + secp256k1.c + ${internal_obj} +) +target_include_directories(secp256k1_static INTERFACE + $ +) +if(NOT MSVC) + set_target_properties(secp256k1_static PROPERTIES + OUTPUT_NAME secp256k1 + ) +endif() +if(SECP256K1_BUILD_STATIC) + set_target_properties(secp256k1_static PROPERTIES EXCLUDE_FROM_ALL FALSE) + list(APPEND ${PROJECT_NAME}_installables secp256k1_static) +endif() + +add_library(binary_interface INTERFACE) +target_compile_definitions(binary_interface INTERFACE + $<$:_CRT_SECURE_NO_WARNINGS> +) + +add_library(link_library INTERFACE) +if(SECP256K1_BUILD_SHARED) + target_link_libraries(link_library INTERFACE secp256k1) +elseif(SECP256K1_BUILD_STATIC) + target_link_libraries(link_library INTERFACE secp256k1_static) +endif() + +if(SECP256K1_BUILD_BENCHMARK) + add_executable(bench bench.c) + target_link_libraries(bench binary_interface link_library) + add_executable(bench_internal bench_internal.c ${internal_obj}) + target_link_libraries(bench_internal binary_interface) + add_executable(bench_ecmult bench_ecmult.c ${internal_obj}) + target_link_libraries(bench_ecmult binary_interface) +endif() + +if(SECP256K1_BUILD_TESTS) + add_executable(noverify_tests tests.c ${internal_obj}) + target_link_libraries(noverify_tests binary_interface) + add_test(noverify_tests noverify_tests) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage") + add_executable(tests tests.c ${internal_obj}) + target_compile_definitions(tests PRIVATE VERIFY) + target_link_libraries(tests binary_interface) + add_test(tests tests) + endif() +endif() + +if(SECP256K1_BUILD_EXHAUSTIVE_TESTS) + # Note: do not include $ in exhaustive_tests (it uses runtime-generated tables). + add_executable(exhaustive_tests tests_exhaustive.c ${common_obj}) + target_compile_definitions(exhaustive_tests PRIVATE $<$>:VERIFY>) + target_link_libraries(exhaustive_tests binary_interface) + add_test(exhaustive_tests exhaustive_tests) +endif() + +if(SECP256K1_BUILD_CTIME_TESTS) + add_executable(ctime_tests ctime_tests.c) + target_link_libraries(ctime_tests binary_interface link_library) +endif() + +install(TARGETS ${${PROJECT_NAME}_installables} + EXPORT ${PROJECT_NAME}-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +set(${PROJECT_NAME}_headers + "${PROJECT_SOURCE_DIR}/include/secp256k1.h" + "${PROJECT_SOURCE_DIR}/include/secp256k1_preallocated.h" +) +if(SECP256K1_ENABLE_MODULE_ECDH) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_ecdh.h") +endif() +if(SECP256K1_ENABLE_MODULE_RECOVERY) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_recovery.h") +endif() +if(SECP256K1_ENABLE_MODULE_EXTRAKEYS) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_extrakeys.h") +endif() +if(SECP256K1_ENABLE_MODULE_SCHNORRSIG) + list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig.h") +endif() +install(FILES ${${PROJECT_NAME}_headers} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}-targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cmake/config.cmake.in + ${PROJECT_NAME}-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + NO_SET_AND_CHECK_MACRO +) +write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake + COMPATIBILITY SameMajorVersion +) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) diff --git a/src/secp256k1/src/assumptions.h b/src/secp256k1/src/assumptions.h index 6dc527b288..8ed04209e9 100644 --- a/src/secp256k1/src/assumptions.h +++ b/src/secp256k1/src/assumptions.h @@ -10,6 +10,9 @@ #include #include "util.h" +#if defined(SECP256K1_INT128_NATIVE) +#include "int128_native.h" +#endif /* This library, like most software, relies on a number of compiler implementation defined (but not undefined) behaviours. Although the behaviours we require are essentially universal we test them specifically here to @@ -55,7 +58,7 @@ struct secp256k1_assumption_checker { /* To int64_t. */ ((int64_t)(uint64_t)0xB123C456D789E012ULL == (int64_t)-(int64_t)0x4EDC3BA928761FEEULL) && -#if defined(SECP256K1_WIDEMUL_INT128) +#if defined(SECP256K1_INT128_NATIVE) ((int64_t)(((uint128_t)0xA1234567B8901234ULL << 64) + 0xC5678901D2345678ULL) == (int64_t)-(int64_t)0x3A9876FE2DCBA988ULL) && (((int64_t)(int128_t)(((uint128_t)0xB1C2D3E4F5A6B7C8ULL << 64) + 0xD9E0F1A2B3C4D5E6ULL)) == (int64_t)(uint64_t)0xD9E0F1A2B3C4D5E6ULL) && (((int64_t)(int128_t)(((uint128_t)0xABCDEF0123456789ULL << 64) + 0x0123456789ABCDEFULL)) == (int64_t)(uint64_t)0x0123456789ABCDEFULL) && @@ -71,7 +74,7 @@ struct secp256k1_assumption_checker { ((((int16_t)0xE9AC) >> 4) == (int16_t)(uint16_t)0xFE9A) && ((((int32_t)0x937C918A) >> 9) == (int32_t)(uint32_t)0xFFC9BE48) && ((((int64_t)0xA8B72231DF9CF4B9ULL) >> 19) == (int64_t)(uint64_t)0xFFFFF516E4463BF3ULL) && -#if defined(SECP256K1_WIDEMUL_INT128) +#if defined(SECP256K1_INT128_NATIVE) ((((int128_t)(((uint128_t)0xCD833A65684A0DBCULL << 64) + 0xB349312F71EA7637ULL)) >> 39) == (int128_t)(((uint128_t)0xFFFFFFFFFF9B0674ULL << 64) + 0xCAD0941B79669262ULL)) && #endif 1) * 2 - 1]; diff --git a/src/secp256k1/src/basic-config.h b/src/secp256k1/src/basic-config.h deleted file mode 100644 index 6f7693cb8f..0000000000 --- a/src/secp256k1/src/basic-config.h +++ /dev/null @@ -1,17 +0,0 @@ -/*********************************************************************** - * Copyright (c) 2013, 2014 Pieter Wuille * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or https://www.opensource.org/licenses/mit-license.php.* - ***********************************************************************/ - -#ifndef SECP256K1_BASIC_CONFIG_H -#define SECP256K1_BASIC_CONFIG_H - -#ifdef USE_BASIC_CONFIG - -#define ECMULT_WINDOW_SIZE 15 -#define ECMULT_GEN_PREC_BITS 4 - -#endif /* USE_BASIC_CONFIG */ - -#endif /* SECP256K1_BASIC_CONFIG_H */ diff --git a/src/secp256k1/src/bench.c b/src/secp256k1/src/bench.c index d5937b763f..833f70718b 100644 --- a/src/secp256k1/src/bench.c +++ b/src/secp256k1/src/bench.c @@ -11,7 +11,7 @@ #include "util.h" #include "bench.h" -void help(int default_iters) { +static void help(int default_iters) { printf("Benchmarks the following algorithms:\n"); printf(" - ECDSA signing/verification\n"); @@ -164,7 +164,7 @@ int main(int argc, char** argv) { /* Check if the user tries to benchmark optional module without building it */ #ifndef ENABLE_MODULE_ECDH - if (have_flag(argc, argv, "ecdh")) { + if (have_flag(argc, argv, "ecdh")) { fprintf(stderr, "./bench: ECDH module not enabled.\n"); fprintf(stderr, "Use ./configure --enable-module-ecdh.\n\n"); return 1; @@ -172,7 +172,7 @@ int main(int argc, char** argv) { #endif #ifndef ENABLE_MODULE_RECOVERY - if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) { + if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) { fprintf(stderr, "./bench: Public key recovery module not enabled.\n"); fprintf(stderr, "Use ./configure --enable-module-recovery.\n\n"); return 1; @@ -180,15 +180,15 @@ int main(int argc, char** argv) { #endif #ifndef ENABLE_MODULE_SCHNORRSIG - if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) { + if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) { fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n"); fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n"); return 1; } #endif - /* ECDSA verification benchmark */ - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + /* ECDSA benchmark */ + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); for (i = 0; i < 32; i++) { data.msg[i] = 1 + i; @@ -206,11 +206,6 @@ int main(int argc, char** argv) { print_output_table_header_row(); if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "verify") || have_flag(argc, argv, "ecdsa_verify")) run_benchmark("ecdsa_verify", bench_verify, NULL, NULL, &data, 10, iters); - secp256k1_context_destroy(data.ctx); - - /* ECDSA signing benchmark */ - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "sign") || have_flag(argc, argv, "ecdsa_sign")) run_benchmark("ecdsa_sign", bench_sign_run, bench_sign_setup, NULL, &data, 10, iters); secp256k1_context_destroy(data.ctx); diff --git a/src/secp256k1/src/bench.h b/src/secp256k1/src/bench.h index aa275fe919..bf9a932ff4 100644 --- a/src/secp256k1/src/bench.h +++ b/src/secp256k1/src/bench.h @@ -7,22 +7,38 @@ #ifndef SECP256K1_BENCH_H #define SECP256K1_BENCH_H +#include #include #include #include -#include "sys/time.h" + +#if (defined(_MSC_VER) && _MSC_VER >= 1900) +# include +#else +# include "sys/time.h" +#endif static int64_t gettime_i64(void) { +#if (defined(_MSC_VER) && _MSC_VER >= 1900) + /* C11 way to get wallclock time */ + struct timespec tv; + if (!timespec_get(&tv, TIME_UTC)) { + fputs("timespec_get failed!", stderr); + exit(1); + } + return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; +#else struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; +#endif } #define FP_EXP (6) #define FP_MULT (1000000LL) /* Format fixed point number. */ -void print_number(const int64_t x) { +static void print_number(const int64_t x) { int64_t x_abs, y; int c, i, rounding, g; /* g = integer part size, c = fractional part size */ size_t ptr; @@ -79,7 +95,7 @@ void print_number(const int64_t x) { printf("%-*s", FP_EXP, &buffer[ptr + g]); /* Prints fractional part */ } -void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setup)(void*), void (*teardown)(void*, int), void* data, int count, int iter) { +static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setup)(void*), void (*teardown)(void*, int), void* data, int count, int iter) { int i; int64_t min = INT64_MAX; int64_t sum = 0; @@ -113,7 +129,7 @@ void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setup)(void printf("\n"); } -int have_flag(int argc, char** argv, char *flag) { +static int have_flag(int argc, char** argv, char *flag) { char** argm = argv + argc; argv++; while (argv != argm) { @@ -129,7 +145,7 @@ int have_flag(int argc, char** argv, char *flag) { returns: - 1 if the user entered an invalid argument - 0 if all the user entered arguments are valid */ -int have_invalid_args(int argc, char** argv, char** valid_args, size_t n) { +static int have_invalid_args(int argc, char** argv, char** valid_args, size_t n) { size_t i; int found_valid; char** argm = argv + argc; @@ -151,7 +167,7 @@ int have_invalid_args(int argc, char** argv, char** valid_args, size_t n) { return 0; } -int get_iters(int default_iters) { +static int get_iters(int default_iters) { char* env = getenv("SECP256K1_BENCH_ITERS"); if (env) { return strtol(env, NULL, 0); @@ -160,7 +176,7 @@ int get_iters(int default_iters) { } } -void print_output_table_header_row(void) { +static void print_output_table_header_row(void) { char* bench_str = "Benchmark"; /* left justified */ char* min_str = " Min(us) "; /* center alignment */ char* avg_str = " Avg(us) "; diff --git a/src/secp256k1/src/bench_ecmult.c b/src/secp256k1/src/bench_ecmult.c index 4030e0263f..98fb798d82 100644 --- a/src/secp256k1/src/bench_ecmult.c +++ b/src/secp256k1/src/bench_ecmult.c @@ -18,7 +18,7 @@ #define POINTS 32768 -void help(char **argv) { +static void help(char **argv) { printf("Benchmark EC multiplication algorithms\n"); printf("\n"); printf("Usage: %s \n", argv[0]); @@ -84,9 +84,7 @@ static void bench_ecmult_teardown_helper(bench_data* data, size_t* seckey_offset } } secp256k1_ecmult_gen(&data->ctx->ecmult_gen_ctx, &tmp, &sum_scalars); - secp256k1_gej_neg(&tmp, &tmp); - secp256k1_gej_add_var(&tmp, &tmp, &sum_output, NULL); - CHECK(secp256k1_gej_is_infinity(&tmp)); + CHECK(secp256k1_gej_eq_var(&tmp, &sum_output)); } static void bench_ecmult_setup(void* arg) { @@ -308,7 +306,7 @@ int main(int argc, char **argv) { } } - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16; if (!have_flag(argc, argv, "simple")) { data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size); diff --git a/src/secp256k1/src/bench_internal.c b/src/secp256k1/src/bench_internal.c index 7eb3af28d7..c248ab8ebc 100644 --- a/src/secp256k1/src/bench_internal.c +++ b/src/secp256k1/src/bench_internal.c @@ -27,7 +27,7 @@ typedef struct { int wnaf[256]; } bench_inv; -void bench_setup(void* arg) { +static void bench_setup(void* arg) { bench_inv *data = (bench_inv*)arg; static const unsigned char init[4][32] = { @@ -79,7 +79,7 @@ void bench_setup(void* arg) { memcpy(data->data + 32, init[1], 32); } -void bench_scalar_add(void* arg, int iters) { +static void bench_scalar_add(void* arg, int iters) { int i, j = 0; bench_inv *data = (bench_inv*)arg; @@ -89,7 +89,7 @@ void bench_scalar_add(void* arg, int iters) { CHECK(j <= iters); } -void bench_scalar_negate(void* arg, int iters) { +static void bench_scalar_negate(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -98,7 +98,7 @@ void bench_scalar_negate(void* arg, int iters) { } } -void bench_scalar_mul(void* arg, int iters) { +static void bench_scalar_mul(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -107,18 +107,19 @@ void bench_scalar_mul(void* arg, int iters) { } } -void bench_scalar_split(void* arg, int iters) { +static void bench_scalar_split(void* arg, int iters) { int i, j = 0; bench_inv *data = (bench_inv*)arg; + secp256k1_scalar tmp; for (i = 0; i < iters; i++) { - secp256k1_scalar_split_lambda(&data->scalar[0], &data->scalar[1], &data->scalar[0]); - j += secp256k1_scalar_add(&data->scalar[0], &data->scalar[0], &data->scalar[1]); + secp256k1_scalar_split_lambda(&tmp, &data->scalar[1], &data->scalar[0]); + j += secp256k1_scalar_add(&data->scalar[0], &tmp, &data->scalar[1]); } CHECK(j <= iters); } -void bench_scalar_inverse(void* arg, int iters) { +static void bench_scalar_inverse(void* arg, int iters) { int i, j = 0; bench_inv *data = (bench_inv*)arg; @@ -129,7 +130,7 @@ void bench_scalar_inverse(void* arg, int iters) { CHECK(j <= iters); } -void bench_scalar_inverse_var(void* arg, int iters) { +static void bench_scalar_inverse_var(void* arg, int iters) { int i, j = 0; bench_inv *data = (bench_inv*)arg; @@ -140,7 +141,7 @@ void bench_scalar_inverse_var(void* arg, int iters) { CHECK(j <= iters); } -void bench_field_half(void* arg, int iters) { +static void bench_field_half(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -149,7 +150,7 @@ void bench_field_half(void* arg, int iters) { } } -void bench_field_normalize(void* arg, int iters) { +static void bench_field_normalize(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -158,7 +159,7 @@ void bench_field_normalize(void* arg, int iters) { } } -void bench_field_normalize_weak(void* arg, int iters) { +static void bench_field_normalize_weak(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -167,7 +168,7 @@ void bench_field_normalize_weak(void* arg, int iters) { } } -void bench_field_mul(void* arg, int iters) { +static void bench_field_mul(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -176,7 +177,7 @@ void bench_field_mul(void* arg, int iters) { } } -void bench_field_sqr(void* arg, int iters) { +static void bench_field_sqr(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -185,7 +186,7 @@ void bench_field_sqr(void* arg, int iters) { } } -void bench_field_inverse(void* arg, int iters) { +static void bench_field_inverse(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -195,7 +196,7 @@ void bench_field_inverse(void* arg, int iters) { } } -void bench_field_inverse_var(void* arg, int iters) { +static void bench_field_inverse_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -205,7 +206,7 @@ void bench_field_inverse_var(void* arg, int iters) { } } -void bench_field_sqrt(void* arg, int iters) { +static void bench_field_sqrt(void* arg, int iters) { int i, j = 0; bench_inv *data = (bench_inv*)arg; secp256k1_fe t; @@ -218,7 +219,20 @@ void bench_field_sqrt(void* arg, int iters) { CHECK(j <= iters); } -void bench_group_double_var(void* arg, int iters) { +static void bench_field_is_square_var(void* arg, int iters) { + int i, j = 0; + bench_inv *data = (bench_inv*)arg; + secp256k1_fe t = data->fe[0]; + + for (i = 0; i < iters; i++) { + j += secp256k1_fe_is_square_var(&t); + secp256k1_fe_add(&t, &data->fe[1]); + secp256k1_fe_normalize_var(&t); + } + CHECK(j <= iters); +} + +static void bench_group_double_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -227,7 +241,7 @@ void bench_group_double_var(void* arg, int iters) { } } -void bench_group_add_var(void* arg, int iters) { +static void bench_group_add_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -236,7 +250,7 @@ void bench_group_add_var(void* arg, int iters) { } } -void bench_group_add_affine(void* arg, int iters) { +static void bench_group_add_affine(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -245,7 +259,7 @@ void bench_group_add_affine(void* arg, int iters) { } } -void bench_group_add_affine_var(void* arg, int iters) { +static void bench_group_add_affine_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -254,7 +268,7 @@ void bench_group_add_affine_var(void* arg, int iters) { } } -void bench_group_add_zinv_var(void* arg, int iters) { +static void bench_group_add_zinv_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -263,7 +277,7 @@ void bench_group_add_zinv_var(void* arg, int iters) { } } -void bench_group_to_affine_var(void* arg, int iters) { +static void bench_group_to_affine_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -283,7 +297,7 @@ void bench_group_to_affine_var(void* arg, int iters) { } } -void bench_ecmult_wnaf(void* arg, int iters) { +static void bench_ecmult_wnaf(void* arg, int iters) { int i, bits = 0, overflow = 0; bench_inv *data = (bench_inv*)arg; @@ -295,7 +309,7 @@ void bench_ecmult_wnaf(void* arg, int iters) { CHECK(bits <= 256*iters); } -void bench_wnaf_const(void* arg, int iters) { +static void bench_wnaf_const(void* arg, int iters) { int i, bits = 0, overflow = 0; bench_inv *data = (bench_inv*)arg; @@ -307,8 +321,7 @@ void bench_wnaf_const(void* arg, int iters) { CHECK(bits <= 256*iters); } - -void bench_sha256(void* arg, int iters) { +static void bench_sha256(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; secp256k1_sha256 sha; @@ -320,7 +333,7 @@ void bench_sha256(void* arg, int iters) { } } -void bench_hmac_sha256(void* arg, int iters) { +static void bench_hmac_sha256(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; secp256k1_hmac_sha256 hmac; @@ -332,7 +345,7 @@ void bench_hmac_sha256(void* arg, int iters) { } } -void bench_rfc6979_hmac_sha256(void* arg, int iters) { +static void bench_rfc6979_hmac_sha256(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; secp256k1_rfc6979_hmac_sha256 rng; @@ -343,19 +356,11 @@ void bench_rfc6979_hmac_sha256(void* arg, int iters) { } } -void bench_context_verify(void* arg, int iters) { - int i; - (void)arg; - for (i = 0; i < iters; i++) { - secp256k1_context_destroy(secp256k1_context_create(SECP256K1_CONTEXT_VERIFY)); - } -} - -void bench_context_sign(void* arg, int iters) { +static void bench_context(void* arg, int iters) { int i; (void)arg; for (i = 0; i < iters; i++) { - secp256k1_context_destroy(secp256k1_context_create(SECP256K1_CONTEXT_SIGN)); + secp256k1_context_destroy(secp256k1_context_create(SECP256K1_CONTEXT_NONE)); } } @@ -379,6 +384,7 @@ int main(int argc, char **argv) { if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "mul")) run_benchmark("field_mul", bench_field_mul, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse", bench_field_inverse, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse_var", bench_field_inverse_var, bench_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "issquare")) run_benchmark("field_is_square_var", bench_field_is_square_var, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqrt")) run_benchmark("field_sqrt", bench_field_sqrt, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_double_var", bench_group_double_var, bench_setup, NULL, &data, 10, iters*10); @@ -395,8 +401,7 @@ int main(int argc, char **argv) { if (d || have_flag(argc, argv, "hash") || have_flag(argc, argv, "hmac")) run_benchmark("hash_hmac_sha256", bench_hmac_sha256, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "hash") || have_flag(argc, argv, "rng6979")) run_benchmark("hash_rfc6979_hmac_sha256", bench_rfc6979_hmac_sha256, bench_setup, NULL, &data, 10, iters); - if (d || have_flag(argc, argv, "context") || have_flag(argc, argv, "verify")) run_benchmark("context_verify", bench_context_verify, bench_setup, NULL, &data, 10, 1 + iters/1000); - if (d || have_flag(argc, argv, "context") || have_flag(argc, argv, "sign")) run_benchmark("context_sign", bench_context_sign, bench_setup, NULL, &data, 10, 1 + iters/100); + if (d || have_flag(argc, argv, "context")) run_benchmark("context_create", bench_context, bench_setup, NULL, &data, 10, iters); return 0; } diff --git a/src/secp256k1/src/checkmem.h b/src/secp256k1/src/checkmem.h new file mode 100644 index 0000000000..571e4cc389 --- /dev/null +++ b/src/secp256k1/src/checkmem.h @@ -0,0 +1,88 @@ +/*********************************************************************** + * Copyright (c) 2022 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +/* The code here is inspired by Kris Kwiatkowski's approach in + * https://github.com/kriskwiatkowski/pqc/blob/main/src/common/ct_check.h + * to provide a general interface for memory-checking mechanisms, primarily + * for constant-time checking. + */ + +/* These macros are defined by this header file: + * + * - SECP256K1_CHECKMEM_ENABLED: + * - 1 if memory-checking integration is available, 0 otherwise. + * This is just a compile-time macro. Use the next macro to check it is actually + * available at runtime. + * - SECP256K1_CHECKMEM_RUNNING(): + * - Acts like a function call, returning 1 if memory checking is available + * at runtime. + * - SECP256K1_CHECKMEM_CHECK(p, len): + * - Assert or otherwise fail in case the len-byte memory block pointed to by p is + * not considered entirely defined. + * - SECP256K1_CHECKMEM_CHECK_VERIFY(p, len): + * - Like SECP256K1_CHECKMEM_CHECK, but only works in VERIFY mode. + * - SECP256K1_CHECKMEM_UNDEFINE(p, len): + * - marks the len-byte memory block pointed to by p as undefined data (secret data, + * in the context of constant-time checking). + * - SECP256K1_CHECKMEM_DEFINE(p, len): + * - marks the len-byte memory pointed to by p as defined data (public data, in the + * context of constant-time checking). + * + */ + +#ifndef SECP256K1_CHECKMEM_H +#define SECP256K1_CHECKMEM_H + +/* Define a statement-like macro that ignores the arguments. */ +#define SECP256K1_CHECKMEM_NOOP(p, len) do { (void)(p); (void)(len); } while(0) + +/* If compiling under msan, map the SECP256K1_CHECKMEM_* functionality to msan. + * Choose this preferentially, even when VALGRIND is defined, as msan-compiled + * binaries can't be run under valgrind anyway. */ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# include +# define SECP256K1_CHECKMEM_ENABLED 1 +# define SECP256K1_CHECKMEM_UNDEFINE(p, len) __msan_allocated_memory((p), (len)) +# define SECP256K1_CHECKMEM_DEFINE(p, len) __msan_unpoison((p), (len)) +# define SECP256K1_CHECKMEM_CHECK(p, len) __msan_check_mem_is_initialized((p), (len)) +# define SECP256K1_CHECKMEM_RUNNING() (1) +# endif +#endif + +/* If valgrind integration is desired (through the VALGRIND define), implement the + * SECP256K1_CHECKMEM_* macros using valgrind. */ +#if !defined SECP256K1_CHECKMEM_ENABLED +# if defined VALGRIND +# include +# include +# define SECP256K1_CHECKMEM_ENABLED 1 +# define SECP256K1_CHECKMEM_UNDEFINE(p, len) VALGRIND_MAKE_MEM_UNDEFINED((p), (len)) +# define SECP256K1_CHECKMEM_DEFINE(p, len) VALGRIND_MAKE_MEM_DEFINED((p), (len)) +# define SECP256K1_CHECKMEM_CHECK(p, len) VALGRIND_CHECK_MEM_IS_DEFINED((p), (len)) + /* VALGRIND_MAKE_MEM_DEFINED returns 0 iff not running on memcheck. + * This is more precise than the RUNNING_ON_VALGRIND macro, which + * checks for valgrind in general instead of memcheck specifically. */ +# define SECP256K1_CHECKMEM_RUNNING() (VALGRIND_MAKE_MEM_DEFINED(NULL, 0) != 0) +# endif +#endif + +/* As a fall-back, map these macros to dummy statements. */ +#if !defined SECP256K1_CHECKMEM_ENABLED +# define SECP256K1_CHECKMEM_ENABLED 0 +# define SECP256K1_CHECKMEM_UNDEFINE(p, len) SECP256K1_CHECKMEM_NOOP((p), (len)) +# define SECP256K1_CHECKMEM_DEFINE(p, len) SECP256K1_CHECKMEM_NOOP((p), (len)) +# define SECP256K1_CHECKMEM_CHECK(p, len) SECP256K1_CHECKMEM_NOOP((p), (len)) +# define SECP256K1_CHECKMEM_RUNNING() (0) +#endif + +#if defined VERIFY +#define SECP256K1_CHECKMEM_CHECK_VERIFY(p, len) SECP256K1_CHECKMEM_CHECK((p), (len)) +#else +#define SECP256K1_CHECKMEM_CHECK_VERIFY(p, len) SECP256K1_CHECKMEM_NOOP((p), (len)) +#endif + +#endif /* SECP256K1_CHECKMEM_H */ diff --git a/src/secp256k1/src/valgrind_ctime_test.c b/src/secp256k1/src/ctime_tests.c similarity index 64% rename from src/secp256k1/src/valgrind_ctime_test.c rename to src/secp256k1/src/ctime_tests.c index 6ff0085d34..713eb427d3 100644 --- a/src/secp256k1/src/valgrind_ctime_test.c +++ b/src/secp256k1/src/ctime_tests.c @@ -4,12 +4,15 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ -#include #include #include "../include/secp256k1.h" #include "assumptions.h" -#include "util.h" +#include "checkmem.h" + +#if !SECP256K1_CHECKMEM_ENABLED +# error "This tool cannot be compiled without memory-checking interface (valgrind or msan)" +#endif #ifdef ENABLE_MODULE_ECDH # include "../include/secp256k1_ecdh.h" @@ -27,21 +30,19 @@ #include "../include/secp256k1_schnorrsig.h" #endif -void run_tests(secp256k1_context *ctx, unsigned char *key); +static void run_tests(secp256k1_context *ctx, unsigned char *key); int main(void) { secp256k1_context* ctx; unsigned char key[32]; int ret, i; - if (!RUNNING_ON_VALGRIND) { - fprintf(stderr, "This test can only usefully be run inside valgrind.\n"); - fprintf(stderr, "Usage: libtool --mode=execute valgrind ./valgrind_ctime_test\n"); + if (!SECP256K1_CHECKMEM_RUNNING()) { + fprintf(stderr, "This test can only usefully be run inside valgrind because it was not compiled under msan.\n"); + fprintf(stderr, "Usage: libtool --mode=execute valgrind ./ctime_tests\n"); return 1; } - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN - | SECP256K1_CONTEXT_VERIFY - | SECP256K1_CONTEXT_DECLASSIFY); + ctx = secp256k1_context_create(SECP256K1_CONTEXT_DECLASSIFY); /** In theory, testing with a single secret input should be sufficient: * If control flow depended on secrets the tool would generate an error. */ @@ -53,16 +54,16 @@ int main(void) { /* Test context randomisation. Do this last because it leaves the context * tainted. */ - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_context_randomize(ctx, key); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret); secp256k1_context_destroy(ctx); return 0; } -void run_tests(secp256k1_context *ctx, unsigned char *key) { +static void run_tests(secp256k1_context *ctx, unsigned char *key) { secp256k1_ecdsa_signature signature; secp256k1_pubkey pubkey; size_t siglen = 74; @@ -85,89 +86,89 @@ void run_tests(secp256k1_context *ctx, unsigned char *key) { } /* Test keygen. */ - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ec_pubkey_create(ctx, &pubkey, key); - VALGRIND_MAKE_MEM_DEFINED(&pubkey, sizeof(secp256k1_pubkey)); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret); CHECK(secp256k1_ec_pubkey_serialize(ctx, spubkey, &outputlen, &pubkey, SECP256K1_EC_COMPRESSED) == 1); /* Test signing. */ - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ecdsa_sign(ctx, &signature, msg, key, NULL, NULL); - VALGRIND_MAKE_MEM_DEFINED(&signature, sizeof(secp256k1_ecdsa_signature)); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&signature, sizeof(secp256k1_ecdsa_signature)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret); CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature)); #ifdef ENABLE_MODULE_ECDH /* Test ECDH. */ - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ecdh(ctx, msg, &pubkey, key, NULL, NULL); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); #endif #ifdef ENABLE_MODULE_RECOVERY /* Test signing a recoverable signature. */ - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_signature, msg, key, NULL, NULL); - VALGRIND_MAKE_MEM_DEFINED(&recoverable_signature, sizeof(recoverable_signature)); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&recoverable_signature, sizeof(recoverable_signature)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret); CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &recoverable_signature)); CHECK(recid >= 0 && recid <= 3); #endif - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ec_seckey_verify(ctx, key); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_ec_seckey_negate(ctx, key); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); - VALGRIND_MAKE_MEM_UNDEFINED(msg, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(msg, 32); ret = secp256k1_ec_seckey_tweak_add(ctx, key, msg); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); - VALGRIND_MAKE_MEM_UNDEFINED(msg, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(msg, 32); ret = secp256k1_ec_seckey_tweak_mul(ctx, key, msg); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); /* Test keypair_create and keypair_xonly_tweak_add. */ #ifdef ENABLE_MODULE_EXTRAKEYS - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_keypair_create(ctx, &keypair, key); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); /* The tweak is not treated as a secret in keypair_tweak_add */ - VALGRIND_MAKE_MEM_DEFINED(msg, 32); + SECP256K1_CHECKMEM_DEFINE(msg, 32); ret = secp256k1_keypair_xonly_tweak_add(ctx, &keypair, msg); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); - VALGRIND_MAKE_MEM_UNDEFINED(&keypair, sizeof(keypair)); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(&keypair, sizeof(keypair)); ret = secp256k1_keypair_sec(ctx, key, &keypair); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); #endif #ifdef ENABLE_MODULE_SCHNORRSIG - VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + SECP256K1_CHECKMEM_UNDEFINE(key, 32); ret = secp256k1_keypair_create(ctx, &keypair, key); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); ret = secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL); - VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); #endif } diff --git a/src/secp256k1/src/ecmult.h b/src/secp256k1/src/ecmult.h index b47d8f494a..e28c602506 100644 --- a/src/secp256k1/src/ecmult.h +++ b/src/secp256k1/src/ecmult.h @@ -11,6 +11,17 @@ #include "scalar.h" #include "scratch.h" +#ifndef ECMULT_WINDOW_SIZE +# define ECMULT_WINDOW_SIZE 15 +# ifdef DEBUG_CONFIG +# pragma message DEBUG_CONFIG_MSG("ECMULT_WINDOW_SIZE undefined, assuming default value") +# endif +#endif + +#ifdef DEBUG_CONFIG +# pragma message DEBUG_CONFIG_DEF(ECMULT_WINDOW_SIZE) +#endif + /* Noone will ever need more than a window size of 24. The code might * be correct for larger values of ECMULT_WINDOW_SIZE but this is not * tested. diff --git a/src/secp256k1/src/ecmult_gen.h b/src/secp256k1/src/ecmult_gen.h index f48f266461..a430e8d5d9 100644 --- a/src/secp256k1/src/ecmult_gen.h +++ b/src/secp256k1/src/ecmult_gen.h @@ -10,9 +10,21 @@ #include "scalar.h" #include "group.h" +#ifndef ECMULT_GEN_PREC_BITS +# define ECMULT_GEN_PREC_BITS 4 +# ifdef DEBUG_CONFIG +# pragma message DEBUG_CONFIG_MSG("ECMULT_GEN_PREC_BITS undefined, assuming default value") +# endif +#endif + +#ifdef DEBUG_CONFIG +# pragma message DEBUG_CONFIG_DEF(ECMULT_GEN_PREC_BITS) +#endif + #if ECMULT_GEN_PREC_BITS != 2 && ECMULT_GEN_PREC_BITS != 4 && ECMULT_GEN_PREC_BITS != 8 # error "Set ECMULT_GEN_PREC_BITS to 2, 4 or 8." #endif + #define ECMULT_GEN_PREC_G(bits) (1 << bits) #define ECMULT_GEN_PREC_N(bits) (256 / bits) diff --git a/src/secp256k1/src/ecmult_gen_impl.h b/src/secp256k1/src/ecmult_gen_impl.h index 2c8a503acc..4f5ea9f3c0 100644 --- a/src/secp256k1/src/ecmult_gen_impl.h +++ b/src/secp256k1/src/ecmult_gen_impl.h @@ -88,31 +88,31 @@ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const unsigned char nonce32[32]; secp256k1_rfc6979_hmac_sha256 rng; int overflow; - unsigned char keydata[64] = {0}; + unsigned char keydata[64]; if (seed32 == NULL) { /* When seed is NULL, reset the initial point and blinding value. */ secp256k1_gej_set_ge(&ctx->initial, &secp256k1_ge_const_g); secp256k1_gej_neg(&ctx->initial, &ctx->initial); secp256k1_scalar_set_int(&ctx->blind, 1); + return; } /* The prior blinding value (if not reset) is chained forward by including it in the hash. */ - secp256k1_scalar_get_b32(nonce32, &ctx->blind); + secp256k1_scalar_get_b32(keydata, &ctx->blind); /** Using a CSPRNG allows a failure free interface, avoids needing large amounts of random data, * and guards against weak or adversarial seeds. This is a simpler and safer interface than * asking the caller for blinding values directly and expecting them to retry on failure. */ - memcpy(keydata, nonce32, 32); - if (seed32 != NULL) { - memcpy(keydata + 32, seed32, 32); - } - secp256k1_rfc6979_hmac_sha256_initialize(&rng, keydata, seed32 ? 64 : 32); + VERIFY_CHECK(seed32 != NULL); + memcpy(keydata + 32, seed32, 32); + secp256k1_rfc6979_hmac_sha256_initialize(&rng, keydata, 64); memset(keydata, 0, sizeof(keydata)); /* Accept unobservably small non-uniformity. */ secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); overflow = !secp256k1_fe_set_b32(&s, nonce32); overflow |= secp256k1_fe_is_zero(&s); secp256k1_fe_cmov(&s, &secp256k1_fe_one, overflow); - /* Randomize the projection to defend against multiplier sidechannels. */ + /* Randomize the projection to defend against multiplier sidechannels. + Do this before our own call to secp256k1_ecmult_gen below. */ secp256k1_gej_rescale(&ctx->initial, &s); secp256k1_fe_clear(&s); secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); @@ -121,6 +121,7 @@ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_scalar_cmov(&b, &secp256k1_scalar_one, secp256k1_scalar_is_zero(&b)); secp256k1_rfc6979_hmac_sha256_finalize(&rng); memset(nonce32, 0, 32); + /* The random projection in ctx->initial ensures that gb will have a random projection. */ secp256k1_ecmult_gen(ctx, &gb, &b); secp256k1_scalar_negate(&b, &b); ctx->blind = b; diff --git a/src/secp256k1/src/ecmult_impl.h b/src/secp256k1/src/ecmult_impl.h index bbc820c77c..3776fe73fc 100644 --- a/src/secp256k1/src/ecmult_impl.h +++ b/src/secp256k1/src/ecmult_impl.h @@ -200,9 +200,15 @@ static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a, bit += now; } #ifdef VERIFY - CHECK(carry == 0); - while (bit < 256) { - CHECK(secp256k1_scalar_get_bits(&s, bit++, 1) == 0); + { + int verify_bit = bit; + + VERIFY_CHECK(carry == 0); + + while (verify_bit < 256) { + VERIFY_CHECK(secp256k1_scalar_get_bits(&s, verify_bit, 1) == 0); + verify_bit++; + } } #endif return last_set_bit + 1; diff --git a/src/secp256k1/src/field.h b/src/secp256k1/src/field.h index 2584a494ee..64ceead4d2 100644 --- a/src/secp256k1/src/field.h +++ b/src/secp256k1/src/field.h @@ -18,10 +18,6 @@ * imply normality. */ -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include "util.h" #if defined(SECP256K1_WIDEMUL_INT128) @@ -89,6 +85,9 @@ static void secp256k1_fe_get_b32(unsigned char *r, const secp256k1_fe *a); * as an argument. The magnitude of the output is one higher. */ static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k1_fe *a, int m); +/** Adds a small integer (up to 0x7FFF) to r. The resulting magnitude increases by one. */ +static void secp256k1_fe_add_int(secp256k1_fe *r, int a); + /** Multiplies the passed field element with a small integer constant. Multiplies the magnitude by that * small integer. */ static void secp256k1_fe_mul_int(secp256k1_fe *r, int a); @@ -139,4 +138,7 @@ static void secp256k1_fe_half(secp256k1_fe *r); * magnitude set to 'm' and is normalized if (and only if) 'm' is zero. */ static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m); +/** Determine whether a is a square (modulo p). */ +static int secp256k1_fe_is_square_var(const secp256k1_fe *a); + #endif /* SECP256K1_FIELD_H */ diff --git a/src/secp256k1/src/field_10x26_impl.h b/src/secp256k1/src/field_10x26_impl.h index 21742bf6eb..46b72ce78d 100644 --- a/src/secp256k1/src/field_10x26_impl.h +++ b/src/secp256k1/src/field_10x26_impl.h @@ -7,6 +7,7 @@ #ifndef SECP256K1_FIELD_REPR_IMPL_H #define SECP256K1_FIELD_REPR_IMPL_H +#include "checkmem.h" #include "util.h" #include "field.h" #include "modinv32_impl.h" @@ -481,6 +482,20 @@ SECP256K1_INLINE static void secp256k1_fe_add(secp256k1_fe *r, const secp256k1_f #endif } +SECP256K1_INLINE static void secp256k1_fe_add_int(secp256k1_fe *r, int a) { +#ifdef VERIFY + secp256k1_fe_verify(r); + VERIFY_CHECK(a >= 0); + VERIFY_CHECK(a <= 0x7FFF); +#endif + r->n[0] += a; +#ifdef VERIFY + r->magnitude += 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + #if defined(USE_EXTERNAL_ASM) /* External assembler implementation */ @@ -1132,7 +1147,7 @@ static void secp256k1_fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) { static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { uint32_t mask0, mask1; - VG_CHECK_VERIFY(r->n, sizeof(r->n)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = flag + ~((uint32_t)0); mask1 = ~mask0; r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); @@ -1231,7 +1246,7 @@ static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) { static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint32_t mask0, mask1; - VG_CHECK_VERIFY(r->n, sizeof(r->n)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = flag + ~((uint32_t)0); mask1 = ~mask0; r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); @@ -1364,4 +1379,31 @@ static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *x) { VERIFY_CHECK(secp256k1_fe_normalizes_to_zero(r) == secp256k1_fe_normalizes_to_zero(&tmp)); } +static int secp256k1_fe_is_square_var(const secp256k1_fe *x) { + secp256k1_fe tmp; + secp256k1_modinv32_signed30 s; + int jac, ret; + + tmp = *x; + secp256k1_fe_normalize_var(&tmp); + /* secp256k1_jacobi32_maybe_var cannot deal with input 0. */ + if (secp256k1_fe_is_zero(&tmp)) return 1; + secp256k1_fe_to_signed30(&s, &tmp); + jac = secp256k1_jacobi32_maybe_var(&s, &secp256k1_const_modinfo_fe); + if (jac == 0) { + /* secp256k1_jacobi32_maybe_var failed to compute the Jacobi symbol. Fall back + * to computing a square root. This should be extremely rare with random + * input (except in VERIFY mode, where a lower iteration count is used). */ + secp256k1_fe dummy; + ret = secp256k1_fe_sqrt(&dummy, &tmp); + } else { +#ifdef VERIFY + secp256k1_fe dummy; + VERIFY_CHECK(jac == 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1); +#endif + ret = jac >= 0; + } + return ret; +} + #endif /* SECP256K1_FIELD_REPR_IMPL_H */ diff --git a/src/secp256k1/src/field_5x52_impl.h b/src/secp256k1/src/field_5x52_impl.h index 6bd202f587..4c4466eceb 100644 --- a/src/secp256k1/src/field_5x52_impl.h +++ b/src/secp256k1/src/field_5x52_impl.h @@ -7,10 +7,7 @@ #ifndef SECP256K1_FIELD_REPR_IMPL_H #define SECP256K1_FIELD_REPR_IMPL_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - +#include "checkmem.h" #include "util.h" #include "field.h" #include "modinv64_impl.h" @@ -428,6 +425,20 @@ SECP256K1_INLINE static void secp256k1_fe_mul_int(secp256k1_fe *r, int a) { #endif } +SECP256K1_INLINE static void secp256k1_fe_add_int(secp256k1_fe *r, int a) { +#ifdef VERIFY + secp256k1_fe_verify(r); + VERIFY_CHECK(a >= 0); + VERIFY_CHECK(a <= 0x7FFF); +#endif + r->n[0] += a; +#ifdef VERIFY + r->magnitude += 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + SECP256K1_INLINE static void secp256k1_fe_add(secp256k1_fe *r, const secp256k1_fe *a) { #ifdef VERIFY secp256k1_fe_verify(a); @@ -476,7 +487,7 @@ static void secp256k1_fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) { static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { uint64_t mask0, mask1; - VG_CHECK_VERIFY(r->n, sizeof(r->n)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = flag + ~((uint64_t)0); mask1 = ~mask0; r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); @@ -559,7 +570,7 @@ static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) { static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint64_t mask0, mask1; - VG_CHECK_VERIFY(r->n, sizeof(r->n)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->n, sizeof(r->n)); mask0 = flag + ~((uint64_t)0); mask1 = ~mask0; r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); @@ -667,4 +678,31 @@ static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *x) { #endif } +static int secp256k1_fe_is_square_var(const secp256k1_fe *x) { + secp256k1_fe tmp; + secp256k1_modinv64_signed62 s; + int jac, ret; + + tmp = *x; + secp256k1_fe_normalize_var(&tmp); + /* secp256k1_jacobi64_maybe_var cannot deal with input 0. */ + if (secp256k1_fe_is_zero(&tmp)) return 1; + secp256k1_fe_to_signed62(&s, &tmp); + jac = secp256k1_jacobi64_maybe_var(&s, &secp256k1_const_modinfo_fe); + if (jac == 0) { + /* secp256k1_jacobi64_maybe_var failed to compute the Jacobi symbol. Fall back + * to computing a square root. This should be extremely rare with random + * input (except in VERIFY mode, where a lower iteration count is used). */ + secp256k1_fe dummy; + ret = secp256k1_fe_sqrt(&dummy, &tmp); + } else { +#ifdef VERIFY + secp256k1_fe dummy; + VERIFY_CHECK(jac == 2*secp256k1_fe_sqrt(&dummy, &tmp) - 1); +#endif + ret = jac >= 0; + } + return ret; +} + #endif /* SECP256K1_FIELD_REPR_IMPL_H */ diff --git a/src/secp256k1/src/field_5x52_int128_impl.h b/src/secp256k1/src/field_5x52_int128_impl.h index 0ed6118cc9..18567b95f3 100644 --- a/src/secp256k1/src/field_5x52_int128_impl.h +++ b/src/secp256k1/src/field_5x52_int128_impl.h @@ -9,14 +9,18 @@ #include +#include "int128.h" + #ifdef VERIFY #define VERIFY_BITS(x, n) VERIFY_CHECK(((x) >> (n)) == 0) +#define VERIFY_BITS_128(x, n) VERIFY_CHECK(secp256k1_u128_check_bits((x), (n))) #else #define VERIFY_BITS(x, n) do { } while(0) +#define VERIFY_BITS_128(x, n) do { } while(0) #endif SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) { - uint128_t c, d; + secp256k1_uint128 c, d; uint64_t t3, t4, tx, u0; uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4]; const uint64_t M = 0xFFFFFFFFFFFFFULL, R = 0x1000003D10ULL; @@ -40,121 +44,119 @@ SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t * Note that [x 0 0 0 0 0] = [x*R]. */ - d = (uint128_t)a0 * b[3] - + (uint128_t)a1 * b[2] - + (uint128_t)a2 * b[1] - + (uint128_t)a3 * b[0]; - VERIFY_BITS(d, 114); + secp256k1_u128_mul(&d, a0, b[3]); + secp256k1_u128_accum_mul(&d, a1, b[2]); + secp256k1_u128_accum_mul(&d, a2, b[1]); + secp256k1_u128_accum_mul(&d, a3, b[0]); + VERIFY_BITS_128(&d, 114); /* [d 0 0 0] = [p3 0 0 0] */ - c = (uint128_t)a4 * b[4]; - VERIFY_BITS(c, 112); + secp256k1_u128_mul(&c, a4, b[4]); + VERIFY_BITS_128(&c, 112); /* [c 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ - d += (uint128_t)R * (uint64_t)c; c >>= 64; - VERIFY_BITS(d, 115); - VERIFY_BITS(c, 48); + secp256k1_u128_accum_mul(&d, R, secp256k1_u128_to_u64(&c)); secp256k1_u128_rshift(&c, 64); + VERIFY_BITS_128(&d, 115); + VERIFY_BITS_128(&c, 48); /* [(c<<12) 0 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ - t3 = d & M; d >>= 52; + t3 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(t3, 52); - VERIFY_BITS(d, 63); + VERIFY_BITS_128(&d, 63); /* [(c<<12) 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ - d += (uint128_t)a0 * b[4] - + (uint128_t)a1 * b[3] - + (uint128_t)a2 * b[2] - + (uint128_t)a3 * b[1] - + (uint128_t)a4 * b[0]; - VERIFY_BITS(d, 115); + secp256k1_u128_accum_mul(&d, a0, b[4]); + secp256k1_u128_accum_mul(&d, a1, b[3]); + secp256k1_u128_accum_mul(&d, a2, b[2]); + secp256k1_u128_accum_mul(&d, a3, b[1]); + secp256k1_u128_accum_mul(&d, a4, b[0]); + VERIFY_BITS_128(&d, 115); /* [(c<<12) 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - d += (uint128_t)(R << 12) * (uint64_t)c; - VERIFY_BITS(d, 116); + secp256k1_u128_accum_mul(&d, R << 12, secp256k1_u128_to_u64(&c)); + VERIFY_BITS_128(&d, 116); /* [d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - t4 = d & M; d >>= 52; + t4 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(t4, 52); - VERIFY_BITS(d, 64); + VERIFY_BITS_128(&d, 64); /* [d t4 t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ tx = (t4 >> 48); t4 &= (M >> 4); VERIFY_BITS(tx, 4); VERIFY_BITS(t4, 48); /* [d t4+(tx<<48) t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - c = (uint128_t)a0 * b[0]; - VERIFY_BITS(c, 112); + secp256k1_u128_mul(&c, a0, b[0]); + VERIFY_BITS_128(&c, 112); /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 0 p4 p3 0 0 p0] */ - d += (uint128_t)a1 * b[4] - + (uint128_t)a2 * b[3] - + (uint128_t)a3 * b[2] - + (uint128_t)a4 * b[1]; - VERIFY_BITS(d, 115); + secp256k1_u128_accum_mul(&d, a1, b[4]); + secp256k1_u128_accum_mul(&d, a2, b[3]); + secp256k1_u128_accum_mul(&d, a3, b[2]); + secp256k1_u128_accum_mul(&d, a4, b[1]); + VERIFY_BITS_128(&d, 115); /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - u0 = d & M; d >>= 52; + u0 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(u0, 52); - VERIFY_BITS(d, 63); + VERIFY_BITS_128(&d, 63); /* [d u0 t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ /* [d 0 t4+(tx<<48)+(u0<<52) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ u0 = (u0 << 4) | tx; VERIFY_BITS(u0, 56); /* [d 0 t4+(u0<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - c += (uint128_t)u0 * (R >> 4); - VERIFY_BITS(c, 115); + secp256k1_u128_accum_mul(&c, u0, R >> 4); + VERIFY_BITS_128(&c, 115); /* [d 0 t4 t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - r[0] = c & M; c >>= 52; + r[0] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[0], 52); - VERIFY_BITS(c, 61); + VERIFY_BITS_128(&c, 61); /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 0 p0] */ - c += (uint128_t)a0 * b[1] - + (uint128_t)a1 * b[0]; - VERIFY_BITS(c, 114); + secp256k1_u128_accum_mul(&c, a0, b[1]); + secp256k1_u128_accum_mul(&c, a1, b[0]); + VERIFY_BITS_128(&c, 114); /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 p1 p0] */ - d += (uint128_t)a2 * b[4] - + (uint128_t)a3 * b[3] - + (uint128_t)a4 * b[2]; - VERIFY_BITS(d, 114); + secp256k1_u128_accum_mul(&d, a2, b[4]); + secp256k1_u128_accum_mul(&d, a3, b[3]); + secp256k1_u128_accum_mul(&d, a4, b[2]); + VERIFY_BITS_128(&d, 114); /* [d 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - c += (d & M) * R; d >>= 52; - VERIFY_BITS(c, 115); - VERIFY_BITS(d, 62); + secp256k1_u128_accum_mul(&c, secp256k1_u128_to_u64(&d) & M, R); secp256k1_u128_rshift(&d, 52); + VERIFY_BITS_128(&c, 115); + VERIFY_BITS_128(&d, 62); /* [d 0 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - r[1] = c & M; c >>= 52; + r[1] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[1], 52); - VERIFY_BITS(c, 63); + VERIFY_BITS_128(&c, 63); /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - c += (uint128_t)a0 * b[2] - + (uint128_t)a1 * b[1] - + (uint128_t)a2 * b[0]; - VERIFY_BITS(c, 114); + secp256k1_u128_accum_mul(&c, a0, b[2]); + secp256k1_u128_accum_mul(&c, a1, b[1]); + secp256k1_u128_accum_mul(&c, a2, b[0]); + VERIFY_BITS_128(&c, 114); /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 p2 p1 p0] */ - d += (uint128_t)a3 * b[4] - + (uint128_t)a4 * b[3]; - VERIFY_BITS(d, 114); + secp256k1_u128_accum_mul(&d, a3, b[4]); + secp256k1_u128_accum_mul(&d, a4, b[3]); + VERIFY_BITS_128(&d, 114); /* [d 0 0 t4 t3 c t1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += (uint128_t)R * (uint64_t)d; d >>= 64; - VERIFY_BITS(c, 115); - VERIFY_BITS(d, 50); + secp256k1_u128_accum_mul(&c, R, secp256k1_u128_to_u64(&d)); secp256k1_u128_rshift(&d, 64); + VERIFY_BITS_128(&c, 115); + VERIFY_BITS_128(&d, 50); /* [(d<<12) 0 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[2] = c & M; c >>= 52; + r[2] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[2], 52); - VERIFY_BITS(c, 63); + VERIFY_BITS_128(&c, 63); /* [(d<<12) 0 0 0 t4 t3+c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += (uint128_t)(R << 12) * (uint64_t)d + t3; - VERIFY_BITS(c, 100); + secp256k1_u128_accum_mul(&c, R << 12, secp256k1_u128_to_u64(&d)); + secp256k1_u128_accum_u64(&c, t3); + VERIFY_BITS_128(&c, 100); /* [t4 c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[3] = c & M; c >>= 52; + r[3] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[3], 52); - VERIFY_BITS(c, 48); + VERIFY_BITS_128(&c, 48); /* [t4+c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += t4; - VERIFY_BITS(c, 49); - /* [c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[4] = c; + r[4] = secp256k1_u128_to_u64(&c) + t4; VERIFY_BITS(r[4], 49); /* [r4 r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ } SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t *a) { - uint128_t c, d; + secp256k1_uint128 c, d; uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4]; int64_t t3, t4, tx, u0; const uint64_t M = 0xFFFFFFFFFFFFFULL, R = 0x1000003D10ULL; @@ -170,107 +172,105 @@ SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t * Note that [x 0 0 0 0 0] = [x*R]. */ - d = (uint128_t)(a0*2) * a3 - + (uint128_t)(a1*2) * a2; - VERIFY_BITS(d, 114); + secp256k1_u128_mul(&d, a0*2, a3); + secp256k1_u128_accum_mul(&d, a1*2, a2); + VERIFY_BITS_128(&d, 114); /* [d 0 0 0] = [p3 0 0 0] */ - c = (uint128_t)a4 * a4; - VERIFY_BITS(c, 112); + secp256k1_u128_mul(&c, a4, a4); + VERIFY_BITS_128(&c, 112); /* [c 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ - d += (uint128_t)R * (uint64_t)c; c >>= 64; - VERIFY_BITS(d, 115); - VERIFY_BITS(c, 48); + secp256k1_u128_accum_mul(&d, R, secp256k1_u128_to_u64(&c)); secp256k1_u128_rshift(&c, 64); + VERIFY_BITS_128(&d, 115); + VERIFY_BITS_128(&c, 48); /* [(c<<12) 0 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ - t3 = d & M; d >>= 52; + t3 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(t3, 52); - VERIFY_BITS(d, 63); + VERIFY_BITS_128(&d, 63); /* [(c<<12) 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ a4 *= 2; - d += (uint128_t)a0 * a4 - + (uint128_t)(a1*2) * a3 - + (uint128_t)a2 * a2; - VERIFY_BITS(d, 115); + secp256k1_u128_accum_mul(&d, a0, a4); + secp256k1_u128_accum_mul(&d, a1*2, a3); + secp256k1_u128_accum_mul(&d, a2, a2); + VERIFY_BITS_128(&d, 115); /* [(c<<12) 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - d += (uint128_t)(R << 12) * (uint64_t)c; - VERIFY_BITS(d, 116); + secp256k1_u128_accum_mul(&d, R << 12, secp256k1_u128_to_u64(&c)); + VERIFY_BITS_128(&d, 116); /* [d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - t4 = d & M; d >>= 52; + t4 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(t4, 52); - VERIFY_BITS(d, 64); + VERIFY_BITS_128(&d, 64); /* [d t4 t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ tx = (t4 >> 48); t4 &= (M >> 4); VERIFY_BITS(tx, 4); VERIFY_BITS(t4, 48); /* [d t4+(tx<<48) t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ - c = (uint128_t)a0 * a0; - VERIFY_BITS(c, 112); + secp256k1_u128_mul(&c, a0, a0); + VERIFY_BITS_128(&c, 112); /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 0 p4 p3 0 0 p0] */ - d += (uint128_t)a1 * a4 - + (uint128_t)(a2*2) * a3; - VERIFY_BITS(d, 114); + secp256k1_u128_accum_mul(&d, a1, a4); + secp256k1_u128_accum_mul(&d, a2*2, a3); + VERIFY_BITS_128(&d, 114); /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - u0 = d & M; d >>= 52; + u0 = secp256k1_u128_to_u64(&d) & M; secp256k1_u128_rshift(&d, 52); VERIFY_BITS(u0, 52); - VERIFY_BITS(d, 62); + VERIFY_BITS_128(&d, 62); /* [d u0 t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ /* [d 0 t4+(tx<<48)+(u0<<52) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ u0 = (u0 << 4) | tx; VERIFY_BITS(u0, 56); /* [d 0 t4+(u0<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - c += (uint128_t)u0 * (R >> 4); - VERIFY_BITS(c, 113); + secp256k1_u128_accum_mul(&c, u0, R >> 4); + VERIFY_BITS_128(&c, 113); /* [d 0 t4 t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ - r[0] = c & M; c >>= 52; + r[0] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[0], 52); - VERIFY_BITS(c, 61); + VERIFY_BITS_128(&c, 61); /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 0 p0] */ a0 *= 2; - c += (uint128_t)a0 * a1; - VERIFY_BITS(c, 114); + secp256k1_u128_accum_mul(&c, a0, a1); + VERIFY_BITS_128(&c, 114); /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 p1 p0] */ - d += (uint128_t)a2 * a4 - + (uint128_t)a3 * a3; - VERIFY_BITS(d, 114); + secp256k1_u128_accum_mul(&d, a2, a4); + secp256k1_u128_accum_mul(&d, a3, a3); + VERIFY_BITS_128(&d, 114); /* [d 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - c += (d & M) * R; d >>= 52; - VERIFY_BITS(c, 115); - VERIFY_BITS(d, 62); + secp256k1_u128_accum_mul(&c, secp256k1_u128_to_u64(&d) & M, R); secp256k1_u128_rshift(&d, 52); + VERIFY_BITS_128(&c, 115); + VERIFY_BITS_128(&d, 62); /* [d 0 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - r[1] = c & M; c >>= 52; + r[1] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[1], 52); - VERIFY_BITS(c, 63); + VERIFY_BITS_128(&c, 63); /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ - c += (uint128_t)a0 * a2 - + (uint128_t)a1 * a1; - VERIFY_BITS(c, 114); + secp256k1_u128_accum_mul(&c, a0, a2); + secp256k1_u128_accum_mul(&c, a1, a1); + VERIFY_BITS_128(&c, 114); /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 p2 p1 p0] */ - d += (uint128_t)a3 * a4; - VERIFY_BITS(d, 114); + secp256k1_u128_accum_mul(&d, a3, a4); + VERIFY_BITS_128(&d, 114); /* [d 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += (uint128_t)R * (uint64_t)d; d >>= 64; - VERIFY_BITS(c, 115); - VERIFY_BITS(d, 50); + secp256k1_u128_accum_mul(&c, R, secp256k1_u128_to_u64(&d)); secp256k1_u128_rshift(&d, 64); + VERIFY_BITS_128(&c, 115); + VERIFY_BITS_128(&d, 50); /* [(d<<12) 0 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[2] = c & M; c >>= 52; + r[2] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[2], 52); - VERIFY_BITS(c, 63); + VERIFY_BITS_128(&c, 63); /* [(d<<12) 0 0 0 t4 t3+c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += (uint128_t)(R << 12) * (uint64_t)d + t3; - VERIFY_BITS(c, 100); + secp256k1_u128_accum_mul(&c, R << 12, secp256k1_u128_to_u64(&d)); + secp256k1_u128_accum_u64(&c, t3); + VERIFY_BITS_128(&c, 100); /* [t4 c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[3] = c & M; c >>= 52; + r[3] = secp256k1_u128_to_u64(&c) & M; secp256k1_u128_rshift(&c, 52); VERIFY_BITS(r[3], 52); - VERIFY_BITS(c, 48); + VERIFY_BITS_128(&c, 48); /* [t4+c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - c += t4; - VERIFY_BITS(c, 49); - /* [c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ - r[4] = c; + r[4] = secp256k1_u128_to_u64(&c) + t4; VERIFY_BITS(r[4], 49); /* [r4 r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ } diff --git a/src/secp256k1/src/field_impl.h b/src/secp256k1/src/field_impl.h index 0a4a04d9ac..0a03076bbc 100644 --- a/src/secp256k1/src/field_impl.h +++ b/src/secp256k1/src/field_impl.h @@ -7,10 +7,6 @@ #ifndef SECP256K1_FIELD_IMPL_H #define SECP256K1_FIELD_IMPL_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include "util.h" #if defined(SECP256K1_WIDEMUL_INT128) diff --git a/src/secp256k1/src/group.h b/src/secp256k1/src/group.h index bb7dae1cf7..b79ba597db 100644 --- a/src/secp256k1/src/group.h +++ b/src/secp256k1/src/group.h @@ -23,7 +23,7 @@ typedef struct { #define SECP256K1_GE_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1} /** A group element of the secp256k1 curve, in jacobian coordinates. - * Note: For exhastive test mode, sepc256k1 is replaced by a small subgroup of a different curve. + * Note: For exhastive test mode, secp256k1 is replaced by a small subgroup of a different curve. */ typedef struct { secp256k1_fe x; /* actual X: x/z^2 */ @@ -97,6 +97,9 @@ static void secp256k1_gej_set_infinity(secp256k1_gej *r); /** Set a group element (jacobian) equal to another which is given in affine coordinates. */ static void secp256k1_gej_set_ge(secp256k1_gej *r, const secp256k1_ge *a); +/** Check two group elements (jacobian) for equality in variable time. */ +static int secp256k1_gej_eq_var(const secp256k1_gej *a, const secp256k1_gej *b); + /** Compare the X coordinate of a group element (jacobian). */ static int secp256k1_gej_eq_x_var(const secp256k1_fe *x, const secp256k1_gej *a); diff --git a/src/secp256k1/src/group_impl.h b/src/secp256k1/src/group_impl.h index 63735ab682..82ce3f8d8b 100644 --- a/src/secp256k1/src/group_impl.h +++ b/src/secp256k1/src/group_impl.h @@ -10,59 +10,69 @@ #include "field.h" #include "group.h" +/* Begin of section generated by sage/gen_exhaustive_groups.sage. */ +#define SECP256K1_G_ORDER_7 SECP256K1_GE_CONST(\ + 0x66625d13, 0x317ffe44, 0x63d32cff, 0x1ca02b9b,\ + 0xe5c6d070, 0x50b4b05e, 0x81cc30db, 0xf5166f0a,\ + 0x1e60e897, 0xa7c00c7c, 0x2df53eb6, 0x98274ff4,\ + 0x64252f42, 0x8ca44e17, 0x3b25418c, 0xff4ab0cf\ +) #define SECP256K1_G_ORDER_13 SECP256K1_GE_CONST(\ - 0xc3459c3d, 0x35326167, 0xcd86cce8, 0x07a2417f,\ - 0x5b8bd567, 0xde8538ee, 0x0d507b0c, 0xd128f5bb,\ - 0x8e467fec, 0xcd30000a, 0x6cc1184e, 0x25d382c2,\ - 0xa2f4494e, 0x2fbe9abc, 0x8b64abac, 0xd005fb24\ + 0xa2482ff8, 0x4bf34edf, 0xa51262fd, 0xe57921db,\ + 0xe0dd2cb7, 0xa5914790, 0xbc71631f, 0xc09704fb,\ + 0x942536cb, 0xa3e49492, 0x3a701cc3, 0xee3e443f,\ + 0xdf182aa9, 0x15b8aa6a, 0x166d3b19, 0xba84b045\ ) #define SECP256K1_G_ORDER_199 SECP256K1_GE_CONST(\ - 0x226e653f, 0xc8df7744, 0x9bacbf12, 0x7d1dcbf9,\ - 0x87f05b2a, 0xe7edbd28, 0x1f564575, 0xc48dcf18,\ - 0xa13872c2, 0xe933bb17, 0x5d9ffd5b, 0xb5b6e10c,\ - 0x57fe3c00, 0xbaaaa15a, 0xe003ec3e, 0x9c269bae\ + 0x7fb07b5c, 0xd07c3bda, 0x553902e2, 0x7a87ea2c,\ + 0x35108a7f, 0x051f41e5, 0xb76abad5, 0x1f2703ad,\ + 0x0a251539, 0x5b4c4438, 0x952a634f, 0xac10dd4d,\ + 0x6d6f4745, 0x98990c27, 0x3a4f3116, 0xd32ff969\ ) /** Generator for secp256k1, value 'g' defined in * "Standards for Efficient Cryptography" (SEC2) 2.7.1. */ #define SECP256K1_G SECP256K1_GE_CONST(\ - 0x79BE667EUL, 0xF9DCBBACUL, 0x55A06295UL, 0xCE870B07UL,\ - 0x029BFCDBUL, 0x2DCE28D9UL, 0x59F2815BUL, 0x16F81798UL,\ - 0x483ADA77UL, 0x26A3C465UL, 0x5DA4FBFCUL, 0x0E1108A8UL,\ - 0xFD17B448UL, 0xA6855419UL, 0x9C47D08FUL, 0xFB10D4B8UL\ + 0x79be667e, 0xf9dcbbac, 0x55a06295, 0xce870b07,\ + 0x029bfcdb, 0x2dce28d9, 0x59f2815b, 0x16f81798,\ + 0x483ada77, 0x26a3c465, 0x5da4fbfc, 0x0e1108a8,\ + 0xfd17b448, 0xa6855419, 0x9c47d08f, 0xfb10d4b8\ ) /* These exhaustive group test orders and generators are chosen such that: * - The field size is equal to that of secp256k1, so field code is the same. - * - The curve equation is of the form y^2=x^3+B for some constant B. - * - The subgroup has a generator 2*P, where P.x=1. + * - The curve equation is of the form y^2=x^3+B for some small constant B. + * - The subgroup has a generator 2*P, where P.x is as small as possible. * - The subgroup has size less than 1000 to permit exhaustive testing. * - The subgroup admits an endomorphism of the form lambda*(x,y) == (beta*x,y). - * - * These parameters are generated using sage/gen_exhaustive_groups.sage. */ #if defined(EXHAUSTIVE_TEST_ORDER) -# if EXHAUSTIVE_TEST_ORDER == 13 +# if EXHAUSTIVE_TEST_ORDER == 7 + +static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G_ORDER_7; +#define SECP256K1_B 6 + +# elif EXHAUSTIVE_TEST_ORDER == 13 + static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G_ORDER_13; +#define SECP256K1_B 2 -static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST( - 0x3d3486b2, 0x159a9ca5, 0xc75638be, 0xb23a69bc, - 0x946a45ab, 0x24801247, 0xb4ed2b8e, 0x26b6a417 -); # elif EXHAUSTIVE_TEST_ORDER == 199 + static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G_ORDER_199; +#define SECP256K1_B 4 -static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST( - 0x2cca28fa, 0xfc614b80, 0x2a3db42b, 0x00ba00b1, - 0xbea8d943, 0xdace9ab2, 0x9536daea, 0x0074defb -); # else # error No known generator for the specified exhaustive test group order. # endif #else + static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G; +#define SECP256K1_B 7 -static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 7); #endif +/* End of section generated by sage/gen_exhaustive_groups.sage. */ + +static const secp256k1_fe secp256k1_fe_const_b = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, SECP256K1_B); static void secp256k1_ge_set_gej_zinv(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zi) { secp256k1_fe zi2; @@ -217,7 +227,7 @@ static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int o secp256k1_fe_sqr(&x2, x); secp256k1_fe_mul(&x3, x, &x2); r->infinity = 0; - secp256k1_fe_add(&x3, &secp256k1_fe_const_b); + secp256k1_fe_add_int(&x3, SECP256K1_B); if (!secp256k1_fe_sqrt(&r->y, &x3)) { return 0; } @@ -236,6 +246,13 @@ static void secp256k1_gej_set_ge(secp256k1_gej *r, const secp256k1_ge *a) { secp256k1_fe_set_int(&r->z, 1); } +static int secp256k1_gej_eq_var(const secp256k1_gej *a, const secp256k1_gej *b) { + secp256k1_gej tmp; + secp256k1_gej_neg(&tmp, a); + secp256k1_gej_add_var(&tmp, &tmp, b, NULL); + return secp256k1_gej_is_infinity(&tmp); +} + static int secp256k1_gej_eq_x_var(const secp256k1_fe *x, const secp256k1_gej *a) { secp256k1_fe r, r2; VERIFY_CHECK(!a->infinity); @@ -265,7 +282,7 @@ static int secp256k1_ge_is_valid_var(const secp256k1_ge *a) { /* y^2 = x^3 + 7 */ secp256k1_fe_sqr(&y2, &a->y); secp256k1_fe_sqr(&x3, &a->x); secp256k1_fe_mul(&x3, &x3, &a->x); - secp256k1_fe_add(&x3, &secp256k1_fe_const_b); + secp256k1_fe_add_int(&x3, SECP256K1_B); secp256k1_fe_normalize_weak(&x3); return secp256k1_fe_equal_var(&y2, &x3); } @@ -515,11 +532,11 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const /* Operations: 7 mul, 5 sqr, 24 add/cmov/half/mul_int/negate/normalize_weak/normalizes_to_zero */ secp256k1_fe zz, u1, u2, s1, s2, t, tt, m, n, q, rr; secp256k1_fe m_alt, rr_alt; - int infinity, degenerate; + int degenerate; VERIFY_CHECK(!b->infinity); VERIFY_CHECK(a->infinity == 0 || a->infinity == 1); - /** In: + /* In: * Eric Brier and Marc Joye, Weierstrass Elliptic Curves and Side-Channel Attacks. * In D. Naccache and P. Paillier, Eds., Public Key Cryptography, vol. 2274 of Lecture Notes in Computer Science, pages 335-345. Springer-Verlag, 2002. * we find as solution for a unified addition/doubling formula: @@ -581,10 +598,9 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_fe_negate(&m_alt, &u2, 1); /* Malt = -X2*Z1^2 */ secp256k1_fe_mul(&tt, &u1, &m_alt); /* tt = -U1*U2 (2) */ secp256k1_fe_add(&rr, &tt); /* rr = R = T^2-U1*U2 (3) */ - /** If lambda = R/M = 0/0 we have a problem (except in the "trivial" - * case that Z = z1z2 = 0, and this is special-cased later on). */ - degenerate = secp256k1_fe_normalizes_to_zero(&m) & - secp256k1_fe_normalizes_to_zero(&rr); + /* If lambda = R/M = R/0 we have a problem (except in the "trivial" + * case that Z = z1z2 = 0, and this is special-cased later on). */ + degenerate = secp256k1_fe_normalizes_to_zero(&m); /* This only occurs when y1 == -y2 and x1^3 == x2^3, but x1 != x2. * This means either x1 == beta*x2 or beta*x1 == x2, where beta is * a nontrivial cube root of one. In either case, an alternate @@ -596,7 +612,7 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_fe_cmov(&rr_alt, &rr, !degenerate); secp256k1_fe_cmov(&m_alt, &m, !degenerate); - /* Now Ralt / Malt = lambda and is guaranteed not to be 0/0. + /* Now Ralt / Malt = lambda and is guaranteed not to be Ralt / 0. * From here on out Ralt and Malt represent the numerator * and denominator of lambda; R and M represent the explicit * expressions x1^2 + x2^2 + x1x2 and y1 + y2. */ @@ -611,7 +627,6 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_fe_cmov(&n, &m, degenerate); /* n = M^3 * Malt (2) */ secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */ secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Z3 = Malt*Z (1) */ - infinity = secp256k1_fe_normalizes_to_zero(&r->z) & ~a->infinity; secp256k1_fe_add(&t, &q); /* t = Ralt^2 + Q (2) */ r->x = t; /* r->x = X3 = Ralt^2 + Q (2) */ secp256k1_fe_mul_int(&t, 2); /* t = 2*X3 (4) */ @@ -621,11 +636,28 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_fe_negate(&r->y, &t, 3); /* r->y = -(Ralt*(2*X3 + Q) + M^3*Malt) (4) */ secp256k1_fe_half(&r->y); /* r->y = Y3 = -(Ralt*(2*X3 + Q) + M^3*Malt)/2 (3) */ - /** In case a->infinity == 1, replace r with (b->x, b->y, 1). */ + /* In case a->infinity == 1, replace r with (b->x, b->y, 1). */ secp256k1_fe_cmov(&r->x, &b->x, a->infinity); secp256k1_fe_cmov(&r->y, &b->y, a->infinity); secp256k1_fe_cmov(&r->z, &secp256k1_fe_one, a->infinity); - r->infinity = infinity; + + /* Set r->infinity if r->z is 0. + * + * If a->infinity is set, then r->infinity = (r->z == 0) = (1 == 0) = false, + * which is correct because the function assumes that b is not infinity. + * + * Now assume !a->infinity. This implies Z = Z1 != 0. + * + * Case y1 = -y2: + * In this case we could have a = -b, namely if x1 = x2. + * We have degenerate = true, r->z = (x1 - x2) * Z. + * Then r->infinity = ((x1 - x2)Z == 0) = (x1 == x2) = (a == -b). + * + * Case y1 != -y2: + * In this case, we can't have a = -b. + * We have degenerate = false, r->z = (y1 + y2) * Z. + * Then r->infinity = ((y1 + y2)Z == 0) = (y1 == -y2) = false. */ + r->infinity = secp256k1_fe_normalizes_to_zero(&r->z); } static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *s) { diff --git a/src/secp256k1/src/int128.h b/src/secp256k1/src/int128.h new file mode 100644 index 0000000000..5355fbfae0 --- /dev/null +++ b/src/secp256k1/src/int128.h @@ -0,0 +1,90 @@ +#ifndef SECP256K1_INT128_H +#define SECP256K1_INT128_H + +#include "util.h" + +#if defined(SECP256K1_WIDEMUL_INT128) +# if defined(SECP256K1_INT128_NATIVE) +# include "int128_native.h" +# elif defined(SECP256K1_INT128_STRUCT) +# include "int128_struct.h" +# else +# error "Please select int128 implementation" +# endif + +/* Construct an unsigned 128-bit value from a high and a low 64-bit value. */ +static SECP256K1_INLINE void secp256k1_u128_load(secp256k1_uint128 *r, uint64_t hi, uint64_t lo); + +/* Multiply two unsigned 64-bit values a and b and write the result to r. */ +static SECP256K1_INLINE void secp256k1_u128_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b); + +/* Multiply two unsigned 64-bit values a and b and add the result to r. + * The final result is taken modulo 2^128. + */ +static SECP256K1_INLINE void secp256k1_u128_accum_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b); + +/* Add an unsigned 64-bit value a to r. + * The final result is taken modulo 2^128. + */ +static SECP256K1_INLINE void secp256k1_u128_accum_u64(secp256k1_uint128 *r, uint64_t a); + +/* Unsigned (logical) right shift. + * Non-constant time in n. + */ +static SECP256K1_INLINE void secp256k1_u128_rshift(secp256k1_uint128 *r, unsigned int n); + +/* Return the low 64-bits of a 128-bit value as an unsigned 64-bit value. */ +static SECP256K1_INLINE uint64_t secp256k1_u128_to_u64(const secp256k1_uint128 *a); + +/* Return the high 64-bits of a 128-bit value as an unsigned 64-bit value. */ +static SECP256K1_INLINE uint64_t secp256k1_u128_hi_u64(const secp256k1_uint128 *a); + +/* Write an unsigned 64-bit value to r. */ +static SECP256K1_INLINE void secp256k1_u128_from_u64(secp256k1_uint128 *r, uint64_t a); + +/* Tests if r is strictly less than to 2^n. + * n must be strictly less than 128. + */ +static SECP256K1_INLINE int secp256k1_u128_check_bits(const secp256k1_uint128 *r, unsigned int n); + +/* Construct an signed 128-bit value from a high and a low 64-bit value. */ +static SECP256K1_INLINE void secp256k1_i128_load(secp256k1_int128 *r, int64_t hi, uint64_t lo); + +/* Multiply two signed 64-bit values a and b and write the result to r. */ +static SECP256K1_INLINE void secp256k1_i128_mul(secp256k1_int128 *r, int64_t a, int64_t b); + +/* Multiply two signed 64-bit values a and b and add the result to r. + * Overflow or underflow from the addition is undefined behaviour. + */ +static SECP256K1_INLINE void secp256k1_i128_accum_mul(secp256k1_int128 *r, int64_t a, int64_t b); + +/* Compute a*d - b*c from signed 64-bit values and write the result to r. */ +static SECP256K1_INLINE void secp256k1_i128_det(secp256k1_int128 *r, int64_t a, int64_t b, int64_t c, int64_t d); + +/* Signed (arithmetic) right shift. + * Non-constant time in b. + */ +static SECP256K1_INLINE void secp256k1_i128_rshift(secp256k1_int128 *r, unsigned int b); + +/* Return the input value modulo 2^64. */ +static SECP256K1_INLINE uint64_t secp256k1_i128_to_u64(const secp256k1_int128 *a); + +/* Return the value as a signed 64-bit value. + * Requires the input to be between INT64_MIN and INT64_MAX. + */ +static SECP256K1_INLINE int64_t secp256k1_i128_to_i64(const secp256k1_int128 *a); + +/* Write a signed 64-bit value to r. */ +static SECP256K1_INLINE void secp256k1_i128_from_i64(secp256k1_int128 *r, int64_t a); + +/* Compare two 128-bit values for equality. */ +static SECP256K1_INLINE int secp256k1_i128_eq_var(const secp256k1_int128 *a, const secp256k1_int128 *b); + +/* Tests if r is equal to sign*2^n (sign must be 1 or -1). + * n must be strictly less than 127. + */ +static SECP256K1_INLINE int secp256k1_i128_check_pow2(const secp256k1_int128 *r, unsigned int n, int sign); + +#endif + +#endif diff --git a/src/secp256k1/src/int128_impl.h b/src/secp256k1/src/int128_impl.h new file mode 100644 index 0000000000..cfc573408a --- /dev/null +++ b/src/secp256k1/src/int128_impl.h @@ -0,0 +1,18 @@ +#ifndef SECP256K1_INT128_IMPL_H +#define SECP256K1_INT128_IMPL_H + +#include "util.h" + +#include "int128.h" + +#if defined(SECP256K1_WIDEMUL_INT128) +# if defined(SECP256K1_INT128_NATIVE) +# include "int128_native_impl.h" +# elif defined(SECP256K1_INT128_STRUCT) +# include "int128_struct_impl.h" +# else +# error "Please select int128 implementation" +# endif +#endif + +#endif diff --git a/src/secp256k1/src/int128_native.h b/src/secp256k1/src/int128_native.h new file mode 100644 index 0000000000..7c97aafc74 --- /dev/null +++ b/src/secp256k1/src/int128_native.h @@ -0,0 +1,19 @@ +#ifndef SECP256K1_INT128_NATIVE_H +#define SECP256K1_INT128_NATIVE_H + +#include +#include "util.h" + +#if !defined(UINT128_MAX) && defined(__SIZEOF_INT128__) +SECP256K1_GNUC_EXT typedef unsigned __int128 uint128_t; +SECP256K1_GNUC_EXT typedef __int128 int128_t; +# define UINT128_MAX ((uint128_t)(-1)) +# define INT128_MAX ((int128_t)(UINT128_MAX >> 1)) +# define INT128_MIN (-INT128_MAX - 1) +/* No (U)INT128_C macros because compilers providing __int128 do not support 128-bit literals. */ +#endif + +typedef uint128_t secp256k1_uint128; +typedef int128_t secp256k1_int128; + +#endif diff --git a/src/secp256k1/src/int128_native_impl.h b/src/secp256k1/src/int128_native_impl.h new file mode 100644 index 0000000000..996e542cf9 --- /dev/null +++ b/src/secp256k1/src/int128_native_impl.h @@ -0,0 +1,93 @@ +#ifndef SECP256K1_INT128_NATIVE_IMPL_H +#define SECP256K1_INT128_NATIVE_IMPL_H + +#include "int128.h" + +static SECP256K1_INLINE void secp256k1_u128_load(secp256k1_uint128 *r, uint64_t hi, uint64_t lo) { + *r = (((uint128_t)hi) << 64) + lo; +} + +static SECP256K1_INLINE void secp256k1_u128_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b) { + *r = (uint128_t)a * b; +} + +static SECP256K1_INLINE void secp256k1_u128_accum_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b) { + *r += (uint128_t)a * b; +} + +static SECP256K1_INLINE void secp256k1_u128_accum_u64(secp256k1_uint128 *r, uint64_t a) { + *r += a; +} + +static SECP256K1_INLINE void secp256k1_u128_rshift(secp256k1_uint128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + *r >>= n; +} + +static SECP256K1_INLINE uint64_t secp256k1_u128_to_u64(const secp256k1_uint128 *a) { + return (uint64_t)(*a); +} + +static SECP256K1_INLINE uint64_t secp256k1_u128_hi_u64(const secp256k1_uint128 *a) { + return (uint64_t)(*a >> 64); +} + +static SECP256K1_INLINE void secp256k1_u128_from_u64(secp256k1_uint128 *r, uint64_t a) { + *r = a; +} + +static SECP256K1_INLINE int secp256k1_u128_check_bits(const secp256k1_uint128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + return (*r >> n == 0); +} + +static SECP256K1_INLINE void secp256k1_i128_load(secp256k1_int128 *r, int64_t hi, uint64_t lo) { + *r = (((uint128_t)(uint64_t)hi) << 64) + lo; +} + +static SECP256K1_INLINE void secp256k1_i128_mul(secp256k1_int128 *r, int64_t a, int64_t b) { + *r = (int128_t)a * b; +} + +static SECP256K1_INLINE void secp256k1_i128_accum_mul(secp256k1_int128 *r, int64_t a, int64_t b) { + int128_t ab = (int128_t)a * b; + VERIFY_CHECK(0 <= ab ? *r <= INT128_MAX - ab : INT128_MIN - ab <= *r); + *r += ab; +} + +static SECP256K1_INLINE void secp256k1_i128_det(secp256k1_int128 *r, int64_t a, int64_t b, int64_t c, int64_t d) { + int128_t ad = (int128_t)a * d; + int128_t bc = (int128_t)b * c; + VERIFY_CHECK(0 <= bc ? INT128_MIN + bc <= ad : ad <= INT128_MAX + bc); + *r = ad - bc; +} + +static SECP256K1_INLINE void secp256k1_i128_rshift(secp256k1_int128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + *r >>= n; +} + +static SECP256K1_INLINE uint64_t secp256k1_i128_to_u64(const secp256k1_int128 *a) { + return (uint64_t)*a; +} + +static SECP256K1_INLINE int64_t secp256k1_i128_to_i64(const secp256k1_int128 *a) { + VERIFY_CHECK(INT64_MIN <= *a && *a <= INT64_MAX); + return *a; +} + +static SECP256K1_INLINE void secp256k1_i128_from_i64(secp256k1_int128 *r, int64_t a) { + *r = a; +} + +static SECP256K1_INLINE int secp256k1_i128_eq_var(const secp256k1_int128 *a, const secp256k1_int128 *b) { + return *a == *b; +} + +static SECP256K1_INLINE int secp256k1_i128_check_pow2(const secp256k1_int128 *r, unsigned int n, int sign) { + VERIFY_CHECK(n < 127); + VERIFY_CHECK(sign == 1 || sign == -1); + return (*r == (int128_t)((uint128_t)sign << n)); +} + +#endif diff --git a/src/secp256k1/src/int128_struct.h b/src/secp256k1/src/int128_struct.h new file mode 100644 index 0000000000..6156f82cc2 --- /dev/null +++ b/src/secp256k1/src/int128_struct.h @@ -0,0 +1,14 @@ +#ifndef SECP256K1_INT128_STRUCT_H +#define SECP256K1_INT128_STRUCT_H + +#include +#include "util.h" + +typedef struct { + uint64_t lo; + uint64_t hi; +} secp256k1_uint128; + +typedef secp256k1_uint128 secp256k1_int128; + +#endif diff --git a/src/secp256k1/src/int128_struct_impl.h b/src/secp256k1/src/int128_struct_impl.h new file mode 100644 index 0000000000..2eb337cb54 --- /dev/null +++ b/src/secp256k1/src/int128_struct_impl.h @@ -0,0 +1,199 @@ +#ifndef SECP256K1_INT128_STRUCT_IMPL_H +#define SECP256K1_INT128_STRUCT_IMPL_H + +#include "int128.h" + +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) /* MSVC */ +# include +# if defined(_M_ARM64) || defined(SECP256K1_MSVC_MULH_TEST_OVERRIDE) +/* On ARM64 MSVC, use __(u)mulh for the upper half of 64x64 multiplications. + (Define SECP256K1_MSVC_MULH_TEST_OVERRIDE to test this code path on X64, + which supports both __(u)mulh and _umul128.) */ +# if defined(SECP256K1_MSVC_MULH_TEST_OVERRIDE) +# pragma message(__FILE__ ": SECP256K1_MSVC_MULH_TEST_OVERRIDE is defined, forcing use of __(u)mulh.") +# endif +static SECP256K1_INLINE uint64_t secp256k1_umul128(uint64_t a, uint64_t b, uint64_t* hi) { + *hi = __umulh(a, b); + return a * b; +} + +static SECP256K1_INLINE int64_t secp256k1_mul128(int64_t a, int64_t b, int64_t* hi) { + *hi = __mulh(a, b); + return (uint64_t)a * (uint64_t)b; +} +# else +/* On x84_64 MSVC, use native _(u)mul128 for 64x64->128 multiplications. */ +# define secp256k1_umul128 _umul128 +# define secp256k1_mul128 _mul128 +# endif +#else +/* On other systems, emulate 64x64->128 multiplications using 32x32->64 multiplications. */ +static SECP256K1_INLINE uint64_t secp256k1_umul128(uint64_t a, uint64_t b, uint64_t* hi) { + uint64_t ll = (uint64_t)(uint32_t)a * (uint32_t)b; + uint64_t lh = (uint32_t)a * (b >> 32); + uint64_t hl = (a >> 32) * (uint32_t)b; + uint64_t hh = (a >> 32) * (b >> 32); + uint64_t mid34 = (ll >> 32) + (uint32_t)lh + (uint32_t)hl; + *hi = hh + (lh >> 32) + (hl >> 32) + (mid34 >> 32); + return (mid34 << 32) + (uint32_t)ll; +} + +static SECP256K1_INLINE int64_t secp256k1_mul128(int64_t a, int64_t b, int64_t* hi) { + uint64_t ll = (uint64_t)(uint32_t)a * (uint32_t)b; + int64_t lh = (uint32_t)a * (b >> 32); + int64_t hl = (a >> 32) * (uint32_t)b; + int64_t hh = (a >> 32) * (b >> 32); + uint64_t mid34 = (ll >> 32) + (uint32_t)lh + (uint32_t)hl; + *hi = hh + (lh >> 32) + (hl >> 32) + (mid34 >> 32); + return (mid34 << 32) + (uint32_t)ll; +} +#endif + +static SECP256K1_INLINE void secp256k1_u128_load(secp256k1_uint128 *r, uint64_t hi, uint64_t lo) { + r->hi = hi; + r->lo = lo; +} + +static SECP256K1_INLINE void secp256k1_u128_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b) { + r->lo = secp256k1_umul128(a, b, &r->hi); +} + +static SECP256K1_INLINE void secp256k1_u128_accum_mul(secp256k1_uint128 *r, uint64_t a, uint64_t b) { + uint64_t lo, hi; + lo = secp256k1_umul128(a, b, &hi); + r->lo += lo; + r->hi += hi + (r->lo < lo); +} + +static SECP256K1_INLINE void secp256k1_u128_accum_u64(secp256k1_uint128 *r, uint64_t a) { + r->lo += a; + r->hi += r->lo < a; +} + +/* Unsigned (logical) right shift. + * Non-constant time in n. + */ +static SECP256K1_INLINE void secp256k1_u128_rshift(secp256k1_uint128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + if (n >= 64) { + r->lo = r->hi >> (n-64); + r->hi = 0; + } else if (n > 0) { + r->lo = ((1U * r->hi) << (64-n)) | r->lo >> n; + r->hi >>= n; + } +} + +static SECP256K1_INLINE uint64_t secp256k1_u128_to_u64(const secp256k1_uint128 *a) { + return a->lo; +} + +static SECP256K1_INLINE uint64_t secp256k1_u128_hi_u64(const secp256k1_uint128 *a) { + return a->hi; +} + +static SECP256K1_INLINE void secp256k1_u128_from_u64(secp256k1_uint128 *r, uint64_t a) { + r->hi = 0; + r->lo = a; +} + +static SECP256K1_INLINE int secp256k1_u128_check_bits(const secp256k1_uint128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + return n >= 64 ? r->hi >> (n - 64) == 0 + : r->hi == 0 && r->lo >> n == 0; +} + +static SECP256K1_INLINE void secp256k1_i128_load(secp256k1_int128 *r, int64_t hi, uint64_t lo) { + r->hi = hi; + r->lo = lo; +} + +static SECP256K1_INLINE void secp256k1_i128_mul(secp256k1_int128 *r, int64_t a, int64_t b) { + int64_t hi; + r->lo = (uint64_t)secp256k1_mul128(a, b, &hi); + r->hi = (uint64_t)hi; +} + +static SECP256K1_INLINE void secp256k1_i128_accum_mul(secp256k1_int128 *r, int64_t a, int64_t b) { + int64_t hi; + uint64_t lo = (uint64_t)secp256k1_mul128(a, b, &hi); + r->lo += lo; + hi += r->lo < lo; + /* Verify no overflow. + * If r represents a positive value (the sign bit is not set) and the value we are adding is a positive value (the sign bit is not set), + * then we require that the resulting value also be positive (the sign bit is not set). + * Note that (X <= Y) means (X implies Y) when X and Y are boolean values (i.e. 0 or 1). + */ + VERIFY_CHECK((r->hi <= 0x7fffffffffffffffu && (uint64_t)hi <= 0x7fffffffffffffffu) <= (r->hi + (uint64_t)hi <= 0x7fffffffffffffffu)); + /* Verify no underflow. + * If r represents a negative value (the sign bit is set) and the value we are adding is a negative value (the sign bit is set), + * then we require that the resulting value also be negative (the sign bit is set). + */ + VERIFY_CHECK((r->hi > 0x7fffffffffffffffu && (uint64_t)hi > 0x7fffffffffffffffu) <= (r->hi + (uint64_t)hi > 0x7fffffffffffffffu)); + r->hi += hi; +} + +static SECP256K1_INLINE void secp256k1_i128_dissip_mul(secp256k1_int128 *r, int64_t a, int64_t b) { + int64_t hi; + uint64_t lo = (uint64_t)secp256k1_mul128(a, b, &hi); + hi += r->lo < lo; + /* Verify no overflow. + * If r represents a positive value (the sign bit is not set) and the value we are subtracting is a negative value (the sign bit is set), + * then we require that the resulting value also be positive (the sign bit is not set). + */ + VERIFY_CHECK((r->hi <= 0x7fffffffffffffffu && (uint64_t)hi > 0x7fffffffffffffffu) <= (r->hi - (uint64_t)hi <= 0x7fffffffffffffffu)); + /* Verify no underflow. + * If r represents a negative value (the sign bit is set) and the value we are subtracting is a positive value (the sign sign bit is not set), + * then we require that the resulting value also be negative (the sign bit is set). + */ + VERIFY_CHECK((r->hi > 0x7fffffffffffffffu && (uint64_t)hi <= 0x7fffffffffffffffu) <= (r->hi - (uint64_t)hi > 0x7fffffffffffffffu)); + r->hi -= hi; + r->lo -= lo; +} + +static SECP256K1_INLINE void secp256k1_i128_det(secp256k1_int128 *r, int64_t a, int64_t b, int64_t c, int64_t d) { + secp256k1_i128_mul(r, a, d); + secp256k1_i128_dissip_mul(r, b, c); +} + +/* Signed (arithmetic) right shift. + * Non-constant time in n. + */ +static SECP256K1_INLINE void secp256k1_i128_rshift(secp256k1_int128 *r, unsigned int n) { + VERIFY_CHECK(n < 128); + if (n >= 64) { + r->lo = (uint64_t)((int64_t)(r->hi) >> (n-64)); + r->hi = (uint64_t)((int64_t)(r->hi) >> 63); + } else if (n > 0) { + r->lo = ((1U * r->hi) << (64-n)) | r->lo >> n; + r->hi = (uint64_t)((int64_t)(r->hi) >> n); + } +} + +static SECP256K1_INLINE uint64_t secp256k1_i128_to_u64(const secp256k1_int128 *a) { + return a->lo; +} + +static SECP256K1_INLINE int64_t secp256k1_i128_to_i64(const secp256k1_int128 *a) { + /* Verify that a represents a 64 bit signed value by checking that the high bits are a sign extension of the low bits. */ + VERIFY_CHECK(a->hi == -(a->lo >> 63)); + return (int64_t)secp256k1_i128_to_u64(a); +} + +static SECP256K1_INLINE void secp256k1_i128_from_i64(secp256k1_int128 *r, int64_t a) { + r->hi = (uint64_t)(a >> 63); + r->lo = (uint64_t)a; +} + +static SECP256K1_INLINE int secp256k1_i128_eq_var(const secp256k1_int128 *a, const secp256k1_int128 *b) { + return a->hi == b->hi && a->lo == b->lo; +} + +static SECP256K1_INLINE int secp256k1_i128_check_pow2(const secp256k1_int128 *r, unsigned int n, int sign) { + VERIFY_CHECK(n < 127); + VERIFY_CHECK(sign == 1 || sign == -1); + return n >= 64 ? r->hi == (uint64_t)sign << (n - 64) && r->lo == 0 + : r->hi == (uint64_t)((sign - 1) >> 1) && r->lo == (uint64_t)sign << n; +} + +#endif diff --git a/src/secp256k1/src/modinv32.h b/src/secp256k1/src/modinv32.h index 0efdda9ab5..846c642f8c 100644 --- a/src/secp256k1/src/modinv32.h +++ b/src/secp256k1/src/modinv32.h @@ -7,10 +7,6 @@ #ifndef SECP256K1_MODINV32_H #define SECP256K1_MODINV32_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include "util.h" /* A signed 30-bit limb representation of integers. @@ -39,4 +35,9 @@ static void secp256k1_modinv32_var(secp256k1_modinv32_signed30 *x, const secp256 /* Same as secp256k1_modinv32_var, but constant time in x (not in the modulus). */ static void secp256k1_modinv32(secp256k1_modinv32_signed30 *x, const secp256k1_modinv32_modinfo *modinfo); +/* Compute the Jacobi symbol for (x | modinfo->modulus). x must be coprime with modulus (and thus + * cannot be 0, as modulus >= 3). All limbs of x must be non-negative. Returns 0 if the result + * cannot be computed. */ +static int secp256k1_jacobi32_maybe_var(const secp256k1_modinv32_signed30 *x, const secp256k1_modinv32_modinfo *modinfo); + #endif /* SECP256K1_MODINV32_H */ diff --git a/src/secp256k1/src/modinv32_impl.h b/src/secp256k1/src/modinv32_impl.h index 661c5fc04c..643750560e 100644 --- a/src/secp256k1/src/modinv32_impl.h +++ b/src/secp256k1/src/modinv32_impl.h @@ -232,6 +232,21 @@ static int32_t secp256k1_modinv32_divsteps_30(int32_t zeta, uint32_t f0, uint32_ return zeta; } +/* inv256[i] = -(2*i+1)^-1 (mod 256) */ +static const uint8_t secp256k1_modinv32_inv256[128] = { + 0xFF, 0x55, 0x33, 0x49, 0xC7, 0x5D, 0x3B, 0x11, 0x0F, 0xE5, 0xC3, 0x59, + 0xD7, 0xED, 0xCB, 0x21, 0x1F, 0x75, 0x53, 0x69, 0xE7, 0x7D, 0x5B, 0x31, + 0x2F, 0x05, 0xE3, 0x79, 0xF7, 0x0D, 0xEB, 0x41, 0x3F, 0x95, 0x73, 0x89, + 0x07, 0x9D, 0x7B, 0x51, 0x4F, 0x25, 0x03, 0x99, 0x17, 0x2D, 0x0B, 0x61, + 0x5F, 0xB5, 0x93, 0xA9, 0x27, 0xBD, 0x9B, 0x71, 0x6F, 0x45, 0x23, 0xB9, + 0x37, 0x4D, 0x2B, 0x81, 0x7F, 0xD5, 0xB3, 0xC9, 0x47, 0xDD, 0xBB, 0x91, + 0x8F, 0x65, 0x43, 0xD9, 0x57, 0x6D, 0x4B, 0xA1, 0x9F, 0xF5, 0xD3, 0xE9, + 0x67, 0xFD, 0xDB, 0xB1, 0xAF, 0x85, 0x63, 0xF9, 0x77, 0x8D, 0x6B, 0xC1, + 0xBF, 0x15, 0xF3, 0x09, 0x87, 0x1D, 0xFB, 0xD1, 0xCF, 0xA5, 0x83, 0x19, + 0x97, 0xAD, 0x8B, 0xE1, 0xDF, 0x35, 0x13, 0x29, 0xA7, 0x3D, 0x1B, 0xF1, + 0xEF, 0xC5, 0xA3, 0x39, 0xB7, 0xCD, 0xAB, 0x01 +}; + /* Compute the transition matrix and eta for 30 divsteps (variable time). * * Input: eta: initial eta @@ -243,21 +258,6 @@ static int32_t secp256k1_modinv32_divsteps_30(int32_t zeta, uint32_t f0, uint32_ * Implements the divsteps_n_matrix_var function from the explanation. */ static int32_t secp256k1_modinv32_divsteps_30_var(int32_t eta, uint32_t f0, uint32_t g0, secp256k1_modinv32_trans2x2 *t) { - /* inv256[i] = -(2*i+1)^-1 (mod 256) */ - static const uint8_t inv256[128] = { - 0xFF, 0x55, 0x33, 0x49, 0xC7, 0x5D, 0x3B, 0x11, 0x0F, 0xE5, 0xC3, 0x59, - 0xD7, 0xED, 0xCB, 0x21, 0x1F, 0x75, 0x53, 0x69, 0xE7, 0x7D, 0x5B, 0x31, - 0x2F, 0x05, 0xE3, 0x79, 0xF7, 0x0D, 0xEB, 0x41, 0x3F, 0x95, 0x73, 0x89, - 0x07, 0x9D, 0x7B, 0x51, 0x4F, 0x25, 0x03, 0x99, 0x17, 0x2D, 0x0B, 0x61, - 0x5F, 0xB5, 0x93, 0xA9, 0x27, 0xBD, 0x9B, 0x71, 0x6F, 0x45, 0x23, 0xB9, - 0x37, 0x4D, 0x2B, 0x81, 0x7F, 0xD5, 0xB3, 0xC9, 0x47, 0xDD, 0xBB, 0x91, - 0x8F, 0x65, 0x43, 0xD9, 0x57, 0x6D, 0x4B, 0xA1, 0x9F, 0xF5, 0xD3, 0xE9, - 0x67, 0xFD, 0xDB, 0xB1, 0xAF, 0x85, 0x63, 0xF9, 0x77, 0x8D, 0x6B, 0xC1, - 0xBF, 0x15, 0xF3, 0x09, 0x87, 0x1D, 0xFB, 0xD1, 0xCF, 0xA5, 0x83, 0x19, - 0x97, 0xAD, 0x8B, 0xE1, 0xDF, 0x35, 0x13, 0x29, 0xA7, 0x3D, 0x1B, 0xF1, - 0xEF, 0xC5, 0xA3, 0x39, 0xB7, 0xCD, 0xAB, 0x01 - }; - /* Transformation matrix; see comments in secp256k1_modinv32_divsteps_30. */ uint32_t u = 1, v = 0, q = 0, r = 1; uint32_t f = f0, g = g0, m; @@ -297,7 +297,7 @@ static int32_t secp256k1_modinv32_divsteps_30_var(int32_t eta, uint32_t f0, uint VERIFY_CHECK(limit > 0 && limit <= 30); m = (UINT32_MAX >> (32 - limit)) & 255U; /* Find what multiple of f must be added to g to cancel its bottom min(limit, 8) bits. */ - w = (g * inv256[(f >> 1) & 127]) & m; + w = (g * secp256k1_modinv32_inv256[(f >> 1) & 127]) & m; /* Do so. */ g += f * w; q += u * w; @@ -317,6 +317,86 @@ static int32_t secp256k1_modinv32_divsteps_30_var(int32_t eta, uint32_t f0, uint return eta; } +/* Compute the transition matrix and eta for 30 posdivsteps (variable time, eta=-delta), and keeps track + * of the Jacobi symbol along the way. f0 and g0 must be f and g mod 2^32 rather than 2^30, because + * Jacobi tracking requires knowing (f mod 8) rather than just (f mod 2). + * + * Input: eta: initial eta + * f0: bottom limb of initial f + * g0: bottom limb of initial g + * Output: t: transition matrix + * Input/Output: (*jacp & 1) is bitflipped if and only if the Jacobi symbol of (f | g) changes sign + * by applying the returned transformation matrix to it. The other bits of *jacp may + * change, but are meaningless. + * Return: final eta + */ +static int32_t secp256k1_modinv32_posdivsteps_30_var(int32_t eta, uint32_t f0, uint32_t g0, secp256k1_modinv32_trans2x2 *t, int *jacp) { + /* Transformation matrix. */ + uint32_t u = 1, v = 0, q = 0, r = 1; + uint32_t f = f0, g = g0, m; + uint16_t w; + int i = 30, limit, zeros; + int jac = *jacp; + + for (;;) { + /* Use a sentinel bit to count zeros only up to i. */ + zeros = secp256k1_ctz32_var(g | (UINT32_MAX << i)); + /* Perform zeros divsteps at once; they all just divide g by two. */ + g >>= zeros; + u <<= zeros; + v <<= zeros; + eta -= zeros; + i -= zeros; + /* Update the bottom bit of jac: when dividing g by an odd power of 2, + * if (f mod 8) is 3 or 5, the Jacobi symbol changes sign. */ + jac ^= (zeros & ((f >> 1) ^ (f >> 2))); + /* We're done once we've done 30 posdivsteps. */ + if (i == 0) break; + VERIFY_CHECK((f & 1) == 1); + VERIFY_CHECK((g & 1) == 1); + VERIFY_CHECK((u * f0 + v * g0) == f << (30 - i)); + VERIFY_CHECK((q * f0 + r * g0) == g << (30 - i)); + /* If eta is negative, negate it and replace f,g with g,f. */ + if (eta < 0) { + uint32_t tmp; + eta = -eta; + /* Update bottom bit of jac: when swapping f and g, the Jacobi symbol changes sign + * if both f and g are 3 mod 4. */ + jac ^= ((f & g) >> 1); + tmp = f; f = g; g = tmp; + tmp = u; u = q; q = tmp; + tmp = v; v = r; r = tmp; + } + /* eta is now >= 0. In what follows we're going to cancel out the bottom bits of g. No more + * than i can be cancelled out (as we'd be done before that point), and no more than eta+1 + * can be done as its sign will flip once that happens. */ + limit = ((int)eta + 1) > i ? i : ((int)eta + 1); + /* m is a mask for the bottom min(limit, 8) bits (our table only supports 8 bits). */ + VERIFY_CHECK(limit > 0 && limit <= 30); + m = (UINT32_MAX >> (32 - limit)) & 255U; + /* Find what multiple of f must be added to g to cancel its bottom min(limit, 8) bits. */ + w = (g * secp256k1_modinv32_inv256[(f >> 1) & 127]) & m; + /* Do so. */ + g += f * w; + q += u * w; + r += v * w; + VERIFY_CHECK((g & m) == 0); + } + /* Return data in t and return value. */ + t->u = (int32_t)u; + t->v = (int32_t)v; + t->q = (int32_t)q; + t->r = (int32_t)r; + /* The determinant of t must be a power of two. This guarantees that multiplication with t + * does not change the gcd of f and g, apart from adding a power-of-2 factor to it (which + * will be divided out again). As each divstep's individual matrix has determinant 2 or -2, + * the aggregate of 30 of them will have determinant 2^30 or -2^30. */ + VERIFY_CHECK((int64_t)t->u * t->r - (int64_t)t->v * t->q == ((int64_t)1) << 30 || + (int64_t)t->u * t->r - (int64_t)t->v * t->q == -(((int64_t)1) << 30)); + *jacp = jac; + return eta; +} + /* Compute (t/2^30) * [d, e] mod modulus, where t is a transition matrix for 30 divsteps. * * On input and output, d and e are in range (-2*modulus,modulus). All output limbs will be in range @@ -335,10 +415,8 @@ static void secp256k1_modinv32_update_de_30(secp256k1_modinv32_signed30 *d, secp VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(d, 9, &modinfo->modulus, 1) < 0); /* d < modulus */ VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(e, 9, &modinfo->modulus, -2) > 0); /* e > -2*modulus */ VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(e, 9, &modinfo->modulus, 1) < 0); /* e < modulus */ - VERIFY_CHECK((labs(u) + labs(v)) >= 0); /* |u|+|v| doesn't overflow */ - VERIFY_CHECK((labs(q) + labs(r)) >= 0); /* |q|+|r| doesn't overflow */ - VERIFY_CHECK((labs(u) + labs(v)) <= M30 + 1); /* |u|+|v| <= 2^30 */ - VERIFY_CHECK((labs(q) + labs(r)) <= M30 + 1); /* |q|+|r| <= 2^30 */ + VERIFY_CHECK(labs(u) <= (M30 + 1 - labs(v))); /* |u|+|v| <= 2^30 */ + VERIFY_CHECK(labs(q) <= (M30 + 1 - labs(r))); /* |q|+|r| <= 2^30 */ #endif /* [md,me] start as zero; plus [u,q] if d is negative; plus [v,r] if e is negative. */ sd = d->v[8] >> 31; @@ -584,4 +662,74 @@ static void secp256k1_modinv32_var(secp256k1_modinv32_signed30 *x, const secp256 *x = d; } +/* Do up to 50 iterations of 30 posdivsteps (up to 1500 steps; more is extremely rare) each until f=1. + * In VERIFY mode use a lower number of iterations (750, close to the median 756), so failure actually occurs. */ +#ifdef VERIFY +#define JACOBI32_ITERATIONS 25 +#else +#define JACOBI32_ITERATIONS 50 +#endif + +/* Compute the Jacobi symbol of x modulo modinfo->modulus (variable time). gcd(x,modulus) must be 1. */ +static int secp256k1_jacobi32_maybe_var(const secp256k1_modinv32_signed30 *x, const secp256k1_modinv32_modinfo *modinfo) { + /* Start with f=modulus, g=x, eta=-1. */ + secp256k1_modinv32_signed30 f = modinfo->modulus; + secp256k1_modinv32_signed30 g = *x; + int j, len = 9; + int32_t eta = -1; /* eta = -delta; delta is initially 1 */ + int32_t cond, fn, gn; + int jac = 0; + int count; + + /* The input limbs must all be non-negative. */ + VERIFY_CHECK(g.v[0] >= 0 && g.v[1] >= 0 && g.v[2] >= 0 && g.v[3] >= 0 && g.v[4] >= 0 && g.v[5] >= 0 && g.v[6] >= 0 && g.v[7] >= 0 && g.v[8] >= 0); + + /* If x > 0, then if the loop below converges, it converges to f=g=gcd(x,modulus). Since we + * require that gcd(x,modulus)=1 and modulus>=3, x cannot be 0. Thus, we must reach f=1 (or + * time out). */ + VERIFY_CHECK((g.v[0] | g.v[1] | g.v[2] | g.v[3] | g.v[4] | g.v[5] | g.v[6] | g.v[7] | g.v[8]) != 0); + + for (count = 0; count < JACOBI32_ITERATIONS; ++count) { + /* Compute transition matrix and new eta after 30 posdivsteps. */ + secp256k1_modinv32_trans2x2 t; + eta = secp256k1_modinv32_posdivsteps_30_var(eta, f.v[0] | ((uint32_t)f.v[1] << 30), g.v[0] | ((uint32_t)g.v[1] << 30), &t, &jac); + /* Update f,g using that transition matrix. */ +#ifdef VERIFY + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&f, len, &modinfo->modulus, 0) > 0); /* f > 0 */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&f, len, &modinfo->modulus, 1) <= 0); /* f <= modulus */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&g, len, &modinfo->modulus, 0) > 0); /* g > 0 */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&g, len, &modinfo->modulus, 1) < 0); /* g < modulus */ +#endif + secp256k1_modinv32_update_fg_30_var(len, &f, &g, &t); + /* If the bottom limb of f is 1, there is a chance that f=1. */ + if (f.v[0] == 1) { + cond = 0; + /* Check if the other limbs are also 0. */ + for (j = 1; j < len; ++j) { + cond |= f.v[j]; + } + /* If so, we're done. If f=1, the Jacobi symbol (g | f)=1. */ + if (cond == 0) return 1 - 2*(jac & 1); + } + + /* Determine if len>1 and limb (len-1) of both f and g is 0. */ + fn = f.v[len - 1]; + gn = g.v[len - 1]; + cond = ((int32_t)len - 2) >> 31; + cond |= fn; + cond |= gn; + /* If so, reduce length. */ + if (cond == 0) --len; +#ifdef VERIFY + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&f, len, &modinfo->modulus, 0) > 0); /* f > 0 */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&f, len, &modinfo->modulus, 1) <= 0); /* f <= modulus */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&g, len, &modinfo->modulus, 0) > 0); /* g > 0 */ + VERIFY_CHECK(secp256k1_modinv32_mul_cmp_30(&g, len, &modinfo->modulus, 1) < 0); /* g < modulus */ +#endif + } + + /* The loop failed to converge to f=g after 1500 iterations. Return 0, indicating unknown result. */ + return 0; +} + #endif /* SECP256K1_MODINV32_IMPL_H */ diff --git a/src/secp256k1/src/modinv64.h b/src/secp256k1/src/modinv64.h index da506dfa9f..f4208e6c23 100644 --- a/src/secp256k1/src/modinv64.h +++ b/src/secp256k1/src/modinv64.h @@ -7,10 +7,6 @@ #ifndef SECP256K1_MODINV64_H #define SECP256K1_MODINV64_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include "util.h" #ifndef SECP256K1_WIDEMUL_INT128 @@ -43,4 +39,9 @@ static void secp256k1_modinv64_var(secp256k1_modinv64_signed62 *x, const secp256 /* Same as secp256k1_modinv64_var, but constant time in x (not in the modulus). */ static void secp256k1_modinv64(secp256k1_modinv64_signed62 *x, const secp256k1_modinv64_modinfo *modinfo); +/* Compute the Jacobi symbol for (x | modinfo->modulus). x must be coprime with modulus (and thus + * cannot be 0, as modulus >= 3). All limbs of x must be non-negative. Returns 0 if the result + * cannot be computed. */ +static int secp256k1_jacobi64_maybe_var(const secp256k1_modinv64_signed62 *x, const secp256k1_modinv64_modinfo *modinfo); + #endif /* SECP256K1_MODINV64_H */ diff --git a/src/secp256k1/src/modinv64_impl.h b/src/secp256k1/src/modinv64_impl.h index 0743a9c821..e33727d385 100644 --- a/src/secp256k1/src/modinv64_impl.h +++ b/src/secp256k1/src/modinv64_impl.h @@ -7,10 +7,9 @@ #ifndef SECP256K1_MODINV64_IMPL_H #define SECP256K1_MODINV64_IMPL_H +#include "int128.h" #include "modinv64.h" -#include "util.h" - /* This file implements modular inversion based on the paper "Fast constant-time gcd computation and * modular inversion" by Daniel J. Bernstein and Bo-Yin Yang. * @@ -18,6 +17,15 @@ * implementation for N=62, using 62-bit signed limbs represented as int64_t. */ +/* Data type for transition matrices (see section 3 of explanation). + * + * t = [ u v ] + * [ q r ] + */ +typedef struct { + int64_t u, v, q, r; +} secp256k1_modinv64_trans2x2; + #ifdef VERIFY /* Helper function to compute the absolute value of an int64_t. * (we don't use abs/labs/llabs as it depends on the int sizes). */ @@ -31,16 +39,18 @@ static const secp256k1_modinv64_signed62 SECP256K1_SIGNED62_ONE = {{1}}; /* Compute a*factor and put it in r. All but the top limb in r will be in range [0,2^62). */ static void secp256k1_modinv64_mul_62(secp256k1_modinv64_signed62 *r, const secp256k1_modinv64_signed62 *a, int alen, int64_t factor) { - const int64_t M62 = (int64_t)(UINT64_MAX >> 2); - int128_t c = 0; + const uint64_t M62 = UINT64_MAX >> 2; + secp256k1_int128 c, d; int i; + secp256k1_i128_from_i64(&c, 0); for (i = 0; i < 4; ++i) { - if (i < alen) c += (int128_t)a->v[i] * factor; - r->v[i] = (int64_t)c & M62; c >>= 62; + if (i < alen) secp256k1_i128_accum_mul(&c, a->v[i], factor); + r->v[i] = secp256k1_i128_to_u64(&c) & M62; secp256k1_i128_rshift(&c, 62); } - if (4 < alen) c += (int128_t)a->v[4] * factor; - VERIFY_CHECK(c == (int64_t)c); - r->v[4] = (int64_t)c; + if (4 < alen) secp256k1_i128_accum_mul(&c, a->v[4], factor); + secp256k1_i128_from_i64(&d, secp256k1_i128_to_i64(&c)); + VERIFY_CHECK(secp256k1_i128_eq_var(&c, &d)); + r->v[4] = secp256k1_i128_to_i64(&c); } /* Return -1 for ab*factor. A has alen limbs; b has 5. */ @@ -60,6 +70,15 @@ static int secp256k1_modinv64_mul_cmp_62(const secp256k1_modinv64_signed62 *a, i } return 0; } + +/* Check if the determinant of t is equal to 1 << n. If abs, check if |det t| == 1 << n. */ +static int secp256k1_modinv64_det_check_pow2(const secp256k1_modinv64_trans2x2 *t, unsigned int n, int abs) { + secp256k1_int128 a; + secp256k1_i128_det(&a, t->u, t->v, t->q, t->r); + if (secp256k1_i128_check_pow2(&a, n, 1)) return 1; + if (abs && secp256k1_i128_check_pow2(&a, n, -1)) return 1; + return 0; +} #endif /* Take as input a signed62 number in range (-2*modulus,modulus), and add a multiple of the modulus @@ -136,15 +155,6 @@ static void secp256k1_modinv64_normalize_62(secp256k1_modinv64_signed62 *r, int6 #endif } -/* Data type for transition matrices (see section 3 of explanation). - * - * t = [ u v ] - * [ q r ] - */ -typedef struct { - int64_t u, v, q, r; -} secp256k1_modinv64_trans2x2; - /* Compute the transition matrix and eta for 59 divsteps (where zeta=-(delta+1/2)). * Note that the transformation matrix is scaled by 2^62 and not 2^59. * @@ -203,13 +213,15 @@ static int64_t secp256k1_modinv64_divsteps_59(int64_t zeta, uint64_t f0, uint64_ t->v = (int64_t)v; t->q = (int64_t)q; t->r = (int64_t)r; +#ifdef VERIFY /* The determinant of t must be a power of two. This guarantees that multiplication with t * does not change the gcd of f and g, apart from adding a power-of-2 factor to it (which * will be divided out again). As each divstep's individual matrix has determinant 2, the * aggregate of 59 of them will have determinant 2^59. Multiplying with the initial * 8*identity (which has determinant 2^6) means the overall outputs has determinant * 2^65. */ - VERIFY_CHECK((int128_t)t->u * t->r - (int128_t)t->v * t->q == ((int128_t)1) << 65); + VERIFY_CHECK(secp256k1_modinv64_det_check_pow2(t, 65, 0)); +#endif return zeta; } @@ -256,7 +268,7 @@ static int64_t secp256k1_modinv64_divsteps_62_var(int64_t eta, uint64_t f0, uint tmp = v; v = r; r = -tmp; /* Use a formula to cancel out up to 6 bits of g. Also, no more than i can be cancelled * out (as we'd be done before that point), and no more than eta+1 can be done as its - * will flip again once that happens. */ + * sign will flip again once that happens. */ limit = ((int)eta + 1) > i ? i : ((int)eta + 1); VERIFY_CHECK(limit > 0 && limit <= 62); /* m is a mask for the bottom min(limit, 6) bits. */ @@ -286,11 +298,105 @@ static int64_t secp256k1_modinv64_divsteps_62_var(int64_t eta, uint64_t f0, uint t->v = (int64_t)v; t->q = (int64_t)q; t->r = (int64_t)r; +#ifdef VERIFY /* The determinant of t must be a power of two. This guarantees that multiplication with t * does not change the gcd of f and g, apart from adding a power-of-2 factor to it (which * will be divided out again). As each divstep's individual matrix has determinant 2, the * aggregate of 62 of them will have determinant 2^62. */ - VERIFY_CHECK((int128_t)t->u * t->r - (int128_t)t->v * t->q == ((int128_t)1) << 62); + VERIFY_CHECK(secp256k1_modinv64_det_check_pow2(t, 62, 0)); +#endif + return eta; +} + +/* Compute the transition matrix and eta for 62 posdivsteps (variable time, eta=-delta), and keeps track + * of the Jacobi symbol along the way. f0 and g0 must be f and g mod 2^64 rather than 2^62, because + * Jacobi tracking requires knowing (f mod 8) rather than just (f mod 2). + * + * Input: eta: initial eta + * f0: bottom limb of initial f + * g0: bottom limb of initial g + * Output: t: transition matrix + * Input/Output: (*jacp & 1) is bitflipped if and only if the Jacobi symbol of (f | g) changes sign + * by applying the returned transformation matrix to it. The other bits of *jacp may + * change, but are meaningless. + * Return: final eta + */ +static int64_t secp256k1_modinv64_posdivsteps_62_var(int64_t eta, uint64_t f0, uint64_t g0, secp256k1_modinv64_trans2x2 *t, int *jacp) { + /* Transformation matrix; see comments in secp256k1_modinv64_divsteps_62. */ + uint64_t u = 1, v = 0, q = 0, r = 1; + uint64_t f = f0, g = g0, m; + uint32_t w; + int i = 62, limit, zeros; + int jac = *jacp; + + for (;;) { + /* Use a sentinel bit to count zeros only up to i. */ + zeros = secp256k1_ctz64_var(g | (UINT64_MAX << i)); + /* Perform zeros divsteps at once; they all just divide g by two. */ + g >>= zeros; + u <<= zeros; + v <<= zeros; + eta -= zeros; + i -= zeros; + /* Update the bottom bit of jac: when dividing g by an odd power of 2, + * if (f mod 8) is 3 or 5, the Jacobi symbol changes sign. */ + jac ^= (zeros & ((f >> 1) ^ (f >> 2))); + /* We're done once we've done 62 posdivsteps. */ + if (i == 0) break; + VERIFY_CHECK((f & 1) == 1); + VERIFY_CHECK((g & 1) == 1); + VERIFY_CHECK((u * f0 + v * g0) == f << (62 - i)); + VERIFY_CHECK((q * f0 + r * g0) == g << (62 - i)); + /* If eta is negative, negate it and replace f,g with g,f. */ + if (eta < 0) { + uint64_t tmp; + eta = -eta; + tmp = f; f = g; g = tmp; + tmp = u; u = q; q = tmp; + tmp = v; v = r; r = tmp; + /* Update bottom bit of jac: when swapping f and g, the Jacobi symbol changes sign + * if both f and g are 3 mod 4. */ + jac ^= ((f & g) >> 1); + /* Use a formula to cancel out up to 6 bits of g. Also, no more than i can be cancelled + * out (as we'd be done before that point), and no more than eta+1 can be done as its + * sign will flip again once that happens. */ + limit = ((int)eta + 1) > i ? i : ((int)eta + 1); + VERIFY_CHECK(limit > 0 && limit <= 62); + /* m is a mask for the bottom min(limit, 6) bits. */ + m = (UINT64_MAX >> (64 - limit)) & 63U; + /* Find what multiple of f must be added to g to cancel its bottom min(limit, 6) + * bits. */ + w = (f * g * (f * f - 2)) & m; + } else { + /* In this branch, use a simpler formula that only lets us cancel up to 4 bits of g, as + * eta tends to be smaller here. */ + limit = ((int)eta + 1) > i ? i : ((int)eta + 1); + VERIFY_CHECK(limit > 0 && limit <= 62); + /* m is a mask for the bottom min(limit, 4) bits. */ + m = (UINT64_MAX >> (64 - limit)) & 15U; + /* Find what multiple of f must be added to g to cancel its bottom min(limit, 4) + * bits. */ + w = f + (((f + 1) & 4) << 1); + w = (-w * g) & m; + } + g += f * w; + q += u * w; + r += v * w; + VERIFY_CHECK((g & m) == 0); + } + /* Return data in t and return value. */ + t->u = (int64_t)u; + t->v = (int64_t)v; + t->q = (int64_t)q; + t->r = (int64_t)r; +#ifdef VERIFY + /* The determinant of t must be a power of two. This guarantees that multiplication with t + * does not change the gcd of f and g, apart from adding a power-of-2 factor to it (which + * will be divided out again). As each divstep's individual matrix has determinant 2 or -2, + * the aggregate of 62 of them will have determinant 2^62 or -2^62. */ + VERIFY_CHECK(secp256k1_modinv64_det_check_pow2(t, 62, 1)); +#endif + *jacp = jac; return eta; } @@ -302,21 +408,19 @@ static int64_t secp256k1_modinv64_divsteps_62_var(int64_t eta, uint64_t f0, uint * This implements the update_de function from the explanation. */ static void secp256k1_modinv64_update_de_62(secp256k1_modinv64_signed62 *d, secp256k1_modinv64_signed62 *e, const secp256k1_modinv64_trans2x2 *t, const secp256k1_modinv64_modinfo* modinfo) { - const int64_t M62 = (int64_t)(UINT64_MAX >> 2); + const uint64_t M62 = UINT64_MAX >> 2; const int64_t d0 = d->v[0], d1 = d->v[1], d2 = d->v[2], d3 = d->v[3], d4 = d->v[4]; const int64_t e0 = e->v[0], e1 = e->v[1], e2 = e->v[2], e3 = e->v[3], e4 = e->v[4]; const int64_t u = t->u, v = t->v, q = t->q, r = t->r; int64_t md, me, sd, se; - int128_t cd, ce; + secp256k1_int128 cd, ce; #ifdef VERIFY VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(d, 5, &modinfo->modulus, -2) > 0); /* d > -2*modulus */ VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(d, 5, &modinfo->modulus, 1) < 0); /* d < modulus */ VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(e, 5, &modinfo->modulus, -2) > 0); /* e > -2*modulus */ VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(e, 5, &modinfo->modulus, 1) < 0); /* e < modulus */ - VERIFY_CHECK((secp256k1_modinv64_abs(u) + secp256k1_modinv64_abs(v)) >= 0); /* |u|+|v| doesn't overflow */ - VERIFY_CHECK((secp256k1_modinv64_abs(q) + secp256k1_modinv64_abs(r)) >= 0); /* |q|+|r| doesn't overflow */ - VERIFY_CHECK((secp256k1_modinv64_abs(u) + secp256k1_modinv64_abs(v)) <= M62 + 1); /* |u|+|v| <= 2^62 */ - VERIFY_CHECK((secp256k1_modinv64_abs(q) + secp256k1_modinv64_abs(r)) <= M62 + 1); /* |q|+|r| <= 2^62 */ + VERIFY_CHECK(secp256k1_modinv64_abs(u) <= (((int64_t)1 << 62) - secp256k1_modinv64_abs(v))); /* |u|+|v| <= 2^62 */ + VERIFY_CHECK(secp256k1_modinv64_abs(q) <= (((int64_t)1 << 62) - secp256k1_modinv64_abs(r))); /* |q|+|r| <= 2^62 */ #endif /* [md,me] start as zero; plus [u,q] if d is negative; plus [v,r] if e is negative. */ sd = d4 >> 63; @@ -324,54 +428,64 @@ static void secp256k1_modinv64_update_de_62(secp256k1_modinv64_signed62 *d, secp md = (u & sd) + (v & se); me = (q & sd) + (r & se); /* Begin computing t*[d,e]. */ - cd = (int128_t)u * d0 + (int128_t)v * e0; - ce = (int128_t)q * d0 + (int128_t)r * e0; + secp256k1_i128_mul(&cd, u, d0); + secp256k1_i128_accum_mul(&cd, v, e0); + secp256k1_i128_mul(&ce, q, d0); + secp256k1_i128_accum_mul(&ce, r, e0); /* Correct md,me so that t*[d,e]+modulus*[md,me] has 62 zero bottom bits. */ - md -= (modinfo->modulus_inv62 * (uint64_t)cd + md) & M62; - me -= (modinfo->modulus_inv62 * (uint64_t)ce + me) & M62; + md -= (modinfo->modulus_inv62 * secp256k1_i128_to_u64(&cd) + md) & M62; + me -= (modinfo->modulus_inv62 * secp256k1_i128_to_u64(&ce) + me) & M62; /* Update the beginning of computation for t*[d,e]+modulus*[md,me] now md,me are known. */ - cd += (int128_t)modinfo->modulus.v[0] * md; - ce += (int128_t)modinfo->modulus.v[0] * me; + secp256k1_i128_accum_mul(&cd, modinfo->modulus.v[0], md); + secp256k1_i128_accum_mul(&ce, modinfo->modulus.v[0], me); /* Verify that the low 62 bits of the computation are indeed zero, and then throw them away. */ - VERIFY_CHECK(((int64_t)cd & M62) == 0); cd >>= 62; - VERIFY_CHECK(((int64_t)ce & M62) == 0); ce >>= 62; + VERIFY_CHECK((secp256k1_i128_to_u64(&cd) & M62) == 0); secp256k1_i128_rshift(&cd, 62); + VERIFY_CHECK((secp256k1_i128_to_u64(&ce) & M62) == 0); secp256k1_i128_rshift(&ce, 62); /* Compute limb 1 of t*[d,e]+modulus*[md,me], and store it as output limb 0 (= down shift). */ - cd += (int128_t)u * d1 + (int128_t)v * e1; - ce += (int128_t)q * d1 + (int128_t)r * e1; + secp256k1_i128_accum_mul(&cd, u, d1); + secp256k1_i128_accum_mul(&cd, v, e1); + secp256k1_i128_accum_mul(&ce, q, d1); + secp256k1_i128_accum_mul(&ce, r, e1); if (modinfo->modulus.v[1]) { /* Optimize for the case where limb of modulus is zero. */ - cd += (int128_t)modinfo->modulus.v[1] * md; - ce += (int128_t)modinfo->modulus.v[1] * me; + secp256k1_i128_accum_mul(&cd, modinfo->modulus.v[1], md); + secp256k1_i128_accum_mul(&ce, modinfo->modulus.v[1], me); } - d->v[0] = (int64_t)cd & M62; cd >>= 62; - e->v[0] = (int64_t)ce & M62; ce >>= 62; + d->v[0] = secp256k1_i128_to_u64(&cd) & M62; secp256k1_i128_rshift(&cd, 62); + e->v[0] = secp256k1_i128_to_u64(&ce) & M62; secp256k1_i128_rshift(&ce, 62); /* Compute limb 2 of t*[d,e]+modulus*[md,me], and store it as output limb 1. */ - cd += (int128_t)u * d2 + (int128_t)v * e2; - ce += (int128_t)q * d2 + (int128_t)r * e2; + secp256k1_i128_accum_mul(&cd, u, d2); + secp256k1_i128_accum_mul(&cd, v, e2); + secp256k1_i128_accum_mul(&ce, q, d2); + secp256k1_i128_accum_mul(&ce, r, e2); if (modinfo->modulus.v[2]) { /* Optimize for the case where limb of modulus is zero. */ - cd += (int128_t)modinfo->modulus.v[2] * md; - ce += (int128_t)modinfo->modulus.v[2] * me; + secp256k1_i128_accum_mul(&cd, modinfo->modulus.v[2], md); + secp256k1_i128_accum_mul(&ce, modinfo->modulus.v[2], me); } - d->v[1] = (int64_t)cd & M62; cd >>= 62; - e->v[1] = (int64_t)ce & M62; ce >>= 62; + d->v[1] = secp256k1_i128_to_u64(&cd) & M62; secp256k1_i128_rshift(&cd, 62); + e->v[1] = secp256k1_i128_to_u64(&ce) & M62; secp256k1_i128_rshift(&ce, 62); /* Compute limb 3 of t*[d,e]+modulus*[md,me], and store it as output limb 2. */ - cd += (int128_t)u * d3 + (int128_t)v * e3; - ce += (int128_t)q * d3 + (int128_t)r * e3; + secp256k1_i128_accum_mul(&cd, u, d3); + secp256k1_i128_accum_mul(&cd, v, e3); + secp256k1_i128_accum_mul(&ce, q, d3); + secp256k1_i128_accum_mul(&ce, r, e3); if (modinfo->modulus.v[3]) { /* Optimize for the case where limb of modulus is zero. */ - cd += (int128_t)modinfo->modulus.v[3] * md; - ce += (int128_t)modinfo->modulus.v[3] * me; + secp256k1_i128_accum_mul(&cd, modinfo->modulus.v[3], md); + secp256k1_i128_accum_mul(&ce, modinfo->modulus.v[3], me); } - d->v[2] = (int64_t)cd & M62; cd >>= 62; - e->v[2] = (int64_t)ce & M62; ce >>= 62; + d->v[2] = secp256k1_i128_to_u64(&cd) & M62; secp256k1_i128_rshift(&cd, 62); + e->v[2] = secp256k1_i128_to_u64(&ce) & M62; secp256k1_i128_rshift(&ce, 62); /* Compute limb 4 of t*[d,e]+modulus*[md,me], and store it as output limb 3. */ - cd += (int128_t)u * d4 + (int128_t)v * e4; - ce += (int128_t)q * d4 + (int128_t)r * e4; - cd += (int128_t)modinfo->modulus.v[4] * md; - ce += (int128_t)modinfo->modulus.v[4] * me; - d->v[3] = (int64_t)cd & M62; cd >>= 62; - e->v[3] = (int64_t)ce & M62; ce >>= 62; + secp256k1_i128_accum_mul(&cd, u, d4); + secp256k1_i128_accum_mul(&cd, v, e4); + secp256k1_i128_accum_mul(&ce, q, d4); + secp256k1_i128_accum_mul(&ce, r, e4); + secp256k1_i128_accum_mul(&cd, modinfo->modulus.v[4], md); + secp256k1_i128_accum_mul(&ce, modinfo->modulus.v[4], me); + d->v[3] = secp256k1_i128_to_u64(&cd) & M62; secp256k1_i128_rshift(&cd, 62); + e->v[3] = secp256k1_i128_to_u64(&ce) & M62; secp256k1_i128_rshift(&ce, 62); /* What remains is limb 5 of t*[d,e]+modulus*[md,me]; store it as output limb 4. */ - d->v[4] = (int64_t)cd; - e->v[4] = (int64_t)ce; + d->v[4] = secp256k1_i128_to_i64(&cd); + e->v[4] = secp256k1_i128_to_i64(&ce); #ifdef VERIFY VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(d, 5, &modinfo->modulus, -2) > 0); /* d > -2*modulus */ VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(d, 5, &modinfo->modulus, 1) < 0); /* d < modulus */ @@ -385,40 +499,50 @@ static void secp256k1_modinv64_update_de_62(secp256k1_modinv64_signed62 *d, secp * This implements the update_fg function from the explanation. */ static void secp256k1_modinv64_update_fg_62(secp256k1_modinv64_signed62 *f, secp256k1_modinv64_signed62 *g, const secp256k1_modinv64_trans2x2 *t) { - const int64_t M62 = (int64_t)(UINT64_MAX >> 2); + const uint64_t M62 = UINT64_MAX >> 2; const int64_t f0 = f->v[0], f1 = f->v[1], f2 = f->v[2], f3 = f->v[3], f4 = f->v[4]; const int64_t g0 = g->v[0], g1 = g->v[1], g2 = g->v[2], g3 = g->v[3], g4 = g->v[4]; const int64_t u = t->u, v = t->v, q = t->q, r = t->r; - int128_t cf, cg; + secp256k1_int128 cf, cg; /* Start computing t*[f,g]. */ - cf = (int128_t)u * f0 + (int128_t)v * g0; - cg = (int128_t)q * f0 + (int128_t)r * g0; + secp256k1_i128_mul(&cf, u, f0); + secp256k1_i128_accum_mul(&cf, v, g0); + secp256k1_i128_mul(&cg, q, f0); + secp256k1_i128_accum_mul(&cg, r, g0); /* Verify that the bottom 62 bits of the result are zero, and then throw them away. */ - VERIFY_CHECK(((int64_t)cf & M62) == 0); cf >>= 62; - VERIFY_CHECK(((int64_t)cg & M62) == 0); cg >>= 62; + VERIFY_CHECK((secp256k1_i128_to_u64(&cf) & M62) == 0); secp256k1_i128_rshift(&cf, 62); + VERIFY_CHECK((secp256k1_i128_to_u64(&cg) & M62) == 0); secp256k1_i128_rshift(&cg, 62); /* Compute limb 1 of t*[f,g], and store it as output limb 0 (= down shift). */ - cf += (int128_t)u * f1 + (int128_t)v * g1; - cg += (int128_t)q * f1 + (int128_t)r * g1; - f->v[0] = (int64_t)cf & M62; cf >>= 62; - g->v[0] = (int64_t)cg & M62; cg >>= 62; + secp256k1_i128_accum_mul(&cf, u, f1); + secp256k1_i128_accum_mul(&cf, v, g1); + secp256k1_i128_accum_mul(&cg, q, f1); + secp256k1_i128_accum_mul(&cg, r, g1); + f->v[0] = secp256k1_i128_to_u64(&cf) & M62; secp256k1_i128_rshift(&cf, 62); + g->v[0] = secp256k1_i128_to_u64(&cg) & M62; secp256k1_i128_rshift(&cg, 62); /* Compute limb 2 of t*[f,g], and store it as output limb 1. */ - cf += (int128_t)u * f2 + (int128_t)v * g2; - cg += (int128_t)q * f2 + (int128_t)r * g2; - f->v[1] = (int64_t)cf & M62; cf >>= 62; - g->v[1] = (int64_t)cg & M62; cg >>= 62; + secp256k1_i128_accum_mul(&cf, u, f2); + secp256k1_i128_accum_mul(&cf, v, g2); + secp256k1_i128_accum_mul(&cg, q, f2); + secp256k1_i128_accum_mul(&cg, r, g2); + f->v[1] = secp256k1_i128_to_u64(&cf) & M62; secp256k1_i128_rshift(&cf, 62); + g->v[1] = secp256k1_i128_to_u64(&cg) & M62; secp256k1_i128_rshift(&cg, 62); /* Compute limb 3 of t*[f,g], and store it as output limb 2. */ - cf += (int128_t)u * f3 + (int128_t)v * g3; - cg += (int128_t)q * f3 + (int128_t)r * g3; - f->v[2] = (int64_t)cf & M62; cf >>= 62; - g->v[2] = (int64_t)cg & M62; cg >>= 62; + secp256k1_i128_accum_mul(&cf, u, f3); + secp256k1_i128_accum_mul(&cf, v, g3); + secp256k1_i128_accum_mul(&cg, q, f3); + secp256k1_i128_accum_mul(&cg, r, g3); + f->v[2] = secp256k1_i128_to_u64(&cf) & M62; secp256k1_i128_rshift(&cf, 62); + g->v[2] = secp256k1_i128_to_u64(&cg) & M62; secp256k1_i128_rshift(&cg, 62); /* Compute limb 4 of t*[f,g], and store it as output limb 3. */ - cf += (int128_t)u * f4 + (int128_t)v * g4; - cg += (int128_t)q * f4 + (int128_t)r * g4; - f->v[3] = (int64_t)cf & M62; cf >>= 62; - g->v[3] = (int64_t)cg & M62; cg >>= 62; + secp256k1_i128_accum_mul(&cf, u, f4); + secp256k1_i128_accum_mul(&cf, v, g4); + secp256k1_i128_accum_mul(&cg, q, f4); + secp256k1_i128_accum_mul(&cg, r, g4); + f->v[3] = secp256k1_i128_to_u64(&cf) & M62; secp256k1_i128_rshift(&cf, 62); + g->v[3] = secp256k1_i128_to_u64(&cg) & M62; secp256k1_i128_rshift(&cg, 62); /* What remains is limb 5 of t*[f,g]; store it as output limb 4. */ - f->v[4] = (int64_t)cf; - g->v[4] = (int64_t)cg; + f->v[4] = secp256k1_i128_to_i64(&cf); + g->v[4] = secp256k1_i128_to_i64(&cg); } /* Compute (t/2^62) * [f, g], where t is a transition matrix for 62 divsteps. @@ -428,33 +552,37 @@ static void secp256k1_modinv64_update_fg_62(secp256k1_modinv64_signed62 *f, secp * This implements the update_fg function from the explanation. */ static void secp256k1_modinv64_update_fg_62_var(int len, secp256k1_modinv64_signed62 *f, secp256k1_modinv64_signed62 *g, const secp256k1_modinv64_trans2x2 *t) { - const int64_t M62 = (int64_t)(UINT64_MAX >> 2); + const uint64_t M62 = UINT64_MAX >> 2; const int64_t u = t->u, v = t->v, q = t->q, r = t->r; int64_t fi, gi; - int128_t cf, cg; + secp256k1_int128 cf, cg; int i; VERIFY_CHECK(len > 0); /* Start computing t*[f,g]. */ fi = f->v[0]; gi = g->v[0]; - cf = (int128_t)u * fi + (int128_t)v * gi; - cg = (int128_t)q * fi + (int128_t)r * gi; + secp256k1_i128_mul(&cf, u, fi); + secp256k1_i128_accum_mul(&cf, v, gi); + secp256k1_i128_mul(&cg, q, fi); + secp256k1_i128_accum_mul(&cg, r, gi); /* Verify that the bottom 62 bits of the result are zero, and then throw them away. */ - VERIFY_CHECK(((int64_t)cf & M62) == 0); cf >>= 62; - VERIFY_CHECK(((int64_t)cg & M62) == 0); cg >>= 62; + VERIFY_CHECK((secp256k1_i128_to_u64(&cf) & M62) == 0); secp256k1_i128_rshift(&cf, 62); + VERIFY_CHECK((secp256k1_i128_to_u64(&cg) & M62) == 0); secp256k1_i128_rshift(&cg, 62); /* Now iteratively compute limb i=1..len of t*[f,g], and store them in output limb i-1 (shifting * down by 62 bits). */ for (i = 1; i < len; ++i) { fi = f->v[i]; gi = g->v[i]; - cf += (int128_t)u * fi + (int128_t)v * gi; - cg += (int128_t)q * fi + (int128_t)r * gi; - f->v[i - 1] = (int64_t)cf & M62; cf >>= 62; - g->v[i - 1] = (int64_t)cg & M62; cg >>= 62; + secp256k1_i128_accum_mul(&cf, u, fi); + secp256k1_i128_accum_mul(&cf, v, gi); + secp256k1_i128_accum_mul(&cg, q, fi); + secp256k1_i128_accum_mul(&cg, r, gi); + f->v[i - 1] = secp256k1_i128_to_u64(&cf) & M62; secp256k1_i128_rshift(&cf, 62); + g->v[i - 1] = secp256k1_i128_to_u64(&cg) & M62; secp256k1_i128_rshift(&cg, 62); } /* What remains is limb (len) of t*[f,g]; store it as output limb (len-1). */ - f->v[len - 1] = (int64_t)cf; - g->v[len - 1] = (int64_t)cg; + f->v[len - 1] = secp256k1_i128_to_i64(&cf); + g->v[len - 1] = secp256k1_i128_to_i64(&cg); } /* Compute the inverse of x modulo modinfo->modulus, and replace x with it (constant time in x). */ @@ -590,4 +718,74 @@ static void secp256k1_modinv64_var(secp256k1_modinv64_signed62 *x, const secp256 *x = d; } +/* Do up to 25 iterations of 62 posdivsteps (up to 1550 steps; more is extremely rare) each until f=1. + * In VERIFY mode use a lower number of iterations (744, close to the median 756), so failure actually occurs. */ +#ifdef VERIFY +#define JACOBI64_ITERATIONS 12 +#else +#define JACOBI64_ITERATIONS 25 +#endif + +/* Compute the Jacobi symbol of x modulo modinfo->modulus (variable time). gcd(x,modulus) must be 1. */ +static int secp256k1_jacobi64_maybe_var(const secp256k1_modinv64_signed62 *x, const secp256k1_modinv64_modinfo *modinfo) { + /* Start with f=modulus, g=x, eta=-1. */ + secp256k1_modinv64_signed62 f = modinfo->modulus; + secp256k1_modinv64_signed62 g = *x; + int j, len = 5; + int64_t eta = -1; /* eta = -delta; delta is initially 1 */ + int64_t cond, fn, gn; + int jac = 0; + int count; + + /* The input limbs must all be non-negative. */ + VERIFY_CHECK(g.v[0] >= 0 && g.v[1] >= 0 && g.v[2] >= 0 && g.v[3] >= 0 && g.v[4] >= 0); + + /* If x > 0, then if the loop below converges, it converges to f=g=gcd(x,modulus). Since we + * require that gcd(x,modulus)=1 and modulus>=3, x cannot be 0. Thus, we must reach f=1 (or + * time out). */ + VERIFY_CHECK((g.v[0] | g.v[1] | g.v[2] | g.v[3] | g.v[4]) != 0); + + for (count = 0; count < JACOBI64_ITERATIONS; ++count) { + /* Compute transition matrix and new eta after 62 posdivsteps. */ + secp256k1_modinv64_trans2x2 t; + eta = secp256k1_modinv64_posdivsteps_62_var(eta, f.v[0] | ((uint64_t)f.v[1] << 62), g.v[0] | ((uint64_t)g.v[1] << 62), &t, &jac); + /* Update f,g using that transition matrix. */ +#ifdef VERIFY + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&f, len, &modinfo->modulus, 0) > 0); /* f > 0 */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&f, len, &modinfo->modulus, 1) <= 0); /* f <= modulus */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&g, len, &modinfo->modulus, 0) > 0); /* g > 0 */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&g, len, &modinfo->modulus, 1) < 0); /* g < modulus */ +#endif + secp256k1_modinv64_update_fg_62_var(len, &f, &g, &t); + /* If the bottom limb of f is 1, there is a chance that f=1. */ + if (f.v[0] == 1) { + cond = 0; + /* Check if the other limbs are also 0. */ + for (j = 1; j < len; ++j) { + cond |= f.v[j]; + } + /* If so, we're done. When f=1, the Jacobi symbol (g | f)=1. */ + if (cond == 0) return 1 - 2*(jac & 1); + } + + /* Determine if len>1 and limb (len-1) of both f and g is 0. */ + fn = f.v[len - 1]; + gn = g.v[len - 1]; + cond = ((int64_t)len - 2) >> 63; + cond |= fn; + cond |= gn; + /* If so, reduce length. */ + if (cond == 0) --len; +#ifdef VERIFY + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&f, len, &modinfo->modulus, 0) > 0); /* f > 0 */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&f, len, &modinfo->modulus, 1) <= 0); /* f <= modulus */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&g, len, &modinfo->modulus, 0) > 0); /* g > 0 */ + VERIFY_CHECK(secp256k1_modinv64_mul_cmp_62(&g, len, &modinfo->modulus, 1) < 0); /* g < modulus */ +#endif + } + + /* The loop failed to converge to f=g after 1550 iterations. Return 0, indicating unknown result. */ + return 0; +} + #endif /* SECP256K1_MODINV64_IMPL_H */ diff --git a/src/secp256k1/src/modules/ecdh/bench_impl.h b/src/secp256k1/src/modules/ecdh/bench_impl.h index 94d833462f..c23aaa94d1 100644 --- a/src/secp256k1/src/modules/ecdh/bench_impl.h +++ b/src/secp256k1/src/modules/ecdh/bench_impl.h @@ -7,7 +7,7 @@ #ifndef SECP256K1_MODULE_ECDH_BENCH_H #define SECP256K1_MODULE_ECDH_BENCH_H -#include "../include/secp256k1_ecdh.h" +#include "../../../include/secp256k1_ecdh.h" typedef struct { secp256k1_context *ctx; @@ -42,7 +42,7 @@ static void bench_ecdh(void* arg, int iters) { } } -void run_ecdh_bench(int iters, int argc, char** argv) { +static void run_ecdh_bench(int iters, int argc, char** argv) { bench_ecdh_data data; int d = argc == 1; diff --git a/src/secp256k1/src/modules/ecdh/tests_impl.h b/src/secp256k1/src/modules/ecdh/tests_impl.h index 10b7075c38..fa6f232227 100644 --- a/src/secp256k1/src/modules/ecdh/tests_impl.h +++ b/src/secp256k1/src/modules/ecdh/tests_impl.h @@ -7,7 +7,7 @@ #ifndef SECP256K1_MODULE_ECDH_TESTS_H #define SECP256K1_MODULE_ECDH_TESTS_H -int ecdh_hash_function_test_fail(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { +static int ecdh_hash_function_test_fail(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { (void)output; (void)x; (void)y; @@ -15,7 +15,7 @@ int ecdh_hash_function_test_fail(unsigned char *output, const unsigned char *x, return 0; } -int ecdh_hash_function_custom(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { +static int ecdh_hash_function_custom(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { (void)data; /* Save x and y as uncompressed public key */ output[0] = 0x04; @@ -24,9 +24,9 @@ int ecdh_hash_function_custom(unsigned char *output, const unsigned char *x, con return 1; } -void test_ecdh_api(void) { +static void test_ecdh_api(void) { /* Setup context that just counts errors */ - secp256k1_context *tctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *tctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_pubkey point; unsigned char res[32]; unsigned char s_one[32] = { 0 }; @@ -53,14 +53,14 @@ void test_ecdh_api(void) { secp256k1_context_destroy(tctx); } -void test_ecdh_generator_basepoint(void) { +static void test_ecdh_generator_basepoint(void) { unsigned char s_one[32] = { 0 }; secp256k1_pubkey point[2]; int i; s_one[31] = 1; /* Check against pubkey creation when the basepoint is the generator */ - for (i = 0; i < 2 * count; ++i) { + for (i = 0; i < 2 * COUNT; ++i) { secp256k1_sha256 sha; unsigned char s_b32[32]; unsigned char output_ecdh[65]; @@ -72,20 +72,20 @@ void test_ecdh_generator_basepoint(void) { random_scalar_order(&s); secp256k1_scalar_get_b32(s_b32, &s); - CHECK(secp256k1_ec_pubkey_create(ctx, &point[0], s_one) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &point[1], s_b32) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point[0], s_one) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point[1], s_b32) == 1); /* compute using ECDH function with custom hash function */ - CHECK(secp256k1_ecdh(ctx, output_ecdh, &point[0], s_b32, ecdh_hash_function_custom, NULL) == 1); + CHECK(secp256k1_ecdh(CTX, output_ecdh, &point[0], s_b32, ecdh_hash_function_custom, NULL) == 1); /* compute "explicitly" */ - CHECK(secp256k1_ec_pubkey_serialize(ctx, point_ser, &point_ser_len, &point[1], SECP256K1_EC_UNCOMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, point_ser, &point_ser_len, &point[1], SECP256K1_EC_UNCOMPRESSED) == 1); /* compare */ CHECK(secp256k1_memcmp_var(output_ecdh, point_ser, 65) == 0); /* compute using ECDH function with default hash function */ - CHECK(secp256k1_ecdh(ctx, output_ecdh, &point[0], s_b32, NULL, NULL) == 1); + CHECK(secp256k1_ecdh(CTX, output_ecdh, &point[0], s_b32, NULL, NULL) == 1); /* compute "explicitly" */ - CHECK(secp256k1_ec_pubkey_serialize(ctx, point_ser, &point_ser_len, &point[1], SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, point_ser, &point_ser_len, &point[1], SECP256K1_EC_COMPRESSED) == 1); secp256k1_sha256_initialize(&sha); secp256k1_sha256_write(&sha, point_ser, point_ser_len); secp256k1_sha256_finalize(&sha, output_ser); @@ -94,7 +94,7 @@ void test_ecdh_generator_basepoint(void) { } } -void test_bad_scalar(void) { +static void test_bad_scalar(void) { unsigned char s_zero[32] = { 0 }; unsigned char s_overflow[32] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -110,21 +110,21 @@ void test_bad_scalar(void) { /* Create random point */ random_scalar_order(&rand); secp256k1_scalar_get_b32(s_rand, &rand); - CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_rand) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_rand) == 1); /* Try to multiply it by bad values */ - CHECK(secp256k1_ecdh(ctx, output, &point, s_zero, NULL, NULL) == 0); - CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow, NULL, NULL) == 0); + CHECK(secp256k1_ecdh(CTX, output, &point, s_zero, NULL, NULL) == 0); + CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, NULL, NULL) == 0); /* ...and a good one */ s_overflow[31] -= 1; - CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow, NULL, NULL) == 1); + CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, NULL, NULL) == 1); /* Hash function failure results in ecdh failure */ - CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow, ecdh_hash_function_test_fail, NULL) == 0); + CHECK(secp256k1_ecdh(CTX, output, &point, s_overflow, ecdh_hash_function_test_fail, NULL) == 0); } /** Test that ECDH(sG, 1/s) == ECDH((1/s)G, s) == ECDH(G, 1) for a few random s. */ -void test_result_basepoint(void) { +static void test_result_basepoint(void) { secp256k1_pubkey point; secp256k1_scalar rand; unsigned char s[32]; @@ -136,26 +136,26 @@ void test_result_basepoint(void) { unsigned char s_one[32] = { 0 }; s_one[31] = 1; - CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_one) == 1); - CHECK(secp256k1_ecdh(ctx, out_base, &point, s_one, NULL, NULL) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_one) == 1); + CHECK(secp256k1_ecdh(CTX, out_base, &point, s_one, NULL, NULL) == 1); - for (i = 0; i < 2 * count; i++) { + for (i = 0; i < 2 * COUNT; i++) { random_scalar_order(&rand); secp256k1_scalar_get_b32(s, &rand); secp256k1_scalar_inverse(&rand, &rand); secp256k1_scalar_get_b32(s_inv, &rand); - CHECK(secp256k1_ec_pubkey_create(ctx, &point, s) == 1); - CHECK(secp256k1_ecdh(ctx, out, &point, s_inv, NULL, NULL) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point, s) == 1); + CHECK(secp256k1_ecdh(CTX, out, &point, s_inv, NULL, NULL) == 1); CHECK(secp256k1_memcmp_var(out, out_base, 32) == 0); - CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_inv) == 1); - CHECK(secp256k1_ecdh(ctx, out_inv, &point, s, NULL, NULL) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &point, s_inv) == 1); + CHECK(secp256k1_ecdh(CTX, out_inv, &point, s, NULL, NULL) == 1); CHECK(secp256k1_memcmp_var(out_inv, out_base, 32) == 0); } } -void run_ecdh_tests(void) { +static void run_ecdh_tests(void) { test_ecdh_api(); test_ecdh_generator_basepoint(); test_bad_scalar(); diff --git a/src/secp256k1/src/modules/extrakeys/tests_exhaustive_impl.h b/src/secp256k1/src/modules/extrakeys/tests_exhaustive_impl.h index d4a2f5bdf4..5ecc90d50f 100644 --- a/src/secp256k1/src/modules/extrakeys/tests_exhaustive_impl.h +++ b/src/secp256k1/src/modules/extrakeys/tests_exhaustive_impl.h @@ -7,8 +7,8 @@ #ifndef SECP256K1_MODULE_EXTRAKEYS_TESTS_EXHAUSTIVE_H #define SECP256K1_MODULE_EXTRAKEYS_TESTS_EXHAUSTIVE_H -#include "src/modules/extrakeys/main_impl.h" #include "../../../include/secp256k1_extrakeys.h" +#include "main_impl.h" static void test_exhaustive_extrakeys(const secp256k1_context *ctx, const secp256k1_ge* group) { secp256k1_keypair keypair[EXHAUSTIVE_TEST_ORDER - 1]; diff --git a/src/secp256k1/src/modules/extrakeys/tests_impl.h b/src/secp256k1/src/modules/extrakeys/tests_impl.h index c8a99f4466..ae1655923b 100644 --- a/src/secp256k1/src/modules/extrakeys/tests_impl.h +++ b/src/secp256k1/src/modules/extrakeys/tests_impl.h @@ -9,14 +9,12 @@ #include "../../../include/secp256k1_extrakeys.h" -static secp256k1_context* api_test_context(int flags, int *ecount) { - secp256k1_context *ctx0 = secp256k1_context_create(flags); +static void set_counting_callbacks(secp256k1_context *ctx0, int *ecount) { secp256k1_context_set_error_callback(ctx0, counting_illegal_callback_fn, ecount); secp256k1_context_set_illegal_callback(ctx0, counting_illegal_callback_fn, ecount); - return ctx0; } -void test_xonly_pubkey(void) { +static void test_xonly_pubkey(void) { secp256k1_pubkey pk; secp256k1_xonly_pubkey xonly_pk, xonly_pk_tmp; secp256k1_ge pk1; @@ -31,56 +29,53 @@ void test_xonly_pubkey(void) { int i; int ecount; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount); - secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount); + + set_counting_callbacks(CTX, &ecount); secp256k1_testrand256(sk); memset(ones32, 0xFF, 32); secp256k1_testrand256(xy_sk); - CHECK(secp256k1_ec_pubkey_create(sign, &pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 1); /* Test xonly_pubkey_from_pubkey */ ecount = 0; - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(sign, &xonly_pk, &pk_parity, &pk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &xonly_pk, &pk_parity, &pk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, NULL, &pk_parity, &pk) == 0); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, NULL, &pk_parity, &pk) == 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, NULL) == 0); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, NULL) == 0); CHECK(ecount == 2); memset(&pk, 0, sizeof(pk)); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 0); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 0); CHECK(ecount == 3); /* Choose a secret key such that the resulting pubkey and xonly_pubkey match. */ memset(sk, 0, sizeof(sk)); sk[0] = 1; - CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 1); CHECK(secp256k1_memcmp_var(&pk, &xonly_pk, sizeof(pk)) == 0); CHECK(pk_parity == 0); /* Choose a secret key such that pubkey and xonly_pubkey are each others * negation. */ sk[0] = 2; - CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 1); CHECK(secp256k1_memcmp_var(&xonly_pk, &pk, sizeof(xonly_pk)) != 0); CHECK(pk_parity == 1); - secp256k1_pubkey_load(ctx, &pk1, &pk); - secp256k1_pubkey_load(ctx, &pk2, (secp256k1_pubkey *) &xonly_pk); + secp256k1_pubkey_load(CTX, &pk1, &pk); + secp256k1_pubkey_load(CTX, &pk2, (secp256k1_pubkey *) &xonly_pk); CHECK(secp256k1_fe_equal(&pk1.x, &pk2.x) == 1); secp256k1_fe_negate(&y, &pk2.y, 1); CHECK(secp256k1_fe_equal(&pk1.y, &y) == 1); /* Test xonly_pubkey_serialize and xonly_pubkey_parse */ ecount = 0; - CHECK(secp256k1_xonly_pubkey_serialize(none, NULL, &xonly_pk) == 0); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, NULL, &xonly_pk) == 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, NULL) == 0); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, NULL) == 0); CHECK(secp256k1_memcmp_var(buf32, zeros64, 32) == 0); CHECK(ecount == 2); { @@ -88,56 +83,52 @@ void test_xonly_pubkey(void) { * special casing. */ secp256k1_xonly_pubkey pk_tmp; memset(&pk_tmp, 0, sizeof(pk_tmp)); - CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk_tmp) == 0); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, &pk_tmp) == 0); } /* pubkey_load called illegal callback */ CHECK(ecount == 3); - CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, &xonly_pk) == 1); ecount = 0; - CHECK(secp256k1_xonly_pubkey_parse(none, NULL, buf32) == 0); + CHECK(secp256k1_xonly_pubkey_parse(CTX, NULL, buf32) == 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, NULL) == 0); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk, NULL) == 0); CHECK(ecount == 2); /* Serialization and parse roundtrip */ - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, NULL, &pk) == 1); - CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &xonly_pk) == 1); - CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, buf32) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, &xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk_tmp, buf32) == 1); CHECK(secp256k1_memcmp_var(&xonly_pk, &xonly_pk_tmp, sizeof(xonly_pk)) == 0); /* Test parsing invalid field elements */ memset(&xonly_pk, 1, sizeof(xonly_pk)); /* Overflowing field element */ - CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, ones32) == 0); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk, ones32) == 0); CHECK(secp256k1_memcmp_var(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0); memset(&xonly_pk, 1, sizeof(xonly_pk)); /* There's no point with x-coordinate 0 on secp256k1 */ - CHECK(secp256k1_xonly_pubkey_parse(none, &xonly_pk, zeros64) == 0); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk, zeros64) == 0); CHECK(secp256k1_memcmp_var(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0); /* If a random 32-byte string can not be parsed with ec_pubkey_parse * (because interpreted as X coordinate it does not correspond to a point on * the curve) then xonly_pubkey_parse should fail as well. */ - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { unsigned char rand33[33]; secp256k1_testrand256(&rand33[1]); rand33[0] = SECP256K1_TAG_PUBKEY_EVEN; - if (!secp256k1_ec_pubkey_parse(ctx, &pk, rand33, 33)) { + if (!secp256k1_ec_pubkey_parse(CTX, &pk, rand33, 33)) { memset(&xonly_pk, 1, sizeof(xonly_pk)); - CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 0); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk, &rand33[1]) == 0); CHECK(secp256k1_memcmp_var(&xonly_pk, zeros64, sizeof(xonly_pk)) == 0); } else { - CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk, &rand33[1]) == 1); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pk, &rand33[1]) == 1); } } CHECK(ecount == 2); - - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(verify); } -void test_xonly_pubkey_comparison(void) { +static void test_xonly_pubkey_comparison(void) { unsigned char pk1_ser[32] = { 0x58, 0x84, 0xb3, 0xa2, 0x4b, 0x97, 0x37, 0x88, 0x92, 0x38, 0xa6, 0x26, 0x62, 0x52, 0x35, 0x11, 0xd0, 0x9a, 0xa1, 0x1b, 0x80, 0x0b, 0x5e, 0x93, 0x80, 0x26, 0x11, 0xef, 0x67, 0x4b, 0xd9, 0x23 @@ -149,32 +140,31 @@ void test_xonly_pubkey_comparison(void) { secp256k1_xonly_pubkey pk1; secp256k1_xonly_pubkey pk2; int ecount = 0; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - CHECK(secp256k1_xonly_pubkey_parse(none, &pk1, pk1_ser) == 1); - CHECK(secp256k1_xonly_pubkey_parse(none, &pk2, pk2_ser) == 1); + set_counting_callbacks(CTX, &ecount); + + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk1, pk1_ser) == 1); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk2, pk2_ser) == 1); - CHECK(secp256k1_xonly_pubkey_cmp(none, NULL, &pk2) < 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, NULL, &pk2) < 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, NULL) > 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk1, NULL) > 0); CHECK(ecount == 2); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk2) < 0); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk1) > 0); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk1) == 0); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk2) == 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk1, &pk2) < 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk2, &pk1) > 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk1, &pk1) == 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk2, &pk2) == 0); CHECK(ecount == 2); memset(&pk1, 0, sizeof(pk1)); /* illegal pubkey */ - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk2) < 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk1, &pk2) < 0); CHECK(ecount == 3); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk1, &pk1) == 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk1, &pk1) == 0); CHECK(ecount == 5); - CHECK(secp256k1_xonly_pubkey_cmp(none, &pk2, &pk1) > 0); + CHECK(secp256k1_xonly_pubkey_cmp(CTX, &pk2, &pk1) > 0); CHECK(ecount == 6); - - secp256k1_context_destroy(none); } -void test_xonly_pubkey_tweak(void) { +static void test_xonly_pubkey_tweak(void) { unsigned char zeros64[64] = { 0 }; unsigned char overflows[32]; unsigned char sk[32]; @@ -186,50 +176,49 @@ void test_xonly_pubkey_tweak(void) { int i; int ecount; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount); - secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount); + + set_counting_callbacks(CTX, &ecount); memset(overflows, 0xff, sizeof(overflows)); secp256k1_testrand256(tweak); secp256k1_testrand256(sk); - CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &internal_pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &internal_xonly_pk, &pk_parity, &internal_pk) == 1); ecount = 0; - CHECK(secp256k1_xonly_pubkey_tweak_add(none, &output_pk, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add(sign, &output_pk, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, NULL, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, NULL, &internal_xonly_pk, tweak) == 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, NULL, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, NULL, tweak) == 0); CHECK(ecount == 2); /* NULL internal_xonly_pk zeroes the output_pk */ CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, NULL) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, NULL) == 0); CHECK(ecount == 3); /* NULL tweak zeroes the output_pk */ CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); /* Invalid tweak zeroes the output_pk */ - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, overflows) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, overflows) == 0); CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); /* A zero tweak is fine */ - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, zeros64) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, zeros64) == 1); /* Fails if the resulting key was infinity */ - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { secp256k1_scalar scalar_tweak; /* Because sk may be negated before adding, we need to try with tweak = * sk as well as tweak = -sk. */ secp256k1_scalar_set_b32(&scalar_tweak, sk, NULL); secp256k1_scalar_negate(&scalar_tweak, &scalar_tweak); secp256k1_scalar_get_b32(tweak, &scalar_tweak); - CHECK((secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, sk) == 0) - || (secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0)); + CHECK((secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, sk) == 0) + || (secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 0)); CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); } @@ -237,16 +226,12 @@ void test_xonly_pubkey_tweak(void) { memset(&internal_xonly_pk, 0, sizeof(internal_xonly_pk)); secp256k1_testrand256(tweak); ecount = 0; - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 0); CHECK(ecount == 1); CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); - - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(verify); } -void test_xonly_pubkey_tweak_check(void) { +static void test_xonly_pubkey_tweak_check(void) { unsigned char zeros64[64] = { 0 }; unsigned char overflows[32]; unsigned char sk[32]; @@ -260,64 +245,59 @@ void test_xonly_pubkey_tweak_check(void) { unsigned char tweak[32]; int ecount; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount); - secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount); + + set_counting_callbacks(CTX, &ecount); memset(overflows, 0xff, sizeof(overflows)); secp256k1_testrand256(tweak); secp256k1_testrand256(sk); - CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &internal_pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &internal_xonly_pk, &pk_parity, &internal_pk) == 1); ecount = 0; - CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &output_xonly_pk, &pk_parity, &output_pk) == 1); - CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &output_xonly_pk) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(none, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &output_xonly_pk, &pk_parity, &output_pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, &output_xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(sign, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, NULL, pk_parity, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, NULL, pk_parity, &internal_xonly_pk, tweak) == 0); CHECK(ecount == 1); /* invalid pk_parity value */ - CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, 2, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, 2, &internal_xonly_pk, tweak) == 0); CHECK(ecount == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, NULL, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, NULL, tweak) == 0); CHECK(ecount == 2); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, NULL) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, &internal_xonly_pk, NULL) == 0); CHECK(ecount == 3); memset(tweak, 1, sizeof(tweak)); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &internal_xonly_pk, NULL, &internal_pk) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, tweak) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &output_xonly_pk, &pk_parity, &output_pk) == 1); - CHECK(secp256k1_xonly_pubkey_serialize(ctx, output_pk32, &output_xonly_pk) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &internal_xonly_pk, NULL, &internal_pk) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &output_xonly_pk, &pk_parity, &output_pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, output_pk32, &output_xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk32, pk_parity, &internal_xonly_pk, tweak) == 1); /* Wrong pk_parity */ - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, !pk_parity, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk32, !pk_parity, &internal_xonly_pk, tweak) == 0); /* Wrong public key */ - CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &internal_xonly_pk) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, buf32, pk_parity, &internal_xonly_pk, tweak) == 0); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, buf32, &internal_xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, buf32, pk_parity, &internal_xonly_pk, tweak) == 0); /* Overflowing tweak not allowed */ - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, overflows) == 0); - CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, overflows) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk32, pk_parity, &internal_xonly_pk, overflows) == 0); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk, &internal_xonly_pk, overflows) == 0); CHECK(secp256k1_memcmp_var(&output_pk, zeros64, sizeof(output_pk)) == 0); CHECK(ecount == 3); - - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(verify); } /* Starts with an initial pubkey and recursively creates N_PUBKEYS - 1 * additional pubkeys by calling tweak_add. Then verifies every tweak starting * from the last pubkey. */ #define N_PUBKEYS 32 -void test_xonly_pubkey_tweak_recursive(void) { +static void test_xonly_pubkey_tweak_recursive(void) { unsigned char sk[32]; secp256k1_pubkey pk[N_PUBKEYS]; unsigned char pk_serialized[32]; @@ -325,28 +305,28 @@ void test_xonly_pubkey_tweak_recursive(void) { int i; secp256k1_testrand256(sk); - CHECK(secp256k1_ec_pubkey_create(ctx, &pk[0], sk) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk[0], sk) == 1); /* Add tweaks */ for (i = 0; i < N_PUBKEYS - 1; i++) { secp256k1_xonly_pubkey xonly_pk; memset(tweak[i], i + 1, sizeof(tweak[i])); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i]) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &pk[i + 1], &xonly_pk, tweak[i]) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk[i]) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &pk[i + 1], &xonly_pk, tweak[i]) == 1); } /* Verify tweaks */ for (i = N_PUBKEYS - 1; i > 0; i--) { secp256k1_xonly_pubkey xonly_pk; int pk_parity; - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk[i]) == 1); - CHECK(secp256k1_xonly_pubkey_serialize(ctx, pk_serialized, &xonly_pk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i - 1]) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, pk_serialized, pk_parity, &xonly_pk, tweak[i - 1]) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk[i]) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, pk_serialized, &xonly_pk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk[i - 1]) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, pk_serialized, pk_parity, &xonly_pk, tweak[i - 1]) == 1); } } #undef N_PUBKEYS -void test_keypair(void) { +static void test_keypair(void) { unsigned char sk[32]; unsigned char sk_tmp[32]; unsigned char zeros96[96] = { 0 }; @@ -356,12 +336,9 @@ void test_keypair(void) { secp256k1_xonly_pubkey xonly_pk, xonly_pk_tmp; int pk_parity, pk_parity_tmp; int ecount; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount); - secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount); - secp256k1_context *sttc = secp256k1_context_clone(secp256k1_context_no_precomp); - secp256k1_context_set_error_callback(sttc, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sttc, counting_illegal_callback_fn, &ecount); + + set_counting_callbacks(CTX, &ecount); + set_counting_callbacks(STATIC_CTX, &ecount); CHECK(sizeof(zeros96) == sizeof(keypair)); memset(overflows, 0xFF, sizeof(overflows)); @@ -369,107 +346,105 @@ void test_keypair(void) { /* Test keypair_create */ ecount = 0; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(none, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) != 0); CHECK(ecount == 0); - CHECK(secp256k1_keypair_create(verify, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) != 0); CHECK(ecount == 0); - CHECK(secp256k1_keypair_create(sign, NULL, sk) == 0); + CHECK(secp256k1_keypair_create(CTX, NULL, sk) == 0); CHECK(ecount == 1); - CHECK(secp256k1_keypair_create(sign, &keypair, NULL) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, NULL) == 0); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) == 0); CHECK(ecount == 2); - CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); CHECK(ecount == 2); - CHECK(secp256k1_keypair_create(sttc, &keypair, sk) == 0); + CHECK(secp256k1_keypair_create(STATIC_CTX, &keypair, sk) == 0); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) == 0); CHECK(ecount == 3); /* Invalid secret key */ - CHECK(secp256k1_keypair_create(sign, &keypair, zeros96) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, zeros96) == 0); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) == 0); - CHECK(secp256k1_keypair_create(sign, &keypair, overflows) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, overflows) == 0); CHECK(secp256k1_memcmp_var(zeros96, &keypair, sizeof(keypair)) == 0); /* Test keypair_pub */ ecount = 0; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_pub(none, &pk, &keypair) == 1); - CHECK(secp256k1_keypair_pub(none, NULL, &keypair) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_pub(CTX, &pk, &keypair) == 1); + CHECK(secp256k1_keypair_pub(CTX, NULL, &keypair) == 0); CHECK(ecount == 1); - CHECK(secp256k1_keypair_pub(none, &pk, NULL) == 0); + CHECK(secp256k1_keypair_pub(CTX, &pk, NULL) == 0); CHECK(ecount == 2); CHECK(secp256k1_memcmp_var(zeros96, &pk, sizeof(pk)) == 0); /* Using an invalid keypair is fine for keypair_pub */ memset(&keypair, 0, sizeof(keypair)); - CHECK(secp256k1_keypair_pub(none, &pk, &keypair) == 1); + CHECK(secp256k1_keypair_pub(CTX, &pk, &keypair) == 1); CHECK(secp256k1_memcmp_var(zeros96, &pk, sizeof(pk)) == 0); /* keypair holds the same pubkey as pubkey_create */ - CHECK(secp256k1_ec_pubkey_create(sign, &pk, sk) == 1); - CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); - CHECK(secp256k1_keypair_pub(none, &pk_tmp, &keypair) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_pub(CTX, &pk_tmp, &keypair) == 1); CHECK(secp256k1_memcmp_var(&pk, &pk_tmp, sizeof(pk)) == 0); /** Test keypair_xonly_pub **/ ecount = 0; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk, &pk_parity, &keypair) == 1); - CHECK(secp256k1_keypair_xonly_pub(none, NULL, &pk_parity, &keypair) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk, &pk_parity, &keypair) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, NULL, &pk_parity, &keypair) == 0); CHECK(ecount == 1); - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk, NULL, &keypair) == 1); - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk, &pk_parity, NULL) == 0); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk, NULL, &keypair) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk, &pk_parity, NULL) == 0); CHECK(ecount == 2); CHECK(secp256k1_memcmp_var(zeros96, &xonly_pk, sizeof(xonly_pk)) == 0); /* Using an invalid keypair will set the xonly_pk to 0 (first reset * xonly_pk). */ - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk, &pk_parity, &keypair) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk, &pk_parity, &keypair) == 1); memset(&keypair, 0, sizeof(keypair)); - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk, &pk_parity, &keypair) == 0); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk, &pk_parity, &keypair) == 0); CHECK(secp256k1_memcmp_var(zeros96, &xonly_pk, sizeof(xonly_pk)) == 0); CHECK(ecount == 3); /** keypair holds the same xonly pubkey as pubkey_create **/ - CHECK(secp256k1_ec_pubkey_create(sign, &pk, sk) == 1); - CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &xonly_pk, &pk_parity, &pk) == 1); - CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_pub(none, &xonly_pk_tmp, &pk_parity_tmp, &keypair) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, sk) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, &pk_parity, &pk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pk_tmp, &pk_parity_tmp, &keypair) == 1); CHECK(secp256k1_memcmp_var(&xonly_pk, &xonly_pk_tmp, sizeof(pk)) == 0); CHECK(pk_parity == pk_parity_tmp); /* Test keypair_seckey */ ecount = 0; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_sec(none, sk_tmp, &keypair) == 1); - CHECK(secp256k1_keypair_sec(none, NULL, &keypair) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_sec(CTX, sk_tmp, &keypair) == 1); + CHECK(secp256k1_keypair_sec(CTX, NULL, &keypair) == 0); CHECK(ecount == 1); - CHECK(secp256k1_keypair_sec(none, sk_tmp, NULL) == 0); + CHECK(secp256k1_keypair_sec(CTX, sk_tmp, NULL) == 0); CHECK(ecount == 2); CHECK(secp256k1_memcmp_var(zeros96, sk_tmp, sizeof(sk_tmp)) == 0); /* keypair returns the same seckey it got */ - CHECK(secp256k1_keypair_create(sign, &keypair, sk) == 1); - CHECK(secp256k1_keypair_sec(none, sk_tmp, &keypair) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_sec(CTX, sk_tmp, &keypair) == 1); CHECK(secp256k1_memcmp_var(sk, sk_tmp, sizeof(sk_tmp)) == 0); /* Using an invalid keypair is fine for keypair_seckey */ memset(&keypair, 0, sizeof(keypair)); - CHECK(secp256k1_keypair_sec(none, sk_tmp, &keypair) == 1); + CHECK(secp256k1_keypair_sec(CTX, sk_tmp, &keypair) == 1); CHECK(secp256k1_memcmp_var(zeros96, sk_tmp, sizeof(sk_tmp)) == 0); - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(verify); - secp256k1_context_destroy(sttc); + secp256k1_context_set_error_callback(STATIC_CTX, NULL, NULL); + secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); } -void test_keypair_add(void) { +static void test_keypair_add(void) { unsigned char sk[32]; secp256k1_keypair keypair; unsigned char overflows[32]; @@ -477,51 +452,50 @@ void test_keypair_add(void) { unsigned char tweak[32]; int i; int ecount = 0; - secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount); - secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount); - secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount); + + set_counting_callbacks(CTX, &ecount); CHECK(sizeof(zeros96) == sizeof(keypair)); secp256k1_testrand256(sk); secp256k1_testrand256(tweak); memset(overflows, 0xFF, 32); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(none, &keypair, tweak) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_keypair_xonly_tweak_add(sign, &keypair, tweak) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 1); CHECK(ecount == 0); - CHECK(secp256k1_keypair_xonly_tweak_add(verify, &keypair, tweak) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(verify, NULL, tweak) == 0); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, NULL, tweak) == 0); CHECK(ecount == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(verify, &keypair, NULL) == 0); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, NULL) == 0); CHECK(ecount == 2); /* This does not set the keypair to zeroes */ CHECK(secp256k1_memcmp_var(&keypair, zeros96, sizeof(keypair)) != 0); /* Invalid tweak zeroes the keypair */ - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(ctx, &keypair, overflows) == 0); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, overflows) == 0); CHECK(secp256k1_memcmp_var(&keypair, zeros96, sizeof(keypair)) == 0); /* A zero tweak is fine */ - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(ctx, &keypair, zeros96) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, zeros96) == 1); /* Fails if the resulting keypair was (sk=0, pk=infinity) */ - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { secp256k1_scalar scalar_tweak; secp256k1_keypair keypair_tmp; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); memcpy(&keypair_tmp, &keypair, sizeof(keypair)); /* Because sk may be negated before adding, we need to try with tweak = * sk as well as tweak = -sk. */ secp256k1_scalar_set_b32(&scalar_tweak, sk, NULL); secp256k1_scalar_negate(&scalar_tweak, &scalar_tweak); secp256k1_scalar_get_b32(tweak, &scalar_tweak); - CHECK((secp256k1_keypair_xonly_tweak_add(ctx, &keypair, sk) == 0) - || (secp256k1_keypair_xonly_tweak_add(ctx, &keypair_tmp, tweak) == 0)); + CHECK((secp256k1_keypair_xonly_tweak_add(CTX, &keypair, sk) == 0) + || (secp256k1_keypair_xonly_tweak_add(CTX, &keypair_tmp, tweak) == 0)); CHECK(secp256k1_memcmp_var(&keypair, zeros96, sizeof(keypair)) == 0 || secp256k1_memcmp_var(&keypair_tmp, zeros96, sizeof(keypair_tmp)) == 0); } @@ -530,23 +504,23 @@ void test_keypair_add(void) { memset(&keypair, 0, sizeof(keypair)); secp256k1_testrand256(tweak); ecount = 0; - CHECK(secp256k1_keypair_xonly_tweak_add(verify, &keypair, tweak) == 0); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 0); CHECK(ecount == 1); CHECK(secp256k1_memcmp_var(&keypair, zeros96, sizeof(keypair)) == 0); /* Only seckey part of keypair invalid */ - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); memset(&keypair, 0, 32); - CHECK(secp256k1_keypair_xonly_tweak_add(verify, &keypair, tweak) == 0); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 0); CHECK(ecount == 2); /* Only pubkey part of keypair invalid */ - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); memset(&keypair.data[32], 0, 64); - CHECK(secp256k1_keypair_xonly_tweak_add(verify, &keypair, tweak) == 0); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 0); CHECK(ecount == 3); /* Check that the keypair_tweak_add implementation is correct */ - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - for (i = 0; i < count; i++) { + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + for (i = 0; i < COUNT; i++) { secp256k1_xonly_pubkey internal_pk; secp256k1_xonly_pubkey output_pk; secp256k1_pubkey output_pk_xy; @@ -556,30 +530,27 @@ void test_keypair_add(void) { int pk_parity; secp256k1_testrand256(tweak); - CHECK(secp256k1_keypair_xonly_pub(ctx, &internal_pk, NULL, &keypair) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(ctx, &keypair, tweak) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &output_pk, &pk_parity, &keypair) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &internal_pk, NULL, &keypair) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &output_pk, &pk_parity, &keypair) == 1); /* Check that it passes xonly_pubkey_tweak_add_check */ - CHECK(secp256k1_xonly_pubkey_serialize(ctx, pk32, &output_pk) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, pk32, pk_parity, &internal_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, pk32, &output_pk) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, pk32, pk_parity, &internal_pk, tweak) == 1); /* Check that the resulting pubkey matches xonly_pubkey_tweak_add */ - CHECK(secp256k1_keypair_pub(ctx, &output_pk_xy, &keypair) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk_expected, &internal_pk, tweak) == 1); + CHECK(secp256k1_keypair_pub(CTX, &output_pk_xy, &keypair) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add(CTX, &output_pk_expected, &internal_pk, tweak) == 1); CHECK(secp256k1_memcmp_var(&output_pk_xy, &output_pk_expected, sizeof(output_pk_xy)) == 0); /* Check that the secret key in the keypair is tweaked correctly */ - CHECK(secp256k1_keypair_sec(none, sk32, &keypair) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &output_pk_expected, sk32) == 1); + CHECK(secp256k1_keypair_sec(CTX, sk32, &keypair) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &output_pk_expected, sk32) == 1); CHECK(secp256k1_memcmp_var(&output_pk_xy, &output_pk_expected, sizeof(output_pk_xy)) == 0); } - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(verify); } -void run_extrakeys_tests(void) { +static void run_extrakeys_tests(void) { /* xonly key test cases */ test_xonly_pubkey(); test_xonly_pubkey_tweak(); diff --git a/src/secp256k1/src/modules/recovery/bench_impl.h b/src/secp256k1/src/modules/recovery/bench_impl.h index 4a9e886910..57108d4524 100644 --- a/src/secp256k1/src/modules/recovery/bench_impl.h +++ b/src/secp256k1/src/modules/recovery/bench_impl.h @@ -7,7 +7,7 @@ #ifndef SECP256K1_MODULE_RECOVERY_BENCH_H #define SECP256K1_MODULE_RECOVERY_BENCH_H -#include "../include/secp256k1_recovery.h" +#include "../../../include/secp256k1_recovery.h" typedef struct { secp256k1_context *ctx; @@ -15,7 +15,7 @@ typedef struct { unsigned char sig[64]; } bench_recover_data; -void bench_recover(void* arg, int iters) { +static void bench_recover(void* arg, int iters) { int i; bench_recover_data *data = (bench_recover_data*)arg; secp256k1_pubkey pubkey; @@ -36,7 +36,7 @@ void bench_recover(void* arg, int iters) { } } -void bench_recover_setup(void* arg) { +static void bench_recover_setup(void* arg) { int i; bench_recover_data *data = (bench_recover_data*)arg; @@ -48,11 +48,11 @@ void bench_recover_setup(void* arg) { } } -void run_recovery_bench(int iters, int argc, char** argv) { +static void run_recovery_bench(int iters, int argc, char** argv) { bench_recover_data data; int d = argc == 1; - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) run_benchmark("ecdsa_recover", bench_recover, bench_recover_setup, NULL, &data, 10, iters); diff --git a/src/secp256k1/src/modules/recovery/tests_exhaustive_impl.h b/src/secp256k1/src/modules/recovery/tests_exhaustive_impl.h index 590a972ed3..6bbc02b9a8 100644 --- a/src/secp256k1/src/modules/recovery/tests_exhaustive_impl.h +++ b/src/secp256k1/src/modules/recovery/tests_exhaustive_impl.h @@ -7,10 +7,10 @@ #ifndef SECP256K1_MODULE_RECOVERY_EXHAUSTIVE_TESTS_H #define SECP256K1_MODULE_RECOVERY_EXHAUSTIVE_TESTS_H -#include "src/modules/recovery/main_impl.h" +#include "main_impl.h" #include "../../../include/secp256k1_recovery.h" -void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1_ge *group) { +static void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1_ge *group) { int i, j, k; uint64_t iter = 0; @@ -43,8 +43,7 @@ void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1 (k * (EXHAUSTIVE_TEST_ORDER - s)) % EXHAUSTIVE_TEST_ORDER == (i + r * j) % EXHAUSTIVE_TEST_ORDER); /* The recid's second bit is for conveying overflow (R.x value >= group order). * In the actual secp256k1 this is an astronomically unlikely event, but in the - * small group used here, it will be the case for all points except the ones where - * R.x=1 (which the group is specifically selected to have). + * small group used here, it will almost certainly be the case for all points. * Note that this isn't actually useful; full recovery would need to convey * floor(R.x / group_order), but only one bit is used as that is sufficient * in the real group. */ @@ -79,7 +78,7 @@ void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1 } } -void test_exhaustive_recovery_verify(const secp256k1_context *ctx, const secp256k1_ge *group) { +static void test_exhaustive_recovery_verify(const secp256k1_context *ctx, const secp256k1_ge *group) { /* This is essentially a copy of test_exhaustive_verify, with recovery added */ int s, r, msg, key; uint64_t iter = 0; diff --git a/src/secp256k1/src/modules/recovery/tests_impl.h b/src/secp256k1/src/modules/recovery/tests_impl.h index abf62f7f3a..3502c71ffe 100644 --- a/src/secp256k1/src/modules/recovery/tests_impl.h +++ b/src/secp256k1/src/modules/recovery/tests_impl.h @@ -28,13 +28,8 @@ static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned c return secp256k1_testrand_bits(1); } -void test_ecdsa_recovery_api(void) { +static void test_ecdsa_recovery_api(void) { /* Setup contexts that just count errors */ - secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - secp256k1_context *sttc = secp256k1_context_clone(secp256k1_context_no_precomp); secp256k1_pubkey pubkey; secp256k1_pubkey recpubkey; secp256k1_ecdsa_signature normal_sig; @@ -50,110 +45,89 @@ void test_ecdsa_recovery_api(void) { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(vrfy, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(both, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(sttc, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sttc, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(STATIC_CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(STATIC_CTX, counting_illegal_callback_fn, &ecount); /* Construct and verify corresponding public key. */ - CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, privkey) == 1); /* Check bad contexts and NULLs for signing */ ecount = 0; - CHECK(secp256k1_ecdsa_sign_recoverable(none, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, privkey, NULL, NULL) == 1); CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_sign_recoverable(sign, &recsig, message, privkey, NULL, NULL) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_sign_recoverable(vrfy, &recsig, message, privkey, NULL, NULL) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_sign_recoverable(both, NULL, message, privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, NULL, message, privkey, NULL, NULL) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, NULL, privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, NULL, privkey, NULL, NULL) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, NULL, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, NULL, NULL, NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_ecdsa_sign_recoverable(sttc, &recsig, message, privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(STATIC_CTX, &recsig, message, privkey, NULL, NULL) == 0); CHECK(ecount == 4); /* This will fail or succeed randomly, and in either case will not ARG_CHECK failure */ - secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, recovery_test_nonce_function, NULL); + secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, privkey, recovery_test_nonce_function, NULL); CHECK(ecount == 4); /* These will all fail, but not in ARG_CHECK way */ - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, zero_privkey, NULL, NULL) == 0); - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, over_privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, zero_privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, over_privkey, NULL, NULL) == 0); /* This one will succeed. */ - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, privkey, NULL, NULL) == 1); CHECK(ecount == 4); /* Check signing with a goofy nonce function */ /* Check bad contexts and NULLs for recovery */ ecount = 0; - CHECK(secp256k1_ecdsa_recover(none, &recpubkey, &recsig, message) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_recover(sign, &recpubkey, &recsig, message) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_recover(vrfy, &recpubkey, &recsig, message) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, message) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &recpubkey, &recsig, message) == 1); CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_recover(both, NULL, &recsig, message) == 0); + CHECK(secp256k1_ecdsa_recover(CTX, NULL, &recsig, message) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_recover(both, &recpubkey, NULL, message) == 0); + CHECK(secp256k1_ecdsa_recover(CTX, &recpubkey, NULL, message) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, NULL) == 0); + CHECK(secp256k1_ecdsa_recover(CTX, &recpubkey, &recsig, NULL) == 0); CHECK(ecount == 3); /* Check NULLs for conversion */ - CHECK(secp256k1_ecdsa_sign(both, &normal_sig, message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &normal_sig, message, privkey, NULL, NULL) == 1); ecount = 0; - CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, NULL, &recsig) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, NULL, &recsig) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, NULL) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, &normal_sig, NULL) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, &recsig) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, &normal_sig, &recsig) == 1); /* Check NULLs for de/serialization */ - CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &recsig, message, privkey, NULL, NULL) == 1); ecount = 0; - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, NULL, &recid, &recsig) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, NULL, &recid, &recsig) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, NULL, &recsig) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, sig, NULL, &recsig) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, NULL) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, sig, &recid, NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, &recsig) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, sig, &recid, &recsig) == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, NULL, sig, recid) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, NULL, sig, recid) == 0); CHECK(ecount == 4); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, NULL, recid) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, NULL, recid) == 0); CHECK(ecount == 5); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, -1) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, -1) == 0); CHECK(ecount == 6); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, 5) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, 5) == 0); CHECK(ecount == 7); /* overflow in signature will fail but not affect ecount */ memcpy(sig, over_privkey, 32); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, recid) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, recid) == 0); CHECK(ecount == 7); /* cleanup */ - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(vrfy); - secp256k1_context_destroy(both); - secp256k1_context_destroy(sttc); + secp256k1_context_set_error_callback(STATIC_CTX, NULL, NULL); + secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); } -void test_ecdsa_recovery_end_to_end(void) { +static void test_ecdsa_recovery_end_to_end(void) { unsigned char extra[32] = {0x00}; unsigned char privkey[32]; unsigned char message[32]; @@ -174,45 +148,45 @@ void test_ecdsa_recovery_end_to_end(void) { } /* Construct and verify corresponding public key. */ - CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, privkey) == 1); /* Serialize/parse compact and verify/recover. */ extra[0] = 0; - CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[0], message, privkey, NULL, NULL) == 1); - CHECK(secp256k1_ecdsa_sign(ctx, &signature[0], message, privkey, NULL, NULL) == 1); - CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[4], message, privkey, NULL, NULL) == 1); - CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[1], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &rsignature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &rsignature[4], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &rsignature[1], message, privkey, NULL, extra) == 1); extra[31] = 1; - CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[2], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &rsignature[2], message, privkey, NULL, extra) == 1); extra[31] = 0; extra[0] = 1; - CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[3], message, privkey, NULL, extra) == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &rsignature[4]) == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(CTX, &rsignature[3], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, sig, &recid, &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, &signature[4], &rsignature[4]) == 1); CHECK(secp256k1_memcmp_var(&signature[4], &signature[0], 64) == 0); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[4], message, &pubkey) == 1); memset(&rsignature[4], 0, sizeof(rsignature[4])); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, &signature[4], &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[4], message, &pubkey) == 1); /* Parse compact (with recovery id) and recover. */ - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &recpubkey, &rsignature[4], message) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &recpubkey, &rsignature[4], message) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &recpubkey, sizeof(pubkey)) == 0); /* Serialize/destroy/parse signature and verify again. */ - CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(CTX, sig, &recid, &rsignature[4]) == 1); sig[secp256k1_testrand_bits(6)] += 1 + secp256k1_testrand_int(255); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); - CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(CTX, &signature[4], &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[4], message, &pubkey) == 0); /* Recover again */ - CHECK(secp256k1_ecdsa_recover(ctx, &recpubkey, &rsignature[4], message) == 0 || + CHECK(secp256k1_ecdsa_recover(CTX, &recpubkey, &rsignature[4], message) == 0 || secp256k1_memcmp_var(&pubkey, &recpubkey, sizeof(pubkey)) != 0); } /* Tests several edge cases. */ -void test_ecdsa_recovery_edge_cases(void) { +static void test_ecdsa_recovery_edge_cases(void) { const unsigned char msg32[32] = { 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 'v', 'e', 'r', 'y', ' ', 's', @@ -248,14 +222,14 @@ void test_ecdsa_recovery_edge_cases(void) { secp256k1_ecdsa_signature sig; int recid; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 0)); - CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 1)); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 2)); - CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 3)); - CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sig64, 0)); + CHECK(!secp256k1_ecdsa_recover(CTX, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sig64, 1)); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sig64, 2)); + CHECK(!secp256k1_ecdsa_recover(CTX, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sig64, 3)); + CHECK(!secp256k1_ecdsa_recover(CTX, &pubkey, &rsig, msg32)); for (recid = 0; recid < 4; recid++) { int i; @@ -300,40 +274,40 @@ void test_ecdsa_recovery_edge_cases(void) { 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x45, 0x02, 0x01, 0x04 }; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigb64, recid) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 1); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sigb64, recid) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkeyb, &rsig, msg32) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbder, sizeof(sigbder)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 1); for (recid2 = 0; recid2 < 4; recid2++) { secp256k1_pubkey pubkey2b; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigb64, recid2) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkey2b, &rsig, msg32) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sigb64, recid2) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkey2b, &rsig, msg32) == 1); /* Verifying with (order + r,4) should always fail. */ - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderlong, sizeof(sigbderlong)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderlong, sizeof(sigbderlong)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 0); } /* DER parsing tests. */ /* Zero length r/s. */ - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder_zr, sizeof(sigcder_zr)) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder_zs, sizeof(sigcder_zs)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigcder_zr, sizeof(sigcder_zr)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigcder_zs, sizeof(sigcder_zs)) == 0); /* Leading zeros. */ - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt1, sizeof(sigbderalt1)) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt2, sizeof(sigbderalt2)) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt3, sizeof(sigbderalt3)) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt4, sizeof(sigbderalt4)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt1, sizeof(sigbderalt1)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt2, sizeof(sigbderalt2)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt3, sizeof(sigbderalt3)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt4, sizeof(sigbderalt4)) == 0); sigbderalt3[4] = 1; - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt3, sizeof(sigbderalt3)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt3, sizeof(sigbderalt3)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 0); sigbderalt4[7] = 1; - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt4, sizeof(sigbderalt4)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbderalt4, sizeof(sigbderalt4)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 0); /* Damage signature. */ sigbder[7]++; - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbder, sizeof(sigbder)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 0); sigbder[7]--; - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, 6) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder) - 1) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbder, 6) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbder, sizeof(sigbder) - 1) == 0); for(i = 0; i < 8; i++) { int c; unsigned char orig = sigbder[i]; @@ -343,7 +317,7 @@ void test_ecdsa_recovery_edge_cases(void) { continue; } sigbder[i] = c; - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 0 || secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigbder, sizeof(sigbder)) == 0 || secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyb) == 0); } sigbder[i] = orig; } @@ -364,33 +338,33 @@ void test_ecdsa_recovery_edge_cases(void) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, }; secp256k1_pubkey pubkeyc; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyc, &rsig, msg32) == 1); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkeyc, &rsig, msg32) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyc) == 1); sigcder[4] = 0; sigc64[31] = 0; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkeyb, &rsig, msg32) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyc) == 0); sigcder[4] = 1; sigcder[7] = 0; sigc64[31] = 1; sigc64[63] = 0; - CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); - CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 0); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 0); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(CTX, &pubkeyb, &rsig, msg32) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg32, &pubkeyc) == 0); } } -void run_recovery_tests(void) { +static void run_recovery_tests(void) { int i; - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { test_ecdsa_recovery_api(); } - for (i = 0; i < 64*count; i++) { + for (i = 0; i < 64*COUNT; i++) { test_ecdsa_recovery_end_to_end(); } test_ecdsa_recovery_edge_cases(); diff --git a/src/secp256k1/src/modules/schnorrsig/bench_impl.h b/src/secp256k1/src/modules/schnorrsig/bench_impl.h index 41f393c84d..93a878ede3 100644 --- a/src/secp256k1/src/modules/schnorrsig/bench_impl.h +++ b/src/secp256k1/src/modules/schnorrsig/bench_impl.h @@ -21,7 +21,7 @@ typedef struct { const unsigned char **msgs; } bench_schnorrsig_data; -void bench_schnorrsig_sign(void* arg, int iters) { +static void bench_schnorrsig_sign(void* arg, int iters) { bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg; int i; unsigned char msg[MSGLEN] = {0}; @@ -34,7 +34,7 @@ void bench_schnorrsig_sign(void* arg, int iters) { } } -void bench_schnorrsig_verify(void* arg, int iters) { +static void bench_schnorrsig_verify(void* arg, int iters) { bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg; int i; @@ -45,12 +45,12 @@ void bench_schnorrsig_verify(void* arg, int iters) { } } -void run_schnorrsig_bench(int iters, int argc, char** argv) { +static void run_schnorrsig_bench(int iters, int argc, char** argv) { int i; bench_schnorrsig_data data; int d = argc == 1; - data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); data.keypairs = (const secp256k1_keypair **)malloc(iters * sizeof(secp256k1_keypair *)); data.pk = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); data.msgs = (const unsigned char **)malloc(iters * sizeof(unsigned char *)); @@ -91,10 +91,12 @@ void run_schnorrsig_bench(int iters, int argc, char** argv) { free((void *)data.msgs[i]); free((void *)data.sigs[i]); } - free(data.keypairs); - free(data.pk); - free(data.msgs); - free(data.sigs); + + /* Casting to (void *) avoids a stupid warning in MSVC. */ + free((void *)data.keypairs); + free((void *)data.pk); + free((void *)data.msgs); + free((void *)data.sigs); secp256k1_context_destroy(data.ctx); } diff --git a/src/secp256k1/src/modules/schnorrsig/tests_exhaustive_impl.h b/src/secp256k1/src/modules/schnorrsig/tests_exhaustive_impl.h index d8df9dd2df..55f9028a63 100644 --- a/src/secp256k1/src/modules/schnorrsig/tests_exhaustive_impl.h +++ b/src/secp256k1/src/modules/schnorrsig/tests_exhaustive_impl.h @@ -8,7 +8,7 @@ #define SECP256K1_MODULE_SCHNORRSIG_TESTS_EXHAUSTIVE_H #include "../../../include/secp256k1_schnorrsig.h" -#include "src/modules/schnorrsig/main_impl.h" +#include "main_impl.h" static const unsigned char invalid_pubkey_bytes[][32] = { /* 0 */ diff --git a/src/secp256k1/src/modules/schnorrsig/tests_impl.h b/src/secp256k1/src/modules/schnorrsig/tests_impl.h index 25840b8fa7..062005ee63 100644 --- a/src/secp256k1/src/modules/schnorrsig/tests_impl.h +++ b/src/secp256k1/src/modules/schnorrsig/tests_impl.h @@ -12,7 +12,7 @@ /* Checks that a bit flip in the n_flip-th argument (that has n_bytes many * bytes) changes the hash function */ -void nonce_function_bip340_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes, size_t msglen, size_t algolen) { +static void nonce_function_bip340_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes, size_t msglen, size_t algolen) { unsigned char nonces[2][32]; CHECK(nonce_function_bip340(nonces[0], args[0], msglen, args[1], args[2], args[3], algolen, args[4]) == 1); secp256k1_testrand_flip(args[n_flip], n_bytes); @@ -23,7 +23,7 @@ void nonce_function_bip340_bitflip(unsigned char **args, size_t n_flip, size_t n /* Tests for the equality of two sha256 structs. This function only produces a * correct result if an integer multiple of 64 many bytes have been written * into the hash functions. */ -void test_sha256_eq(const secp256k1_sha256 *sha1, const secp256k1_sha256 *sha2) { +static void test_sha256_eq(const secp256k1_sha256 *sha1, const secp256k1_sha256 *sha2) { /* Is buffer fully consumed? */ CHECK((sha1->bytes & 0x3F) == 0); @@ -31,7 +31,7 @@ void test_sha256_eq(const secp256k1_sha256 *sha1, const secp256k1_sha256 *sha2) CHECK(secp256k1_memcmp_var(sha1->s, sha2->s, sizeof(sha1->s)) == 0); } -void run_nonce_function_bip340_tests(void) { +static void run_nonce_function_bip340_tests(void) { unsigned char tag[13] = "BIP0340/nonce"; unsigned char aux_tag[11] = "BIP0340/aux"; unsigned char algo[13] = "BIP0340/nonce"; @@ -72,7 +72,7 @@ void run_nonce_function_bip340_tests(void) { args[2] = pk; args[3] = algo; args[4] = aux_rand; - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { nonce_function_bip340_bitflip(args, 0, 32, msglen, algolen); nonce_function_bip340_bitflip(args, 1, 32, msglen, algolen); nonce_function_bip340_bitflip(args, 2, 32, msglen, algolen); @@ -90,7 +90,7 @@ void run_nonce_function_bip340_tests(void) { secp256k1_testrand_bytes_test(algo, algolen); CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, algo, algolen, NULL) == 1); - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { unsigned char nonce2[32]; uint32_t offset = secp256k1_testrand_int(msglen - 1); size_t msglen_tmp = (msglen + offset) % msglen; @@ -114,7 +114,7 @@ void run_nonce_function_bip340_tests(void) { CHECK(secp256k1_memcmp_var(nonce_z, nonce, 32) == 0); } -void test_schnorrsig_api(void) { +static void test_schnorrsig_api(void) { unsigned char sk1[32]; unsigned char sk2[32]; unsigned char sk3[32]; @@ -128,108 +128,82 @@ void test_schnorrsig_api(void) { secp256k1_schnorrsig_extraparams invalid_extraparams = {{ 0 }, NULL, NULL}; /** setup **/ - secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - secp256k1_context *sttc = secp256k1_context_clone(secp256k1_context_no_precomp); - int ecount; - - secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(vrfy, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(both, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(sttc, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sttc, counting_illegal_callback_fn, &ecount); + int ecount = 0; + + secp256k1_context_set_error_callback(CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(STATIC_CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(STATIC_CTX, counting_illegal_callback_fn, &ecount); secp256k1_testrand256(sk1); secp256k1_testrand256(sk2); secp256k1_testrand256(sk3); secp256k1_testrand256(msg); - CHECK(secp256k1_keypair_create(ctx, &keypairs[0], sk1) == 1); - CHECK(secp256k1_keypair_create(ctx, &keypairs[1], sk2) == 1); - CHECK(secp256k1_keypair_create(ctx, &keypairs[2], sk3) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk[0], NULL, &keypairs[0]) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk[1], NULL, &keypairs[1]) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk[2], NULL, &keypairs[2]) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypairs[0], sk1) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypairs[1], sk2) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypairs[2], sk3) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk[0], NULL, &keypairs[0]) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk[1], NULL, &keypairs[1]) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk[2], NULL, &keypairs[2]) == 1); memset(&zero_pk, 0, sizeof(zero_pk)); /** main test body **/ ecount = 0; - CHECK(secp256k1_schnorrsig_sign32(none, sig, msg, &keypairs[0], NULL) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign32(vrfy, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypairs[0], NULL) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign32(sign, NULL, msg, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(CTX, NULL, msg, &keypairs[0], NULL) == 0); CHECK(ecount == 1); - CHECK(secp256k1_schnorrsig_sign32(sign, sig, NULL, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, NULL, &keypairs[0], NULL) == 0); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, NULL, NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, NULL, NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &invalid_keypair, NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &invalid_keypair, NULL) == 0); CHECK(ecount == 4); - CHECK(secp256k1_schnorrsig_sign32(sttc, sig, msg, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(STATIC_CTX, sig, msg, &keypairs[0], NULL) == 0); CHECK(ecount == 5); ecount = 0; - CHECK(secp256k1_schnorrsig_sign_custom(none, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign_custom(vrfy, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign_custom(sign, NULL, msg, sizeof(msg), &keypairs[0], &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, NULL, msg, sizeof(msg), &keypairs[0], &extraparams) == 0); CHECK(ecount == 1); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, NULL, sizeof(msg), &keypairs[0], &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, NULL, sizeof(msg), &keypairs[0], &extraparams) == 0); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, NULL, 0, &keypairs[0], &extraparams) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, NULL, 0, &keypairs[0], &extraparams) == 1); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, msg, sizeof(msg), NULL, &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), NULL, &extraparams) == 0); CHECK(ecount == 3); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, msg, sizeof(msg), &invalid_keypair, &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &invalid_keypair, &extraparams) == 0); CHECK(ecount == 4); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, msg, sizeof(msg), &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], NULL) == 1); CHECK(ecount == 4); - CHECK(secp256k1_schnorrsig_sign_custom(sign, sig, msg, sizeof(msg), &keypairs[0], &invalid_extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypairs[0], &invalid_extraparams) == 0); CHECK(ecount == 5); - CHECK(secp256k1_schnorrsig_sign_custom(sttc, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(STATIC_CTX, sig, msg, sizeof(msg), &keypairs[0], &extraparams) == 0); CHECK(ecount == 6); ecount = 0; - CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1); - CHECK(secp256k1_schnorrsig_verify(none, sig, msg, sizeof(msg), &pk[0]) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_verify(sign, sig, msg, sizeof(msg), &pk[0]) == 1); - CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_verify(vrfy, sig, msg, sizeof(msg), &pk[0]) == 1); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk[0]) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_verify(vrfy, NULL, msg, sizeof(msg), &pk[0]) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, NULL, msg, sizeof(msg), &pk[0]) == 0); CHECK(ecount == 1); - CHECK(secp256k1_schnorrsig_verify(vrfy, sig, NULL, sizeof(msg), &pk[0]) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, NULL, sizeof(msg), &pk[0]) == 0); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_verify(vrfy, sig, NULL, 0, &pk[0]) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, NULL, 0, &pk[0]) == 0); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_verify(vrfy, sig, msg, sizeof(msg), NULL) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_schnorrsig_verify(vrfy, sig, msg, sizeof(msg), &zero_pk) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &zero_pk) == 0); CHECK(ecount == 4); - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(vrfy); - secp256k1_context_destroy(both); - secp256k1_context_destroy(sttc); + secp256k1_context_set_error_callback(STATIC_CTX, NULL, NULL); + secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); } /* Checks that hash initialized by secp256k1_schnorrsig_sha256_tagged has the * expected state. */ -void test_schnorrsig_sha256_tagged(void) { +static void test_schnorrsig_sha256_tagged(void) { unsigned char tag[17] = "BIP0340/challenge"; secp256k1_sha256 sha; secp256k1_sha256 sha_optimized; @@ -241,33 +215,33 @@ void test_schnorrsig_sha256_tagged(void) { /* Helper function for schnorrsig_bip_vectors * Signs the message and checks that it's the same as expected_sig. */ -void test_schnorrsig_bip_vectors_check_signing(const unsigned char *sk, const unsigned char *pk_serialized, const unsigned char *aux_rand, const unsigned char *msg32, const unsigned char *expected_sig) { +static void test_schnorrsig_bip_vectors_check_signing(const unsigned char *sk, const unsigned char *pk_serialized, const unsigned char *aux_rand, const unsigned char *msg32, const unsigned char *expected_sig) { unsigned char sig[64]; secp256k1_keypair keypair; secp256k1_xonly_pubkey pk, pk_expected; - CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); - CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg32, &keypair, aux_rand)); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg32, &keypair, aux_rand)); CHECK(secp256k1_memcmp_var(sig, expected_sig, 64) == 0); - CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk_expected, pk_serialized)); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk_expected, pk_serialized)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); CHECK(secp256k1_memcmp_var(&pk, &pk_expected, sizeof(pk)) == 0); - CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg32, 32, &pk)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg32, 32, &pk)); } /* Helper function for schnorrsig_bip_vectors * Checks that both verify and verify_batch (TODO) return the same value as expected. */ -void test_schnorrsig_bip_vectors_check_verify(const unsigned char *pk_serialized, const unsigned char *msg32, const unsigned char *sig, int expected) { +static void test_schnorrsig_bip_vectors_check_verify(const unsigned char *pk_serialized, const unsigned char *msg32, const unsigned char *sig, int expected) { secp256k1_xonly_pubkey pk; - CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk, pk_serialized)); - CHECK(expected == secp256k1_schnorrsig_verify(ctx, sig, msg32, 32, &pk)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &pk, pk_serialized)); + CHECK(expected == secp256k1_schnorrsig_verify(CTX, sig, msg32, 32, &pk)); } /* Test vectors according to BIP-340 ("Schnorr Signatures for secp256k1"). See * https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv. */ -void test_schnorrsig_bip_vectors(void) { +static void test_schnorrsig_bip_vectors(void) { { /* Test vector 0 */ const unsigned char sk[32] = { @@ -460,7 +434,7 @@ void test_schnorrsig_bip_vectors(void) { }; secp256k1_xonly_pubkey pk_parsed; /* No need to check the signature of the test vector as parsing the pubkey already fails */ - CHECK(!secp256k1_xonly_pubkey_parse(ctx, &pk_parsed, pk)); + CHECK(!secp256k1_xonly_pubkey_parse(CTX, &pk_parsed, pk)); } { /* Test vector 6 */ @@ -680,7 +654,7 @@ void test_schnorrsig_bip_vectors(void) { }; secp256k1_xonly_pubkey pk_parsed; /* No need to check the signature of the test vector as parsing the pubkey already fails */ - CHECK(!secp256k1_xonly_pubkey_parse(ctx, &pk_parsed, pk)); + CHECK(!secp256k1_xonly_pubkey_parse(CTX, &pk_parsed, pk)); } } @@ -725,7 +699,7 @@ static int nonce_function_overflowing(unsigned char *nonce32, const unsigned cha return 1; } -void test_schnorrsig_sign(void) { +static void test_schnorrsig_sign(void) { unsigned char sk[32]; secp256k1_xonly_pubkey pk; secp256k1_keypair keypair; @@ -738,36 +712,36 @@ void test_schnorrsig_sign(void) { secp256k1_testrand256(sk); secp256k1_testrand256(aux_rand); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); - CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pk)); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); /* Check that deprecated alias gives the same result */ - CHECK(secp256k1_schnorrsig_sign(ctx, sig2, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_sign(CTX, sig2, msg, &keypair, NULL) == 1); CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0); /* Test different nonce functions */ - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pk)); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); memset(sig, 1, sizeof(sig)); extraparams.noncefp = nonce_function_failing; - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 0); CHECK(secp256k1_memcmp_var(sig, zeros64, sizeof(sig)) == 0); memset(&sig, 1, sizeof(sig)); extraparams.noncefp = nonce_function_0; - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 0); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 0); CHECK(secp256k1_memcmp_var(sig, zeros64, sizeof(sig)) == 0); memset(&sig, 1, sizeof(sig)); extraparams.noncefp = nonce_function_overflowing; - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pk)); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &pk)); /* When using the default nonce function, schnorrsig_sign_custom produces * the same result as schnorrsig_sign with aux_rand = extraparams.ndata */ extraparams.noncefp = NULL; extraparams.ndata = aux_rand; - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); - CHECK(secp256k1_schnorrsig_sign32(ctx, sig2, msg, &keypair, extraparams.ndata) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig2, msg, &keypair, extraparams.ndata) == 1); CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0); } @@ -775,7 +749,7 @@ void test_schnorrsig_sign(void) { /* Creates N_SIGS valid signatures and verifies them with verify and * verify_batch (TODO). Then flips some bits and checks that verification now * fails. */ -void test_schnorrsig_sign_verify(void) { +static void test_schnorrsig_sign_verify(void) { unsigned char sk[32]; unsigned char msg[N_SIGS][32]; unsigned char sig[N_SIGS][64]; @@ -785,13 +759,13 @@ void test_schnorrsig_sign_verify(void) { secp256k1_scalar s; secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); - CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk)); + CHECK(secp256k1_keypair_xonly_pub(CTX, &pk, NULL, &keypair)); for (i = 0; i < N_SIGS; i++) { secp256k1_testrand256(msg[i]); - CHECK(secp256k1_schnorrsig_sign32(ctx, sig[i], msg[i], &keypair, NULL)); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[i], msg[i], sizeof(msg[i]), &pk)); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[i], msg[i], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[i], msg[i], sizeof(msg[i]), &pk)); } { @@ -801,40 +775,40 @@ void test_schnorrsig_sign_verify(void) { size_t byte_idx = secp256k1_testrand_bits(5); unsigned char xorbyte = secp256k1_testrand_int(254)+1; sig[sig_idx][byte_idx] ^= xorbyte; - CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); + CHECK(!secp256k1_schnorrsig_verify(CTX, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); sig[sig_idx][byte_idx] ^= xorbyte; byte_idx = secp256k1_testrand_bits(5); sig[sig_idx][32+byte_idx] ^= xorbyte; - CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); + CHECK(!secp256k1_schnorrsig_verify(CTX, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); sig[sig_idx][32+byte_idx] ^= xorbyte; byte_idx = secp256k1_testrand_bits(5); msg[sig_idx][byte_idx] ^= xorbyte; - CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); + CHECK(!secp256k1_schnorrsig_verify(CTX, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); msg[sig_idx][byte_idx] ^= xorbyte; /* Check that above bitflips have been reversed correctly */ - CHECK(secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); } /* Test overflowing s */ - CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL)); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[0], msg[0], sizeof(msg[0]), &pk)); memset(&sig[0][32], 0xFF, 32); - CHECK(!secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); + CHECK(!secp256k1_schnorrsig_verify(CTX, sig[0], msg[0], sizeof(msg[0]), &pk)); /* Test negative s */ - CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL)); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[0], msg[0], sizeof(msg[0]), &pk)); secp256k1_scalar_set_b32(&s, &sig[0][32], NULL); secp256k1_scalar_negate(&s, &s); secp256k1_scalar_get_b32(&sig[0][32], &s); - CHECK(!secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); + CHECK(!secp256k1_schnorrsig_verify(CTX, sig[0], msg[0], sizeof(msg[0]), &pk)); /* The empty message can be signed & verified */ - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig[0], NULL, 0, &keypair, NULL) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], NULL, 0, &pk) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig[0], NULL, 0, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[0], NULL, 0, &pk) == 1); { /* Test varying message lengths */ @@ -843,16 +817,16 @@ void test_schnorrsig_sign_verify(void) { for (i = 0; i < sizeof(msg_large); i += 32) { secp256k1_testrand256(&msg_large[i]); } - CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig[0], msg_large, msglen, &keypair, NULL) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg_large, msglen, &pk) == 1); + CHECK(secp256k1_schnorrsig_sign_custom(CTX, sig[0], msg_large, msglen, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[0], msg_large, msglen, &pk) == 1); /* Verification for a random wrong message length fails */ msglen = (msglen + (sizeof(msg_large) - 1)) % sizeof(msg_large); - CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg_large, msglen, &pk) == 0); + CHECK(secp256k1_schnorrsig_verify(CTX, sig[0], msg_large, msglen, &pk) == 0); } } #undef N_SIGS -void test_schnorrsig_taproot(void) { +static void test_schnorrsig_taproot(void) { unsigned char sk[32]; secp256k1_keypair keypair; secp256k1_xonly_pubkey internal_pk; @@ -866,36 +840,36 @@ void test_schnorrsig_taproot(void) { /* Create output key */ secp256k1_testrand256(sk); - CHECK(secp256k1_keypair_create(ctx, &keypair, sk) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &internal_pk, NULL, &keypair) == 1); + CHECK(secp256k1_keypair_create(CTX, &keypair, sk) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &internal_pk, NULL, &keypair) == 1); /* In actual taproot the tweak would be hash of internal_pk */ - CHECK(secp256k1_xonly_pubkey_serialize(ctx, tweak, &internal_pk) == 1); - CHECK(secp256k1_keypair_xonly_tweak_add(ctx, &keypair, tweak) == 1); - CHECK(secp256k1_keypair_xonly_pub(ctx, &output_pk, &pk_parity, &keypair) == 1); - CHECK(secp256k1_xonly_pubkey_serialize(ctx, output_pk_bytes, &output_pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, tweak, &internal_pk) == 1); + CHECK(secp256k1_keypair_xonly_tweak_add(CTX, &keypair, tweak) == 1); + CHECK(secp256k1_keypair_xonly_pub(CTX, &output_pk, &pk_parity, &keypair) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, output_pk_bytes, &output_pk) == 1); /* Key spend */ secp256k1_testrand256(msg); - CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(CTX, sig, msg, &keypair, NULL) == 1); /* Verify key spend */ - CHECK(secp256k1_xonly_pubkey_parse(ctx, &output_pk, output_pk_bytes) == 1); - CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &output_pk) == 1); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &output_pk, output_pk_bytes) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, sig, msg, sizeof(msg), &output_pk) == 1); /* Script spend */ - CHECK(secp256k1_xonly_pubkey_serialize(ctx, internal_pk_bytes, &internal_pk) == 1); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, internal_pk_bytes, &internal_pk) == 1); /* Verify script spend */ - CHECK(secp256k1_xonly_pubkey_parse(ctx, &internal_pk, internal_pk_bytes) == 1); - CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &internal_pk, internal_pk_bytes) == 1); + CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } -void run_schnorrsig_tests(void) { +static void run_schnorrsig_tests(void) { int i; run_nonce_function_bip340_tests(); test_schnorrsig_api(); test_schnorrsig_sha256_tagged(); test_schnorrsig_bip_vectors(); - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { test_schnorrsig_sign(); test_schnorrsig_sign_verify(); } diff --git a/src/secp256k1/src/precompute_ecmult.c b/src/secp256k1/src/precompute_ecmult.c index 5ccbcb3c57..10aba5b97d 100644 --- a/src/secp256k1/src/precompute_ecmult.c +++ b/src/secp256k1/src/precompute_ecmult.c @@ -7,17 +7,14 @@ #include #include -/* Autotools creates libsecp256k1-config.h, of which ECMULT_WINDOW_SIZE is needed. - ifndef guard so downstream users can define their own if they do not use autotools. */ -#if !defined(ECMULT_WINDOW_SIZE) -#include "libsecp256k1-config.h" -#endif - #include "../include/secp256k1.h" + #include "assumptions.h" #include "util.h" + #include "field_impl.h" #include "group_impl.h" +#include "int128_impl.h" #include "ecmult.h" #include "ecmult_compute_table_impl.h" @@ -71,9 +68,6 @@ int main(void) { fprintf(fp, "/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and\n"); fprintf(fp, " * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.\n"); fprintf(fp, " */\n"); - fprintf(fp, "#if defined HAVE_CONFIG_H\n"); - fprintf(fp, "# include \"libsecp256k1-config.h\"\n"); - fprintf(fp, "#endif\n"); fprintf(fp, "#include \"../include/secp256k1.h\"\n"); fprintf(fp, "#include \"group.h\"\n"); fprintf(fp, "#include \"ecmult.h\"\n"); diff --git a/src/secp256k1/src/precompute_ecmult_gen.c b/src/secp256k1/src/precompute_ecmult_gen.c index 7c6359c402..bfe212fdd2 100644 --- a/src/secp256k1/src/precompute_ecmult_gen.c +++ b/src/secp256k1/src/precompute_ecmult_gen.c @@ -8,9 +8,12 @@ #include #include "../include/secp256k1.h" + #include "assumptions.h" #include "util.h" + #include "group.h" +#include "int128_impl.h" #include "ecmult_gen.h" #include "ecmult_gen_compute_table_impl.h" @@ -30,9 +33,6 @@ int main(int argc, char **argv) { fprintf(fp, "/* This file was automatically generated by precompute_ecmult_gen. */\n"); fprintf(fp, "/* See ecmult_gen_impl.h for details about the contents of this file. */\n"); - fprintf(fp, "#if defined HAVE_CONFIG_H\n"); - fprintf(fp, "# include \"libsecp256k1-config.h\"\n"); - fprintf(fp, "#endif\n"); fprintf(fp, "#include \"../include/secp256k1.h\"\n"); fprintf(fp, "#include \"group.h\"\n"); fprintf(fp, "#include \"ecmult_gen.h\"\n"); diff --git a/src/secp256k1/src/precomputed_ecmult.c b/src/secp256k1/src/precomputed_ecmult.c index 3e67f37b74..fbc634ef1b 100644 --- a/src/secp256k1/src/precomputed_ecmult.c +++ b/src/secp256k1/src/precomputed_ecmult.c @@ -2,9 +2,6 @@ /* This file contains an array secp256k1_pre_g with odd multiples of the base point G and * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G. */ -#if defined HAVE_CONFIG_H -# include "libsecp256k1-config.h" -#endif #include "../include/secp256k1.h" #include "group.h" #include "ecmult.h" diff --git a/src/secp256k1/src/precomputed_ecmult.h b/src/secp256k1/src/precomputed_ecmult.h index 949b62c874..a4aa83e4ca 100644 --- a/src/secp256k1/src/precomputed_ecmult.h +++ b/src/secp256k1/src/precomputed_ecmult.h @@ -13,7 +13,9 @@ extern "C" { #include "group.h" #if defined(EXHAUSTIVE_TEST_ORDER) -#if EXHAUSTIVE_TEST_ORDER == 13 +# if EXHAUSTIVE_TEST_ORDER == 7 +# define WINDOW_G 3 +# elif EXHAUSTIVE_TEST_ORDER == 13 # define WINDOW_G 4 # elif EXHAUSTIVE_TEST_ORDER == 199 # define WINDOW_G 8 diff --git a/src/secp256k1/src/precomputed_ecmult_gen.c b/src/secp256k1/src/precomputed_ecmult_gen.c index d67291fcf5..e9d62a1c1b 100644 --- a/src/secp256k1/src/precomputed_ecmult_gen.c +++ b/src/secp256k1/src/precomputed_ecmult_gen.c @@ -1,8 +1,5 @@ /* This file was automatically generated by precompute_ecmult_gen. */ /* See ecmult_gen_impl.h for details about the contents of this file. */ -#if defined HAVE_CONFIG_H -# include "libsecp256k1-config.h" -#endif #include "../include/secp256k1.h" #include "group.h" #include "ecmult_gen.h" diff --git a/src/secp256k1/src/scalar.h b/src/secp256k1/src/scalar.h index aaaa3d8827..63c0d646a3 100644 --- a/src/secp256k1/src/scalar.h +++ b/src/secp256k1/src/scalar.h @@ -9,10 +9,6 @@ #include "util.h" -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #if defined(EXHAUSTIVE_TEST_ORDER) #include "scalar_low.h" #elif defined(SECP256K1_WIDEMUL_INT128) @@ -92,9 +88,10 @@ static int secp256k1_scalar_eq(const secp256k1_scalar *a, const secp256k1_scalar /** Find r1 and r2 such that r1+r2*2^128 = k. */ static void secp256k1_scalar_split_128(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *k); -/** Find r1 and r2 such that r1+r2*lambda = k, - * where r1 and r2 or their negations are maximum 128 bits long (see secp256k1_ge_mul_lambda). */ -static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *k); +/** Find r1 and r2 such that r1+r2*lambda = k, where r1 and r2 or their + * negations are maximum 128 bits long (see secp256k1_ge_mul_lambda). It is + * required that r1, r2, and k all point to different objects. */ +static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT r1, secp256k1_scalar * SECP256K1_RESTRICT r2, const secp256k1_scalar * SECP256K1_RESTRICT k); /** Multiply a and b (without taking the modulus!), divide by 2**shift, and round to the nearest integer. Shift must be at least 256. */ static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b, unsigned int shift); diff --git a/src/secp256k1/src/scalar_4x64_impl.h b/src/secp256k1/src/scalar_4x64_impl.h index a1def26fca..1b83575b3e 100644 --- a/src/secp256k1/src/scalar_4x64_impl.h +++ b/src/secp256k1/src/scalar_4x64_impl.h @@ -7,6 +7,8 @@ #ifndef SECP256K1_SCALAR_REPR_IMPL_H #define SECP256K1_SCALAR_REPR_IMPL_H +#include "checkmem.h" +#include "int128.h" #include "modinv64_impl.h" /* Limbs of the secp256k1 order. */ @@ -69,50 +71,61 @@ SECP256K1_INLINE static int secp256k1_scalar_check_overflow(const secp256k1_scal } SECP256K1_INLINE static int secp256k1_scalar_reduce(secp256k1_scalar *r, unsigned int overflow) { - uint128_t t; + secp256k1_uint128 t; VERIFY_CHECK(overflow <= 1); - t = (uint128_t)r->d[0] + overflow * SECP256K1_N_C_0; - r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)r->d[1] + overflow * SECP256K1_N_C_1; - r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)r->d[2] + overflow * SECP256K1_N_C_2; - r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint64_t)r->d[3]; - r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; + secp256k1_u128_from_u64(&t, r->d[0]); + secp256k1_u128_accum_u64(&t, overflow * SECP256K1_N_C_0); + r->d[0] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[1]); + secp256k1_u128_accum_u64(&t, overflow * SECP256K1_N_C_1); + r->d[1] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[2]); + secp256k1_u128_accum_u64(&t, overflow * SECP256K1_N_C_2); + r->d[2] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[3]); + r->d[3] = secp256k1_u128_to_u64(&t); return overflow; } static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { int overflow; - uint128_t t = (uint128_t)a->d[0] + b->d[0]; - r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)a->d[1] + b->d[1]; - r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)a->d[2] + b->d[2]; - r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)a->d[3] + b->d[3]; - r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - overflow = t + secp256k1_scalar_check_overflow(r); + secp256k1_uint128 t; + secp256k1_u128_from_u64(&t, a->d[0]); + secp256k1_u128_accum_u64(&t, b->d[0]); + r->d[0] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, a->d[1]); + secp256k1_u128_accum_u64(&t, b->d[1]); + r->d[1] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, a->d[2]); + secp256k1_u128_accum_u64(&t, b->d[2]); + r->d[2] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, a->d[3]); + secp256k1_u128_accum_u64(&t, b->d[3]); + r->d[3] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + overflow = secp256k1_u128_to_u64(&t) + secp256k1_scalar_check_overflow(r); VERIFY_CHECK(overflow == 0 || overflow == 1); secp256k1_scalar_reduce(r, overflow); return overflow; } static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { - uint128_t t; + secp256k1_uint128 t; VERIFY_CHECK(bit < 256); bit += ((uint32_t) flag - 1) & 0x100; /* forcing (bit >> 6) > 3 makes this a noop */ - t = (uint128_t)r->d[0] + (((uint64_t)((bit >> 6) == 0)) << (bit & 0x3F)); - r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)r->d[1] + (((uint64_t)((bit >> 6) == 1)) << (bit & 0x3F)); - r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)r->d[2] + (((uint64_t)((bit >> 6) == 2)) << (bit & 0x3F)); - r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; - t += (uint128_t)r->d[3] + (((uint64_t)((bit >> 6) == 3)) << (bit & 0x3F)); - r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; + secp256k1_u128_from_u64(&t, r->d[0]); + secp256k1_u128_accum_u64(&t, ((uint64_t)((bit >> 6) == 0)) << (bit & 0x3F)); + r->d[0] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[1]); + secp256k1_u128_accum_u64(&t, ((uint64_t)((bit >> 6) == 1)) << (bit & 0x3F)); + r->d[1] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[2]); + secp256k1_u128_accum_u64(&t, ((uint64_t)((bit >> 6) == 2)) << (bit & 0x3F)); + r->d[2] = secp256k1_u128_to_u64(&t); secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[3]); + secp256k1_u128_accum_u64(&t, ((uint64_t)((bit >> 6) == 3)) << (bit & 0x3F)); + r->d[3] = secp256k1_u128_to_u64(&t); #ifdef VERIFY - VERIFY_CHECK((t >> 64) == 0); - VERIFY_CHECK(secp256k1_scalar_check_overflow(r) == 0); + VERIFY_CHECK(secp256k1_u128_hi_u64(&t) == 0); #endif } @@ -141,14 +154,19 @@ SECP256K1_INLINE static int secp256k1_scalar_is_zero(const secp256k1_scalar *a) static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a) { uint64_t nonzero = 0xFFFFFFFFFFFFFFFFULL * (secp256k1_scalar_is_zero(a) == 0); - uint128_t t = (uint128_t)(~a->d[0]) + SECP256K1_N_0 + 1; - r->d[0] = t & nonzero; t >>= 64; - t += (uint128_t)(~a->d[1]) + SECP256K1_N_1; - r->d[1] = t & nonzero; t >>= 64; - t += (uint128_t)(~a->d[2]) + SECP256K1_N_2; - r->d[2] = t & nonzero; t >>= 64; - t += (uint128_t)(~a->d[3]) + SECP256K1_N_3; - r->d[3] = t & nonzero; + secp256k1_uint128 t; + secp256k1_u128_from_u64(&t, ~a->d[0]); + secp256k1_u128_accum_u64(&t, SECP256K1_N_0 + 1); + r->d[0] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, ~a->d[1]); + secp256k1_u128_accum_u64(&t, SECP256K1_N_1); + r->d[1] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, ~a->d[2]); + secp256k1_u128_accum_u64(&t, SECP256K1_N_2); + r->d[2] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, ~a->d[3]); + secp256k1_u128_accum_u64(&t, SECP256K1_N_3); + r->d[3] = secp256k1_u128_to_u64(&t) & nonzero; } SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) { @@ -172,14 +190,19 @@ static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { * if we are flag = 1, mask = 11...11 and this is identical to secp256k1_scalar_negate */ uint64_t mask = !flag - 1; uint64_t nonzero = (secp256k1_scalar_is_zero(r) != 0) - 1; - uint128_t t = (uint128_t)(r->d[0] ^ mask) + ((SECP256K1_N_0 + 1) & mask); - r->d[0] = t & nonzero; t >>= 64; - t += (uint128_t)(r->d[1] ^ mask) + (SECP256K1_N_1 & mask); - r->d[1] = t & nonzero; t >>= 64; - t += (uint128_t)(r->d[2] ^ mask) + (SECP256K1_N_2 & mask); - r->d[2] = t & nonzero; t >>= 64; - t += (uint128_t)(r->d[3] ^ mask) + (SECP256K1_N_3 & mask); - r->d[3] = t & nonzero; + secp256k1_uint128 t; + secp256k1_u128_from_u64(&t, r->d[0] ^ mask); + secp256k1_u128_accum_u64(&t, (SECP256K1_N_0 + 1) & mask); + r->d[0] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[1] ^ mask); + secp256k1_u128_accum_u64(&t, SECP256K1_N_1 & mask); + r->d[1] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[2] ^ mask); + secp256k1_u128_accum_u64(&t, SECP256K1_N_2 & mask); + r->d[2] = secp256k1_u128_to_u64(&t) & nonzero; secp256k1_u128_rshift(&t, 64); + secp256k1_u128_accum_u64(&t, r->d[3] ^ mask); + secp256k1_u128_accum_u64(&t, SECP256K1_N_3 & mask); + r->d[3] = secp256k1_u128_to_u64(&t) & nonzero; return 2 * (mask == 0) - 1; } @@ -189,9 +212,10 @@ static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { #define muladd(a,b) { \ uint64_t tl, th; \ { \ - uint128_t t = (uint128_t)a * b; \ - th = t >> 64; /* at most 0xFFFFFFFFFFFFFFFE */ \ - tl = t; \ + secp256k1_uint128 t; \ + secp256k1_u128_mul(&t, a, b); \ + th = secp256k1_u128_hi_u64(&t); /* at most 0xFFFFFFFFFFFFFFFE */ \ + tl = secp256k1_u128_to_u64(&t); \ } \ c0 += tl; /* overflow is handled on the next line */ \ th += (c0 < tl); /* at most 0xFFFFFFFFFFFFFFFF */ \ @@ -204,9 +228,10 @@ static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { #define muladd_fast(a,b) { \ uint64_t tl, th; \ { \ - uint128_t t = (uint128_t)a * b; \ - th = t >> 64; /* at most 0xFFFFFFFFFFFFFFFE */ \ - tl = t; \ + secp256k1_uint128 t; \ + secp256k1_u128_mul(&t, a, b); \ + th = secp256k1_u128_hi_u64(&t); /* at most 0xFFFFFFFFFFFFFFFE */ \ + tl = secp256k1_u128_to_u64(&t); \ } \ c0 += tl; /* overflow is handled on the next line */ \ th += (c0 < tl); /* at most 0xFFFFFFFFFFFFFFFF */ \ @@ -484,8 +509,8 @@ static void secp256k1_scalar_reduce_512(secp256k1_scalar *r, const uint64_t *l) : "g"(p0), "g"(p1), "g"(p2), "g"(p3), "g"(p4), "D"(r), "i"(SECP256K1_N_C_0), "i"(SECP256K1_N_C_1) : "rax", "rdx", "r8", "r9", "r10", "cc", "memory"); #else - uint128_t c; - uint64_t c0, c1, c2; + secp256k1_uint128 c128; + uint64_t c, c0, c1, c2; uint64_t n0 = l[4], n1 = l[5], n2 = l[6], n3 = l[7]; uint64_t m0, m1, m2, m3, m4, m5; uint32_t m6; @@ -542,14 +567,18 @@ static void secp256k1_scalar_reduce_512(secp256k1_scalar *r, const uint64_t *l) /* Reduce 258 bits into 256. */ /* r[0..3] = p[0..3] + p[4] * SECP256K1_N_C. */ - c = p0 + (uint128_t)SECP256K1_N_C_0 * p4; - r->d[0] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; - c += p1 + (uint128_t)SECP256K1_N_C_1 * p4; - r->d[1] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; - c += p2 + (uint128_t)p4; - r->d[2] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; - c += p3; - r->d[3] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; + secp256k1_u128_from_u64(&c128, p0); + secp256k1_u128_accum_mul(&c128, SECP256K1_N_C_0, p4); + r->d[0] = secp256k1_u128_to_u64(&c128); secp256k1_u128_rshift(&c128, 64); + secp256k1_u128_accum_u64(&c128, p1); + secp256k1_u128_accum_mul(&c128, SECP256K1_N_C_1, p4); + r->d[1] = secp256k1_u128_to_u64(&c128); secp256k1_u128_rshift(&c128, 64); + secp256k1_u128_accum_u64(&c128, p2); + secp256k1_u128_accum_u64(&c128, p4); + r->d[2] = secp256k1_u128_to_u64(&c128); secp256k1_u128_rshift(&c128, 64); + secp256k1_u128_accum_u64(&c128, p3); + r->d[3] = secp256k1_u128_to_u64(&c128); + c = secp256k1_u128_hi_u64(&c128); #endif /* Final reduction of r. */ @@ -782,7 +811,7 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint64_t mask0, mask1; - VG_CHECK_VERIFY(r->d, sizeof(r->d)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->d, sizeof(r->d)); mask0 = flag + ~((uint64_t)0); mask1 = ~mask0; r->d[0] = (r->d[0] & mask0) | (a->d[0] & mask1); diff --git a/src/secp256k1/src/scalar_8x32_impl.h b/src/secp256k1/src/scalar_8x32_impl.h index 62c7ae7156..c433adce75 100644 --- a/src/secp256k1/src/scalar_8x32_impl.h +++ b/src/secp256k1/src/scalar_8x32_impl.h @@ -7,6 +7,7 @@ #ifndef SECP256K1_SCALAR_REPR_IMPL_H #define SECP256K1_SCALAR_REPR_IMPL_H +#include "checkmem.h" #include "modinv32_impl.h" /* Limbs of the secp256k1 order. */ @@ -631,7 +632,7 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint32_t mask0, mask1; - VG_CHECK_VERIFY(r->d, sizeof(r->d)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r->d, sizeof(r->d)); mask0 = flag + ~((uint32_t)0); mask1 = ~mask0; r->d[0] = (r->d[0] & mask0) | (a->d[0] & mask1); diff --git a/src/secp256k1/src/scalar_impl.h b/src/secp256k1/src/scalar_impl.h index 1b690e3944..bed7f95fcb 100644 --- a/src/secp256k1/src/scalar_impl.h +++ b/src/secp256k1/src/scalar_impl.h @@ -14,10 +14,6 @@ #include "scalar.h" #include "util.h" -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #if defined(EXHAUSTIVE_TEST_ORDER) #include "scalar_low_impl.h" #elif defined(SECP256K1_WIDEMUL_INT128) @@ -37,15 +33,18 @@ static int secp256k1_scalar_set_b32_seckey(secp256k1_scalar *r, const unsigned c return (!overflow) & (!secp256k1_scalar_is_zero(r)); } -/* These parameters are generated using sage/gen_exhaustive_groups.sage. */ #if defined(EXHAUSTIVE_TEST_ORDER) -# if EXHAUSTIVE_TEST_ORDER == 13 +/* Begin of section generated by sage/gen_exhaustive_groups.sage. */ +# if EXHAUSTIVE_TEST_ORDER == 7 +# define EXHAUSTIVE_TEST_LAMBDA 2 +# elif EXHAUSTIVE_TEST_ORDER == 13 # define EXHAUSTIVE_TEST_LAMBDA 9 # elif EXHAUSTIVE_TEST_ORDER == 199 # define EXHAUSTIVE_TEST_LAMBDA 92 # else # error No known lambda for the specified exhaustive test group order. # endif +/* End of section generated by sage/gen_exhaustive_groups.sage. */ /** * Find r1 and r2 given k, such that r1 + r2 * lambda == k mod n; unlike in the @@ -53,7 +52,10 @@ static int secp256k1_scalar_set_b32_seckey(secp256k1_scalar *r, const unsigned c * nontrivial to get full test coverage for the exhaustive tests. We therefore * (arbitrarily) set r2 = k + 5 (mod n) and r1 = k - r2 * lambda (mod n). */ -static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *k) { +static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT r1, secp256k1_scalar * SECP256K1_RESTRICT r2, const secp256k1_scalar * SECP256K1_RESTRICT k) { + VERIFY_CHECK(r1 != k); + VERIFY_CHECK(r2 != k); + VERIFY_CHECK(r1 != r2); *r2 = (*k + 5) % EXHAUSTIVE_TEST_ORDER; *r1 = (*k + (EXHAUSTIVE_TEST_ORDER - *r2) * EXHAUSTIVE_TEST_LAMBDA) % EXHAUSTIVE_TEST_ORDER; } @@ -120,7 +122,7 @@ static void secp256k1_scalar_split_lambda_verify(const secp256k1_scalar *r1, con * * See proof below. */ -static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *k) { +static void secp256k1_scalar_split_lambda(secp256k1_scalar * SECP256K1_RESTRICT r1, secp256k1_scalar * SECP256K1_RESTRICT r2, const secp256k1_scalar * SECP256K1_RESTRICT k) { secp256k1_scalar c1, c2; static const secp256k1_scalar minus_b1 = SECP256K1_SCALAR_CONST( 0x00000000UL, 0x00000000UL, 0x00000000UL, 0x00000000UL, @@ -140,6 +142,7 @@ static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar ); VERIFY_CHECK(r1 != k); VERIFY_CHECK(r2 != k); + VERIFY_CHECK(r1 != r2); /* these _var calls are constant time since the shift amount is constant */ secp256k1_scalar_mul_shift_var(&c1, k, &g1, 384); secp256k1_scalar_mul_shift_var(&c2, k, &g2, 384); diff --git a/src/secp256k1/src/scalar_low_impl.h b/src/secp256k1/src/scalar_low_impl.h index 7176f0b2ca..e780083339 100644 --- a/src/secp256k1/src/scalar_low_impl.h +++ b/src/secp256k1/src/scalar_low_impl.h @@ -7,6 +7,7 @@ #ifndef SECP256K1_SCALAR_REPR_IMPL_H #define SECP256K1_SCALAR_REPR_IMPL_H +#include "checkmem.h" #include "scalar.h" #include @@ -115,7 +116,7 @@ SECP256K1_INLINE static int secp256k1_scalar_eq(const secp256k1_scalar *a, const static SECP256K1_INLINE void secp256k1_scalar_cmov(secp256k1_scalar *r, const secp256k1_scalar *a, int flag) { uint32_t mask0, mask1; - VG_CHECK_VERIFY(r, sizeof(*r)); + SECP256K1_CHECKMEM_CHECK_VERIFY(r, sizeof(*r)); mask0 = flag + ~((uint32_t)0); mask1 = ~mask0; *r = (*r & mask0) | (*a & mask1); diff --git a/src/secp256k1/src/scratch_impl.h b/src/secp256k1/src/scratch_impl.h index 688e18eb66..f71a20b963 100644 --- a/src/secp256k1/src/scratch_impl.h +++ b/src/secp256k1/src/scratch_impl.h @@ -25,11 +25,11 @@ static secp256k1_scratch* secp256k1_scratch_create(const secp256k1_callback* err static void secp256k1_scratch_destroy(const secp256k1_callback* error_callback, secp256k1_scratch* scratch) { if (scratch != NULL) { - VERIFY_CHECK(scratch->alloc_size == 0); /* all checkpoints should be applied */ if (secp256k1_memcmp_var(scratch->magic, "scratch", 8) != 0) { secp256k1_callback_call(error_callback, "invalid scratch space"); return; } + VERIFY_CHECK(scratch->alloc_size == 0); /* all checkpoints should be applied */ memset(scratch->magic, 0, sizeof(scratch->magic)); free(scratch); } diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c index 8f34c35283..7af333ca90 100644 --- a/src/secp256k1/src/secp256k1.c +++ b/src/secp256k1/src/secp256k1.c @@ -4,13 +4,26 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ +/* This is a C project. It should not be compiled with a C++ compiler, + * and we error out if we detect one. + * + * We still want to be able to test the project with a C++ compiler + * because it is still good to know if this will lead to real trouble, so + * there is a possibility to override the check. But be warned that + * compiling with a C++ compiler is not supported. */ +#if defined(__cplusplus) && !defined(SECP256K1_CPLUSPLUS_TEST_OVERRIDE) +#error Trying to compile a C project with a C++ compiler. +#endif + #define SECP256K1_BUILD #include "../include/secp256k1.h" #include "../include/secp256k1_preallocated.h" #include "assumptions.h" +#include "checkmem.h" #include "util.h" + #include "field_impl.h" #include "scalar_impl.h" #include "group_impl.h" @@ -20,6 +33,7 @@ #include "ecdsa_impl.h" #include "eckey_impl.h" #include "hash_impl.h" +#include "int128_impl.h" #include "scratch_impl.h" #include "selftest.h" @@ -27,10 +41,6 @@ # error "secp256k1.h processed without SECP256K1_BUILD defined while building secp256k1.c" #endif -#if defined(VALGRIND) -# include -#endif - #define ARG_CHECK(cond) do { \ if (EXPECT(!(cond), 0)) { \ secp256k1_callback_call(&ctx->illegal_callback, #cond); \ @@ -38,12 +48,15 @@ } \ } while(0) -#define ARG_CHECK_NO_RETURN(cond) do { \ +#define ARG_CHECK_VOID(cond) do { \ if (EXPECT(!(cond), 0)) { \ secp256k1_callback_call(&ctx->illegal_callback, #cond); \ + return; \ } \ } while(0) +/* Note that whenever you change the context struct, you must also change the + * context_eq function. */ struct secp256k1_context_struct { secp256k1_ecmult_gen_context ecmult_gen_ctx; secp256k1_callback illegal_callback; @@ -51,13 +64,29 @@ struct secp256k1_context_struct { int declassify; }; -static const secp256k1_context secp256k1_context_no_precomp_ = { +static const secp256k1_context secp256k1_context_static_ = { { 0 }, { secp256k1_default_illegal_callback_fn, 0 }, { secp256k1_default_error_callback_fn, 0 }, 0 }; -const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_no_precomp_; +const secp256k1_context *secp256k1_context_static = &secp256k1_context_static_; +const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_static_; + +/* Helper function that determines if a context is proper, i.e., is not the static context or a copy thereof. + * + * This is intended for "context" functions such as secp256k1_context_clone. Function which need specific + * features of a context should still check for these features directly. For example, a function that needs + * ecmult_gen should directly check for the existence of the ecmult_gen context. */ +static int secp256k1_context_is_proper(const secp256k1_context* ctx) { + return secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx); +} + +void secp256k1_selftest(void) { + if (!secp256k1_selftest_passes()) { + secp256k1_callback_call(&default_error_callback, "self test failed"); + } +} size_t secp256k1_context_preallocated_size(unsigned int flags) { size_t ret = sizeof(secp256k1_context); @@ -70,22 +99,26 @@ size_t secp256k1_context_preallocated_size(unsigned int flags) { return 0; } + if (EXPECT(!SECP256K1_CHECKMEM_RUNNING() && (flags & SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY), 0)) { + secp256k1_callback_call(&default_illegal_callback, + "Declassify flag requires running with memory checking"); + return 0; + } + return ret; } size_t secp256k1_context_preallocated_clone_size(const secp256k1_context* ctx) { - size_t ret = sizeof(secp256k1_context); VERIFY_CHECK(ctx != NULL); - return ret; + ARG_CHECK(secp256k1_context_is_proper(ctx)); + return sizeof(secp256k1_context); } secp256k1_context* secp256k1_context_preallocated_create(void* prealloc, unsigned int flags) { size_t prealloc_size; secp256k1_context* ret; - if (!secp256k1_selftest()) { - secp256k1_callback_call(&default_error_callback, "self test failed"); - } + secp256k1_selftest(); prealloc_size = secp256k1_context_preallocated_size(flags); if (prealloc_size == 0) { @@ -119,6 +152,7 @@ secp256k1_context* secp256k1_context_preallocated_clone(const secp256k1_context* secp256k1_context* ret; VERIFY_CHECK(ctx != NULL); ARG_CHECK(prealloc != NULL); + ARG_CHECK(secp256k1_context_is_proper(ctx)); ret = (secp256k1_context*)prealloc; *ret = *ctx; @@ -130,6 +164,8 @@ secp256k1_context* secp256k1_context_clone(const secp256k1_context* ctx) { size_t prealloc_size; VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_context_is_proper(ctx)); + prealloc_size = secp256k1_context_preallocated_clone_size(ctx); ret = (secp256k1_context*)checked_malloc(&ctx->error_callback, prealloc_size); ret = secp256k1_context_preallocated_clone(ctx, ret); @@ -137,21 +173,33 @@ secp256k1_context* secp256k1_context_clone(const secp256k1_context* ctx) { } void secp256k1_context_preallocated_destroy(secp256k1_context* ctx) { - ARG_CHECK_NO_RETURN(ctx != secp256k1_context_no_precomp); - if (ctx != NULL) { - secp256k1_ecmult_gen_context_clear(&ctx->ecmult_gen_ctx); + ARG_CHECK_VOID(ctx == NULL || secp256k1_context_is_proper(ctx)); + + /* Defined as noop */ + if (ctx == NULL) { + return; } + + secp256k1_ecmult_gen_context_clear(&ctx->ecmult_gen_ctx); } void secp256k1_context_destroy(secp256k1_context* ctx) { - if (ctx != NULL) { - secp256k1_context_preallocated_destroy(ctx); - free(ctx); + ARG_CHECK_VOID(ctx == NULL || secp256k1_context_is_proper(ctx)); + + /* Defined as noop */ + if (ctx == NULL) { + return; } + + secp256k1_context_preallocated_destroy(ctx); + free(ctx); } void secp256k1_context_set_illegal_callback(secp256k1_context* ctx, void (*fun)(const char* message, void* data), const void* data) { - ARG_CHECK_NO_RETURN(ctx != secp256k1_context_no_precomp); + /* We compare pointers instead of checking secp256k1_context_is_proper() here + because setting callbacks is allowed on *copies* of the static context: + it's harmless and makes testing easier. */ + ARG_CHECK_VOID(ctx != secp256k1_context_static); if (fun == NULL) { fun = secp256k1_default_illegal_callback_fn; } @@ -160,7 +208,10 @@ void secp256k1_context_set_illegal_callback(secp256k1_context* ctx, void (*fun)( } void secp256k1_context_set_error_callback(secp256k1_context* ctx, void (*fun)(const char* message, void* data), const void* data) { - ARG_CHECK_NO_RETURN(ctx != secp256k1_context_no_precomp); + /* We compare pointers instead of checking secp256k1_context_is_proper() here + because setting callbacks is allowed on *copies* of the static context: + it's harmless and makes testing easier. */ + ARG_CHECK_VOID(ctx != secp256k1_context_static); if (fun == NULL) { fun = secp256k1_default_error_callback_fn; } @@ -179,17 +230,10 @@ void secp256k1_scratch_space_destroy(const secp256k1_context *ctx, secp256k1_scr } /* Mark memory as no-longer-secret for the purpose of analysing constant-time behaviour - * of the software. This is setup for use with valgrind but could be substituted with - * the appropriate instrumentation for other analysis tools. + * of the software. */ static SECP256K1_INLINE void secp256k1_declassify(const secp256k1_context* ctx, const void *p, size_t len) { -#if defined(VALGRIND) - if (EXPECT(ctx->declassify,0)) VALGRIND_MAKE_MEM_DEFINED(p, len); -#else - (void)ctx; - (void)p; - (void)len; -#endif + if (EXPECT(ctx->declassify, 0)) SECP256K1_CHECKMEM_DEFINE(p, len); } static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) { @@ -705,6 +749,8 @@ int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context* ctx, secp256k1_pubkey int secp256k1_context_randomize(secp256k1_context* ctx, const unsigned char *seed32) { VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_context_is_proper(ctx)); + if (secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)) { secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); } diff --git a/src/secp256k1/src/selftest.h b/src/secp256k1/src/selftest.h index 52f1b8442e..d083ac9524 100644 --- a/src/secp256k1/src/selftest.h +++ b/src/secp256k1/src/selftest.h @@ -25,7 +25,7 @@ static int secp256k1_selftest_sha256(void) { return secp256k1_memcmp_var(out, output32, 32) == 0; } -static int secp256k1_selftest(void) { +static int secp256k1_selftest_passes(void) { return secp256k1_selftest_sha256(); } diff --git a/src/secp256k1/src/testrand.h b/src/secp256k1/src/testrand.h index bd149bb1b4..d109bb9f8b 100644 --- a/src/secp256k1/src/testrand.h +++ b/src/secp256k1/src/testrand.h @@ -7,10 +7,6 @@ #ifndef SECP256K1_TESTRAND_H #define SECP256K1_TESTRAND_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - /* A non-cryptographic RNG used only for test infrastructure. */ /** Seed the pseudorandom number generator for testing. */ diff --git a/src/secp256k1/src/tests.c b/src/secp256k1/src/tests.c index dd53173930..1c0d797349 100644 --- a/src/secp256k1/src/tests.c +++ b/src/secp256k1/src/tests.c @@ -4,10 +4,6 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include #include #include @@ -18,6 +14,7 @@ #include "../include/secp256k1.h" #include "../include/secp256k1_preallocated.h" #include "testrand_impl.h" +#include "checkmem.h" #include "util.h" #include "../contrib/lax_der_parsing.c" @@ -26,18 +23,52 @@ #include "modinv32_impl.h" #ifdef SECP256K1_WIDEMUL_INT128 #include "modinv64_impl.h" +#include "int128_impl.h" #endif -#define CONDITIONAL_TEST(cnt, nam) if (count < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else +#define CONDITIONAL_TEST(cnt, nam) if (COUNT < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else -static int count = 64; -static secp256k1_context *ctx = NULL; +static int COUNT = 64; +static secp256k1_context *CTX = NULL; +static secp256k1_context *STATIC_CTX = NULL; + +static int all_bytes_equal(const void* s, unsigned char value, size_t n) { + const unsigned char *p = s; + size_t i; + + for (i = 0; i < n; i++) { + if (p[i] != value) { + return 0; + } + } + return 1; +} + +/* TODO Use CHECK_ILLEGAL(_VOID) everywhere and get rid of the uncounting callback */ +/* CHECK that expr_or_stmt calls the illegal callback of ctx exactly once + * + * For checking functions that use ARG_CHECK_VOID */ +#define CHECK_ILLEGAL_VOID(ctx, expr_or_stmt) do { \ + int32_t _calls_to_illegal_callback = 0; \ + secp256k1_callback _saved_illegal_cb = ctx->illegal_callback; \ + secp256k1_context_set_illegal_callback(ctx, \ + counting_illegal_callback_fn, &_calls_to_illegal_callback); \ + { expr_or_stmt; } \ + ctx->illegal_callback = _saved_illegal_cb; \ + CHECK(_calls_to_illegal_callback == 1); \ +} while(0); + +/* CHECK that expr calls the illegal callback of ctx exactly once and that expr == 0 + * + * For checking functions that use ARG_CHECK */ +#define CHECK_ILLEGAL(ctx, expr) CHECK_ILLEGAL_VOID(ctx, CHECK((expr) == 0)) static void counting_illegal_callback_fn(const char* str, void* data) { /* Dummy callback function that just counts. */ int32_t *p; (void)str; p = data; + CHECK(*p != INT32_MAX); (*p)++; } @@ -46,10 +77,11 @@ static void uncounting_illegal_callback_fn(const char* str, void* data) { int32_t *p; (void)str; p = data; + CHECK(*p != INT32_MIN); (*p)--; } -void random_field_element_test(secp256k1_fe *fe) { +static void random_field_element_test(secp256k1_fe *fe) { do { unsigned char b32[32]; secp256k1_testrand256_test(b32); @@ -59,7 +91,7 @@ void random_field_element_test(secp256k1_fe *fe) { } while(1); } -void random_field_element_magnitude(secp256k1_fe *fe) { +static void random_field_element_magnitude(secp256k1_fe *fe) { secp256k1_fe zero; int n = secp256k1_testrand_int(9); secp256k1_fe_normalize(fe); @@ -75,7 +107,7 @@ void random_field_element_magnitude(secp256k1_fe *fe) { #endif } -void random_group_element_test(secp256k1_ge *ge) { +static void random_group_element_test(secp256k1_ge *ge) { secp256k1_fe fe; do { random_field_element_test(&fe); @@ -87,7 +119,7 @@ void random_group_element_test(secp256k1_ge *ge) { ge->infinity = 0; } -void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge *ge) { +static void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge *ge) { secp256k1_fe z2, z3; do { random_field_element_test(&gej->z); @@ -102,13 +134,13 @@ void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge * gej->infinity = ge->infinity; } -void random_gej_test(secp256k1_gej *gej) { +static void random_gej_test(secp256k1_gej *gej) { secp256k1_ge ge; random_group_element_test(&ge); random_group_element_jacobian_test(gej, &ge); } -void random_scalar_order_test(secp256k1_scalar *num) { +static void random_scalar_order_test(secp256k1_scalar *num) { do { unsigned char b32[32]; int overflow = 0; @@ -121,7 +153,7 @@ void random_scalar_order_test(secp256k1_scalar *num) { } while(1); } -void random_scalar_order(secp256k1_scalar *num) { +static void random_scalar_order(secp256k1_scalar *num) { do { unsigned char b32[32]; int overflow = 0; @@ -134,76 +166,179 @@ void random_scalar_order(secp256k1_scalar *num) { } while(1); } -void random_scalar_order_b32(unsigned char *b32) { +static void random_scalar_order_b32(unsigned char *b32) { secp256k1_scalar num; random_scalar_order(&num); secp256k1_scalar_get_b32(b32, &num); } -void run_context_tests(int use_prealloc) { +static void run_selftest_tests(void) { + /* Test public API */ + secp256k1_selftest(); +} + +static int ecmult_gen_context_eq(const secp256k1_ecmult_gen_context *a, const secp256k1_ecmult_gen_context *b) { + return a->built == b->built + && secp256k1_scalar_eq(&a->blind, &b->blind) + && secp256k1_gej_eq_var(&a->initial, &b->initial); +} + +static int context_eq(const secp256k1_context *a, const secp256k1_context *b) { + return a->declassify == b->declassify + && ecmult_gen_context_eq(&a->ecmult_gen_ctx, &b->ecmult_gen_ctx) + && a->illegal_callback.fn == b->illegal_callback.fn + && a->illegal_callback.data == b->illegal_callback.data + && a->error_callback.fn == b->error_callback.fn + && a->error_callback.data == b->error_callback.data; +} + +static void run_deprecated_context_flags_test(void) { + /* Check that a context created with any of the flags in the flags array is + * identical to the NONE context. */ + unsigned int flags[] = { SECP256K1_CONTEXT_SIGN, + SECP256K1_CONTEXT_VERIFY, + SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY }; + secp256k1_context *none_ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + int i; + for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++) { + secp256k1_context *tmp_ctx; + CHECK(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE) == secp256k1_context_preallocated_size(flags[i])); + tmp_ctx = secp256k1_context_create(flags[i]); + CHECK(context_eq(none_ctx, tmp_ctx)); + secp256k1_context_destroy(tmp_ctx); + } + secp256k1_context_destroy(none_ctx); +} + +static void run_ec_illegal_argument_tests(void) { + int ecount = 0; + int ecount2 = 10; secp256k1_pubkey pubkey; secp256k1_pubkey zero_pubkey; secp256k1_ecdsa_signature sig; unsigned char ctmp[32]; - int32_t ecount; - int32_t ecount2; - secp256k1_context *none; - secp256k1_context *sign; - secp256k1_context *vrfy; - secp256k1_context *both; - secp256k1_context *sttc; - void *none_prealloc = NULL; - void *sign_prealloc = NULL; - void *vrfy_prealloc = NULL; - void *both_prealloc = NULL; - void *sttc_prealloc = NULL; + + /* Setup */ + secp256k1_context_set_illegal_callback(STATIC_CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount2); + memset(ctmp, 1, 32); + memset(&zero_pubkey, 0, sizeof(zero_pubkey)); + + /* Verify context-type checking illegal-argument errors. */ + CHECK(secp256k1_ec_pubkey_create(STATIC_CTX, &pubkey, ctmp) == 0); + CHECK(ecount == 1); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ecdsa_sign(STATIC_CTX, &sig, ctmp, ctmp, NULL, NULL) == 0); + CHECK(ecount == 2); + SECP256K1_CHECKMEM_UNDEFINE(&sig, sizeof(sig)); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, ctmp, ctmp, NULL, NULL) == 1); + SECP256K1_CHECKMEM_CHECK(&sig, sizeof(sig)); + CHECK(ecount2 == 10); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, ctmp, &pubkey) == 1); + CHECK(ecount2 == 10); + CHECK(secp256k1_ecdsa_verify(STATIC_CTX, &sig, ctmp, &pubkey) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp) == 1); + CHECK(ecount2 == 10); + CHECK(secp256k1_ec_pubkey_tweak_add(STATIC_CTX, &pubkey, ctmp) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, ctmp) == 1); + CHECK(ecount2 == 10); + CHECK(secp256k1_ec_pubkey_negate(STATIC_CTX, &pubkey) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_negate(CTX, &pubkey) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_negate(STATIC_CTX, &zero_pubkey) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ec_pubkey_negate(CTX, NULL) == 0); + CHECK(ecount2 == 11); + CHECK(secp256k1_ec_pubkey_tweak_mul(STATIC_CTX, &pubkey, ctmp) == 1); + CHECK(ecount == 3); + + /* Clean up */ + secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); +} + +static void run_static_context_tests(int use_prealloc) { + /* Check that deprecated secp256k1_context_no_precomp is an alias to secp256k1_context_static. */ + CHECK(secp256k1_context_no_precomp == secp256k1_context_static); + + { + unsigned char seed[32] = {0x17}; + + /* Randomizing secp256k1_context_static is not supported. */ + CHECK_ILLEGAL(STATIC_CTX, secp256k1_context_randomize(STATIC_CTX, seed)); + CHECK_ILLEGAL(STATIC_CTX, secp256k1_context_randomize(STATIC_CTX, NULL)); + + /* Destroying or cloning secp256k1_context_static is not supported. */ + if (use_prealloc) { + CHECK_ILLEGAL(STATIC_CTX, secp256k1_context_preallocated_clone_size(STATIC_CTX)); + { + secp256k1_context *my_static_ctx = malloc(sizeof(*STATIC_CTX)); + CHECK(my_static_ctx != NULL); + memset(my_static_ctx, 0x2a, sizeof(*my_static_ctx)); + CHECK_ILLEGAL(STATIC_CTX, secp256k1_context_preallocated_clone(STATIC_CTX, my_static_ctx)); + CHECK(all_bytes_equal(my_static_ctx, 0x2a, sizeof(*my_static_ctx))); + free(my_static_ctx); + } + CHECK_ILLEGAL_VOID(STATIC_CTX, secp256k1_context_preallocated_destroy(STATIC_CTX)); + } else { + CHECK_ILLEGAL(STATIC_CTX, secp256k1_context_clone(STATIC_CTX)); + CHECK_ILLEGAL_VOID(STATIC_CTX, secp256k1_context_destroy(STATIC_CTX)); + } + } + + { + /* Verify that setting and resetting illegal callback works */ + int32_t dummy = 0; + secp256k1_context_set_illegal_callback(STATIC_CTX, counting_illegal_callback_fn, &dummy); + CHECK(STATIC_CTX->illegal_callback.fn == counting_illegal_callback_fn); + CHECK(STATIC_CTX->illegal_callback.data == &dummy); + secp256k1_context_set_illegal_callback(STATIC_CTX, NULL, NULL); + CHECK(STATIC_CTX->illegal_callback.fn == secp256k1_default_illegal_callback_fn); + CHECK(STATIC_CTX->illegal_callback.data == NULL); + } +} + +static void run_proper_context_tests(int use_prealloc) { + int32_t dummy = 0; + secp256k1_context *my_ctx, *my_ctx_fresh; + void *my_ctx_prealloc = NULL; + unsigned char seed[32] = {0x17}; secp256k1_gej pubj; secp256k1_ge pub; secp256k1_scalar msg, key, nonce; secp256k1_scalar sigr, sigs; + /* Fresh reference context for comparison */ + my_ctx_fresh = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + if (use_prealloc) { - none_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); - sign_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); - vrfy_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); - both_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); - sttc_prealloc = malloc(secp256k1_context_preallocated_clone_size(secp256k1_context_no_precomp)); - CHECK(none_prealloc != NULL); - CHECK(sign_prealloc != NULL); - CHECK(vrfy_prealloc != NULL); - CHECK(both_prealloc != NULL); - CHECK(sttc_prealloc != NULL); - none = secp256k1_context_preallocated_create(none_prealloc, SECP256K1_CONTEXT_NONE); - sign = secp256k1_context_preallocated_create(sign_prealloc, SECP256K1_CONTEXT_SIGN); - vrfy = secp256k1_context_preallocated_create(vrfy_prealloc, SECP256K1_CONTEXT_VERIFY); - both = secp256k1_context_preallocated_create(both_prealloc, SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - sttc = secp256k1_context_preallocated_clone(secp256k1_context_no_precomp, sttc_prealloc); + my_ctx_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); + CHECK(my_ctx_prealloc != NULL); + my_ctx = secp256k1_context_preallocated_create(my_ctx_prealloc, SECP256K1_CONTEXT_NONE); } else { - none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - sttc = secp256k1_context_clone(secp256k1_context_no_precomp); + my_ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); } - memset(&zero_pubkey, 0, sizeof(zero_pubkey)); + /* Randomize and reset randomization */ + CHECK(context_eq(my_ctx, my_ctx_fresh)); + CHECK(secp256k1_context_randomize(my_ctx, seed) == 1); + CHECK(!context_eq(my_ctx, my_ctx_fresh)); + CHECK(secp256k1_context_randomize(my_ctx, NULL) == 1); + CHECK(context_eq(my_ctx, my_ctx_fresh)); - ecount = 0; - ecount2 = 10; - secp256k1_context_set_illegal_callback(sttc, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount2); /* set error callback (to a function that still aborts in case malloc() fails in secp256k1_context_clone() below) */ - secp256k1_context_set_error_callback(sign, secp256k1_default_illegal_callback_fn, NULL); - CHECK(sign->error_callback.fn != vrfy->error_callback.fn); - CHECK(sign->error_callback.fn == secp256k1_default_illegal_callback_fn); + secp256k1_context_set_error_callback(my_ctx, secp256k1_default_illegal_callback_fn, NULL); + CHECK(my_ctx->error_callback.fn != secp256k1_default_error_callback_fn); + CHECK(my_ctx->error_callback.fn == secp256k1_default_illegal_callback_fn); /* check if sizes for cloning are consistent */ - CHECK(secp256k1_context_preallocated_clone_size(none) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); - CHECK(secp256k1_context_preallocated_clone_size(sign) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); - CHECK(secp256k1_context_preallocated_clone_size(vrfy) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); - CHECK(secp256k1_context_preallocated_clone_size(both) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); - CHECK(secp256k1_context_preallocated_clone_size(sttc) >= sizeof(secp256k1_context)); + CHECK(secp256k1_context_preallocated_clone_size(my_ctx) == secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); /*** clone and destroy all of them to make sure cloning was complete ***/ { @@ -211,226 +346,170 @@ void run_context_tests(int use_prealloc) { if (use_prealloc) { /* clone into a non-preallocated context and then again into a new preallocated one. */ - ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_preallocated_destroy(ctx_tmp); - free(none_prealloc); none_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); CHECK(none_prealloc != NULL); - ctx_tmp = none; none = secp256k1_context_preallocated_clone(none, none_prealloc); secp256k1_context_destroy(ctx_tmp); - - ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_preallocated_destroy(ctx_tmp); - free(sign_prealloc); sign_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); CHECK(sign_prealloc != NULL); - ctx_tmp = sign; sign = secp256k1_context_preallocated_clone(sign, sign_prealloc); secp256k1_context_destroy(ctx_tmp); - - ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_preallocated_destroy(ctx_tmp); - free(vrfy_prealloc); vrfy_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); CHECK(vrfy_prealloc != NULL); - ctx_tmp = vrfy; vrfy = secp256k1_context_preallocated_clone(vrfy, vrfy_prealloc); secp256k1_context_destroy(ctx_tmp); - - ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_preallocated_destroy(ctx_tmp); - free(both_prealloc); both_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); CHECK(both_prealloc != NULL); - ctx_tmp = both; both = secp256k1_context_preallocated_clone(both, both_prealloc); secp256k1_context_destroy(ctx_tmp); + ctx_tmp = my_ctx; + my_ctx = secp256k1_context_clone(my_ctx); + CHECK(context_eq(ctx_tmp, my_ctx)); + secp256k1_context_preallocated_destroy(ctx_tmp); + + free(my_ctx_prealloc); + my_ctx_prealloc = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); + CHECK(my_ctx_prealloc != NULL); + ctx_tmp = my_ctx; + my_ctx = secp256k1_context_preallocated_clone(my_ctx, my_ctx_prealloc); + CHECK(context_eq(ctx_tmp, my_ctx)); + secp256k1_context_destroy(ctx_tmp); } else { /* clone into a preallocated context and then again into a new non-preallocated one. */ void *prealloc_tmp; - prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); CHECK(prealloc_tmp != NULL); - ctx_tmp = none; none = secp256k1_context_preallocated_clone(none, prealloc_tmp); secp256k1_context_destroy(ctx_tmp); - ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_preallocated_destroy(ctx_tmp); - free(prealloc_tmp); - - prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN)); CHECK(prealloc_tmp != NULL); - ctx_tmp = sign; sign = secp256k1_context_preallocated_clone(sign, prealloc_tmp); secp256k1_context_destroy(ctx_tmp); - ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_preallocated_destroy(ctx_tmp); - free(prealloc_tmp); - - prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_VERIFY)); CHECK(prealloc_tmp != NULL); - ctx_tmp = vrfy; vrfy = secp256k1_context_preallocated_clone(vrfy, prealloc_tmp); secp256k1_context_destroy(ctx_tmp); - ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_preallocated_destroy(ctx_tmp); - free(prealloc_tmp); - - prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)); CHECK(prealloc_tmp != NULL); - ctx_tmp = both; both = secp256k1_context_preallocated_clone(both, prealloc_tmp); secp256k1_context_destroy(ctx_tmp); - ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_preallocated_destroy(ctx_tmp); + prealloc_tmp = malloc(secp256k1_context_preallocated_size(SECP256K1_CONTEXT_NONE)); + CHECK(prealloc_tmp != NULL); + ctx_tmp = my_ctx; + my_ctx = secp256k1_context_preallocated_clone(my_ctx, prealloc_tmp); + CHECK(context_eq(ctx_tmp, my_ctx)); + secp256k1_context_destroy(ctx_tmp); + + ctx_tmp = my_ctx; + my_ctx = secp256k1_context_clone(my_ctx); + CHECK(context_eq(ctx_tmp, my_ctx)); + secp256k1_context_preallocated_destroy(ctx_tmp); free(prealloc_tmp); } } /* Verify that the error callback makes it across the clone. */ - CHECK(sign->error_callback.fn != vrfy->error_callback.fn); - CHECK(sign->error_callback.fn == secp256k1_default_illegal_callback_fn); + CHECK(my_ctx->error_callback.fn != secp256k1_default_error_callback_fn); + CHECK(my_ctx->error_callback.fn == secp256k1_default_illegal_callback_fn); /* And that it resets back to default. */ - secp256k1_context_set_error_callback(sign, NULL, NULL); - CHECK(vrfy->error_callback.fn == sign->error_callback.fn); + secp256k1_context_set_error_callback(my_ctx, NULL, NULL); + CHECK(my_ctx->error_callback.fn == secp256k1_default_error_callback_fn); + CHECK(context_eq(my_ctx, my_ctx_fresh)); + + /* Verify that setting and resetting illegal callback works */ + secp256k1_context_set_illegal_callback(my_ctx, counting_illegal_callback_fn, &dummy); + CHECK(my_ctx->illegal_callback.fn == counting_illegal_callback_fn); + CHECK(my_ctx->illegal_callback.data == &dummy); + secp256k1_context_set_illegal_callback(my_ctx, NULL, NULL); + CHECK(my_ctx->illegal_callback.fn == secp256k1_default_illegal_callback_fn); + CHECK(my_ctx->illegal_callback.data == NULL); + CHECK(context_eq(my_ctx, my_ctx_fresh)); /*** attempt to use them ***/ random_scalar_order_test(&msg); random_scalar_order_test(&key); - secp256k1_ecmult_gen(&both->ecmult_gen_ctx, &pubj, &key); + secp256k1_ecmult_gen(&my_ctx->ecmult_gen_ctx, &pubj, &key); secp256k1_ge_set_gej(&pub, &pubj); - /* Verify context-type checking illegal-argument errors. */ - memset(ctmp, 1, 32); - CHECK(secp256k1_ec_pubkey_create(sttc, &pubkey, ctmp) == 0); - CHECK(ecount == 1); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(sign, &pubkey, ctmp) == 1); - VG_CHECK(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ecdsa_sign(sttc, &sig, ctmp, ctmp, NULL, NULL) == 0); - CHECK(ecount == 2); - VG_UNDEF(&sig, sizeof(sig)); - CHECK(secp256k1_ecdsa_sign(sign, &sig, ctmp, ctmp, NULL, NULL) == 1); - VG_CHECK(&sig, sizeof(sig)); - CHECK(ecount2 == 10); - CHECK(secp256k1_ecdsa_verify(sign, &sig, ctmp, &pubkey) == 1); - CHECK(ecount2 == 10); - CHECK(secp256k1_ecdsa_verify(sttc, &sig, ctmp, &pubkey) == 1); - CHECK(ecount == 2); - CHECK(secp256k1_ec_pubkey_tweak_add(sign, &pubkey, ctmp) == 1); - CHECK(ecount2 == 10); - CHECK(secp256k1_ec_pubkey_tweak_add(sttc, &pubkey, ctmp) == 1); - CHECK(ecount == 2); - CHECK(secp256k1_ec_pubkey_tweak_mul(sign, &pubkey, ctmp) == 1); - CHECK(ecount2 == 10); - CHECK(secp256k1_ec_pubkey_negate(sttc, &pubkey) == 1); - CHECK(ecount == 2); - CHECK(secp256k1_ec_pubkey_negate(sign, &pubkey) == 1); - CHECK(ecount == 2); - CHECK(secp256k1_ec_pubkey_negate(sign, NULL) == 0); - CHECK(ecount2 == 11); - CHECK(secp256k1_ec_pubkey_negate(sttc, &zero_pubkey) == 0); - CHECK(ecount == 3); - CHECK(secp256k1_ec_pubkey_tweak_mul(sttc, &pubkey, ctmp) == 1); - CHECK(ecount == 3); - CHECK(secp256k1_context_randomize(sttc, ctmp) == 1); - CHECK(ecount == 3); - CHECK(secp256k1_context_randomize(sttc, NULL) == 1); - CHECK(ecount == 3); - CHECK(secp256k1_context_randomize(sign, ctmp) == 1); - CHECK(ecount2 == 11); - CHECK(secp256k1_context_randomize(sign, NULL) == 1); - CHECK(ecount2 == 11); - secp256k1_context_set_illegal_callback(sttc, NULL, NULL); - secp256k1_context_set_illegal_callback(sign, NULL, NULL); - /* obtain a working nonce */ do { random_scalar_order_test(&nonce); - } while(!secp256k1_ecdsa_sig_sign(&both->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); + } while(!secp256k1_ecdsa_sig_sign(&my_ctx->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); /* try signing */ - CHECK(secp256k1_ecdsa_sig_sign(&sign->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); - CHECK(secp256k1_ecdsa_sig_sign(&both->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); + CHECK(secp256k1_ecdsa_sig_sign(&my_ctx->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); /* try verifying */ CHECK(secp256k1_ecdsa_sig_verify(&sigr, &sigs, &pub, &msg)); - CHECK(secp256k1_ecdsa_sig_verify(&sigr, &sigs, &pub, &msg)); /* cleanup */ if (use_prealloc) { - secp256k1_context_preallocated_destroy(none); - secp256k1_context_preallocated_destroy(sign); - secp256k1_context_preallocated_destroy(vrfy); - secp256k1_context_preallocated_destroy(both); - secp256k1_context_preallocated_destroy(sttc); - free(none_prealloc); - free(sign_prealloc); - free(vrfy_prealloc); - free(both_prealloc); - free(sttc_prealloc); + secp256k1_context_preallocated_destroy(my_ctx); + free(my_ctx_prealloc); } else { - secp256k1_context_destroy(none); - secp256k1_context_destroy(sign); - secp256k1_context_destroy(vrfy); - secp256k1_context_destroy(both); - secp256k1_context_destroy(sttc); + secp256k1_context_destroy(my_ctx); } + secp256k1_context_destroy(my_ctx_fresh); + /* Defined as no-op. */ secp256k1_context_destroy(NULL); secp256k1_context_preallocated_destroy(NULL); - } -void run_scratch_tests(void) { +static void run_scratch_tests(void) { const size_t adj_alloc = ((500 + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; int32_t ecount = 0; size_t checkpoint; size_t checkpoint_2; - secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_scratch_space *scratch; secp256k1_scratch_space local_scratch; - /* Test public API */ - secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); - secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(CTX, counting_illegal_callback_fn, &ecount); - scratch = secp256k1_scratch_space_create(none, 1000); + /* Test public API */ + scratch = secp256k1_scratch_space_create(CTX, 1000); CHECK(scratch != NULL); CHECK(ecount == 0); /* Test internal API */ - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 0) == 1000); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 1) == 1000 - (ALIGNMENT - 1)); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 0) == 1000); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 1) == 1000 - (ALIGNMENT - 1)); CHECK(scratch->alloc_size == 0); CHECK(scratch->alloc_size % ALIGNMENT == 0); /* Allocating 500 bytes succeeds */ - checkpoint = secp256k1_scratch_checkpoint(&none->error_callback, scratch); - CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, 500) != NULL); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 0) == 1000 - adj_alloc); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 1) == 1000 - adj_alloc - (ALIGNMENT - 1)); + checkpoint = secp256k1_scratch_checkpoint(&CTX->error_callback, scratch); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, 500) != NULL); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 0) == 1000 - adj_alloc); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 1) == 1000 - adj_alloc - (ALIGNMENT - 1)); CHECK(scratch->alloc_size != 0); CHECK(scratch->alloc_size % ALIGNMENT == 0); /* Allocating another 501 bytes fails */ - CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, 501) == NULL); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 0) == 1000 - adj_alloc); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 1) == 1000 - adj_alloc - (ALIGNMENT - 1)); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, 501) == NULL); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 0) == 1000 - adj_alloc); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 1) == 1000 - adj_alloc - (ALIGNMENT - 1)); CHECK(scratch->alloc_size != 0); CHECK(scratch->alloc_size % ALIGNMENT == 0); /* ...but it succeeds once we apply the checkpoint to undo it */ - secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, checkpoint); + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint); CHECK(scratch->alloc_size == 0); - CHECK(secp256k1_scratch_max_allocation(&none->error_callback, scratch, 0) == 1000); - CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, 500) != NULL); + CHECK(secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 0) == 1000); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, 500) != NULL); CHECK(scratch->alloc_size != 0); /* try to apply a bad checkpoint */ - checkpoint_2 = secp256k1_scratch_checkpoint(&none->error_callback, scratch); - secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, checkpoint); + checkpoint_2 = secp256k1_scratch_checkpoint(&CTX->error_callback, scratch); + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint); CHECK(ecount == 0); - secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, checkpoint_2); /* checkpoint_2 is after checkpoint */ + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint_2); /* checkpoint_2 is after checkpoint */ CHECK(ecount == 1); - secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, (size_t) -1); /* this is just wildly invalid */ + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, (size_t) -1); /* this is just wildly invalid */ CHECK(ecount == 2); /* try to use badly initialized scratch space */ - secp256k1_scratch_space_destroy(none, scratch); + secp256k1_scratch_space_destroy(CTX, scratch); memset(&local_scratch, 0, sizeof(local_scratch)); scratch = &local_scratch; - CHECK(!secp256k1_scratch_max_allocation(&none->error_callback, scratch, 0)); + CHECK(!secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, 0)); CHECK(ecount == 3); - CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, 500) == NULL); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, 500) == NULL); CHECK(ecount == 4); - secp256k1_scratch_space_destroy(none, scratch); + secp256k1_scratch_space_destroy(CTX, scratch); CHECK(ecount == 5); /* Test that large integers do not wrap around in a bad way */ - scratch = secp256k1_scratch_space_create(none, 1000); + scratch = secp256k1_scratch_space_create(CTX, 1000); /* Try max allocation with a large number of objects. Only makes sense if * ALIGNMENT is greater than 1 because otherwise the objects take no extra * space. */ - CHECK(ALIGNMENT <= 1 || !secp256k1_scratch_max_allocation(&none->error_callback, scratch, (SIZE_MAX / (ALIGNMENT - 1)) + 1)); + CHECK(ALIGNMENT <= 1 || !secp256k1_scratch_max_allocation(&CTX->error_callback, scratch, (SIZE_MAX / (ALIGNMENT - 1)) + 1)); /* Try allocating SIZE_MAX to test wrap around which only happens if * ALIGNMENT > 1, otherwise it returns NULL anyway because the scratch * space is too small. */ - CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, SIZE_MAX) == NULL); - secp256k1_scratch_space_destroy(none, scratch); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, SIZE_MAX) == NULL); + secp256k1_scratch_space_destroy(CTX, scratch); /* cleanup */ - secp256k1_scratch_space_destroy(none, NULL); /* no-op */ - secp256k1_context_destroy(none); + secp256k1_scratch_space_destroy(CTX, NULL); /* no-op */ + + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); + secp256k1_context_set_error_callback(CTX, NULL, NULL); } -void run_ctz_tests(void) { +static void run_ctz_tests(void) { static const uint32_t b32[] = {1, 0xffffffff, 0x5e56968f, 0xe0d63129}; static const uint64_t b64[] = {1, 0xffffffffffffffff, 0xbcd02462139b3fc3, 0x98b5f80c769693ef}; int shift; @@ -451,7 +530,7 @@ void run_ctz_tests(void) { /***** HASH TESTS *****/ -void run_sha256_known_output_tests(void) { +static void run_sha256_known_output_tests(void) { static const char *inputs[] = { "", "abc", "message digest", "secure hash algorithm", "SHA256 is considered to be safe", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", @@ -552,7 +631,7 @@ for x in digests: print(x + ',') ``` */ -void run_sha256_counter_tests(void) { +static void run_sha256_counter_tests(void) { static const char *input = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno"; static const secp256k1_sha256 midstates[] = { {{0xa2b5c8bb, 0x26c88bb3, 0x2abdc3d2, 0x9def99a3, 0xdfd21a6e, 0x41fe585b, 0x7ef2c440, 0x2b79adda}, @@ -610,7 +689,7 @@ void run_sha256_counter_tests(void) { } } -void run_hmac_sha256_tests(void) { +static void run_hmac_sha256_tests(void) { static const char *keys[6] = { "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", "\x4a\x65\x66\x65", @@ -654,7 +733,7 @@ void run_hmac_sha256_tests(void) { } } -void run_rfc6979_hmac_sha256_tests(void) { +static void run_rfc6979_hmac_sha256_tests(void) { static const unsigned char key1[65] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x4b, 0xf5, 0x12, 0x2f, 0x34, 0x45, 0x54, 0xc5, 0x3b, 0xde, 0x2e, 0xbb, 0x8c, 0xd2, 0xb7, 0xe3, 0xd1, 0x60, 0x0a, 0xd6, 0x31, 0xc3, 0x85, 0xa5, 0xd7, 0xcc, 0xe2, 0x3c, 0x77, 0x85, 0x45, 0x9a, 0}; static const unsigned char out1[3][32] = { {0x4f, 0xe2, 0x95, 0x25, 0xb2, 0x08, 0x68, 0x09, 0x15, 0x9a, 0xcd, 0xf0, 0x50, 0x6e, 0xfb, 0x86, 0xb0, 0xec, 0x93, 0x2c, 0x7b, 0xa4, 0x42, 0x56, 0xab, 0x32, 0x1e, 0x42, 0x1e, 0x67, 0xe9, 0xfb}, @@ -695,9 +774,8 @@ void run_rfc6979_hmac_sha256_tests(void) { secp256k1_rfc6979_hmac_sha256_finalize(&rng); } -void run_tagged_sha256_tests(void) { +static void run_tagged_sha256_tests(void) { int ecount = 0; - secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); unsigned char tag[32] = { 0 }; unsigned char msg[32] = { 0 }; unsigned char hash32[32]; @@ -708,28 +786,27 @@ void run_tagged_sha256_tests(void) { 0xE2, 0x76, 0x55, 0x9A, 0x3B, 0xDE, 0x55, 0xB3 }; - secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); /* API test */ - CHECK(secp256k1_tagged_sha256(none, hash32, tag, sizeof(tag), msg, sizeof(msg)) == 1); - CHECK(secp256k1_tagged_sha256(none, NULL, tag, sizeof(tag), msg, sizeof(msg)) == 0); + CHECK(secp256k1_tagged_sha256(CTX, hash32, tag, sizeof(tag), msg, sizeof(msg)) == 1); + CHECK(secp256k1_tagged_sha256(CTX, NULL, tag, sizeof(tag), msg, sizeof(msg)) == 0); CHECK(ecount == 1); - CHECK(secp256k1_tagged_sha256(none, hash32, NULL, 0, msg, sizeof(msg)) == 0); + CHECK(secp256k1_tagged_sha256(CTX, hash32, NULL, 0, msg, sizeof(msg)) == 0); CHECK(ecount == 2); - CHECK(secp256k1_tagged_sha256(none, hash32, tag, sizeof(tag), NULL, 0) == 0); + CHECK(secp256k1_tagged_sha256(CTX, hash32, tag, sizeof(tag), NULL, 0) == 0); CHECK(ecount == 3); /* Static test vector */ memcpy(tag, "tag", 3); memcpy(msg, "msg", 3); - CHECK(secp256k1_tagged_sha256(none, hash32, tag, 3, msg, 3) == 1); + CHECK(secp256k1_tagged_sha256(CTX, hash32, tag, 3, msg, 3) == 1); CHECK(secp256k1_memcmp_var(hash32, hash_expected, sizeof(hash32)) == 0); - secp256k1_context_destroy(none); } /***** RANDOM TESTS *****/ -void test_rand_bits(int rand32, int bits) { +static void test_rand_bits(int rand32, int bits) { /* (1-1/2^B)^rounds[B] < 1/10^9, so rounds is the number of iterations to * get a false negative chance below once in a billion */ static const unsigned int rounds[7] = {1, 30, 73, 156, 322, 653, 1316}; @@ -764,7 +841,7 @@ void test_rand_bits(int rand32, int bits) { } /* Subrange must be a whole divisor of range, and at most 64 */ -void test_rand_int(uint32_t range, uint32_t subrange) { +static void test_rand_int(uint32_t range, uint32_t subrange) { /* (1-1/subrange)^rounds < 1/10^9 */ int rounds = (subrange * 2073) / 100; int i; @@ -780,7 +857,7 @@ void test_rand_int(uint32_t range, uint32_t subrange) { CHECK(((~x) << (64 - subrange)) == 0); } -void run_rand_bits(void) { +static void run_rand_bits(void) { size_t b; test_rand_bits(1, 32); for (b = 1; b <= 32; b++) { @@ -788,7 +865,7 @@ void run_rand_bits(void) { } } -void run_rand_int(void) { +static void run_rand_int(void) { static const uint32_t ms[] = {1, 3, 17, 1000, 13771, 999999, 33554432}; static const uint32_t ss[] = {1, 3, 6, 9, 13, 31, 64}; unsigned int m, s; @@ -802,7 +879,7 @@ void run_rand_int(void) { /***** MODINV TESTS *****/ /* Compute the modular inverse of (odd) x mod 2^64. */ -uint64_t modinv2p64(uint64_t x) { +static uint64_t modinv2p64(uint64_t x) { /* If w = 1/x mod 2^(2^L), then w*(2 - w*x) = 1/x mod 2^(2^(L+1)). See * Hacker's Delight second edition, Henry S. Warren, Jr., pages 245-247 for * why. Start with L=0, for which it is true for every odd x that @@ -814,11 +891,12 @@ uint64_t modinv2p64(uint64_t x) { return w; } -/* compute out = (a*b) mod m; if b=NULL, treat b=1. + +/* compute out = (a*b) mod m; if b=NULL, treat b=1; if m=NULL, treat m=infinity. * * Out is a 512-bit number (represented as 32 uint16_t's in LE order). The other * arguments are 256-bit numbers (represented as 16 uint16_t's in LE order). */ -void mulmod256(uint16_t* out, const uint16_t* a, const uint16_t* b, const uint16_t* m) { +static void mulmod256(uint16_t* out, const uint16_t* a, const uint16_t* b, const uint16_t* m) { uint16_t mul[32]; uint64_t c = 0; int i, j; @@ -856,51 +934,53 @@ void mulmod256(uint16_t* out, const uint16_t* a, const uint16_t* b, const uint16 } } - /* Compute the highest set bit in m. */ - for (i = 255; i >= 0; --i) { - if ((m[i >> 4] >> (i & 15)) & 1) { - m_bitlen = i; - break; + if (m) { + /* Compute the highest set bit in m. */ + for (i = 255; i >= 0; --i) { + if ((m[i >> 4] >> (i & 15)) & 1) { + m_bitlen = i; + break; + } } - } - /* Try do mul -= m<= 0; --i) { - uint16_t mul2[32]; - int64_t cs; - - /* Compute mul2 = mul - m<= 0 && bitpos < 256) { - sub |= ((m[bitpos >> 4] >> (bitpos & 15)) & 1) << p; + /* Try do mul -= m<= 0; --i) { + uint16_t mul2[32]; + int64_t cs; + + /* Compute mul2 = mul - m<= 0 && bitpos < 256) { + sub |= ((m[bitpos >> 4] >> (bitpos & 15)) & 1) << p; + } } + /* Add mul[j]-sub to accumulator, and shift bottom 16 bits out to mul2[j]. */ + cs += mul[j]; + cs -= sub; + mul2[j] = (cs & 0xFFFF); + cs >>= 16; + } + /* If remainder of subtraction is 0, set mul = mul2. */ + if (cs == 0) { + memcpy(mul, mul2, sizeof(mul)); } - /* Add mul[j]-sub to accumulator, and shift bottom 16 bits out to mul2[j]. */ - cs += mul[j]; - cs -= sub; - mul2[j] = (cs & 0xFFFF); - cs >>= 16; } - /* If remainder of subtraction is 0, set mul = mul2. */ - if (cs == 0) { - memcpy(mul, mul2, sizeof(mul)); + /* Sanity check: test that all limbs higher than m's highest are zero */ + for (i = (m_bitlen >> 4) + 1; i < 32; ++i) { + CHECK(mul[i] == 0); } } - /* Sanity check: test that all limbs higher than m's highest are zero */ - for (i = (m_bitlen >> 4) + 1; i < 32; ++i) { - CHECK(mul[i] == 0); - } memcpy(out, mul, 32); } /* Convert a 256-bit number represented as 16 uint16_t's to signed30 notation. */ -void uint16_to_signed30(secp256k1_modinv32_signed30* out, const uint16_t* in) { +static void uint16_to_signed30(secp256k1_modinv32_signed30* out, const uint16_t* in) { int i; memset(out->v, 0, sizeof(out->v)); for (i = 0; i < 256; ++i) { @@ -909,7 +989,7 @@ void uint16_to_signed30(secp256k1_modinv32_signed30* out, const uint16_t* in) { } /* Convert a 256-bit number in signed30 notation to a representation as 16 uint16_t's. */ -void signed30_to_uint16(uint16_t* out, const secp256k1_modinv32_signed30* in) { +static void signed30_to_uint16(uint16_t* out, const secp256k1_modinv32_signed30* in) { int i; memset(out, 0, 32); for (i = 0; i < 256; ++i) { @@ -918,7 +998,7 @@ void signed30_to_uint16(uint16_t* out, const secp256k1_modinv32_signed30* in) { } /* Randomly mutate the sign of limbs in signed30 representation, without changing the value. */ -void mutate_sign_signed30(secp256k1_modinv32_signed30* x) { +static void mutate_sign_signed30(secp256k1_modinv32_signed30* x) { int i; for (i = 0; i < 16; ++i) { int pos = secp256k1_testrand_bits(3); @@ -933,7 +1013,7 @@ void mutate_sign_signed30(secp256k1_modinv32_signed30* x) { } /* Test secp256k1_modinv32{_var}, using inputs in 16-bit limb format, and returning inverse. */ -void test_modinv32_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod) { +static void test_modinv32_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod) { uint16_t tmp[16]; secp256k1_modinv32_signed30 x; secp256k1_modinv32_modinfo m; @@ -942,12 +1022,32 @@ void test_modinv32_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod uint16_to_signed30(&x, in); nonzero = (x.v[0] | x.v[1] | x.v[2] | x.v[3] | x.v[4] | x.v[5] | x.v[6] | x.v[7] | x.v[8]) != 0; uint16_to_signed30(&m.modulus, mod); - mutate_sign_signed30(&m.modulus); /* compute 1/modulus mod 2^30 */ m.modulus_inv30 = modinv2p64(m.modulus.v[0]) & 0x3fffffff; CHECK(((m.modulus_inv30 * m.modulus.v[0]) & 0x3fffffff) == 1); + /* Test secp256k1_jacobi32_maybe_var. */ + if (nonzero) { + int jac; + uint16_t sqr[16], negone[16]; + mulmod256(sqr, in, in, mod); + uint16_to_signed30(&x, sqr); + /* Compute jacobi symbol of in^2, which must be 1 (or uncomputable). */ + jac = secp256k1_jacobi32_maybe_var(&x, &m); + CHECK(jac == 0 || jac == 1); + /* Then compute the jacobi symbol of -(in^2). x and -x have opposite + * jacobi symbols if and only if (mod % 4) == 3. */ + negone[0] = mod[0] - 1; + for (i = 1; i < 16; ++i) negone[i] = mod[i]; + mulmod256(sqr, sqr, negone, mod); + uint16_to_signed30(&x, sqr); + jac = secp256k1_jacobi32_maybe_var(&x, &m); + CHECK(jac == 0 || jac == 1 - (mod[0] & 2)); + } + + uint16_to_signed30(&x, in); + mutate_sign_signed30(&m.modulus); for (vartime = 0; vartime < 2; ++vartime) { /* compute inverse */ (vartime ? secp256k1_modinv32_var : secp256k1_modinv32)(&x, &m); @@ -971,7 +1071,7 @@ void test_modinv32_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod #ifdef SECP256K1_WIDEMUL_INT128 /* Convert a 256-bit number represented as 16 uint16_t's to signed62 notation. */ -void uint16_to_signed62(secp256k1_modinv64_signed62* out, const uint16_t* in) { +static void uint16_to_signed62(secp256k1_modinv64_signed62* out, const uint16_t* in) { int i; memset(out->v, 0, sizeof(out->v)); for (i = 0; i < 256; ++i) { @@ -980,7 +1080,7 @@ void uint16_to_signed62(secp256k1_modinv64_signed62* out, const uint16_t* in) { } /* Convert a 256-bit number in signed62 notation to a representation as 16 uint16_t's. */ -void signed62_to_uint16(uint16_t* out, const secp256k1_modinv64_signed62* in) { +static void signed62_to_uint16(uint16_t* out, const secp256k1_modinv64_signed62* in) { int i; memset(out, 0, 32); for (i = 0; i < 256; ++i) { @@ -989,7 +1089,7 @@ void signed62_to_uint16(uint16_t* out, const secp256k1_modinv64_signed62* in) { } /* Randomly mutate the sign of limbs in signed62 representation, without changing the value. */ -void mutate_sign_signed62(secp256k1_modinv64_signed62* x) { +static void mutate_sign_signed62(secp256k1_modinv64_signed62* x) { static const int64_t M62 = (int64_t)(UINT64_MAX >> 2); int i; for (i = 0; i < 8; ++i) { @@ -1005,7 +1105,7 @@ void mutate_sign_signed62(secp256k1_modinv64_signed62* x) { } /* Test secp256k1_modinv64{_var}, using inputs in 16-bit limb format, and returning inverse. */ -void test_modinv64_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod) { +static void test_modinv64_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod) { static const int64_t M62 = (int64_t)(UINT64_MAX >> 2); uint16_t tmp[16]; secp256k1_modinv64_signed62 x; @@ -1015,12 +1115,32 @@ void test_modinv64_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod uint16_to_signed62(&x, in); nonzero = (x.v[0] | x.v[1] | x.v[2] | x.v[3] | x.v[4]) != 0; uint16_to_signed62(&m.modulus, mod); - mutate_sign_signed62(&m.modulus); /* compute 1/modulus mod 2^62 */ m.modulus_inv62 = modinv2p64(m.modulus.v[0]) & M62; CHECK(((m.modulus_inv62 * m.modulus.v[0]) & M62) == 1); + /* Test secp256k1_jacobi64_maybe_var. */ + if (nonzero) { + int jac; + uint16_t sqr[16], negone[16]; + mulmod256(sqr, in, in, mod); + uint16_to_signed62(&x, sqr); + /* Compute jacobi symbol of in^2, which must be 1 (or uncomputable). */ + jac = secp256k1_jacobi64_maybe_var(&x, &m); + CHECK(jac == 0 || jac == 1); + /* Then compute the jacobi symbol of -(in^2). x and -x have opposite + * jacobi symbols if and only if (mod % 4) == 3. */ + negone[0] = mod[0] - 1; + for (i = 1; i < 16; ++i) negone[i] = mod[i]; + mulmod256(sqr, sqr, negone, mod); + uint16_to_signed62(&x, sqr); + jac = secp256k1_jacobi64_maybe_var(&x, &m); + CHECK(jac == 0 || jac == 1 - (mod[0] & 2)); + } + + uint16_to_signed62(&x, in); + mutate_sign_signed62(&m.modulus); for (vartime = 0; vartime < 2; ++vartime) { /* compute inverse */ (vartime ? secp256k1_modinv64_var : secp256k1_modinv64)(&x, &m); @@ -1044,7 +1164,7 @@ void test_modinv64_uint16(uint16_t* out, const uint16_t* in, const uint16_t* mod #endif /* test if a and b are coprime */ -int coprime(const uint16_t* a, const uint16_t* b) { +static int coprime(const uint16_t* a, const uint16_t* b) { uint16_t x[16], y[16], t[16]; int i; int iszero; @@ -1074,7 +1194,7 @@ int coprime(const uint16_t* a, const uint16_t* b) { return 1; } -void run_modinv_tests(void) { +static void run_modinv_tests(void) { /* Fixed test cases. Each tuple is (input, modulus, output), each as 16x16 bits in LE order. */ static const uint16_t CASES[][3][16] = { /* Test cases triggering edge cases in divsteps */ @@ -1676,7 +1796,7 @@ void run_modinv_tests(void) { #endif } - for (i = 0; i < 100 * count; ++i) { + for (i = 0; i < 100 * COUNT; ++i) { /* 256-bit numbers in 16-uint16_t's notation */ static const uint16_t ZERO[16] = {0}; uint16_t xd[16]; /* the number (in range [0,2^256)) to be inverted */ @@ -1701,7 +1821,7 @@ void run_modinv_tests(void) { #endif /* In a few cases, also test with input=0 */ - if (i < count) { + if (i < COUNT) { test_modinv32_uint16(id, ZERO, md); #ifdef SECP256K1_WIDEMUL_INT128 test_modinv64_uint16(id, ZERO, md); @@ -1710,10 +1830,335 @@ void run_modinv_tests(void) { } } -/***** SCALAR TESTS *****/ +/***** INT128 TESTS *****/ + +#ifdef SECP256K1_WIDEMUL_INT128 +/* Add two 256-bit numbers (represented as 16 uint16_t's in LE order) together mod 2^256. */ +static void add256(uint16_t* out, const uint16_t* a, const uint16_t* b) { + int i; + uint32_t carry = 0; + for (i = 0; i < 16; ++i) { + carry += a[i]; + carry += b[i]; + out[i] = carry; + carry >>= 16; + } +} + +/* Negate a 256-bit number (represented as 16 uint16_t's in LE order) mod 2^256. */ +static void neg256(uint16_t* out, const uint16_t* a) { + int i; + uint32_t carry = 1; + for (i = 0; i < 16; ++i) { + carry += (uint16_t)~a[i]; + out[i] = carry; + carry >>= 16; + } +} + +/* Right-shift a 256-bit number (represented as 16 uint16_t's in LE order). */ +static void rshift256(uint16_t* out, const uint16_t* a, int n, int sign_extend) { + uint16_t sign = sign_extend && (a[15] >> 15); + int i, j; + for (i = 15; i >= 0; --i) { + uint16_t v = 0; + for (j = 0; j < 16; ++j) { + int frompos = i*16 + j + n; + if (frompos >= 256) { + v |= sign << j; + } else { + v |= ((uint16_t)((a[frompos >> 4] >> (frompos & 15)) & 1)) << j; + } + } + out[i] = v; + } +} + +/* Load a 64-bit unsigned integer into an array of 16 uint16_t's in LE order representing a 256-bit value. */ +static void load256u64(uint16_t* out, uint64_t v, int is_signed) { + int i; + uint64_t sign = is_signed && (v >> 63) ? UINT64_MAX : 0; + for (i = 0; i < 4; ++i) { + out[i] = v >> (16 * i); + } + for (i = 4; i < 16; ++i) { + out[i] = sign; + } +} + +/* Load a 128-bit unsigned integer into an array of 16 uint16_t's in LE order representing a 256-bit value. */ +static void load256two64(uint16_t* out, uint64_t hi, uint64_t lo, int is_signed) { + int i; + uint64_t sign = is_signed && (hi >> 63) ? UINT64_MAX : 0; + for (i = 0; i < 4; ++i) { + out[i] = lo >> (16 * i); + } + for (i = 4; i < 8; ++i) { + out[i] = hi >> (16 * (i - 4)); + } + for (i = 8; i < 16; ++i) { + out[i] = sign; + } +} + +/* Check whether the 256-bit value represented by array of 16-bit values is in range -2^127 < v < 2^127. */ +static int int256is127(const uint16_t* v) { + int all_0 = ((v[7] & 0x8000) == 0), all_1 = ((v[7] & 0x8000) == 0x8000); + int i; + for (i = 8; i < 16; ++i) { + if (v[i] != 0) all_0 = 0; + if (v[i] != 0xffff) all_1 = 0; + } + return all_0 || all_1; +} + +static void load256u128(uint16_t* out, const secp256k1_uint128* v) { + uint64_t lo = secp256k1_u128_to_u64(v), hi = secp256k1_u128_hi_u64(v); + load256two64(out, hi, lo, 0); +} + +static void load256i128(uint16_t* out, const secp256k1_int128* v) { + uint64_t lo; + int64_t hi; + secp256k1_int128 c = *v; + lo = secp256k1_i128_to_u64(&c); + secp256k1_i128_rshift(&c, 64); + hi = secp256k1_i128_to_i64(&c); + load256two64(out, hi, lo, 1); +} + +static void run_int128_test_case(void) { + unsigned char buf[32]; + uint64_t v[4]; + secp256k1_int128 swa, swz; + secp256k1_uint128 uwa, uwz; + uint64_t ub, uc; + int64_t sb, sc; + uint16_t rswa[16], rswz[32], rswr[32], ruwa[16], ruwz[32], ruwr[32]; + uint16_t rub[16], ruc[16], rsb[16], rsc[16]; + int i; + + /* Generate 32-byte random value. */ + secp256k1_testrand256_test(buf); + /* Convert into 4 64-bit integers. */ + for (i = 0; i < 4; ++i) { + uint64_t vi = 0; + int j; + for (j = 0; j < 8; ++j) vi = (vi << 8) + buf[8*i + j]; + v[i] = vi; + } + /* Convert those into a 128-bit value and two 64-bit values (signed and unsigned). */ + secp256k1_u128_load(&uwa, v[1], v[0]); + secp256k1_i128_load(&swa, v[1], v[0]); + ub = v[2]; + sb = v[2]; + uc = v[3]; + sc = v[3]; + /* Load those also into 16-bit array representations. */ + load256u128(ruwa, &uwa); + load256i128(rswa, &swa); + load256u64(rub, ub, 0); + load256u64(rsb, sb, 1); + load256u64(ruc, uc, 0); + load256u64(rsc, sc, 1); + /* test secp256k1_u128_mul */ + mulmod256(ruwr, rub, ruc, NULL); + secp256k1_u128_mul(&uwz, ub, uc); + load256u128(ruwz, &uwz); + CHECK(secp256k1_memcmp_var(ruwr, ruwz, 16) == 0); + /* test secp256k1_u128_accum_mul */ + mulmod256(ruwr, rub, ruc, NULL); + add256(ruwr, ruwr, ruwa); + uwz = uwa; + secp256k1_u128_accum_mul(&uwz, ub, uc); + load256u128(ruwz, &uwz); + CHECK(secp256k1_memcmp_var(ruwr, ruwz, 16) == 0); + /* test secp256k1_u128_accum_u64 */ + add256(ruwr, rub, ruwa); + uwz = uwa; + secp256k1_u128_accum_u64(&uwz, ub); + load256u128(ruwz, &uwz); + CHECK(secp256k1_memcmp_var(ruwr, ruwz, 16) == 0); + /* test secp256k1_u128_rshift */ + rshift256(ruwr, ruwa, uc % 128, 0); + uwz = uwa; + secp256k1_u128_rshift(&uwz, uc % 128); + load256u128(ruwz, &uwz); + CHECK(secp256k1_memcmp_var(ruwr, ruwz, 16) == 0); + /* test secp256k1_u128_to_u64 */ + CHECK(secp256k1_u128_to_u64(&uwa) == v[0]); + /* test secp256k1_u128_hi_u64 */ + CHECK(secp256k1_u128_hi_u64(&uwa) == v[1]); + /* test secp256k1_u128_from_u64 */ + secp256k1_u128_from_u64(&uwz, ub); + load256u128(ruwz, &uwz); + CHECK(secp256k1_memcmp_var(rub, ruwz, 16) == 0); + /* test secp256k1_u128_check_bits */ + { + int uwa_bits = 0; + int j; + for (j = 0; j < 128; ++j) { + if (ruwa[j / 16] >> (j % 16)) uwa_bits = 1 + j; + } + for (j = 0; j < 128; ++j) { + CHECK(secp256k1_u128_check_bits(&uwa, j) == (uwa_bits <= j)); + } + } + /* test secp256k1_i128_mul */ + mulmod256(rswr, rsb, rsc, NULL); + secp256k1_i128_mul(&swz, sb, sc); + load256i128(rswz, &swz); + CHECK(secp256k1_memcmp_var(rswr, rswz, 16) == 0); + /* test secp256k1_i128_accum_mul */ + mulmod256(rswr, rsb, rsc, NULL); + add256(rswr, rswr, rswa); + if (int256is127(rswr)) { + swz = swa; + secp256k1_i128_accum_mul(&swz, sb, sc); + load256i128(rswz, &swz); + CHECK(secp256k1_memcmp_var(rswr, rswz, 16) == 0); + } + /* test secp256k1_i128_det */ + { + uint16_t rsd[16], rse[16], rst[32]; + int64_t sd = v[0], se = v[1]; + load256u64(rsd, sd, 1); + load256u64(rse, se, 1); + mulmod256(rst, rsc, rsd, NULL); + neg256(rst, rst); + mulmod256(rswr, rsb, rse, NULL); + add256(rswr, rswr, rst); + secp256k1_i128_det(&swz, sb, sc, sd, se); + load256i128(rswz, &swz); + CHECK(secp256k1_memcmp_var(rswr, rswz, 16) == 0); + } + /* test secp256k1_i128_rshift */ + rshift256(rswr, rswa, uc % 127, 1); + swz = swa; + secp256k1_i128_rshift(&swz, uc % 127); + load256i128(rswz, &swz); + CHECK(secp256k1_memcmp_var(rswr, rswz, 16) == 0); + /* test secp256k1_i128_to_u64 */ + CHECK(secp256k1_i128_to_u64(&swa) == v[0]); + /* test secp256k1_i128_from_i64 */ + secp256k1_i128_from_i64(&swz, sb); + load256i128(rswz, &swz); + CHECK(secp256k1_memcmp_var(rsb, rswz, 16) == 0); + /* test secp256k1_i128_to_i64 */ + CHECK(secp256k1_i128_to_i64(&swz) == sb); + /* test secp256k1_i128_eq_var */ + { + int expect = (uc & 1); + swz = swa; + if (!expect) { + /* Make sure swz != swa */ + uint64_t v0c = v[0], v1c = v[1]; + if (ub & 64) { + v1c ^= (((uint64_t)1) << (ub & 63)); + } else { + v0c ^= (((uint64_t)1) << (ub & 63)); + } + secp256k1_i128_load(&swz, v1c, v0c); + } + CHECK(secp256k1_i128_eq_var(&swa, &swz) == expect); + } + /* test secp256k1_i128_check_pow2 (sign == 1) */ + { + int expect = (uc & 1); + int pos = ub % 127; + if (expect) { + /* If expect==1, set swz to exactly 2^pos. */ + uint64_t hi = 0; + uint64_t lo = 0; + if (pos >= 64) { + hi = (((uint64_t)1) << (pos & 63)); + } else { + lo = (((uint64_t)1) << (pos & 63)); + } + secp256k1_i128_load(&swz, hi, lo); + } else { + /* If expect==0, set swz = swa, but update expect=1 if swa happens to equal 2^pos. */ + if (pos >= 64) { + if ((v[1] == (((uint64_t)1) << (pos & 63))) && v[0] == 0) expect = 1; + } else { + if ((v[0] == (((uint64_t)1) << (pos & 63))) && v[1] == 0) expect = 1; + } + swz = swa; + } + CHECK(secp256k1_i128_check_pow2(&swz, pos, 1) == expect); + } + /* test secp256k1_i128_check_pow2 (sign == -1) */ + { + int expect = (uc & 1); + int pos = ub % 127; + if (expect) { + /* If expect==1, set swz to exactly -2^pos. */ + uint64_t hi = ~(uint64_t)0; + uint64_t lo = ~(uint64_t)0; + if (pos >= 64) { + hi <<= (pos & 63); + lo = 0; + } else { + lo <<= (pos & 63); + } + secp256k1_i128_load(&swz, hi, lo); + } else { + /* If expect==0, set swz = swa, but update expect=1 if swa happens to equal -2^pos. */ + if (pos >= 64) { + if ((v[1] == ((~(uint64_t)0) << (pos & 63))) && v[0] == 0) expect = 1; + } else { + if ((v[0] == ((~(uint64_t)0) << (pos & 63))) && v[1] == ~(uint64_t)0) expect = 1; + } + swz = swa; + } + CHECK(secp256k1_i128_check_pow2(&swz, pos, -1) == expect); + } +} + +static void run_int128_tests(void) { + { /* secp256k1_u128_accum_mul */ + secp256k1_uint128 res; + + /* Check secp256k1_u128_accum_mul overflow */ + secp256k1_u128_mul(&res, UINT64_MAX, UINT64_MAX); + secp256k1_u128_accum_mul(&res, UINT64_MAX, UINT64_MAX); + CHECK(secp256k1_u128_to_u64(&res) == 2); + CHECK(secp256k1_u128_hi_u64(&res) == 18446744073709551612U); + } + { /* secp256k1_u128_accum_mul */ + secp256k1_int128 res; + + /* Compute INT128_MAX = 2^127 - 1 with secp256k1_i128_accum_mul */ + secp256k1_i128_mul(&res, INT64_MAX, INT64_MAX); + secp256k1_i128_accum_mul(&res, INT64_MAX, INT64_MAX); + CHECK(secp256k1_i128_to_u64(&res) == 2); + secp256k1_i128_accum_mul(&res, 4, 9223372036854775807); + secp256k1_i128_accum_mul(&res, 1, 1); + CHECK(secp256k1_i128_to_u64(&res) == UINT64_MAX); + secp256k1_i128_rshift(&res, 64); + CHECK(secp256k1_i128_to_i64(&res) == INT64_MAX); + + /* Compute INT128_MIN = - 2^127 with secp256k1_i128_accum_mul */ + secp256k1_i128_mul(&res, INT64_MAX, INT64_MIN); + CHECK(secp256k1_i128_to_u64(&res) == (uint64_t)INT64_MIN); + secp256k1_i128_accum_mul(&res, INT64_MAX, INT64_MIN); + CHECK(secp256k1_i128_to_u64(&res) == 0); + secp256k1_i128_accum_mul(&res, 2, INT64_MIN); + CHECK(secp256k1_i128_to_u64(&res) == 0); + secp256k1_i128_rshift(&res, 64); + CHECK(secp256k1_i128_to_i64(&res) == INT64_MIN); + } + { + /* Randomized tests. */ + int i; + for (i = 0; i < 256 * COUNT; ++i) run_int128_test_case(); + } +} +#endif +/***** SCALAR TESTS *****/ -void scalar_test(void) { +static void scalar_test(void) { secp256k1_scalar s; secp256k1_scalar s1; secp256k1_scalar s2; @@ -1878,7 +2323,7 @@ void scalar_test(void) { } -void run_scalar_set_b32_seckey_tests(void) { +static void run_scalar_set_b32_seckey_tests(void) { unsigned char b32[32]; secp256k1_scalar s1; secp256k1_scalar s2; @@ -1895,12 +2340,12 @@ void run_scalar_set_b32_seckey_tests(void) { CHECK(secp256k1_scalar_set_b32_seckey(&s2, b32) == 0); } -void run_scalar_tests(void) { +static void run_scalar_tests(void) { int i; - for (i = 0; i < 128 * count; i++) { + for (i = 0; i < 128 * COUNT; i++) { scalar_test(); } - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { run_scalar_set_b32_seckey_tests(); } @@ -2503,7 +2948,7 @@ void run_scalar_tests(void) { /***** FIELD TESTS *****/ -void random_fe(secp256k1_fe *x) { +static void random_fe(secp256k1_fe *x) { unsigned char bin[32]; do { secp256k1_testrand256(bin); @@ -2513,7 +2958,7 @@ void random_fe(secp256k1_fe *x) { } while(1); } -void random_fe_test(secp256k1_fe *x) { +static void random_fe_test(secp256k1_fe *x) { unsigned char bin[32]; do { secp256k1_testrand256_test(bin); @@ -2523,7 +2968,7 @@ void random_fe_test(secp256k1_fe *x) { } while(1); } -void random_fe_non_zero(secp256k1_fe *nz) { +static void random_fe_non_zero(secp256k1_fe *nz) { int tries = 10; while (--tries >= 0) { random_fe(nz); @@ -2536,7 +2981,7 @@ void random_fe_non_zero(secp256k1_fe *nz) { CHECK(tries >= 0); } -void random_fe_non_square(secp256k1_fe *ns) { +static void random_fe_non_square(secp256k1_fe *ns) { secp256k1_fe r; random_fe_non_zero(ns); if (secp256k1_fe_sqrt(&r, ns)) { @@ -2544,7 +2989,7 @@ void random_fe_non_square(secp256k1_fe *ns) { } } -int check_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { +static int check_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { secp256k1_fe an = *a; secp256k1_fe bn = *b; secp256k1_fe_normalize_weak(&an); @@ -2552,7 +2997,7 @@ int check_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { return secp256k1_fe_equal_var(&an, &bn); } -void run_field_convert(void) { +static void run_field_convert(void) { static const unsigned char b32[32] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, @@ -2583,7 +3028,7 @@ void run_field_convert(void) { } /* Returns true if two field elements have the same representation. */ -int fe_identical(const secp256k1_fe *a, const secp256k1_fe *b) { +static int fe_identical(const secp256k1_fe *a, const secp256k1_fe *b) { int ret = 1; #ifdef VERIFY ret &= (a->magnitude == b->magnitude); @@ -2594,7 +3039,7 @@ int fe_identical(const secp256k1_fe *a, const secp256k1_fe *b) { return ret; } -void run_field_half(void) { +static void run_field_half(void) { secp256k1_fe t, u; int m; @@ -2643,14 +3088,15 @@ void run_field_half(void) { } } -void run_field_misc(void) { +static void run_field_misc(void) { secp256k1_fe x; secp256k1_fe y; secp256k1_fe z; secp256k1_fe q; + int v; secp256k1_fe fe5 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 5); int i, j; - for (i = 0; i < 1000 * count; i++) { + for (i = 0; i < 1000 * COUNT; i++) { secp256k1_fe_storage xs, ys, zs; if (i & 1) { random_fe(&x); @@ -2658,6 +3104,14 @@ void run_field_misc(void) { random_fe_test(&x); } random_fe_non_zero(&y); + v = secp256k1_testrand_bits(15); + /* Test that fe_add_int is equivalent to fe_set_int + fe_add. */ + secp256k1_fe_set_int(&q, v); /* q = v */ + z = x; /* z = x */ + secp256k1_fe_add(&z, &q); /* z = x+v */ + q = x; /* q = x */ + secp256k1_fe_add_int(&q, v); /* q = x+v */ + CHECK(check_fe_equal(&q, &z)); /* Test the fe equality and comparison operations. */ CHECK(secp256k1_fe_cmp_var(&x, &x) == 0); CHECK(secp256k1_fe_equal_var(&x, &x)); @@ -2735,7 +3189,7 @@ void run_field_misc(void) { } } -void test_fe_mul(const secp256k1_fe* a, const secp256k1_fe* b, int use_sqr) +static void test_fe_mul(const secp256k1_fe* a, const secp256k1_fe* b, int use_sqr) { secp256k1_fe c, an, bn; /* Variables in BE 32-byte format. */ @@ -2778,9 +3232,9 @@ void test_fe_mul(const secp256k1_fe* a, const secp256k1_fe* b, int use_sqr) CHECK(secp256k1_memcmp_var(t16, c16, 32) == 0); } -void run_fe_mul(void) { +static void run_fe_mul(void) { int i; - for (i = 0; i < 100 * count; ++i) { + for (i = 0; i < 100 * COUNT; ++i) { secp256k1_fe a, b, c, d; random_fe(&a); random_field_element_magnitude(&a); @@ -2799,7 +3253,7 @@ void run_fe_mul(void) { } } -void run_sqr(void) { +static void run_sqr(void) { secp256k1_fe x, s; { @@ -2815,7 +3269,7 @@ void run_sqr(void) { } } -void test_sqrt(const secp256k1_fe *a, const secp256k1_fe *k) { +static void test_sqrt(const secp256k1_fe *a, const secp256k1_fe *k) { secp256k1_fe r1, r2; int v = secp256k1_fe_sqrt(&r1, a); CHECK((v == 0) == (k == NULL)); @@ -2829,7 +3283,7 @@ void test_sqrt(const secp256k1_fe *a, const secp256k1_fe *k) { } } -void run_sqrt(void) { +static void run_sqrt(void) { secp256k1_fe ns, x, s, t; int i; @@ -2851,11 +3305,13 @@ void run_sqrt(void) { for (i = 0; i < 10; i++) { int j; random_fe_non_square(&ns); - for (j = 0; j < count; j++) { + for (j = 0; j < COUNT; j++) { random_fe(&x); secp256k1_fe_sqr(&s, &x); + CHECK(secp256k1_fe_is_square_var(&s)); test_sqrt(&s, &x); secp256k1_fe_negate(&t, &s, 1); + CHECK(!secp256k1_fe_is_square_var(&t)); test_sqrt(&t, NULL); secp256k1_fe_mul(&t, &s, &ns); test_sqrt(&t, NULL); @@ -2882,7 +3338,7 @@ static const secp256k1_fe fe_minus_one = SECP256K1_FE_CONST( * for x!=0 and x!=1: 1/(1/x - 1) + 1 == -1/(x-1) */ -void test_inverse_scalar(secp256k1_scalar* out, const secp256k1_scalar* x, int var) +static void test_inverse_scalar(secp256k1_scalar* out, const secp256k1_scalar* x, int var) { secp256k1_scalar l, r, t; @@ -2904,7 +3360,7 @@ void test_inverse_scalar(secp256k1_scalar* out, const secp256k1_scalar* x, int v CHECK(secp256k1_scalar_is_zero(&l)); /* l == 0 */ } -void test_inverse_field(secp256k1_fe* out, const secp256k1_fe* x, int var) +static void test_inverse_field(secp256k1_fe* out, const secp256k1_fe* x, int var) { secp256k1_fe l, r, t; @@ -2924,12 +3380,12 @@ void test_inverse_field(secp256k1_fe* out, const secp256k1_fe* x, int var) (var ? secp256k1_fe_inv_var : secp256k1_fe_inv)(&r, &r); /* r = 1/(x-1) */ secp256k1_fe_add(&l, &fe_minus_one); /* l = 1/x-1 */ (var ? secp256k1_fe_inv_var : secp256k1_fe_inv)(&l, &l); /* l = 1/(1/x-1) */ - secp256k1_fe_add(&l, &secp256k1_fe_one); /* l = 1/(1/x-1)+1 */ + secp256k1_fe_add_int(&l, 1); /* l = 1/(1/x-1)+1 */ secp256k1_fe_add(&l, &r); /* l = 1/(1/x-1)+1 + 1/(x-1) */ CHECK(secp256k1_fe_normalizes_to_zero_var(&l)); /* l == 0 */ } -void run_inverse_tests(void) +static void run_inverse_tests(void) { /* Fixed test cases for field inverses: pairs of (x, 1/x) mod p. */ static const secp256k1_fe fe_cases[][2] = { @@ -3163,7 +3619,7 @@ void run_inverse_tests(void) } /* test 128*count random inputs; half with testrand256_test, half with testrand256 */ for (testrand = 0; testrand <= 1; ++testrand) { - for (i = 0; i < 64 * count; ++i) { + for (i = 0; i < 64 * COUNT; ++i) { (testrand ? secp256k1_testrand256_test : secp256k1_testrand256)(b32); secp256k1_scalar_set_b32(&x_scalar, b32, NULL); secp256k1_fe_set_b32(&x_fe, b32); @@ -3177,7 +3633,7 @@ void run_inverse_tests(void) /***** GROUP TESTS *****/ -void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { +static void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { CHECK(a->infinity == b->infinity); if (a->infinity) { return; @@ -3187,7 +3643,7 @@ void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { } /* This compares jacobian points including their Z, not just their geometric meaning. */ -int gej_xyz_equals_gej(const secp256k1_gej *a, const secp256k1_gej *b) { +static int gej_xyz_equals_gej(const secp256k1_gej *a, const secp256k1_gej *b) { secp256k1_gej a2; secp256k1_gej b2; int ret = 1; @@ -3208,7 +3664,7 @@ int gej_xyz_equals_gej(const secp256k1_gej *a, const secp256k1_gej *b) { return ret; } -void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { +static void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { secp256k1_fe z2s; secp256k1_fe u1, u2, s1, s2; CHECK(a->infinity == b->infinity); @@ -3225,7 +3681,7 @@ void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { CHECK(secp256k1_fe_equal_var(&s1, &s2)); } -void test_ge(void) { +static void test_ge(void) { int i, i1; int runs = 6; /* 25 points are used: @@ -3234,8 +3690,8 @@ void test_ge(void) { * negation, and then those two again but with randomized Z coordinate. * - The same is then done for lambda*p1 and lambda^2*p1. */ - secp256k1_ge *ge = (secp256k1_ge *)checked_malloc(&ctx->error_callback, sizeof(secp256k1_ge) * (1 + 4 * runs)); - secp256k1_gej *gej = (secp256k1_gej *)checked_malloc(&ctx->error_callback, sizeof(secp256k1_gej) * (1 + 4 * runs)); + secp256k1_ge *ge = (secp256k1_ge *)checked_malloc(&CTX->error_callback, sizeof(secp256k1_ge) * (1 + 4 * runs)); + secp256k1_gej *gej = (secp256k1_gej *)checked_malloc(&CTX->error_callback, sizeof(secp256k1_gej) * (1 + 4 * runs)); secp256k1_fe zf; secp256k1_fe zfi2, zfi3; @@ -3358,7 +3814,7 @@ void test_ge(void) { /* Test adding all points together in random order equals infinity. */ { secp256k1_gej sum = SECP256K1_GEJ_CONST_INFINITY; - secp256k1_gej *gej_shuffled = (secp256k1_gej *)checked_malloc(&ctx->error_callback, (4 * runs + 1) * sizeof(secp256k1_gej)); + secp256k1_gej *gej_shuffled = (secp256k1_gej *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_gej)); for (i = 0; i < 4 * runs + 1; i++) { gej_shuffled[i] = gej[i]; } @@ -3379,7 +3835,7 @@ void test_ge(void) { /* Test batch gej -> ge conversion without known z ratios. */ { - secp256k1_ge *ge_set_all = (secp256k1_ge *)checked_malloc(&ctx->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge)); + secp256k1_ge *ge_set_all = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge)); secp256k1_ge_set_all_gej_var(ge_set_all, gej, 4 * runs + 1); for (i = 0; i < 4 * runs + 1; i++) { secp256k1_fe s; @@ -3424,8 +3880,7 @@ void test_ge(void) { free(gej); } - -void test_intialized_inf(void) { +static void test_intialized_inf(void) { secp256k1_ge p; secp256k1_gej pj, npj, infj1, infj2, infj3; secp256k1_fe zinv; @@ -3457,7 +3912,7 @@ void test_intialized_inf(void) { } -void test_add_neg_y_diff_x(void) { +static void test_add_neg_y_diff_x(void) { /* The point of this test is to check that we can add two points * whose y-coordinates are negatives of each other but whose x * coordinates differ. If the x-coordinates were the same, these @@ -3524,16 +3979,16 @@ void test_add_neg_y_diff_x(void) { ge_equals_gej(&res, &sumj); } -void run_ge(void) { +static void run_ge(void) { int i; - for (i = 0; i < count * 32; i++) { + for (i = 0; i < COUNT * 32; i++) { test_ge(); } test_add_neg_y_diff_x(); test_intialized_inf(); } -void test_gej_cmov(const secp256k1_gej *a, const secp256k1_gej *b) { +static void test_gej_cmov(const secp256k1_gej *a, const secp256k1_gej *b) { secp256k1_gej t = *a; secp256k1_gej_cmov(&t, b, 0); CHECK(gej_xyz_equals_gej(&t, a)); @@ -3541,12 +3996,12 @@ void test_gej_cmov(const secp256k1_gej *a, const secp256k1_gej *b) { CHECK(gej_xyz_equals_gej(&t, b)); } -void run_gej(void) { +static void run_gej(void) { int i; secp256k1_gej a, b; /* Tests for secp256k1_gej_cmov */ - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { secp256k1_gej_set_infinity(&a); secp256k1_gej_set_infinity(&b); test_gej_cmov(&a, &b); @@ -3562,9 +4017,25 @@ void run_gej(void) { test_gej_cmov(&a, &b); test_gej_cmov(&b, &a); } + + /* Tests for secp256k1_gej_eq_var */ + for (i = 0; i < COUNT; i++) { + secp256k1_fe fe; + random_gej_test(&a); + random_gej_test(&b); + CHECK(!secp256k1_gej_eq_var(&a, &b)); + + b = a; + random_field_element_test(&fe); + if (secp256k1_fe_is_zero(&fe)) { + continue; + } + secp256k1_gej_rescale(&a, &fe); + CHECK(secp256k1_gej_eq_var(&a, &b)); + } } -void test_ec_combine(void) { +static void test_ec_combine(void) { secp256k1_scalar sum = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); secp256k1_pubkey data[6]; const secp256k1_pubkey* d[6]; @@ -3577,26 +4048,26 @@ void test_ec_combine(void) { secp256k1_scalar s; random_scalar_order_test(&s); secp256k1_scalar_add(&sum, &sum, &s); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &s); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &Qj, &s); secp256k1_ge_set_gej(&Q, &Qj); secp256k1_pubkey_save(&data[i - 1], &Q); d[i - 1] = &data[i - 1]; - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &sum); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &Qj, &sum); secp256k1_ge_set_gej(&Q, &Qj); secp256k1_pubkey_save(&sd, &Q); - CHECK(secp256k1_ec_pubkey_combine(ctx, &sd2, d, i) == 1); + CHECK(secp256k1_ec_pubkey_combine(CTX, &sd2, d, i) == 1); CHECK(secp256k1_memcmp_var(&sd, &sd2, sizeof(sd)) == 0); } } -void run_ec_combine(void) { +static void run_ec_combine(void) { int i; - for (i = 0; i < count * 8; i++) { + for (i = 0; i < COUNT * 8; i++) { test_ec_combine(); } } -void test_group_decompress(const secp256k1_fe* x) { +static void test_group_decompress(const secp256k1_fe* x) { /* The input itself, normalized. */ secp256k1_fe fex = *x; /* Results of set_xo_var(..., 0), set_xo_var(..., 1). */ @@ -3631,9 +4102,9 @@ void test_group_decompress(const secp256k1_fe* x) { } } -void run_group_decompress(void) { +static void run_group_decompress(void) { int i; - for (i = 0; i < count * 4; i++) { + for (i = 0; i < COUNT * 4; i++) { secp256k1_fe fe; random_fe_test(&fe); test_group_decompress(&fe); @@ -3642,7 +4113,7 @@ void run_group_decompress(void) { /***** ECMULT TESTS *****/ -void test_pre_g_table(const secp256k1_ge_storage * pre_g, size_t n) { +static void test_pre_g_table(const secp256k1_ge_storage * pre_g, size_t n) { /* Tests the pre_g / pre_g_128 tables for consistency. * For independent verification we take a "geometric" approach to verification. * We check that every entry is on-curve. @@ -3692,7 +4163,7 @@ void test_pre_g_table(const secp256k1_ge_storage * pre_g, size_t n) { } } -void run_ecmult_pre_g(void) { +static void run_ecmult_pre_g(void) { secp256k1_ge_storage gs; secp256k1_gej gj; secp256k1_ge g; @@ -3716,7 +4187,7 @@ void run_ecmult_pre_g(void) { CHECK(secp256k1_memcmp_var(&gs, &secp256k1_pre_g_128[0], sizeof(gs)) == 0); } -void run_ecmult_chain(void) { +static void run_ecmult_chain(void) { /* random starting point A (on the curve) */ secp256k1_gej a = SECP256K1_GEJ_CONST( 0x8b30bbe9, 0xae2a9906, 0x96b22f67, 0x0709dff3, @@ -3746,7 +4217,7 @@ void run_ecmult_chain(void) { /* the point being computed */ x = a; - for (i = 0; i < 200*count; i++) { + for (i = 0; i < 200*COUNT; i++) { /* in each iteration, compute X = xn*X + gn*G; */ secp256k1_ecmult(&x, &x, &xn, &gn); /* also compute ae and ge: the actual accumulated factors for A and G */ @@ -3767,20 +4238,15 @@ void run_ecmult_chain(void) { 0xB95CBCA2, 0xC77DA786, 0x539BE8FD, 0x53354D2D, 0x3B4F566A, 0xE6580454, 0x07ED6015, 0xEE1B2A88 ); - - secp256k1_gej_neg(&rp, &rp); - secp256k1_gej_add_var(&rp, &rp, &x, NULL); - CHECK(secp256k1_gej_is_infinity(&rp)); + CHECK(secp256k1_gej_eq_var(&rp, &x)); } } /* redo the computation, but directly with the resulting ae and ge coefficients: */ secp256k1_ecmult(&x2, &a, &ae, &ge); - secp256k1_gej_neg(&x2, &x2); - secp256k1_gej_add_var(&x2, &x2, &x, NULL); - CHECK(secp256k1_gej_is_infinity(&x2)); + CHECK(secp256k1_gej_eq_var(&x, &x2)); } -void test_point_times_order(const secp256k1_gej *point) { +static void test_point_times_order(const secp256k1_gej *point) { /* X * (point + G) + (order-X) * (pointer + G) = 0 */ secp256k1_scalar x; secp256k1_scalar nx; @@ -3844,7 +4310,7 @@ static const secp256k1_scalar scalars_near_split_bounds[20] = { SECP256K1_SCALAR_CONST(0x26c75a99, 0x80b861c1, 0x4a4c3805, 0x1024c8b4, 0x704d760e, 0xe95e7cd3, 0xde1bfdb1, 0xce2c5a45) }; -void test_ecmult_target(const secp256k1_scalar* target, int mode) { +static void test_ecmult_target(const secp256k1_scalar* target, int mode) { /* Mode: 0=ecmult_gen, 1=ecmult, 2=ecmult_const */ secp256k1_scalar n1, n2; secp256k1_ge p; @@ -3864,9 +4330,9 @@ void test_ecmult_target(const secp256k1_scalar* target, int mode) { /* EC multiplications */ if (mode == 0) { - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &p1j, &n1); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &p2j, &n2); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &ptj, target); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &p1j, &n1); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &p2j, &n2); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &ptj, target); } else if (mode == 1) { secp256k1_ecmult(&p1j, &pj, &n1, &zero); secp256k1_ecmult(&p2j, &pj, &n2, &zero); @@ -3883,10 +4349,10 @@ void test_ecmult_target(const secp256k1_scalar* target, int mode) { CHECK(secp256k1_gej_is_infinity(&ptj)); } -void run_ecmult_near_split_bound(void) { +static void run_ecmult_near_split_bound(void) { int i; unsigned j; - for (i = 0; i < 4*count; ++i) { + for (i = 0; i < 4*COUNT; ++i) { for (j = 0; j < sizeof(scalars_near_split_bounds) / sizeof(scalars_near_split_bounds[0]); ++j) { test_ecmult_target(&scalars_near_split_bounds[j], 0); test_ecmult_target(&scalars_near_split_bounds[j], 1); @@ -3895,7 +4361,7 @@ void run_ecmult_near_split_bound(void) { } } -void run_point_times_order(void) { +static void run_point_times_order(void) { int i; secp256k1_fe x = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 2); static const secp256k1_fe xr = SECP256K1_FE_CONST( @@ -3916,7 +4382,7 @@ void run_point_times_order(void) { CHECK(secp256k1_fe_equal_var(&x, &xr)); } -void ecmult_const_random_mult(void) { +static void ecmult_const_random_mult(void) { /* random starting point A (on the curve) */ secp256k1_ge a = SECP256K1_GE_CONST( 0x6d986544, 0x57ff52b8, 0xcf1b8126, 0x5b802a5b, @@ -3943,7 +4409,7 @@ void ecmult_const_random_mult(void) { ge_equals_gej(&expected_b, &b); } -void ecmult_const_commutativity(void) { +static void ecmult_const_commutativity(void) { secp256k1_scalar a; secp256k1_scalar b; secp256k1_gej res1; @@ -3964,7 +4430,7 @@ void ecmult_const_commutativity(void) { ge_equals_ge(&mid1, &mid2); } -void ecmult_const_mult_zero_one(void) { +static void ecmult_const_mult_zero_one(void) { secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); secp256k1_scalar one = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1); secp256k1_scalar negone; @@ -3986,7 +4452,7 @@ void ecmult_const_mult_zero_one(void) { ge_equals_ge(&res2, &point); } -void ecmult_const_chain_multiply(void) { +static void ecmult_const_chain_multiply(void) { /* Check known result (randomly generated test problem from sage) */ const secp256k1_scalar scalar = SECP256K1_SCALAR_CONST( 0x4968d524, 0x2abf9b7a, 0x466abbcf, 0x34b11b6d, @@ -4012,7 +4478,7 @@ void ecmult_const_chain_multiply(void) { ge_equals_gej(&res, &expected_point); } -void run_ecmult_const_tests(void) { +static void run_ecmult_const_tests(void) { ecmult_const_mult_zero_one(); ecmult_const_random_mult(); ecmult_const_commutativity(); @@ -4039,7 +4505,7 @@ static int ecmult_multi_false_callback(secp256k1_scalar *sc, secp256k1_ge *pt, s return 0; } -void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func ecmult_multi) { +static void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func ecmult_multi) { int ncount; secp256k1_scalar szero; secp256k1_scalar sc[32]; @@ -4053,10 +4519,10 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e secp256k1_scalar_set_int(&szero, 0); /* No points to multiply */ - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, NULL, ecmult_multi_callback, &data, 0)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, NULL, ecmult_multi_callback, &data, 0)); /* Check 1- and 2-point multiplies against ecmult */ - for (ncount = 0; ncount < count; ncount++) { + for (ncount = 0; ncount < COUNT; ncount++) { secp256k1_ge ptg; secp256k1_gej ptgj; random_scalar_order(&sc[0]); @@ -4069,38 +4535,30 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e /* only G scalar */ secp256k1_ecmult(&r2, &ptgj, &szero, &sc[0]); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &sc[0], ecmult_multi_callback, &data, 0)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &sc[0], ecmult_multi_callback, &data, 0)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); /* 1-point */ secp256k1_ecmult(&r2, &ptgj, &sc[0], &szero); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 1)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 1)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); /* Try to multiply 1 point, but callback returns false */ - CHECK(!ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_false_callback, &data, 1)); + CHECK(!ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_false_callback, &data, 1)); /* 2-point */ secp256k1_ecmult(&r2, &ptgj, &sc[0], &sc[1]); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 2)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 2)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); /* 2-point with G scalar */ secp256k1_ecmult(&r2, &ptgj, &sc[0], &sc[1]); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &sc[1], ecmult_multi_callback, &data, 1)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &sc[1], ecmult_multi_callback, &data, 1)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); } /* Check infinite outputs of various forms */ - for (ncount = 0; ncount < count; ncount++) { + for (ncount = 0; ncount < COUNT; ncount++) { secp256k1_ge ptg; size_t i, j; size_t sizes[] = { 2, 10, 32 }; @@ -4110,7 +4568,7 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e random_scalar_order(&sc[i]); secp256k1_ge_set_infinity(&pt[i]); } - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); CHECK(secp256k1_gej_is_infinity(&r)); } @@ -4120,7 +4578,7 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e pt[i] = ptg; secp256k1_scalar_set_int(&sc[i], 0); } - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); CHECK(secp256k1_gej_is_infinity(&r)); } @@ -4133,7 +4591,7 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e pt[2 * i + 1] = ptg; } - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); CHECK(secp256k1_gej_is_infinity(&r)); random_scalar_order(&sc[0]); @@ -4146,7 +4604,7 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e secp256k1_ge_neg(&pt[2*i+1], &pt[2*i]); } - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, sizes[j])); CHECK(secp256k1_gej_is_infinity(&r)); } @@ -4161,12 +4619,12 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e secp256k1_scalar_negate(&sc[i], &sc[i]); } - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 32)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 32)); CHECK(secp256k1_gej_is_infinity(&r)); } /* Check random points, constant scalar */ - for (ncount = 0; ncount < count; ncount++) { + for (ncount = 0; ncount < COUNT; ncount++) { size_t i; secp256k1_gej_set_infinity(&r); @@ -4180,14 +4638,12 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e } secp256k1_ecmult(&r2, &r, &sc[0], &szero); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); } /* Check random scalars, constant point */ - for (ncount = 0; ncount < count; ncount++) { + for (ncount = 0; ncount < COUNT; ncount++) { size_t i; secp256k1_ge ptg; secp256k1_gej p0j; @@ -4203,10 +4659,8 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e secp256k1_gej_set_ge(&p0j, &pt[0]); secp256k1_ecmult(&r2, &p0j, &rs, &szero); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); - secp256k1_gej_neg(&r2, &r2); - secp256k1_gej_add_var(&r, &r, &r2, NULL); - CHECK(secp256k1_gej_is_infinity(&r)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); + CHECK(secp256k1_gej_eq_var(&r, &r2)); } /* Sanity check that zero scalars don't cause problems */ @@ -4216,13 +4670,13 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e } secp256k1_scalar_clear(&sc[0]); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 20)); secp256k1_scalar_clear(&sc[1]); secp256k1_scalar_clear(&sc[2]); secp256k1_scalar_clear(&sc[3]); secp256k1_scalar_clear(&sc[4]); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 6)); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 5)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 6)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &r, &szero, ecmult_multi_callback, &data, 5)); CHECK(secp256k1_gej_is_infinity(&r)); /* Run through s0*(t0*P) + s1*(t1*P) exhaustively for many small values of s0, s1, t0, t1 */ @@ -4267,10 +4721,8 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e secp256k1_scalar_add(&tmp1, &tmp1, &tmp2); secp256k1_ecmult(&expected, &ptgj, &tmp1, &szero); - CHECK(ecmult_multi(&ctx->error_callback, scratch, &actual, &szero, ecmult_multi_callback, &data, 2)); - secp256k1_gej_neg(&expected, &expected); - secp256k1_gej_add_var(&actual, &actual, &expected, NULL); - CHECK(secp256k1_gej_is_infinity(&actual)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &actual, &szero, ecmult_multi_callback, &data, 2)); + CHECK(secp256k1_gej_eq_var(&actual, &expected)); } } } @@ -4278,7 +4730,7 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e } } -int test_ecmult_multi_random(secp256k1_scratch *scratch) { +static int test_ecmult_multi_random(secp256k1_scratch *scratch) { /* Large random test for ecmult_multi_* functions which exercises: * - Few or many inputs (0 up to 128, roughly exponentially distributed). * - Few or many 0*P or a*INF inputs (roughly uniformly distributed). @@ -4345,7 +4797,7 @@ int test_ecmult_multi_random(secp256k1_scratch *scratch) { secp256k1_scalar_mul(&scalars[filled], &sc_tmp, &g_scalar); secp256k1_scalar_inverse_var(&sc_tmp, &sc_tmp); secp256k1_scalar_negate(&sc_tmp, &sc_tmp); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &gejs[filled], &sc_tmp); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &gejs[filled], &sc_tmp); ++filled; ++mults; } @@ -4437,16 +4889,14 @@ int test_ecmult_multi_random(secp256k1_scratch *scratch) { /* Invoke ecmult_multi code. */ data.sc = scalars; data.pt = ges; - CHECK(ecmult_multi(&ctx->error_callback, scratch, &computed, g_scalar_ptr, ecmult_multi_callback, &data, filled)); + CHECK(ecmult_multi(&CTX->error_callback, scratch, &computed, g_scalar_ptr, ecmult_multi_callback, &data, filled)); mults += num_nonzero + g_nonzero; /* Compare with expected result. */ - secp256k1_gej_neg(&computed, &computed); - secp256k1_gej_add_var(&computed, &computed, &expected, NULL); - CHECK(secp256k1_gej_is_infinity(&computed)); + CHECK(secp256k1_gej_eq_var(&computed, &expected)); return mults; } -void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { +static void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scalar szero; secp256k1_scalar sc; secp256k1_ge pt; @@ -4461,12 +4911,12 @@ void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scalar_set_int(&szero, 0); /* Try to multiply 1 point, but scratch space is empty.*/ - scratch_empty = secp256k1_scratch_create(&ctx->error_callback, 0); - CHECK(!ecmult_multi(&ctx->error_callback, scratch_empty, &r, &szero, ecmult_multi_callback, &data, 1)); - secp256k1_scratch_destroy(&ctx->error_callback, scratch_empty); + scratch_empty = secp256k1_scratch_create(&CTX->error_callback, 0); + CHECK(!ecmult_multi(&CTX->error_callback, scratch_empty, &r, &szero, ecmult_multi_callback, &data, 1)); + secp256k1_scratch_destroy(&CTX->error_callback, scratch_empty); } -void test_secp256k1_pippenger_bucket_window_inv(void) { +static void test_secp256k1_pippenger_bucket_window_inv(void) { int i; CHECK(secp256k1_pippenger_bucket_window_inv(0) == 0); @@ -4486,7 +4936,7 @@ void test_secp256k1_pippenger_bucket_window_inv(void) { * Probabilistically test the function returning the maximum number of possible points * for a given scratch space. */ -void test_ecmult_multi_pippenger_max_points(void) { +static void test_ecmult_multi_pippenger_max_points(void) { size_t scratch_size = secp256k1_testrand_bits(8); size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12); secp256k1_scratch *scratch; @@ -4497,29 +4947,29 @@ void test_ecmult_multi_pippenger_max_points(void) { size_t i; size_t total_alloc; size_t checkpoint; - scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size); + scratch = secp256k1_scratch_create(&CTX->error_callback, scratch_size); CHECK(scratch != NULL); - checkpoint = secp256k1_scratch_checkpoint(&ctx->error_callback, scratch); - n_points_supported = secp256k1_pippenger_max_points(&ctx->error_callback, scratch); + checkpoint = secp256k1_scratch_checkpoint(&CTX->error_callback, scratch); + n_points_supported = secp256k1_pippenger_max_points(&CTX->error_callback, scratch); if (n_points_supported == 0) { - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); continue; } bucket_window = secp256k1_pippenger_bucket_window(n_points_supported); /* allocate `total_alloc` bytes over `PIPPENGER_SCRATCH_OBJECTS` many allocations */ total_alloc = secp256k1_pippenger_scratch_size(n_points_supported, bucket_window); for (i = 0; i < PIPPENGER_SCRATCH_OBJECTS - 1; i++) { - CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, 1)); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, 1)); total_alloc--; } - CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, total_alloc)); - secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, checkpoint); - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + CHECK(secp256k1_scratch_alloc(&CTX->error_callback, scratch, total_alloc)); + secp256k1_scratch_apply_checkpoint(&CTX->error_callback, scratch, checkpoint); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); } CHECK(bucket_window == PIPPENGER_MAX_BUCKET_WINDOW); } -void test_ecmult_multi_batch_size_helper(void) { +static void test_ecmult_multi_batch_size_helper(void) { size_t n_batches, n_batch_points, max_n_batch_points, n; max_n_batch_points = 0; @@ -4567,12 +5017,12 @@ void test_ecmult_multi_batch_size_helper(void) { * Run secp256k1_ecmult_multi_var with num points and a scratch space restricted to * 1 <= i <= num points. */ -void test_ecmult_multi_batching(void) { +static void test_ecmult_multi_batching(void) { static const int n_points = 2*ECMULT_PIPPENGER_THRESHOLD; secp256k1_scalar scG; secp256k1_scalar szero; - secp256k1_scalar *sc = (secp256k1_scalar *)checked_malloc(&ctx->error_callback, sizeof(secp256k1_scalar) * n_points); - secp256k1_ge *pt = (secp256k1_ge *)checked_malloc(&ctx->error_callback, sizeof(secp256k1_ge) * n_points); + secp256k1_scalar *sc = (secp256k1_scalar *)checked_malloc(&CTX->error_callback, sizeof(secp256k1_scalar) * n_points); + secp256k1_ge *pt = (secp256k1_ge *)checked_malloc(&CTX->error_callback, sizeof(secp256k1_ge) * n_points); secp256k1_gej r; secp256k1_gej r2; ecmult_multi_data data; @@ -4601,46 +5051,46 @@ void test_ecmult_multi_batching(void) { /* Test with empty scratch space. It should compute the correct result using * ecmult_mult_simple algorithm which doesn't require a scratch space. */ - scratch = secp256k1_scratch_create(&ctx->error_callback, 0); - CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); + scratch = secp256k1_scratch_create(&CTX->error_callback, 0); + CHECK(secp256k1_ecmult_multi_var(&CTX->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); secp256k1_gej_add_var(&r, &r, &r2, NULL); CHECK(secp256k1_gej_is_infinity(&r)); - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); /* Test with space for 1 point in pippenger. That's not enough because * ecmult_multi selects strauss which requires more memory. It should * therefore select the simple algorithm. */ - scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_pippenger_scratch_size(1, 1) + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); - CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); + scratch = secp256k1_scratch_create(&CTX->error_callback, secp256k1_pippenger_scratch_size(1, 1) + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); + CHECK(secp256k1_ecmult_multi_var(&CTX->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); secp256k1_gej_add_var(&r, &r, &r2, NULL); CHECK(secp256k1_gej_is_infinity(&r)); - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); for(i = 1; i <= n_points; i++) { if (i > ECMULT_PIPPENGER_THRESHOLD) { int bucket_window = secp256k1_pippenger_bucket_window(i); size_t scratch_size = secp256k1_pippenger_scratch_size(i, bucket_window); - scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); + scratch = secp256k1_scratch_create(&CTX->error_callback, scratch_size + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); } else { size_t scratch_size = secp256k1_strauss_scratch_size(i); - scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); + scratch = secp256k1_scratch_create(&CTX->error_callback, scratch_size + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); } - CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); + CHECK(secp256k1_ecmult_multi_var(&CTX->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); secp256k1_gej_add_var(&r, &r, &r2, NULL); CHECK(secp256k1_gej_is_infinity(&r)); - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); } free(sc); free(pt); } -void run_ecmult_multi_tests(void) { +static void run_ecmult_multi_tests(void) { secp256k1_scratch *scratch; - int64_t todo = (int64_t)320 * count; + int64_t todo = (int64_t)320 * COUNT; test_secp256k1_pippenger_bucket_window_inv(); test_ecmult_multi_pippenger_max_points(); - scratch = secp256k1_scratch_create(&ctx->error_callback, 819200); + scratch = secp256k1_scratch_create(&CTX->error_callback, 819200); test_ecmult_multi(scratch, secp256k1_ecmult_multi_var); test_ecmult_multi(NULL, secp256k1_ecmult_multi_var); test_ecmult_multi(scratch, secp256k1_ecmult_pippenger_batch_single); @@ -4650,18 +5100,18 @@ void run_ecmult_multi_tests(void) { while (todo > 0) { todo -= test_ecmult_multi_random(scratch); } - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); /* Run test_ecmult_multi with space for exactly one point */ - scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_strauss_scratch_size(1) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); + scratch = secp256k1_scratch_create(&CTX->error_callback, secp256k1_strauss_scratch_size(1) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); test_ecmult_multi(scratch, secp256k1_ecmult_multi_var); - secp256k1_scratch_destroy(&ctx->error_callback, scratch); + secp256k1_scratch_destroy(&CTX->error_callback, scratch); test_ecmult_multi_batch_size_helper(); test_ecmult_multi_batching(); } -void test_wnaf(const secp256k1_scalar *number, int w) { +static void test_wnaf(const secp256k1_scalar *number, int w) { secp256k1_scalar x, two, t; int wnaf[256]; int zeroes = -1; @@ -4695,7 +5145,7 @@ void test_wnaf(const secp256k1_scalar *number, int w) { CHECK(secp256k1_scalar_eq(&x, number)); /* check that wnaf represents number */ } -void test_constant_wnaf_negate(const secp256k1_scalar *number) { +static void test_constant_wnaf_negate(const secp256k1_scalar *number) { secp256k1_scalar neg1 = *number; secp256k1_scalar neg2 = *number; int sign1 = 1; @@ -4710,7 +5160,7 @@ void test_constant_wnaf_negate(const secp256k1_scalar *number) { CHECK(secp256k1_scalar_eq(&neg1, &neg2)); } -void test_constant_wnaf(const secp256k1_scalar *number, int w) { +static void test_constant_wnaf(const secp256k1_scalar *number, int w) { secp256k1_scalar x, shift; int wnaf[256] = {0}; int i; @@ -4750,7 +5200,7 @@ void test_constant_wnaf(const secp256k1_scalar *number, int w) { CHECK(secp256k1_scalar_eq(&x, &num)); } -void test_fixed_wnaf(const secp256k1_scalar *number, int w) { +static void test_fixed_wnaf(const secp256k1_scalar *number, int w) { secp256k1_scalar x, shift; int wnaf[256] = {0}; int i; @@ -4787,7 +5237,7 @@ void test_fixed_wnaf(const secp256k1_scalar *number, int w) { /* Checks that the first 8 elements of wnaf are equal to wnaf_expected and the * rest is 0.*/ -void test_fixed_wnaf_small_helper(int *wnaf, int *wnaf_expected, int w) { +static void test_fixed_wnaf_small_helper(int *wnaf, int *wnaf_expected, int w) { int i; for (i = WNAF_SIZE(w)-1; i >= 8; --i) { CHECK(wnaf[i] == 0); @@ -4797,7 +5247,7 @@ void test_fixed_wnaf_small_helper(int *wnaf, int *wnaf_expected, int w) { } } -void test_fixed_wnaf_small(void) { +static void test_fixed_wnaf_small(void) { int w = 4; int wnaf[256] = {0}; int i; @@ -4851,7 +5301,7 @@ void test_fixed_wnaf_small(void) { } } -void run_wnaf(void) { +static void run_wnaf(void) { int i; secp256k1_scalar n = {{0}}; @@ -4883,7 +5333,7 @@ void run_wnaf(void) { /* Test 0 for fixed wnaf */ test_fixed_wnaf_small(); /* Random tests */ - for (i = 0; i < count; i++) { + for (i = 0; i < COUNT; i++) { random_scalar_order(&n); test_wnaf(&n, 4+(i%10)); test_constant_wnaf_negate(&n); @@ -4905,7 +5355,7 @@ static int test_ecmult_accumulate_cb(secp256k1_scalar* sc, secp256k1_ge* pt, siz return 1; } -void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, secp256k1_scratch* scratch) { +static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, secp256k1_scratch* scratch) { /* Compute x*G in 6 different ways, serialize it uncompressed, and feed it into acc. */ secp256k1_gej rj1, rj2, rj3, rj4, rj5, rj6, gj, infj; secp256k1_ge r; @@ -4914,7 +5364,7 @@ void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, se size_t size = 65; secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g); secp256k1_gej_set_infinity(&infj); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj1, x); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &rj1, x); secp256k1_ecmult(&rj2, &gj, x, &zero); secp256k1_ecmult(&rj3, &infj, &zero, x); secp256k1_ecmult_multi_var(NULL, scratch, &rj4, x, NULL, NULL, 0); @@ -4938,7 +5388,7 @@ void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, se } } -void test_ecmult_constants_2bit(void) { +static void test_ecmult_constants_2bit(void) { /* Using test_ecmult_accumulate, test ecmult for: * - For i in 0..36: * - Key i @@ -4951,7 +5401,7 @@ void test_ecmult_constants_2bit(void) { secp256k1_sha256 acc; unsigned char b32[32]; int i, j; - secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 65536); + secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(CTX, 65536); /* Expected hash of all the computed points; created with an independent * implementation. */ @@ -4979,10 +5429,10 @@ void test_ecmult_constants_2bit(void) { secp256k1_sha256_finalize(&acc, b32); CHECK(secp256k1_memcmp_var(b32, expected32, 32) == 0); - secp256k1_scratch_space_destroy(ctx, scratch); + secp256k1_scratch_space_destroy(CTX, scratch); } -void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char* expected32) { +static void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char* expected32) { /* Using test_ecmult_accumulate, test ecmult for: * - Key 0 * - Key 1 @@ -4995,7 +5445,7 @@ void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char unsigned char b32[32]; unsigned char inp[6]; size_t i; - secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 65536); + secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(CTX, 65536); inp[0] = prefix & 0xFF; inp[1] = (prefix >> 8) & 0xFF; @@ -5022,10 +5472,10 @@ void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char secp256k1_sha256_finalize(&acc, b32); CHECK(secp256k1_memcmp_var(b32, expected32, 32) == 0); - secp256k1_scratch_space_destroy(ctx, scratch); + secp256k1_scratch_space_destroy(CTX, scratch); } -void run_ecmult_constants(void) { +static void run_ecmult_constants(void) { /* Expected hashes of all points in the tests below. Computed using an * independent implementation. */ static const unsigned char expected32_6bit20[32] = { @@ -5059,7 +5509,7 @@ void run_ecmult_constants(void) { } } -void test_ecmult_gen_blind(void) { +static void test_ecmult_gen_blind(void) { /* Test ecmult_gen() blinding and confirm that the blinding changes, the affine points match, and the z's don't match. */ secp256k1_scalar key; secp256k1_scalar b; @@ -5069,32 +5519,32 @@ void test_ecmult_gen_blind(void) { secp256k1_gej i; secp256k1_ge pge; random_scalar_order_test(&key); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pgej, &key); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pgej, &key); secp256k1_testrand256(seed32); - b = ctx->ecmult_gen_ctx.blind; - i = ctx->ecmult_gen_ctx.initial; - secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); - CHECK(!secp256k1_scalar_eq(&b, &ctx->ecmult_gen_ctx.blind)); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pgej2, &key); + b = CTX->ecmult_gen_ctx.blind; + i = CTX->ecmult_gen_ctx.initial; + secp256k1_ecmult_gen_blind(&CTX->ecmult_gen_ctx, seed32); + CHECK(!secp256k1_scalar_eq(&b, &CTX->ecmult_gen_ctx.blind)); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pgej2, &key); CHECK(!gej_xyz_equals_gej(&pgej, &pgej2)); - CHECK(!gej_xyz_equals_gej(&i, &ctx->ecmult_gen_ctx.initial)); + CHECK(!gej_xyz_equals_gej(&i, &CTX->ecmult_gen_ctx.initial)); secp256k1_ge_set_gej(&pge, &pgej); ge_equals_gej(&pge, &pgej2); } -void test_ecmult_gen_blind_reset(void) { +static void test_ecmult_gen_blind_reset(void) { /* Test ecmult_gen() blinding reset and confirm that the blinding is consistent. */ secp256k1_scalar b; secp256k1_gej initial; - secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, 0); - b = ctx->ecmult_gen_ctx.blind; - initial = ctx->ecmult_gen_ctx.initial; - secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, 0); - CHECK(secp256k1_scalar_eq(&b, &ctx->ecmult_gen_ctx.blind)); - CHECK(gej_xyz_equals_gej(&initial, &ctx->ecmult_gen_ctx.initial)); + secp256k1_ecmult_gen_blind(&CTX->ecmult_gen_ctx, 0); + b = CTX->ecmult_gen_ctx.blind; + initial = CTX->ecmult_gen_ctx.initial; + secp256k1_ecmult_gen_blind(&CTX->ecmult_gen_ctx, 0); + CHECK(secp256k1_scalar_eq(&b, &CTX->ecmult_gen_ctx.blind)); + CHECK(gej_xyz_equals_gej(&initial, &CTX->ecmult_gen_ctx.initial)); } -void run_ecmult_gen_blind(void) { +static void run_ecmult_gen_blind(void) { int i; test_ecmult_gen_blind_reset(); for (i = 0; i < 10; i++) { @@ -5103,7 +5553,7 @@ void run_ecmult_gen_blind(void) { } /***** ENDOMORPHISH TESTS *****/ -void test_scalar_split(const secp256k1_scalar* full) { +static void test_scalar_split(const secp256k1_scalar* full) { secp256k1_scalar s, s1, slam; const unsigned char zero[32] = {0}; unsigned char tmp[32]; @@ -5130,7 +5580,7 @@ void test_scalar_split(const secp256k1_scalar* full) { } -void run_endomorphism_tests(void) { +static void run_endomorphism_tests(void) { unsigned i; static secp256k1_scalar s; test_scalar_split(&secp256k1_scalar_zero); @@ -5141,7 +5591,7 @@ void run_endomorphism_tests(void) { secp256k1_scalar_add(&s, &secp256k1_const_lambda, &secp256k1_scalar_one); test_scalar_split(&s); - for (i = 0; i < 100U * count; ++i) { + for (i = 0; i < 100U * COUNT; ++i) { secp256k1_scalar full; random_scalar_order_test(&full); test_scalar_split(&full); @@ -5151,19 +5601,19 @@ void run_endomorphism_tests(void) { } } -void ec_pubkey_parse_pointtest(const unsigned char *input, int xvalid, int yvalid) { +static void ec_pubkey_parse_pointtest(const unsigned char *input, int xvalid, int yvalid) { unsigned char pubkeyc[65]; secp256k1_pubkey pubkey; secp256k1_ge ge; size_t pubkeyclen; int32_t ecount; ecount = 0; - secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); for (pubkeyclen = 3; pubkeyclen <= 65; pubkeyclen++) { /* Smaller sizes are tested exhaustively elsewhere. */ int32_t i; memcpy(&pubkeyc[1], input, 64); - VG_UNDEF(&pubkeyc[pubkeyclen], 65 - pubkeyclen); + SECP256K1_CHECKMEM_UNDEFINE(&pubkeyc[pubkeyclen], 65 - pubkeyclen); for (i = 0; i < 256; i++) { /* Try all type bytes. */ int xpass; @@ -5182,29 +5632,29 @@ void ec_pubkey_parse_pointtest(const unsigned char *input, int xvalid, int yvali unsigned char pubkeyo[65]; size_t outl; memset(&pubkey, 0, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); ecount = 0; - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 1); - VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, pubkeyclen) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); outl = 65; - VG_UNDEF(pubkeyo, 65); - CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyo, &outl, &pubkey, SECP256K1_EC_COMPRESSED) == 1); - VG_CHECK(pubkeyo, outl); + SECP256K1_CHECKMEM_UNDEFINE(pubkeyo, 65); + CHECK(secp256k1_ec_pubkey_serialize(CTX, pubkeyo, &outl, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + SECP256K1_CHECKMEM_CHECK(pubkeyo, outl); CHECK(outl == 33); CHECK(secp256k1_memcmp_var(&pubkeyo[1], &pubkeyc[1], 32) == 0); CHECK((pubkeyclen != 33) || (pubkeyo[0] == pubkeyc[0])); if (ypass) { /* This test isn't always done because we decode with alternative signs, so the y won't match. */ CHECK(pubkeyo[0] == ysign); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 1); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 1); memset(&pubkey, 0, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); secp256k1_pubkey_save(&pubkey, &ge); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); outl = 65; - VG_UNDEF(pubkeyo, 65); - CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyo, &outl, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); - VG_CHECK(pubkeyo, outl); + SECP256K1_CHECKMEM_UNDEFINE(pubkeyo, 65); + CHECK(secp256k1_ec_pubkey_serialize(CTX, pubkeyo, &outl, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + SECP256K1_CHECKMEM_CHECK(pubkeyo, outl); CHECK(outl == 65); CHECK(pubkeyo[0] == 4); CHECK(secp256k1_memcmp_var(&pubkeyo[1], input, 64) == 0); @@ -5214,19 +5664,19 @@ void ec_pubkey_parse_pointtest(const unsigned char *input, int xvalid, int yvali /* These cases must fail to parse. */ memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, pubkeyclen) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); } } } - secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); } -void run_ec_pubkey_parse_test(void) { +static void run_ec_pubkey_parse_test(void) { #define SECP256K1_EC_PARSE_TEST_NVALID (12) const unsigned char valid[SECP256K1_EC_PARSE_TEST_NVALID][64] = { { @@ -5415,29 +5865,29 @@ void run_ec_pubkey_parse_test(void) { int32_t ecount2; ecount = 0; /* Nothing should be reading this far into pubkeyc. */ - VG_UNDEF(&pubkeyc[65], 1); - secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + SECP256K1_CHECKMEM_UNDEFINE(&pubkeyc[65], 1); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); /* Zero length claimed, fail, zeroize, no illegal arg error. */ memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(shortkey, 2); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 0) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(shortkey, 2); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, shortkey, 0) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); /* Length one claimed, fail, zeroize, no illegal arg error. */ for (i = 0; i < 256 ; i++) { memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; shortkey[0] = i; - VG_UNDEF(&shortkey[1], 1); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 1) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&shortkey[1], 1); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, shortkey, 1) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); } /* Length two claimed, fail, zeroize, no illegal arg error. */ @@ -5446,102 +5896,102 @@ void run_ec_pubkey_parse_test(void) { ecount = 0; shortkey[0] = i & 255; shortkey[1] = i >> 8; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 2) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, shortkey, 2) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); } memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); /* 33 bytes claimed on otherwise valid input starting with 0x04, fail, zeroize output, no illegal arg error. */ - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 33) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, 33) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); /* NULL pubkey, illegal arg error. Pubkey isn't rewritten before this step, since it's NULL into the parser. */ - CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, pubkeyc, 65) == 0); + CHECK(secp256k1_ec_pubkey_parse(CTX, NULL, pubkeyc, 65) == 0); CHECK(ecount == 2); /* NULL input string. Illegal arg and zeroize output. */ memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, NULL, 65) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, NULL, 65) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 1); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 2); /* 64 bytes claimed on input starting with 0x04, fail, zeroize output, no illegal arg error. */ memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 64) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, 64) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); /* 66 bytes claimed, fail, zeroize output, no illegal arg error. */ memset(&pubkey, 0xfe, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 66) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, 66) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 0); CHECK(ecount == 1); /* Valid parse. */ memset(&pubkey, 0, sizeof(pubkey)); ecount = 0; - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 65) == 1); - CHECK(secp256k1_ec_pubkey_parse(secp256k1_context_no_precomp, &pubkey, pubkeyc, 65) == 1); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, 65) == 1); + CHECK(secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, pubkeyc, 65) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(ecount == 0); - VG_UNDEF(&ge, sizeof(ge)); - CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 1); - VG_CHECK(&ge.x, sizeof(ge.x)); - VG_CHECK(&ge.y, sizeof(ge.y)); - VG_CHECK(&ge.infinity, sizeof(ge.infinity)); + SECP256K1_CHECKMEM_UNDEFINE(&ge, sizeof(ge)); + CHECK(secp256k1_pubkey_load(CTX, &ge, &pubkey) == 1); + SECP256K1_CHECKMEM_CHECK(&ge.x, sizeof(ge.x)); + SECP256K1_CHECKMEM_CHECK(&ge.y, sizeof(ge.y)); + SECP256K1_CHECKMEM_CHECK(&ge.infinity, sizeof(ge.infinity)); ge_equals_ge(&secp256k1_ge_const_g, &ge); CHECK(ecount == 0); /* secp256k1_ec_pubkey_serialize illegal args. */ ecount = 0; len = 65; - CHECK(secp256k1_ec_pubkey_serialize(ctx, NULL, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); + CHECK(secp256k1_ec_pubkey_serialize(CTX, NULL, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); CHECK(ecount == 1); CHECK(len == 0); - CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, NULL, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); + CHECK(secp256k1_ec_pubkey_serialize(CTX, sout, NULL, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); CHECK(ecount == 2); len = 65; - VG_UNDEF(sout, 65); - CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, NULL, SECP256K1_EC_UNCOMPRESSED) == 0); - VG_CHECK(sout, 65); + SECP256K1_CHECKMEM_UNDEFINE(sout, 65); + CHECK(secp256k1_ec_pubkey_serialize(CTX, sout, &len, NULL, SECP256K1_EC_UNCOMPRESSED) == 0); + SECP256K1_CHECKMEM_CHECK(sout, 65); CHECK(ecount == 3); CHECK(len == 0); len = 65; - CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, &pubkey, ~0) == 0); + CHECK(secp256k1_ec_pubkey_serialize(CTX, sout, &len, &pubkey, ~0) == 0); CHECK(ecount == 4); CHECK(len == 0); len = 65; - VG_UNDEF(sout, 65); - CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); - VG_CHECK(sout, 65); + SECP256K1_CHECKMEM_UNDEFINE(sout, 65); + CHECK(secp256k1_ec_pubkey_serialize(CTX, sout, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + SECP256K1_CHECKMEM_CHECK(sout, 65); CHECK(ecount == 4); CHECK(len == 65); /* Multiple illegal args. Should still set arg error only once. */ ecount = 0; ecount2 = 11; - CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, NULL, 65) == 0); + CHECK(secp256k1_ec_pubkey_parse(CTX, NULL, NULL, 65) == 0); CHECK(ecount == 1); /* Does the illegal arg callback actually change the behavior? */ - secp256k1_context_set_illegal_callback(ctx, uncounting_illegal_callback_fn, &ecount2); - CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, NULL, 65) == 0); + secp256k1_context_set_illegal_callback(CTX, uncounting_illegal_callback_fn, &ecount2); + CHECK(secp256k1_ec_pubkey_parse(CTX, NULL, NULL, 65) == 0); CHECK(ecount == 1); CHECK(ecount2 == 10); - secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); /* Try a bunch of prefabbed points with all possible encodings. */ for (i = 0; i < SECP256K1_EC_PARSE_TEST_NVALID; i++) { ec_pubkey_parse_pointtest(valid[i], 1, 1); @@ -5554,7 +6004,7 @@ void run_ec_pubkey_parse_test(void) { } } -void run_eckey_edge_case_test(void) { +static void run_eckey_edge_case_test(void) { const unsigned char orderc[32] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, @@ -5572,65 +6022,65 @@ void run_eckey_edge_case_test(void) { size_t len; int32_t ecount; /* Group order is too large, reject. */ - CHECK(secp256k1_ec_seckey_verify(ctx, orderc) == 0); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, orderc) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_seckey_verify(CTX, orderc) == 0); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, orderc) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); /* Maximum value is too large, reject. */ memset(ctmp, 255, 32); - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 0); memset(&pubkey, 1, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); /* Zero is too small, reject. */ memset(ctmp, 0, 32); - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 0); memset(&pubkey, 1, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); /* One must be accepted. */ ctmp[31] = 0x01; - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 1); memset(&pubkey, 0, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 1); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); pubkey_one = pubkey; /* Group order + 1 is too large, reject. */ memcpy(ctmp, orderc, 32); ctmp[31] = 0x42; - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 0); memset(&pubkey, 1, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); /* -1 must be accepted. */ ctmp[31] = 0x40; - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 1); memset(&pubkey, 0, sizeof(pubkey)); - VG_UNDEF(&pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 1); - VG_CHECK(&pubkey, sizeof(pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, ctmp) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); pubkey_negone = pubkey; /* Tweak of zero leaves the value unchanged. */ memset(ctmp2, 0, 32); - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp, ctmp2) == 1); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp, ctmp2) == 1); CHECK(secp256k1_memcmp_var(orderc, ctmp, 31) == 0 && ctmp[31] == 0x40); memcpy(&pubkey2, &pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp2) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); /* Multiply tweak of zero zeroizes the output. */ - CHECK(secp256k1_ec_seckey_tweak_mul(ctx, ctmp, ctmp2) == 0); + CHECK(secp256k1_ec_seckey_tweak_mul(CTX, ctmp, ctmp2) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp, 32) == 0); - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, ctmp2) == 0); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(pubkey)) == 0); memcpy(&pubkey, &pubkey2, sizeof(pubkey)); /* If seckey_tweak_add or seckey_tweak_mul are called with an overflowing @@ -5638,31 +6088,31 @@ void run_eckey_edge_case_test(void) { memcpy(ctmp, orderc, 32); memset(ctmp2, 0, 32); ctmp2[31] = 0x01; - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp2) == 1); - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp, ctmp2) == 0); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp2) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 0); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp, ctmp2) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp, 32) == 0); memcpy(ctmp, orderc, 32); - CHECK(secp256k1_ec_seckey_tweak_mul(ctx, ctmp, ctmp2) == 0); + CHECK(secp256k1_ec_seckey_tweak_mul(CTX, ctmp, ctmp2) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp, 32) == 0); /* If seckey_tweak_add or seckey_tweak_mul are called with an overflowing tweak, the seckey is zeroized. */ memcpy(ctmp, orderc, 32); ctmp[31] = 0x40; - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp, orderc) == 0); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp, orderc) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp, 32) == 0); memcpy(ctmp, orderc, 32); ctmp[31] = 0x40; - CHECK(secp256k1_ec_seckey_tweak_mul(ctx, ctmp, orderc) == 0); + CHECK(secp256k1_ec_seckey_tweak_mul(CTX, ctmp, orderc) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp, 32) == 0); memcpy(ctmp, orderc, 32); ctmp[31] = 0x40; /* If pubkey_tweak_add or pubkey_tweak_mul are called with an overflowing tweak, the pubkey is zeroized. */ - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, orderc) == 0); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, orderc) == 0); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(pubkey)) == 0); memcpy(&pubkey, &pubkey2, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, orderc) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, orderc) == 0); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(pubkey)) == 0); memcpy(&pubkey, &pubkey2, sizeof(pubkey)); /* If the resulting key in secp256k1_ec_seckey_tweak_add and @@ -5672,145 +6122,145 @@ void run_eckey_edge_case_test(void) { ctmp[31] = 0x40; memset(ctmp2, 0, 32); ctmp2[31] = 1; - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp2, ctmp) == 0); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp2, ctmp) == 0); CHECK(secp256k1_memcmp_var(zeros, ctmp2, 32) == 0); ctmp2[31] = 1; - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp2) == 0); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(pubkey)) == 0); memcpy(&pubkey, &pubkey2, sizeof(pubkey)); /* Tweak computation wraps and results in a key of 1. */ ctmp2[31] = 2; - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp2, ctmp) == 1); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp2, ctmp) == 1); CHECK(secp256k1_memcmp_var(ctmp2, zeros, 31) == 0 && ctmp2[31] == 1); ctmp2[31] = 2; - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp2) == 1); ctmp2[31] = 1; - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, ctmp2) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, ctmp2) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); /* Tweak mul * 2 = 1+1. */ - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp2) == 1); ctmp2[31] = 2; - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey2, ctmp2) == 1); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey2, ctmp2) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); /* Test argument errors. */ ecount = 0; - secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); CHECK(ecount == 0); /* Zeroize pubkey on parse error. */ memset(&pubkey, 0, 32); - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, ctmp2) == 0); CHECK(ecount == 1); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(pubkey)) == 0); memcpy(&pubkey, &pubkey2, sizeof(pubkey)); memset(&pubkey2, 0, 32); - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey2, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey2, ctmp2) == 0); CHECK(ecount == 2); CHECK(secp256k1_memcmp_var(&pubkey2, zeros, sizeof(pubkey2)) == 0); /* Plain argument errors. */ ecount = 0; - CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, ctmp) == 1); CHECK(ecount == 0); - CHECK(secp256k1_ec_seckey_verify(ctx, NULL) == 0); + CHECK(secp256k1_ec_seckey_verify(CTX, NULL) == 0); CHECK(ecount == 1); ecount = 0; memset(ctmp2, 0, 32); ctmp2[31] = 4; - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, NULL, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, NULL, ctmp2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, NULL) == 0); + CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, NULL) == 0); CHECK(ecount == 2); ecount = 0; memset(ctmp2, 0, 32); ctmp2[31] = 4; - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, NULL, ctmp2) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, NULL, ctmp2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, NULL) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, NULL) == 0); CHECK(ecount == 2); ecount = 0; memset(ctmp2, 0, 32); - CHECK(secp256k1_ec_seckey_tweak_add(ctx, NULL, ctmp2) == 0); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, NULL, ctmp2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_seckey_tweak_add(ctx, ctmp, NULL) == 0); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, ctmp, NULL) == 0); CHECK(ecount == 2); ecount = 0; memset(ctmp2, 0, 32); ctmp2[31] = 1; - CHECK(secp256k1_ec_seckey_tweak_mul(ctx, NULL, ctmp2) == 0); + CHECK(secp256k1_ec_seckey_tweak_mul(CTX, NULL, ctmp2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_seckey_tweak_mul(ctx, ctmp, NULL) == 0); + CHECK(secp256k1_ec_seckey_tweak_mul(CTX, ctmp, NULL) == 0); CHECK(ecount == 2); ecount = 0; - CHECK(secp256k1_ec_pubkey_create(ctx, NULL, ctmp) == 0); + CHECK(secp256k1_ec_pubkey_create(CTX, NULL, ctmp) == 0); CHECK(ecount == 1); memset(&pubkey, 1, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, NULL) == 0); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, NULL) == 0); CHECK(ecount == 2); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); /* secp256k1_ec_pubkey_combine tests. */ ecount = 0; pubkeys[0] = &pubkey_one; - VG_UNDEF(&pubkeys[0], sizeof(secp256k1_pubkey *)); - VG_UNDEF(&pubkeys[1], sizeof(secp256k1_pubkey *)); - VG_UNDEF(&pubkeys[2], sizeof(secp256k1_pubkey *)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkeys[0], sizeof(secp256k1_pubkey *)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkeys[1], sizeof(secp256k1_pubkey *)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkeys[2], sizeof(secp256k1_pubkey *)); memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 0) == 0); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 0) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_pubkey_combine(ctx, NULL, pubkeys, 1) == 0); + CHECK(secp256k1_ec_pubkey_combine(CTX, NULL, pubkeys, 1) == 0); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); CHECK(ecount == 2); memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, NULL, 1) == 0); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, NULL, 1) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); CHECK(ecount == 3); pubkeys[0] = &pubkey_negone; memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 1) == 1); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 1) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); CHECK(ecount == 3); len = 33; - CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); - CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp2, &len, &pubkey_negone, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp2, &len, &pubkey_negone, SECP256K1_EC_COMPRESSED) == 1); CHECK(secp256k1_memcmp_var(ctmp, ctmp2, 33) == 0); /* Result is infinity. */ pubkeys[0] = &pubkey_one; pubkeys[1] = &pubkey_negone; memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 2) == 0); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 2) == 0); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); CHECK(ecount == 3); /* Passes through infinity but comes out one. */ pubkeys[2] = &pubkey_one; memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 3) == 1); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 3) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); CHECK(ecount == 3); len = 33; - CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); - CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp2, &len, &pubkey_one, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(CTX, ctmp2, &len, &pubkey_one, SECP256K1_EC_COMPRESSED) == 1); CHECK(secp256k1_memcmp_var(ctmp, ctmp2, 33) == 0); /* Adds to two. */ pubkeys[1] = &pubkey_one; memset(&pubkey, 255, sizeof(secp256k1_pubkey)); - VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); - CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 2) == 1); - VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + SECP256K1_CHECKMEM_UNDEFINE(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(CTX, &pubkey, pubkeys, 2) == 1); + SECP256K1_CHECKMEM_CHECK(&pubkey, sizeof(secp256k1_pubkey)); CHECK(secp256k1_memcmp_var(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); CHECK(ecount == 3); - secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); } -void run_eckey_negate_test(void) { +static void run_eckey_negate_test(void) { unsigned char seckey[32]; unsigned char seckey_tmp[32]; @@ -5818,20 +6268,20 @@ void run_eckey_negate_test(void) { memcpy(seckey_tmp, seckey, 32); /* Verify negation changes the key and changes it back */ - CHECK(secp256k1_ec_seckey_negate(ctx, seckey) == 1); + CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) != 0); - CHECK(secp256k1_ec_seckey_negate(ctx, seckey) == 1); + CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); /* Check that privkey alias gives same result */ - CHECK(secp256k1_ec_seckey_negate(ctx, seckey) == 1); - CHECK(secp256k1_ec_privkey_negate(ctx, seckey_tmp) == 1); + CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); + CHECK(secp256k1_ec_privkey_negate(CTX, seckey_tmp) == 1); CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); /* Negating all 0s fails */ memset(seckey, 0, 32); memset(seckey_tmp, 0, 32); - CHECK(secp256k1_ec_seckey_negate(ctx, seckey) == 0); + CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 0); /* Check that seckey is not modified */ CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); @@ -5841,18 +6291,18 @@ void run_eckey_negate_test(void) { random_scalar_order_b32(seckey); memset(seckey, 0xFF, 16); memset(seckey_tmp, 0, 32); - CHECK(secp256k1_ec_seckey_negate(ctx, seckey) == 0); + CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 0); CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); } -void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) { +static void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) { secp256k1_scalar nonce; do { random_scalar_order_test(&nonce); - } while(!secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, sigr, sigs, key, msg, &nonce, recid)); + } while(!secp256k1_ecdsa_sig_sign(&CTX->ecmult_gen_ctx, sigr, sigs, key, msg, &nonce, recid)); } -void test_ecdsa_sign_verify(void) { +static void test_ecdsa_sign_verify(void) { secp256k1_gej pubj; secp256k1_ge pub; secp256k1_scalar one; @@ -5862,7 +6312,7 @@ void test_ecdsa_sign_verify(void) { int recid; random_scalar_order_test(&msg); random_scalar_order_test(&key); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubj, &key); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &pubj, &key); secp256k1_ge_set_gej(&pub, &pubj); getrec = secp256k1_testrand_bits(1); /* The specific way in which this conditional is written sidesteps a potential bug in clang. @@ -5879,9 +6329,9 @@ void test_ecdsa_sign_verify(void) { CHECK(!secp256k1_ecdsa_sig_verify(&sigr, &sigs, &pub, &msg)); } -void run_ecdsa_sign_verify(void) { +static void run_ecdsa_sign_verify(void) { int i; - for (i = 0; i < 10*count; i++) { + for (i = 0; i < 10*COUNT; i++) { test_ecdsa_sign_verify(); } } @@ -5933,12 +6383,12 @@ static int nonce_function_test_retry(unsigned char *nonce32, const unsigned char return nonce_function_rfc6979(nonce32, msg32, key32, algo16, data, counter - 5); } -int is_empty_signature(const secp256k1_ecdsa_signature *sig) { +static int is_empty_signature(const secp256k1_ecdsa_signature *sig) { static const unsigned char res[sizeof(secp256k1_ecdsa_signature)] = {0}; return secp256k1_memcmp_var(sig, res, sizeof(secp256k1_ecdsa_signature)) == 0; } -void test_ecdsa_end_to_end(void) { +static void test_ecdsa_end_to_end(void) { unsigned char extra[32] = {0x00}; unsigned char privkey[32]; unsigned char message[32]; @@ -5964,24 +6414,24 @@ void test_ecdsa_end_to_end(void) { } /* Construct and verify corresponding public key. */ - CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + CHECK(secp256k1_ec_seckey_verify(CTX, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, privkey) == 1); /* Verify exporting and importing public key. */ - CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyc, &pubkeyclen, &pubkey, secp256k1_testrand_bits(1) == 1 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED)); + CHECK(secp256k1_ec_pubkey_serialize(CTX, pubkeyc, &pubkeyclen, &pubkey, secp256k1_testrand_bits(1) == 1 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED)); memset(&pubkey, 0, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 1); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, pubkeyc, pubkeyclen) == 1); /* Verify negation changes the key and changes it back */ memcpy(&pubkey_tmp, &pubkey, sizeof(pubkey)); - CHECK(secp256k1_ec_pubkey_negate(ctx, &pubkey_tmp) == 1); + CHECK(secp256k1_ec_pubkey_negate(CTX, &pubkey_tmp) == 1); CHECK(secp256k1_memcmp_var(&pubkey_tmp, &pubkey, sizeof(pubkey)) != 0); - CHECK(secp256k1_ec_pubkey_negate(ctx, &pubkey_tmp) == 1); + CHECK(secp256k1_ec_pubkey_negate(CTX, &pubkey_tmp) == 1); CHECK(secp256k1_memcmp_var(&pubkey_tmp, &pubkey, sizeof(pubkey)) == 0); /* Verify private key import and export. */ - CHECK(ec_privkey_export_der(ctx, seckey, &seckeylen, privkey, secp256k1_testrand_bits(1) == 1)); - CHECK(ec_privkey_import_der(ctx, privkey2, seckey, seckeylen) == 1); + CHECK(ec_privkey_export_der(CTX, seckey, &seckeylen, privkey, secp256k1_testrand_bits(1) == 1)); + CHECK(ec_privkey_import_der(CTX, privkey2, seckey, seckeylen) == 1); CHECK(secp256k1_memcmp_var(privkey, privkey2, 32) == 0); /* Optionally tweak the keys using addition. */ @@ -5994,17 +6444,17 @@ void test_ecdsa_end_to_end(void) { secp256k1_pubkey pubkey2; secp256k1_testrand256_test(rnd); memcpy(privkey_tmp, privkey, 32); - ret1 = secp256k1_ec_seckey_tweak_add(ctx, privkey, rnd); - ret2 = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, rnd); + ret1 = secp256k1_ec_seckey_tweak_add(CTX, privkey, rnd); + ret2 = secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, rnd); /* Check that privkey alias gives same result */ - ret3 = secp256k1_ec_privkey_tweak_add(ctx, privkey_tmp, rnd); + ret3 = secp256k1_ec_privkey_tweak_add(CTX, privkey_tmp, rnd); CHECK(ret1 == ret2); CHECK(ret2 == ret3); if (ret1 == 0) { return; } CHECK(secp256k1_memcmp_var(privkey, privkey_tmp, 32) == 0); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); } @@ -6018,29 +6468,29 @@ void test_ecdsa_end_to_end(void) { secp256k1_pubkey pubkey2; secp256k1_testrand256_test(rnd); memcpy(privkey_tmp, privkey, 32); - ret1 = secp256k1_ec_seckey_tweak_mul(ctx, privkey, rnd); - ret2 = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, rnd); + ret1 = secp256k1_ec_seckey_tweak_mul(CTX, privkey, rnd); + ret2 = secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, rnd); /* Check that privkey alias gives same result */ - ret3 = secp256k1_ec_privkey_tweak_mul(ctx, privkey_tmp, rnd); + ret3 = secp256k1_ec_privkey_tweak_mul(CTX, privkey_tmp, rnd); CHECK(ret1 == ret2); CHECK(ret2 == ret3); if (ret1 == 0) { return; } CHECK(secp256k1_memcmp_var(privkey, privkey_tmp, 32) == 0); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); } /* Sign. */ - CHECK(secp256k1_ecdsa_sign(ctx, &signature[0], message, privkey, NULL, NULL) == 1); - CHECK(secp256k1_ecdsa_sign(ctx, &signature[4], message, privkey, NULL, NULL) == 1); - CHECK(secp256k1_ecdsa_sign(ctx, &signature[1], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[4], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[1], message, privkey, NULL, extra) == 1); extra[31] = 1; - CHECK(secp256k1_ecdsa_sign(ctx, &signature[2], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[2], message, privkey, NULL, extra) == 1); extra[31] = 0; extra[0] = 1; - CHECK(secp256k1_ecdsa_sign(ctx, &signature[3], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &signature[3], message, privkey, NULL, extra) == 1); CHECK(secp256k1_memcmp_var(&signature[0], &signature[4], sizeof(signature[0])) == 0); CHECK(secp256k1_memcmp_var(&signature[0], &signature[1], sizeof(signature[0])) != 0); CHECK(secp256k1_memcmp_var(&signature[0], &signature[2], sizeof(signature[0])) != 0); @@ -6049,41 +6499,41 @@ void test_ecdsa_end_to_end(void) { CHECK(secp256k1_memcmp_var(&signature[1], &signature[3], sizeof(signature[0])) != 0); CHECK(secp256k1_memcmp_var(&signature[2], &signature[3], sizeof(signature[0])) != 0); /* Verify. */ - CHECK(secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[1], message, &pubkey) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[2], message, &pubkey) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[3], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[0], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[1], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[2], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[3], message, &pubkey) == 1); /* Test lower-S form, malleate, verify and fail, test again, malleate again */ - CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[0])); - secp256k1_ecdsa_signature_load(ctx, &r, &s, &signature[0]); + CHECK(!secp256k1_ecdsa_signature_normalize(CTX, NULL, &signature[0])); + secp256k1_ecdsa_signature_load(CTX, &r, &s, &signature[0]); secp256k1_scalar_negate(&s, &s); secp256k1_ecdsa_signature_save(&signature[5], &r, &s); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 0); - CHECK(secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); - CHECK(secp256k1_ecdsa_signature_normalize(ctx, &signature[5], &signature[5])); - CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); - CHECK(!secp256k1_ecdsa_signature_normalize(ctx, &signature[5], &signature[5])); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[5], message, &pubkey) == 0); + CHECK(secp256k1_ecdsa_signature_normalize(CTX, NULL, &signature[5])); + CHECK(secp256k1_ecdsa_signature_normalize(CTX, &signature[5], &signature[5])); + CHECK(!secp256k1_ecdsa_signature_normalize(CTX, NULL, &signature[5])); + CHECK(!secp256k1_ecdsa_signature_normalize(CTX, &signature[5], &signature[5])); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[5], message, &pubkey) == 1); secp256k1_scalar_negate(&s, &s); secp256k1_ecdsa_signature_save(&signature[5], &r, &s); - CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 1); + CHECK(!secp256k1_ecdsa_signature_normalize(CTX, NULL, &signature[5])); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[5], message, &pubkey) == 1); CHECK(secp256k1_memcmp_var(&signature[5], &signature[0], 64) == 0); /* Serialize/parse DER and verify again */ - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature[0]) == 1); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, sig, &siglen, &signature[0]) == 1); memset(&signature[0], 0, sizeof(signature[0])); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &signature[0], sig, siglen) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &signature[0], sig, siglen) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &signature[0], message, &pubkey) == 1); /* Serialize/destroy/parse DER and verify again. */ siglen = 74; - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature[0]) == 1); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, sig, &siglen, &signature[0]) == 1); sig[secp256k1_testrand_int(siglen)] += 1 + secp256k1_testrand_int(255); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &signature[0], sig, siglen) == 0 || - secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &signature[0], sig, siglen) == 0 || + secp256k1_ecdsa_verify(CTX, &signature[0], message, &pubkey) == 0); } -void test_random_pubkeys(void) { +static void test_random_pubkeys(void) { secp256k1_ge elem; secp256k1_ge elem2; unsigned char in[65]; @@ -6143,7 +6593,7 @@ void test_random_pubkeys(void) { } } -void run_pubkey_comparison(void) { +static void run_pubkey_comparison(void) { unsigned char pk1_ser[33] = { 0x02, 0x58, 0x84, 0xb3, 0xa2, 0x4b, 0x97, 0x37, 0x88, 0x92, 0x38, 0xa6, 0x26, 0x62, 0x52, 0x35, 0x11, @@ -6158,55 +6608,55 @@ void run_pubkey_comparison(void) { secp256k1_pubkey pk2; int32_t ecount = 0; - CHECK(secp256k1_ec_pubkey_parse(ctx, &pk1, pk1_ser, sizeof(pk1_ser)) == 1); - CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk2_ser, sizeof(pk2_ser)) == 1); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pk1, pk1_ser, sizeof(pk1_ser)) == 1); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pk2, pk2_ser, sizeof(pk2_ser)) == 1); - secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); - CHECK(secp256k1_ec_pubkey_cmp(ctx, NULL, &pk2) < 0); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ec_pubkey_cmp(CTX, NULL, &pk2) < 0); CHECK(ecount == 1); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, NULL) > 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk1, NULL) > 0); CHECK(ecount == 2); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk1) == 0); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk2) == 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk1, &pk2) < 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk1) > 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk1, &pk1) == 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk2) == 0); CHECK(ecount == 2); { secp256k1_pubkey pk_tmp; memset(&pk_tmp, 0, sizeof(pk_tmp)); /* illegal pubkey */ - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk2) < 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk_tmp, &pk2) < 0); CHECK(ecount == 3); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk_tmp, &pk_tmp) == 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk_tmp, &pk_tmp) == 0); CHECK(ecount == 5); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk_tmp) > 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk_tmp) > 0); CHECK(ecount == 6); } - secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); /* Make pk2 the same as pk1 but with 3 rather than 2. Note that in * an uncompressed encoding, these would have the opposite ordering */ pk1_ser[0] = 3; - CHECK(secp256k1_ec_pubkey_parse(ctx, &pk2, pk1_ser, sizeof(pk1_ser)) == 1); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk1, &pk2) < 0); - CHECK(secp256k1_ec_pubkey_cmp(ctx, &pk2, &pk1) > 0); + CHECK(secp256k1_ec_pubkey_parse(CTX, &pk2, pk1_ser, sizeof(pk1_ser)) == 1); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk1, &pk2) < 0); + CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk1) > 0); } -void run_random_pubkeys(void) { +static void run_random_pubkeys(void) { int i; - for (i = 0; i < 10*count; i++) { + for (i = 0; i < 10*COUNT; i++) { test_random_pubkeys(); } } -void run_ecdsa_end_to_end(void) { +static void run_ecdsa_end_to_end(void) { int i; - for (i = 0; i < 64*count; i++) { + for (i = 0; i < 64*COUNT; i++) { test_ecdsa_end_to_end(); } } -int test_ecdsa_der_parse(const unsigned char *sig, size_t siglen, int certainly_der, int certainly_not_der) { +static int test_ecdsa_der_parse(const unsigned char *sig, size_t siglen, int certainly_der, int certainly_not_der) { static const unsigned char zeroes[32] = {0}; int ret = 0; @@ -6223,23 +6673,23 @@ int test_ecdsa_der_parse(const unsigned char *sig, size_t siglen, int certainly_ size_t len_der_lax = 2048; int parsed_der_lax = 0, valid_der_lax = 0, roundtrips_der_lax = 0; - parsed_der = secp256k1_ecdsa_signature_parse_der(ctx, &sig_der, sig, siglen); + parsed_der = secp256k1_ecdsa_signature_parse_der(CTX, &sig_der, sig, siglen); if (parsed_der) { - ret |= (!secp256k1_ecdsa_signature_serialize_compact(ctx, compact_der, &sig_der)) << 0; + ret |= (!secp256k1_ecdsa_signature_serialize_compact(CTX, compact_der, &sig_der)) << 0; valid_der = (secp256k1_memcmp_var(compact_der, zeroes, 32) != 0) && (secp256k1_memcmp_var(compact_der + 32, zeroes, 32) != 0); } if (valid_der) { - ret |= (!secp256k1_ecdsa_signature_serialize_der(ctx, roundtrip_der, &len_der, &sig_der)) << 1; + ret |= (!secp256k1_ecdsa_signature_serialize_der(CTX, roundtrip_der, &len_der, &sig_der)) << 1; roundtrips_der = (len_der == siglen) && secp256k1_memcmp_var(roundtrip_der, sig, siglen) == 0; } - parsed_der_lax = ecdsa_signature_parse_der_lax(ctx, &sig_der_lax, sig, siglen); + parsed_der_lax = ecdsa_signature_parse_der_lax(CTX, &sig_der_lax, sig, siglen); if (parsed_der_lax) { - ret |= (!secp256k1_ecdsa_signature_serialize_compact(ctx, compact_der_lax, &sig_der_lax)) << 10; + ret |= (!secp256k1_ecdsa_signature_serialize_compact(CTX, compact_der_lax, &sig_der_lax)) << 10; valid_der_lax = (secp256k1_memcmp_var(compact_der_lax, zeroes, 32) != 0) && (secp256k1_memcmp_var(compact_der_lax + 32, zeroes, 32) != 0); } if (valid_der_lax) { - ret |= (!secp256k1_ecdsa_signature_serialize_der(ctx, roundtrip_der_lax, &len_der_lax, &sig_der_lax)) << 11; + ret |= (!secp256k1_ecdsa_signature_serialize_der(CTX, roundtrip_der_lax, &len_der_lax, &sig_der_lax)) << 11; roundtrips_der_lax = (len_der_lax == siglen) && secp256k1_memcmp_var(roundtrip_der_lax, sig, siglen) == 0; } @@ -6451,9 +6901,9 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly CHECK(tlen == *len); } -void run_ecdsa_der_parse(void) { +static void run_ecdsa_der_parse(void) { int i,j; - for (i = 0; i < 200 * count; i++) { + for (i = 0; i < 200 * COUNT; i++) { unsigned char buffer[2048]; size_t buflen = 0; int certainly_der = 0; @@ -6483,7 +6933,7 @@ void run_ecdsa_der_parse(void) { } /* Tests several edge cases. */ -void test_ecdsa_edge_cases(void) { +static void test_ecdsa_edge_cases(void) { int t; secp256k1_ecdsa_signature sig; @@ -6497,7 +6947,7 @@ void test_ecdsa_edge_cases(void) { secp256k1_scalar_negate(&ss, &ss); secp256k1_scalar_inverse(&ss, &ss); secp256k1_scalar_set_int(&sr, 1); - secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &keyj, &sr); + secp256k1_ecmult_gen(&CTX->ecmult_gen_ctx, &keyj, &sr); secp256k1_ge_set_gej(&key, &keyj); msg = ss; CHECK(secp256k1_ecdsa_sig_verify(&sr, &ss, &key, &msg) == 0); @@ -6680,71 +7130,71 @@ void test_ecdsa_edge_cases(void) { 0x65, 0xdf, 0xdd, 0x31, 0xb9, 0x3e, 0x29, 0xa9, }; ecount = 0; - secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce) == 0); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce2) == 0); + secp256k1_context_set_illegal_callback(CTX, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, precomputed_nonce_function, nonce) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, precomputed_nonce_function, nonce2) == 0); msg[31] = 0xaa; - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, precomputed_nonce_function, nonce) == 1); CHECK(ecount == 0); - CHECK(secp256k1_ecdsa_sign(ctx, NULL, msg, key, precomputed_nonce_function, nonce2) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, NULL, msg, key, precomputed_nonce_function, nonce2) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, NULL, key, precomputed_nonce_function, nonce2) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, NULL, key, precomputed_nonce_function, nonce2) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, NULL, precomputed_nonce_function, nonce2) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, NULL, precomputed_nonce_function, nonce2) == 0); CHECK(ecount == 3); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce2) == 1); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, key) == 1); - CHECK(secp256k1_ecdsa_verify(ctx, NULL, msg, &pubkey) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, precomputed_nonce_function, nonce2) == 1); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, key) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, NULL, msg, &pubkey) == 0); CHECK(ecount == 4); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, NULL, &pubkey) == 0); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, NULL, &pubkey) == 0); CHECK(ecount == 5); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, NULL) == 0); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg, NULL) == 0); CHECK(ecount == 6); - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg, &pubkey) == 1); CHECK(ecount == 6); - CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, NULL) == 0); + CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey, NULL) == 0); CHECK(ecount == 7); /* That pubkeyload fails via an ARGCHECK is a little odd but makes sense because pubkeys are an opaque data type. */ - CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 0); + CHECK(secp256k1_ecdsa_verify(CTX, &sig, msg, &pubkey) == 0); CHECK(ecount == 8); siglen = 72; - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, NULL, &siglen, &sig) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, NULL, &siglen, &sig) == 0); CHECK(ecount == 9); - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, NULL, &sig) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, signature, NULL, &sig) == 0); CHECK(ecount == 10); - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, NULL) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, signature, &siglen, NULL) == 0); CHECK(ecount == 11); - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, &sig) == 1); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, signature, &siglen, &sig) == 1); CHECK(ecount == 11); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, NULL, signature, siglen) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, NULL, signature, siglen) == 0); CHECK(ecount == 12); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, NULL, siglen) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, NULL, siglen) == 0); CHECK(ecount == 13); - CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, signature, siglen) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(CTX, &sig, signature, siglen) == 1); CHECK(ecount == 13); siglen = 10; /* Too little room for a signature does not fail via ARGCHECK. */ - CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, &sig) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_der(CTX, signature, &siglen, &sig) == 0); CHECK(ecount == 13); ecount = 0; - CHECK(secp256k1_ecdsa_signature_normalize(ctx, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_signature_normalize(CTX, NULL, NULL) == 0); CHECK(ecount == 1); - CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, NULL, &sig) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_compact(CTX, NULL, &sig) == 0); CHECK(ecount == 2); - CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, signature, NULL) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_compact(CTX, signature, NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, signature, &sig) == 1); + CHECK(secp256k1_ecdsa_signature_serialize_compact(CTX, signature, &sig) == 1); CHECK(ecount == 3); - CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, NULL, signature) == 0); + CHECK(secp256k1_ecdsa_signature_parse_compact(CTX, NULL, signature) == 0); CHECK(ecount == 4); - CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, NULL) == 0); + CHECK(secp256k1_ecdsa_signature_parse_compact(CTX, &sig, NULL) == 0); CHECK(ecount == 5); - CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature) == 1); + CHECK(secp256k1_ecdsa_signature_parse_compact(CTX, &sig, signature) == 1); CHECK(ecount == 5); memset(signature, 255, 64); - CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature) == 0); + CHECK(secp256k1_ecdsa_signature_parse_compact(CTX, &sig, signature) == 0); CHECK(ecount == 5); - secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + secp256k1_context_set_illegal_callback(CTX, NULL, NULL); } /* Nonce function corner cases. */ @@ -6761,33 +7211,33 @@ void test_ecdsa_edge_cases(void) { msg[31] = 1; /* High key results in signature failure. */ memset(key, 0xFF, 32); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, NULL, extra) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, NULL, extra) == 0); CHECK(is_empty_signature(&sig)); /* Zero key results in signature failure. */ memset(key, 0, 32); - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, NULL, extra) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, NULL, extra) == 0); CHECK(is_empty_signature(&sig)); /* Nonce function failure results in signature failure. */ key[31] = 1; - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, nonce_function_test_fail, extra) == 0); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, nonce_function_test_fail, extra) == 0); CHECK(is_empty_signature(&sig)); /* The retry loop successfully makes its way to the first good value. */ - CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, nonce_function_test_retry, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig, msg, key, nonce_function_test_retry, extra) == 1); CHECK(!is_empty_signature(&sig)); - CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, nonce_function_rfc6979, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig2, msg, key, nonce_function_rfc6979, extra) == 1); CHECK(!is_empty_signature(&sig2)); CHECK(secp256k1_memcmp_var(&sig, &sig2, sizeof(sig)) == 0); /* The default nonce function is deterministic. */ - CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig2, msg, key, NULL, extra) == 1); CHECK(!is_empty_signature(&sig2)); CHECK(secp256k1_memcmp_var(&sig, &sig2, sizeof(sig)) == 0); /* The default nonce function changes output with different messages. */ for(i = 0; i < 256; i++) { int j; msg[0] = i; - CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig2, msg, key, NULL, extra) == 1); CHECK(!is_empty_signature(&sig2)); - secp256k1_ecdsa_signature_load(ctx, &sr[i], &ss, &sig2); + secp256k1_ecdsa_signature_load(CTX, &sr[i], &ss, &sig2); for (j = 0; j < i; j++) { CHECK(!secp256k1_scalar_eq(&sr[i], &sr[j])); } @@ -6798,9 +7248,9 @@ void test_ecdsa_edge_cases(void) { for(i = 256; i < 512; i++) { int j; key[0] = i - 256; - CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_sign(CTX, &sig2, msg, key, NULL, extra) == 1); CHECK(!is_empty_signature(&sig2)); - secp256k1_ecdsa_signature_load(ctx, &sr[i], &ss, &sig2); + secp256k1_ecdsa_signature_load(CTX, &sr[i], &ss, &sig2); for (j = 0; j < i; j++) { CHECK(!secp256k1_scalar_eq(&sr[i], &sr[j])); } @@ -6815,18 +7265,18 @@ void test_ecdsa_edge_cases(void) { unsigned char nonce2[32]; unsigned char nonce3[32]; unsigned char nonce4[32]; - VG_UNDEF(nonce,32); - VG_UNDEF(nonce2,32); - VG_UNDEF(nonce3,32); - VG_UNDEF(nonce4,32); + SECP256K1_CHECKMEM_UNDEFINE(nonce,32); + SECP256K1_CHECKMEM_UNDEFINE(nonce2,32); + SECP256K1_CHECKMEM_UNDEFINE(nonce3,32); + SECP256K1_CHECKMEM_UNDEFINE(nonce4,32); CHECK(nonce_function_rfc6979(nonce, zeros, zeros, NULL, NULL, 0) == 1); - VG_CHECK(nonce,32); + SECP256K1_CHECKMEM_CHECK(nonce,32); CHECK(nonce_function_rfc6979(nonce2, zeros, zeros, zeros, NULL, 0) == 1); - VG_CHECK(nonce2,32); + SECP256K1_CHECKMEM_CHECK(nonce2,32); CHECK(nonce_function_rfc6979(nonce3, zeros, zeros, NULL, (void *)zeros, 0) == 1); - VG_CHECK(nonce3,32); + SECP256K1_CHECKMEM_CHECK(nonce3,32); CHECK(nonce_function_rfc6979(nonce4, zeros, zeros, zeros, (void *)zeros, 0) == 1); - VG_CHECK(nonce4,32); + SECP256K1_CHECKMEM_CHECK(nonce4,32); CHECK(secp256k1_memcmp_var(nonce, nonce2, 32) != 0); CHECK(secp256k1_memcmp_var(nonce, nonce3, 32) != 0); CHECK(secp256k1_memcmp_var(nonce, nonce4, 32) != 0); @@ -6846,13 +7296,13 @@ void test_ecdsa_edge_cases(void) { 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41, }; size_t outlen = 300; - CHECK(!ec_privkey_export_der(ctx, privkey, &outlen, seckey, 0)); + CHECK(!ec_privkey_export_der(CTX, privkey, &outlen, seckey, 0)); outlen = 300; - CHECK(!ec_privkey_export_der(ctx, privkey, &outlen, seckey, 1)); + CHECK(!ec_privkey_export_der(CTX, privkey, &outlen, seckey, 1)); } } -void run_ecdsa_edge_cases(void) { +static void run_ecdsa_edge_cases(void) { test_ecdsa_edge_cases(); } @@ -6872,7 +7322,7 @@ void run_ecdsa_edge_cases(void) { # include "modules/schnorrsig/tests_impl.h" #endif -void run_secp256k1_memczero_test(void) { +static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -6887,7 +7337,7 @@ void run_secp256k1_memczero_test(void) { CHECK(secp256k1_memcmp_var(buf1, buf2, sizeof(buf1)) == 0); } -void run_secp256k1_byteorder_tests(void) { +static void run_secp256k1_byteorder_tests(void) { const uint32_t x = 0xFF03AB45; const unsigned char x_be[4] = {0xFF, 0x03, 0xAB, 0x45}; unsigned char buf[4]; @@ -6900,7 +7350,7 @@ void run_secp256k1_byteorder_tests(void) { CHECK(x == x_); } -void int_cmov_test(void) { +static void int_cmov_test(void) { int r = INT_MAX; int a = 0; @@ -6925,7 +7375,7 @@ void int_cmov_test(void) { } -void fe_cmov_test(void) { +static void fe_cmov_test(void) { static const secp256k1_fe zero = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0); static const secp256k1_fe one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); static const secp256k1_fe max = SECP256K1_FE_CONST( @@ -6955,7 +7405,7 @@ void fe_cmov_test(void) { CHECK(secp256k1_memcmp_var(&r, &one, sizeof(r)) == 0); } -void fe_storage_cmov_test(void) { +static void fe_storage_cmov_test(void) { static const secp256k1_fe_storage zero = SECP256K1_FE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 0); static const secp256k1_fe_storage one = SECP256K1_FE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 1); static const secp256k1_fe_storage max = SECP256K1_FE_STORAGE_CONST( @@ -6985,7 +7435,7 @@ void fe_storage_cmov_test(void) { CHECK(secp256k1_memcmp_var(&r, &one, sizeof(r)) == 0); } -void scalar_cmov_test(void) { +static void scalar_cmov_test(void) { static const secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); static const secp256k1_scalar one = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1); static const secp256k1_scalar max = SECP256K1_SCALAR_CONST( @@ -7015,7 +7465,7 @@ void scalar_cmov_test(void) { CHECK(secp256k1_memcmp_var(&r, &one, sizeof(r)) == 0); } -void ge_storage_cmov_test(void) { +static void ge_storage_cmov_test(void) { static const secp256k1_ge_storage zero = SECP256K1_GE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); static const secp256k1_ge_storage one = SECP256K1_GE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); static const secp256k1_ge_storage max = SECP256K1_GE_STORAGE_CONST( @@ -7047,7 +7497,7 @@ void ge_storage_cmov_test(void) { CHECK(secp256k1_memcmp_var(&r, &one, sizeof(r)) == 0); } -void run_cmov_tests(void) { +static void run_cmov_tests(void) { int_cmov_test(); fe_cmov_test(); fe_storage_cmov_test(); @@ -7066,40 +7516,69 @@ int main(int argc, char **argv) { /* find iteration count */ if (argc > 1) { - count = strtol(argv[1], NULL, 0); + COUNT = strtol(argv[1], NULL, 0); } else { const char* env = getenv("SECP256K1_TEST_ITERS"); if (env && strlen(env) > 0) { - count = strtol(env, NULL, 0); + COUNT = strtol(env, NULL, 0); } } - if (count <= 0) { + if (COUNT <= 0) { fputs("An iteration count of 0 or less is not allowed.\n", stderr); return EXIT_FAILURE; } - printf("test count = %i\n", count); + printf("test count = %i\n", COUNT); /* find random seed */ secp256k1_testrand_init(argc > 2 ? argv[2] : NULL); - /* initialize */ - run_context_tests(0); - run_context_tests(1); - run_scratch_tests(); - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - if (secp256k1_testrand_bits(1)) { + /*** Setup test environment ***/ + + /* Create a global context available to all tests */ + CTX = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + /* Randomize the context only with probability 15/16 + to make sure we test without context randomization from time to time. + TODO Reconsider this when recalibrating the tests. */ + if (secp256k1_testrand_bits(4)) { unsigned char rand32[32]; secp256k1_testrand256(rand32); - CHECK(secp256k1_context_randomize(ctx, secp256k1_testrand_bits(1) ? rand32 : NULL)); + CHECK(secp256k1_context_randomize(CTX, rand32)); } + /* Make a writable copy of secp256k1_context_static in order to test the effect of API functions + that write to the context. The API does not support cloning the static context, so we use + memcpy instead. The user is not supposed to copy a context but we should still ensure that + the API functions handle copies of the static context gracefully. */ + STATIC_CTX = malloc(sizeof(*secp256k1_context_static)); + CHECK(STATIC_CTX != NULL); + memcpy(STATIC_CTX, secp256k1_context_static, sizeof(secp256k1_context)); + CHECK(!secp256k1_context_is_proper(STATIC_CTX)); + + /*** Run actual tests ***/ + + /* selftest tests */ + run_selftest_tests(); + /* context tests */ + run_proper_context_tests(0); run_proper_context_tests(1); + run_static_context_tests(0); run_static_context_tests(1); + run_deprecated_context_flags_test(); + + /* scratch tests */ + run_scratch_tests(); + + /* randomness tests */ run_rand_bits(); run_rand_int(); + /* integer arithmetic tests */ +#ifdef SECP256K1_WIDEMUL_INT128 + run_int128_tests(); +#endif run_ctz_tests(); run_modinv_tests(); run_inverse_tests(); + /* hash tests */ run_sha256_known_output_tests(); run_sha256_counter_tests(); run_hmac_sha256_tests(); @@ -7152,6 +7631,7 @@ int main(int argc, char **argv) { #endif /* ecdsa tests */ + run_ec_illegal_argument_tests(); run_pubkey_comparison(); run_random_pubkeys(); run_ecdsa_der_parse(); @@ -7178,10 +7658,11 @@ int main(int argc, char **argv) { run_cmov_tests(); - secp256k1_testrand_finish(); + /*** Tear down test environment ***/ + free(STATIC_CTX); + secp256k1_context_destroy(CTX); - /* shutdown */ - secp256k1_context_destroy(ctx); + secp256k1_testrand_finish(); printf("no problems found\n"); return 0; diff --git a/src/secp256k1/src/tests_exhaustive.c b/src/secp256k1/src/tests_exhaustive.c index 6a4e2340f2..86b9334cae 100644 --- a/src/secp256k1/src/tests_exhaustive.c +++ b/src/secp256k1/src/tests_exhaustive.c @@ -4,10 +4,6 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include #include #include @@ -28,7 +24,7 @@ static int count = 2; /** stolen from tests.c */ -void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { +static void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { CHECK(a->infinity == b->infinity); if (a->infinity) { return; @@ -37,7 +33,7 @@ void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { CHECK(secp256k1_fe_equal_var(&a->y, &b->y)); } -void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { +static void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { secp256k1_fe z2s; secp256k1_fe u1, u2, s1, s2; CHECK(a->infinity == b->infinity); @@ -54,7 +50,7 @@ void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { CHECK(secp256k1_fe_equal_var(&s1, &s2)); } -void random_fe(secp256k1_fe *x) { +static void random_fe(secp256k1_fe *x) { unsigned char bin[32]; do { secp256k1_testrand256(bin); @@ -74,7 +70,7 @@ SECP256K1_INLINE static int skip_section(uint64_t* iter) { return ((((uint32_t)*iter ^ (*iter >> 32)) * num_cores) >> 32) != this_core; } -int secp256k1_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg32, +static int secp256k1_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int attempt) { secp256k1_scalar s; @@ -94,7 +90,7 @@ int secp256k1_nonce_function_smallint(unsigned char *nonce32, const unsigned cha return 1; } -void test_exhaustive_endomorphism(const secp256k1_ge *group) { +static void test_exhaustive_endomorphism(const secp256k1_ge *group) { int i; for (i = 0; i < EXHAUSTIVE_TEST_ORDER; i++) { secp256k1_ge res; @@ -103,7 +99,7 @@ void test_exhaustive_endomorphism(const secp256k1_ge *group) { } } -void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_gej *groupj) { +static void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_gej *groupj) { int i, j; uint64_t iter = 0; @@ -163,7 +159,7 @@ void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_gej *gr } } -void test_exhaustive_ecmult(const secp256k1_ge *group, const secp256k1_gej *groupj) { +static void test_exhaustive_ecmult(const secp256k1_ge *group, const secp256k1_gej *groupj) { int i, j, r_log; uint64_t iter = 0; for (r_log = 1; r_log < EXHAUSTIVE_TEST_ORDER; r_log++) { @@ -199,7 +195,7 @@ static int ecmult_multi_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t return 1; } -void test_exhaustive_ecmult_multi(const secp256k1_context *ctx, const secp256k1_ge *group) { +static void test_exhaustive_ecmult_multi(const secp256k1_context *ctx, const secp256k1_ge *group) { int i, j, k, x, y; uint64_t iter = 0; secp256k1_scratch *scratch = secp256k1_scratch_create(&ctx->error_callback, 4096); @@ -229,7 +225,7 @@ void test_exhaustive_ecmult_multi(const secp256k1_context *ctx, const secp256k1_ secp256k1_scratch_destroy(&ctx->error_callback, scratch); } -void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k, int* overflow) { +static void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k, int* overflow) { secp256k1_fe x; unsigned char x_bin[32]; k %= EXHAUSTIVE_TEST_ORDER; @@ -239,7 +235,7 @@ void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k, int* overfl secp256k1_scalar_set_b32(r, x_bin, overflow); } -void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *group) { +static void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *group) { int s, r, msg, key; uint64_t iter = 0; for (s = 1; s < EXHAUSTIVE_TEST_ORDER; s++) { @@ -292,7 +288,7 @@ void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *gr } } -void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_ge *group) { +static void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_ge *group) { int i, j, k; uint64_t iter = 0; @@ -342,15 +338,15 @@ void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_ge *grou } #ifdef ENABLE_MODULE_RECOVERY -#include "src/modules/recovery/tests_exhaustive_impl.h" +#include "modules/recovery/tests_exhaustive_impl.h" #endif #ifdef ENABLE_MODULE_EXTRAKEYS -#include "src/modules/extrakeys/tests_exhaustive_impl.h" +#include "modules/extrakeys/tests_exhaustive_impl.h" #endif #ifdef ENABLE_MODULE_SCHNORRSIG -#include "src/modules/schnorrsig/tests_exhaustive_impl.h" +#include "modules/schnorrsig/tests_exhaustive_impl.h" #endif int main(int argc, char** argv) { @@ -396,7 +392,7 @@ int main(int argc, char** argv) { while (count--) { /* Build context */ - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_testrand256(rand32); CHECK(secp256k1_context_randomize(ctx, rand32)); diff --git a/src/secp256k1/src/util.h b/src/secp256k1/src/util.h index dac86bd77f..e75c5ad552 100644 --- a/src/secp256k1/src/util.h +++ b/src/secp256k1/src/util.h @@ -7,15 +7,16 @@ #ifndef SECP256K1_UTIL_H #define SECP256K1_UTIL_H -#if defined HAVE_CONFIG_H -#include "libsecp256k1-config.h" -#endif - #include #include #include #include +#define STR_(x) #x +#define STR(x) STR_(x) +#define DEBUG_CONFIG_MSG(x) "DEBUG_CONFIG: " x +#define DEBUG_CONFIG_DEF(x) DEBUG_CONFIG_MSG(#x "=" STR(x)) + typedef struct { void (*fn)(const char *text, void* data); const void* data; @@ -96,25 +97,6 @@ static const secp256k1_callback default_error_callback = { #define VERIFY_SETUP(stmt) #endif -/* Define `VG_UNDEF` and `VG_CHECK` when VALGRIND is defined */ -#if !defined(VG_CHECK) -# if defined(VALGRIND) -# include -# define VG_UNDEF(x,y) VALGRIND_MAKE_MEM_UNDEFINED((x),(y)) -# define VG_CHECK(x,y) VALGRIND_CHECK_MEM_IS_DEFINED((x),(y)) -# else -# define VG_UNDEF(x,y) -# define VG_CHECK(x,y) -# endif -#endif - -/* Like `VG_CHECK` but on VERIFY only */ -#if defined(VERIFY) -#define VG_CHECK_VERIFY(x,y) VG_CHECK((x), (y)) -#else -#define VG_CHECK_VERIFY(x,y) -#endif - static SECP256K1_INLINE void *checked_malloc(const secp256k1_callback* cb, size_t size) { void *ret = malloc(size); if (ret == NULL) { @@ -225,28 +207,36 @@ static SECP256K1_INLINE void secp256k1_int_cmov(int *r, const int *a, int flag) *r = (int)(r_masked | a_masked); } -/* If USE_FORCE_WIDEMUL_{INT128,INT64} is set, use that wide multiplication implementation. - * Otherwise use the presence of __SIZEOF_INT128__ to decide. - */ -#if defined(USE_FORCE_WIDEMUL_INT128) +#if defined(USE_FORCE_WIDEMUL_INT128_STRUCT) +/* If USE_FORCE_WIDEMUL_INT128_STRUCT is set, use int128_struct. */ +# define SECP256K1_WIDEMUL_INT128 1 +# define SECP256K1_INT128_STRUCT 1 +#elif defined(USE_FORCE_WIDEMUL_INT128) +/* If USE_FORCE_WIDEMUL_INT128 is set, use int128. */ # define SECP256K1_WIDEMUL_INT128 1 +# define SECP256K1_INT128_NATIVE 1 #elif defined(USE_FORCE_WIDEMUL_INT64) +/* If USE_FORCE_WIDEMUL_INT64 is set, use int64. */ # define SECP256K1_WIDEMUL_INT64 1 #elif defined(UINT128_MAX) || defined(__SIZEOF_INT128__) +/* If a native 128-bit integer type exists, use int128. */ # define SECP256K1_WIDEMUL_INT128 1 +# define SECP256K1_INT128_NATIVE 1 +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) +/* On 64-bit MSVC targets (x86_64 and arm64), use int128_struct + * (which has special logic to implement using intrinsics on those systems). */ +# define SECP256K1_WIDEMUL_INT128 1 +# define SECP256K1_INT128_STRUCT 1 +#elif SIZE_MAX > 0xffffffff +/* Systems with 64-bit pointers (and thus registers) very likely benefit from + * using 64-bit based arithmetic (even if we need to fall back to 32x32->64 based + * multiplication logic). */ +# define SECP256K1_WIDEMUL_INT128 1 +# define SECP256K1_INT128_STRUCT 1 #else +/* Lastly, fall back to int64 based arithmetic. */ # define SECP256K1_WIDEMUL_INT64 1 #endif -#if defined(SECP256K1_WIDEMUL_INT128) -# if !defined(UINT128_MAX) && defined(__SIZEOF_INT128__) -SECP256K1_GNUC_EXT typedef unsigned __int128 uint128_t; -SECP256K1_GNUC_EXT typedef __int128 int128_t; -#define UINT128_MAX ((uint128_t)(-1)) -#define INT128_MAX ((int128_t)(UINT128_MAX >> 1)) -#define INT128_MIN (-INT128_MAX - 1) -/* No (U)INT128_C macros because compilers providing __int128 do not support 128-bit literals. */ -# endif -#endif #ifndef __has_builtin #define __has_builtin(x) 0 @@ -261,7 +251,7 @@ static SECP256K1_INLINE int secp256k1_ctz32_var_debruijn(uint32_t x) { 0x10, 0x07, 0x0C, 0x1A, 0x1F, 0x17, 0x12, 0x05, 0x15, 0x09, 0x0F, 0x0B, 0x1E, 0x11, 0x08, 0x0E, 0x1D, 0x0D, 0x1C, 0x1B }; - return debruijn[((x & -x) * 0x04D7651F) >> 27]; + return debruijn[(uint32_t)((x & -x) * 0x04D7651FU) >> 27]; } /* Determine the number of trailing zero bits in a (non-zero) 64-bit x. @@ -274,7 +264,7 @@ static SECP256K1_INLINE int secp256k1_ctz64_var_debruijn(uint64_t x) { 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 }; - return debruijn[((x & -x) * 0x022FDD63CC95386D) >> 58]; + return debruijn[(uint64_t)((x & -x) * 0x022FDD63CC95386DU) >> 58]; } /* Determine the number of trailing zero bits in a (non-zero) 32-bit x. */ diff --git a/src/serialize.h b/src/serialize.h index cad91085b2..a09d5243d8 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -724,6 +725,10 @@ template void Unserialize(Stream& os, std::shared_p template void Serialize(Stream& os, const std::unique_ptr& p); template void Unserialize(Stream& os, std::unique_ptr& p); +// variant +template void Serialize(Stream& os, const std::variant& v); +template void Unserialize(Stream& is, const std::variant& v); + /** @@ -732,7 +737,14 @@ template void Unserialize(Stream& os, std::unique_p template inline void Serialize(Stream& os, const T& a) { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" +#endif a.Serialize(os); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif } template @@ -1064,6 +1076,46 @@ void Unserialize(Stream& is, std::shared_ptr& p) +// variant +template +void Serialize(Stream& os, const std::variant& v) { + // The 253 limit here is for that if there's a need for more than 253 type variant in the + // future, someone can replace the index uint8 with a var-int without sacrificing backwards + // compatibility. + static_assert(sizeof...(Args) < 253, "variants should hold less than 253 types."); + + Serialize(os, (uint8_t)v.index()); + + std::visit([&](auto& v2) { Serialize(os, v2); }, v); +} + +template +void unserialize_variant_helper(Stream& is, uint8_t index, V& v) {} + +template +void unserialize_variant_helper(Stream& is, uint8_t index, V& v) { + if (index == n) { + T o; + Unserialize(is, o); + v = o; + } else { + unserialize_variant_helper(is, index, v); + } +} + +template +void Unserialize(Stream& is, std::variant& v) { + // The 253 limit here is for that if there's a need for more than 253 type variant in the + // future, someone can replace the index uint8 with a var-int without sacrificing backwards + // compatibility. + static_assert(sizeof...(Args) < 253, "variants should hold less than 253 types."); + + uint8_t index; + Unserialize(is, index); + + unserialize_variant_helper, Args...>(is, index, v); +} + /** * Support for ADD_SERIALIZE_METHODS and READWRITE macro */ diff --git a/src/sync.h b/src/sync.h index 62f57365fd..f9bbdf6913 100644 --- a/src/sync.h +++ b/src/sync.h @@ -160,10 +160,11 @@ class SCOPED_LOCKABLE UniqueLock : public Base bool TryEnter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, (void*)(Base::mutex()), true); - Base::try_lock(); - if (!Base::owns_lock()) - LeaveCritical(); - return Base::owns_lock(); + if (Base::try_lock()) { + return true; + } + LeaveCritical(); + return false; } public: diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000000..ebc70baa79 --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,64 @@ +if(NOT SYSTEM_XXD) + add_executable(xxd xxd/xxd.c) + set(XXD $) +endif() + +add_executable(test_gridcoin + checkpoints_tests.cpp + dos_tests.cpp + accounting_tests.cpp + addrman_tests.cpp + allocator_tests.cpp + base32_tests.cpp + base58_tests.cpp + base64_tests.cpp + bip32_tests.cpp + #compilerbug_tests.cpp + crypto_tests.cpp + fs_tests.cpp + getarg_tests.cpp + gridcoin_tests.cpp + gridcoin/block_finder_tests.cpp + gridcoin/beacon_tests.cpp + gridcoin/claim_tests.cpp + gridcoin/contract_tests.cpp + gridcoin/cpid_tests.cpp + gridcoin/enumbytes_tests.cpp + gridcoin/magnitude_tests.cpp + gridcoin/mrc_tests.cpp + gridcoin/project_tests.cpp + gridcoin/protocol_tests.cpp + gridcoin/researcher_tests.cpp + gridcoin/scraper_registry_tests.cpp + gridcoin/sidestake_tests.cpp + gridcoin/superblock_tests.cpp + key_tests.cpp + merkle_tests.cpp + mruset_tests.cpp + multisig_tests.cpp + netbase_tests.cpp + net_tests.cpp + random_tests.cpp + rpc_tests.cpp + sanity_tests.cpp + scheduler_tests.cpp + script_p2sh_tests.cpp + script_tests.cpp + serialize_tests.cpp + sigopcount_tests.cpp + sync_tests.cpp + test_gridcoin.cpp + transaction_tests.cpp + uint256_tests.cpp + util_tests.cpp + wallet_tests.cpp +) + +add_subdirectory(data) + +target_link_libraries(test_gridcoin PRIVATE + Boost::unit_test_framework + gridcoin_util +) + +add_test(NAME gridcoin_tests COMMAND test_gridcoin) diff --git a/src/test/bignum_tests.cpp b/src/test/bignum_tests.cpp deleted file mode 100755 index c3da4a571c..0000000000 --- a/src/test/bignum_tests.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include -#include - -#include "bignum.h" -#include "util.h" - -#include - -BOOST_AUTO_TEST_SUITE(bignum_tests) - -// Unfortunately there's no standard way of preventing a function from being -// inlined, so we define a macro for it. -// -// You should use it like this: -// NOINLINE void function() {...} -#if defined(__GNUC__) -// This also works and will be defined for any compiler implementing GCC -// extensions, such as Clang and ICC. -#define NOINLINE __attribute__((noinline)) -#elif defined(_MSC_VER) -#define NOINLINE __declspec(noinline) -#else -// We give out a warning because it impacts the correctness of one bignum test. -#warning You should define NOINLINE for your compiler. -#define NOINLINE -#endif - -// For the following test case, it is useful to use additional tools. -// -// The simplest one to use is the compiler flag -ftrapv, which detects integer -// overflows and similar errors. However, due to optimizations and compilers -// taking advantage of undefined behavior sometimes it may not actually detect -// anything. -// -// You can also use compiler-based stack protection to possibly detect possible -// stack buffer overruns. -// -// For more accurate diagnostics, you can use an undefined arithmetic operation -// detector such as the clang's undefined behaviour checker. -// See also: https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation -// -// It might also be useful to use Google's AddressSanitizer to detect -// stack buffer overruns, which valgrind can't currently detect. - -// Let's force this code not to be inlined, in order to actually -// test a generic version of the function. This increases the chance -// that -ftrapv will detect overflows. -NOINLINE void mysetint64(CBigNum& num, int64_t n) -{ - num.setint64(n); -} - -// For each number, we do 2 tests: one with inline code, then we reset the -// value to 0, then the second one with a non-inlined function. -BOOST_AUTO_TEST_CASE(bignum_setint64) -{ - int64_t n; - - { - n = 0; - CBigNum num(n); - BOOST_CHECK(num.ToString() == "0"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "0"); - } - { - n = 1; - CBigNum num(n); - BOOST_CHECK(num.ToString() == "1"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "1"); - } - { - n = -1; - CBigNum num(n); - BOOST_CHECK(num.ToString() == "-1"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "-1"); - } - { - n = 5; - CBigNum num(n); - BOOST_CHECK(num.ToString() == "5"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "5"); - } - { - n = -5; - CBigNum num(n); - BOOST_CHECK(num.ToString() == "-5"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "-5"); - } - { - n = std::numeric_limits::min(); - CBigNum num(n); - BOOST_CHECK(num.ToString() == "-9223372036854775808"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "-9223372036854775808"); - } - { - n = std::numeric_limits::max(); - CBigNum num(n); - BOOST_CHECK(num.ToString() == "9223372036854775807"); - num.setulong(0); - BOOST_CHECK(num.ToString() == "0"); - mysetint64(num, n); - BOOST_CHECK(num.ToString() == "9223372036854775807"); - } -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 6addce5f0b..3e99c8d9b9 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,14 @@ static void TestVector(const Hasher &h, const In &in, const Out &out) { } } +static void TestMD5(const std::string &in, const std::string &hexout) { + assert(hexout.size() == 2 * MD5_DIGEST_LENGTH); + + uint8_t out[MD5_DIGEST_LENGTH]; + GRC__MD5((const uint8_t*)in.data(), in.size(), out); + + BOOST_CHECK(std::memcmp(out, ParseHex(hexout).data(), MD5_DIGEST_LENGTH) == 0); +} static void TestSHA1(const std::string &in, const std::string &hexout) { TestVector(CSHA1(), in, ParseHex(hexout));} static void TestSHA256(const std::string &in, const std::string &hexout) { TestVector(CSHA256(), in, ParseHex(hexout));} static void TestSHA512(const std::string &in, const std::string &hexout) { TestVector(CSHA512(), in, ParseHex(hexout));} @@ -187,6 +196,18 @@ static std::string LongTestString() const std::string test1 = LongTestString(); +BOOST_AUTO_TEST_CASE(md5_testvectors) { + TestMD5("", "d41d8cd98f00b204e9800998ecf8427e"); + TestMD5("a", "0cc175b9c0f1b6a831c399e269772661"); + TestMD5("abc", "900150983cd24fb0d6963f7d28e17f72"); + TestMD5("message digest", "f96b697d7cb7938d525a2f31aaf161d0"); + TestMD5("abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b"); + TestMD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "d174ab98d277d9f5a5611c2c9f419d9f"); + TestMD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "57edf4a22be3c955ac49da2e2107b67a"); + TestMD5(std::string(1000000, 'a'), "7707d6ae4e027c70eea2a935c2296f21"); + TestMD5(test1, "12ebd71b1cadcfde2bb6905987b8a52e"); +} + BOOST_AUTO_TEST_CASE(ripemd160_testvectors) { TestRIPEMD160("", "9c1185a5c5e9fc54612808977ee8f548b2258d31"); TestRIPEMD160("abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); diff --git a/src/test/data/CMakeLists.txt b/src/test/data/CMakeLists.txt new file mode 100644 index 0000000000..784efde82a --- /dev/null +++ b/src/test/data/CMakeLists.txt @@ -0,0 +1,50 @@ +set(JSON_TEST_FILES + base58_keys_valid.json + base58_encode_decode.json + base58_keys_invalid.json + script_valid.json + script_invalid.json + tx_invalid.json + tx_valid.json +) + +set(BINARY_TEST_FILES + mainnet_beacon.bin + superblock_packed.bin + testnet_beacon.bin +) + +set(TEXT_TEST_FILES + superblock.txt + superblock_unpacked.txt +) + +foreach(file_in IN LISTS JSON_TEST_FILES) + set(file_out ${CMAKE_CURRENT_BINARY_DIR}/${file_in}.h) + add_custom_command(OUTPUT ${file_in}.h + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/build-aux/cmake/json2header.cmake ${XXD} ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} ${file_out} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} + ) + add_custom_target(generate_${file_in}.h DEPENDS ${file_in}.h) + add_dependencies(test_gridcoin generate_${file_in}.h) +endforeach() + +foreach(file_in IN LISTS BINARY_TEST_FILES) + get_filename_component(var_name ${file_in} NAME_WE) + add_custom_command(OUTPUT ${file_in}.h + COMMAND ${XXD} -i -n ${var_name}_bin ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} ${CMAKE_CURRENT_BINARY_DIR}/${file_in}.h + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} + ) + add_custom_target(generate_${file_in}.h DEPENDS ${file_in}.h) + add_dependencies(test_gridcoin generate_${file_in}.h) +endforeach() + +foreach(file_in IN LISTS TEXT_TEST_FILES) + set(file_out ${CMAKE_CURRENT_BINARY_DIR}/${file_in}.h) + add_custom_command(OUTPUT ${file_in}.h + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/build-aux/cmake/txt2header.cmake ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} ${file_out} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file_in} + ) + add_custom_target(generate_${file_in}.h DEPENDS ${file_in}.h) + add_dependencies(test_gridcoin generate_${file_in}.h) +endforeach() diff --git a/src/test/dos_tests.cpp b/src/test/dos_tests.cpp index ff7ec22e75..82653c0632 100755 --- a/src/test/dos_tests.cpp +++ b/src/test/dos_tests.cpp @@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vin[j].prevout.hash = txPrev.GetHash(); } BOOST_CHECK(SignSignature(keystore, txPrev, tx, 0)); - // Re-use same signature for other inputs + // Reuse same signature for other inputs // (they don't have to be valid for this test) for (unsigned int j = 1; j < tx.vin.size(); j++) tx.vin[j].scriptSig = tx.vin[0].scriptSig; diff --git a/src/test/gridcoin/appcache_tests.cpp b/src/test/gridcoin/appcache_tests.cpp deleted file mode 100644 index dac8e41b03..0000000000 --- a/src/test/gridcoin/appcache_tests.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2014-2021 The Gridcoin developers -// Distributed under the MIT/X11 software license, see the accompanying -// file COPYING or https://opensource.org/licenses/mit-license.php. - -#include "gridcoin/appcache.h" - -#include - -BOOST_AUTO_TEST_SUITE(appcache_tests) - -BOOST_AUTO_TEST_CASE(appcache_WrittenCacheShouldBeReadable) -{ - ClearCache(Section::PROTOCOL); - WriteCache(Section::PROTOCOL, "key", "hello", 123456789); - BOOST_CHECK(ReadCache(Section::PROTOCOL, "key").value == "hello"); -} - -BOOST_AUTO_TEST_CASE(appcache_ClearCacheShouldClearEntireSection) -{ - ClearCache(Section::PROTOCOL); - WriteCache(Section::PROTOCOL, "key1", "hello", 123456789); - WriteCache(Section::PROTOCOL, "key2", "hello", 123456789); - ClearCache(Section::PROTOCOL); - BOOST_CHECK(ReadCache(Section::PROTOCOL, "key1").value.empty() == true); - BOOST_CHECK(ReadCache(Section::PROTOCOL, "key2").value.empty() == true); -} - -BOOST_AUTO_TEST_CASE(appcache_KeyShouldBeEmptyAfterDeleteCache) -{ - ClearCache(Section::PROTOCOL); - WriteCache(Section::PROTOCOL, "key", "hello", 123456789); - DeleteCache(Section::PROTOCOL, "key"); - BOOST_CHECK(ReadCache(Section::PROTOCOL, "key").value.empty() == true); -} - -BOOST_AUTO_TEST_CASE(appcache_SortedSectionsShouldBeSorted) -{ - ClearCache(Section::PROTOCOL); - WriteCache(Section::PROTOCOL, "b", "321", 0); - WriteCache(Section::PROTOCOL, "a", "123", 0); - - const SortedAppCacheSection& section = ReadSortedCacheSection(Section::PROTOCOL); - auto it = section.begin(); - BOOST_CHECK(it->first == "a"); - BOOST_CHECK(it->second.value == "123"); - - ++it; - BOOST_CHECK(it->first == "b"); - BOOST_CHECK(it->second.value == "321"); -} - - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/beacon_tests.cpp b/src/test/gridcoin/beacon_tests.cpp index 6ef4ad0283..e216919f3a 100644 --- a/src/test/gridcoin/beacon_tests.cpp +++ b/src/test/gridcoin/beacon_tests.cpp @@ -86,6 +86,11 @@ struct TestKey return EncodeBase58(key_id.begin(), key_id.end()); } + static GRC::Cpid Cpid() + { + return GRC::Cpid::Parse("00010203040506070809101112131415"); + } + //! //! \brief Create a beacon payload signature signed by this private key. //! @@ -96,7 +101,7 @@ struct TestKey hasher << GRC::BeaconPayload::CURRENT_VERSION << GRC::Beacon(Public()) - << GRC::Cpid::Parse("00010203040506070809101112131415"); + << Cpid(); std::vector signature; CKey private_key = Private(); @@ -105,6 +110,27 @@ struct TestKey return signature; } + + //! + //! \brief Create a beacon payload signature signed by this private key. + //! + static std::vector Signature(GRC::BeaconPayload payload) + { + CHashWriter hasher(SER_NETWORK, PROTOCOL_VERSION); + + hasher + << payload.m_version + << payload.m_beacon + << payload.m_cpid; + + std::vector signature; + CKey private_key = Private(); + + private_key.Sign(hasher.GetHash(), signature); + + return signature; + } + }; // TestKey @@ -176,21 +202,96 @@ class BeaconRegistryTest if (ctx->m_action == GRC::ContractAction::ADD) { registry.Add(ctx); + + GRC::Beacon_ptr beacon = registry.FindHistorical(ctx.m_tx.GetHash()); + + if (beacon != nullptr) { + std::cout << "add beacon record: " + << "blockheight = " << ctx.m_pindex->nHeight + << ", cpid = " << beacon->m_cpid.ToString() + << ", public key = " << HexStr(beacon->m_public_key) + << ", address = " << beacon->GetAddress().ToString() + << ", timestamp = " << beacon->m_timestamp + << ", hash = " << beacon->m_hash.GetHex() + << ", prev beacon hash = " << beacon->m_previous_hash.GetHex() + << ", status = " << beacon->StatusToString() + << std::endl; + } } if (ctx->m_action == GRC::ContractAction::REMOVE) { registry.Delete(ctx); + + GRC::Beacon_ptr beacon = registry.FindHistorical(ctx.m_tx.GetHash()); + + if (beacon != nullptr) { + std::cout << "delete beacon record: " + << "blockheight = " << ctx.m_pindex->nHeight + << ", cpid = " << beacon->m_cpid.ToString() + << ", public key = " << HexStr(beacon->m_public_key) + << ", address = " << beacon->GetAddress().ToString() + << ", timestamp = " << beacon->m_timestamp + << ", hash = " << beacon->m_hash.GetHex() + << ", prev beacon hash = " << beacon->m_previous_hash.GetHex() + << ", status = " << beacon->StatusToString() + << std::endl; + } } } // Activate the pending beacons that are now verified, and also mark expired pending beacons expired. if (pindex->IsSuperblock()) { + std::vector pending_beacon_hashes; + + for (const auto& iter : element.m_verified_beacons) { + auto found_beacon_iter = registry.PendingBeacons().find(iter); + + if (found_beacon_iter != registry.PendingBeacons().end()) { + pending_beacon_hashes.push_back(found_beacon_iter->second->m_hash); + } + } + registry.ActivatePending(element.m_verified_beacons, pindex->nTime, block_hash, pindex->nHeight); + + for (const auto& iter : pending_beacon_hashes) { + uint256 activated_beacon_hash = Hash(pindex->GetBlockHash(), iter); + + GRC::Beacon_ptr activated_beacon = registry.FindHistorical(activated_beacon_hash); + + if (activated_beacon != nullptr) { + std::cout << "activated beacon record: " + << "blockheight = " << pindex->nHeight + << ", cpid = " << activated_beacon->m_cpid.ToString() + << ", public key = " << HexStr(activated_beacon->m_public_key) + << ", address = " << activated_beacon->GetAddress().ToString() + << ", timestamp = " << activated_beacon->m_timestamp + << ", hash = " << activated_beacon->m_hash.GetHex() + << ", prev beacon hash = " << activated_beacon->m_previous_hash.GetHex() + << ", status = " << activated_beacon->StatusToString() + << std::endl; + } + } + + for (const auto& iter : registry.ExpiredBeacons()) { + if (iter != nullptr) { + std::cout << "expired beacon record: " + << "blockheight = " << pindex->nHeight + << ", cpid = " << iter->m_cpid.ToString() + << ", public key = " << HexStr(iter->m_public_key) + << ", address = " << iter->GetAddress().ToString() + << ", timestamp = " << iter->m_timestamp + << ", hash = " << iter->m_hash.GetHex() + << ", prev beacon hash = " << iter->m_previous_hash.GetHex() + << ", status = " << iter->StatusToString() + << std::endl; + } + + } } } @@ -224,6 +325,16 @@ class BeaconRegistryTest // Create a copy of the referenced beacon object with a shared pointer to it and store. m_local_historical_beacon_map_init[hash] = std::make_shared(*beacon_ptr); + std::cout << "init beacon db record: " + << ", cpid = " << beacon_ptr->m_cpid.ToString() + << ", public key = " << HexStr(beacon_ptr->m_public_key) + << ", address = " << beacon_ptr->GetAddress().ToString() + << ", timestamp = " << beacon_ptr->m_timestamp + << ", hash = " << beacon_ptr->m_hash.GetHex() + << ", prev beacon hash = " << beacon_ptr->m_previous_hash.GetHex() + << ", status = " << beacon_ptr->StatusToString() + << std::endl; + init_beacon_db_iter = init_beacon_db.advance(init_beacon_db_iter); } @@ -265,6 +376,16 @@ class BeaconRegistryTest // Create a copy of the referenced beacon object with a shared pointer to it and store. m_local_historical_beacon_map_reinit[hash] = std::make_shared(*beacon_ptr); + std::cout << "init beacon db record: " + << ", cpid = " << beacon_ptr->m_cpid.ToString() + << ", public key = " << HexStr(beacon_ptr->m_public_key) + << ", address = " << beacon_ptr->GetAddress().ToString() + << ", timestamp = " << beacon_ptr->m_timestamp + << ", hash = " << beacon_ptr->m_hash.GetHex() + << ", prev beacon hash = " << beacon_ptr->m_previous_hash.GetHex() + << ", status = " << beacon_ptr->StatusToString() + << std::endl; + reinit_beacon_db_iter = reinit_beacon_db.advance(reinit_beacon_db_iter); } }; @@ -313,8 +434,8 @@ class BeaconRegistryTest << ", address = " << left.second->GetAddress().ToString() << ", timestamp = " << left.second->m_timestamp << ", hash = " << left.second->m_hash.GetHex() - << ", prev beacon hash = " << left.second->m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left.second->m_status.Raw()) + << ", prev beacon hash = " << left.second->m_previous_hash.GetHex() + << ", status = " << left.second->StatusToString() << std::endl; } @@ -345,11 +466,11 @@ class BeaconRegistryTest std::cout << "init_beacon hash = " << left_beacon_ptr->m_hash.GetHex() << ", reinit_beacon hash = " << right_beacon_iter->second->m_hash.GetHex() << std::endl; - std::cout << "init_beacon prev beacon hash = " << left_beacon_ptr->m_prev_beacon_hash.GetHex() - << ", reinit_beacon prev beacon hash = " << right_beacon_iter->second->m_prev_beacon_hash.GetHex() << std::endl; + std::cout << "init_beacon prev beacon hash = " << left_beacon_ptr->m_previous_hash.GetHex() + << ", reinit_beacon prev beacon hash = " << right_beacon_iter->second->m_previous_hash.GetHex() << std::endl; - std::cout << "init_beacon status = " << ToString(left_beacon_ptr->m_status.Raw()) - << ", reinit_beacon status = " << ToString(right_beacon_iter->second->m_status.Raw()) << std::endl; + std::cout << "init_beacon status = " << left_beacon_ptr->StatusToString() + << ", reinit_beacon status = " << right_beacon_iter->second->StatusToString() << std::endl; } } @@ -375,8 +496,8 @@ class BeaconRegistryTest << ", address = " << left.second->GetAddress().ToString() << ", timestamp = " << left.second->m_timestamp << ", hash = " << left.second->m_hash.GetHex() - << ", prev beacon hash = " << left.second->m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left.second->m_status.Raw()) + << ", prev beacon hash = " << left.second->m_previous_hash.GetHex() + << ", status = " << left.second->StatusToString() << std::endl; } @@ -407,11 +528,11 @@ class BeaconRegistryTest std::cout << "reinit_beacon hash = " << left_beacon_ptr->m_hash.GetHex() << ", init_beacon hash = " << right_beacon_iter->second->m_hash.GetHex() << std::endl; - std::cout << "reinit_beacon prev beacon hash = " << left_beacon_ptr->m_prev_beacon_hash.GetHex() - << ", init_beacon prev beacon hash = " << right_beacon_iter->second->m_prev_beacon_hash.GetHex() << std::endl; + std::cout << "reinit_beacon prev beacon hash = " << left_beacon_ptr->m_previous_hash.GetHex() + << ", init_beacon prev beacon hash = " << right_beacon_iter->second->m_previous_hash.GetHex() << std::endl; - std::cout << "reinit_beacon status = " << ToString(left_beacon_ptr->m_status.Raw()) - << ", init_beacon status = " << ToString(right_beacon_iter->second->m_status.Raw()) << std::endl; + std::cout << "reinit_beacon status = " << left_beacon_ptr->StatusToString() + << ", init_beacon status = " << right_beacon_iter->second->StatusToString() << std::endl; } } @@ -439,8 +560,8 @@ class BeaconRegistryTest << ", address = " << beacon.GetAddress().ToString() << ", timestamp = " << beacon.m_timestamp << ", hash = " << beacon.m_hash.GetHex() - << ", prev beacon hash = " << beacon.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(beacon.m_status.Raw()) + << ", prev beacon hash = " << beacon.m_previous_hash.GetHex() + << ", status = " << beacon.StatusToString() << std::endl; } @@ -456,8 +577,8 @@ class BeaconRegistryTest << ", address = " << beacon.GetAddress().ToString() << ", timestamp = " << beacon.m_timestamp << ", hash = " << beacon.m_hash.GetHex() - << ", prev beacon hash = " << beacon.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(beacon.m_status.Raw()) + << ", prev beacon hash = " << beacon.m_previous_hash.GetHex() + << ", status = " << beacon.StatusToString() << std::endl; } } @@ -485,8 +606,8 @@ class BeaconRegistryTest << ", address = " << left.second.GetAddress().ToString() << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() - << ", prev beacon hash = " << left.second.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() + << ", status = " << left.second.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -509,11 +630,11 @@ class BeaconRegistryTest std::cout << "init_beacon hash = " << left_beacon.m_hash.GetHex() << ", reinit_beacon hash = " << right->second.m_hash.GetHex() << std::endl; - std::cout << "init_beacon prev beacon hash = " << left_beacon.m_prev_beacon_hash.GetHex() - << ", reinit_beacon prev beacon hash = " << right->second.m_prev_beacon_hash.GetHex() << std::endl; + std::cout << "init_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() + << ", reinit_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << "init_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << "init_beacon status = " << left_beacon.StatusToString() + << ", reinit_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -538,8 +659,8 @@ class BeaconRegistryTest << ", address = " << left.second.GetAddress().ToString() << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() - << ", prev beacon hash = " << left.second.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() + << ", status = " << left.second.StatusToString() << std::endl; } @@ -563,11 +684,11 @@ class BeaconRegistryTest std::cout << "reinit_beacon hash = " << left_beacon.m_hash.GetHex() << ", init_beacon hash = " << right->second.m_hash.GetHex() << std::endl; - std::cout << "reinit_beacon prev beacon hash = " << left_beacon.m_prev_beacon_hash.GetHex() - << ", init_beacon prev beacon hash = " << right->second.m_prev_beacon_hash.GetHex() << std::endl; + std::cout << "reinit_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() + << ", init_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << "reinit_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", init_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << "reinit_beacon status = " << left_beacon.StatusToString() + << ", init_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -602,8 +723,8 @@ class BeaconRegistryTest << ", address = " << left_beacon.GetAddress().ToString() << ", timestamp = " << left_beacon.m_timestamp << ", hash = " << left_beacon.m_hash.GetHex() - << ", prev beacon hash = " << left_beacon.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left_beacon.m_status.Raw()) + << ", prev beacon hash = " << left_beacon.m_previous_hash.GetHex() + << ", status = " << left_beacon.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -629,12 +750,12 @@ class BeaconRegistryTest std::cout << "init_pending_beacon hash = " << left_beacon.m_hash.GetHex() << ", reinit_pending_beacon hash = " << right->second.m_hash.GetHex() << std::endl; - std::cout << "init_pending_beacon prev beacon hash = " << left_beacon.m_prev_beacon_hash.GetHex() - << ", reinit_pending_beacon prev beacon hash = " << right->second.m_prev_beacon_hash.GetHex() + std::cout << "init_pending_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() + << ", reinit_pending_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << ", init_pending_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_pending_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << ", init_pending_beacon status = " << left_beacon.StatusToString() + << ", reinit_pending_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -657,8 +778,8 @@ class BeaconRegistryTest << ", address = " << left.second.GetAddress().ToString() << ", timestamp = " << left.second.m_timestamp << ", hash = " << left.second.m_hash.GetHex() - << ", prev beacon hash = " << left.second.m_prev_beacon_hash.GetHex() - << ", status = " << ToString(left.second.m_status.Raw()) + << ", prev beacon hash = " << left.second.m_previous_hash.GetHex() + << ", status = " << left.second.StatusToString() << std::endl; } else if (left_beacon != right->second) @@ -684,12 +805,12 @@ class BeaconRegistryTest std::cout << "init_pending_beacon hash = " << left_beacon.m_hash.GetHex() << ", reinit_pending_beacon hash = " << right->second.m_hash.GetHex() << std::endl; - std::cout << "init_pending_beacon prev beacon hash = " << left_beacon.m_prev_beacon_hash.GetHex() - << ", reinit_pending_beacon prev beacon hash = " << right->second.m_prev_beacon_hash.GetHex() + std::cout << "init_pending_beacon prev beacon hash = " << left_beacon.m_previous_hash.GetHex() + << ", reinit_pending_beacon prev beacon hash = " << right->second.m_previous_hash.GetHex() << std::endl; - std::cout << ", init_pending_beacon status = " << ToString(left_beacon.m_status.Raw()) - << ", reinit_pending_beacon status = " << ToString(right->second.m_status.Raw()) << std::endl; + std::cout << ", init_pending_beacon status = " << left_beacon.StatusToString() + << ", reinit_pending_beacon status = " << right->second.StatusToString() << std::endl; } } @@ -1128,4 +1249,415 @@ BOOST_AUTO_TEST_CASE(beaconstorage_mainnet_test) beacon_registry_test.BeaconDatabaseComparisonChecks_m_pending(); } +BOOST_AUTO_TEST_CASE(beacon_registry_GetBeaconChainletRoot_test) +{ + LogInstance().EnableCategory(BCLog::LogFlags::BEACON); + LogInstance().EnableCategory(BCLog::LogFlags::ACCRUAL); + + FastRandomContext rng(uint256 {0}); + + GRC::BeaconRegistry& registry = GRC::GetBeaconRegistry(); + + // Make sure the registry is reset. + registry.Reset(); + + // This is a trivial initial pending beacon, activation, and two renewals. The typical type of beacon chainlet. + + // Pending beacon + CTransaction tx1 {}; + tx1.nTime = int64_t {1}; + uint256 tx1_hash = tx1.GetHash(); + + CBlockIndex* pindex1 = new CBlockIndex; + pindex1->nVersion = 13; + pindex1->nHeight = 1; + pindex1->nTime = tx1.nTime; + + GRC::Beacon beacon1 {TestKey::Public(), tx1.nTime, tx1_hash}; + beacon1.m_cpid = TestKey::Cpid(); + beacon1.m_status = GRC::Beacon::Status {GRC::BeaconStatusForStorage::PENDING}; + GRC::BeaconPayload beacon_payload1 {2, TestKey::Cpid(), beacon1}; + beacon_payload1.m_signature = TestKey::Signature(beacon_payload1); + + GRC::Contract contract1 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload1); + GRC::ContractContext ctx1 {contract1, tx1, pindex1}; + + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_cpid == TestKey::Cpid()); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_hash == tx1_hash); + BOOST_CHECK(ctx1.m_tx.GetHash() == tx1_hash); + + registry.Add(ctx1); + + BOOST_CHECK(registry.GetBeaconDB().size() == 1); + + std::vector pending_beacons = registry.FindPending(TestKey::Cpid()); + + BOOST_CHECK(pending_beacons.size() == 1); + + if (pending_beacons.size() == 1) { + BOOST_CHECK(pending_beacons[0]->m_cpid == TestKey::Cpid()); + BOOST_CHECK(pending_beacons[0]->m_hash == tx1_hash); + BOOST_CHECK(pending_beacons[0]->m_previous_hash == uint256 {}); + BOOST_CHECK(pending_beacons[0]->m_public_key == TestKey::Public()); + BOOST_CHECK(pending_beacons[0]->m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(pending_beacons[0]->m_timestamp == tx1.nTime); + } + + // Activation + CBlockIndex* pindex2 = new CBlockIndex; + pindex2->nVersion = 13; + pindex2->nHeight = 2; + pindex2->nTime = int64_t {2}; + uint256* block2_phash = new uint256 {rng.rand256()}; + pindex2->phashBlock = block2_phash; + + std::vector beacon_ids {TestKey::Public().GetID()}; + + registry.ActivatePending(beacon_ids, pindex2->nTime, *pindex2->phashBlock, pindex2->nHeight); + + uint256 activated_beacon_hash = Hash(*block2_phash, pending_beacons[0]->m_hash); + + BOOST_CHECK(registry.GetBeaconDB().size() == 2); + + GRC::Beacon_ptr chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == activated_beacon_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::ACTIVE); + // Note that the activated beacon's timestamp is actually the same as the timestamp of the PENDING beacon. (Here + // t = 1; + BOOST_CHECK(chainlet_head->m_timestamp == 1); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + // There is only one entry in the chainlet.. so the head and root are the same. + BOOST_CHECK(chainlet_root->m_hash == chainlet_head->m_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 1); + } + + // Renewal + CTransaction tx3 {}; + tx3.nTime = int64_t {3}; + uint256 tx3_hash = tx3.GetHash(); + CBlockIndex index3 {}; + index3.nVersion = 13; + index3.nHeight = 3; + index3.nTime = tx3.nTime; + + GRC::Beacon beacon3 {TestKey::Public(), tx3.nTime, tx3_hash}; + beacon3.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload3 {2, TestKey::Cpid(), beacon3}; + beacon_payload3.m_signature = TestKey::Signature(beacon_payload3); + + GRC::Contract contract3 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload3); + GRC::ContractContext ctx3 {contract3, tx3, &index3}; + + registry.Add(ctx3); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == tx3_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 2); + } + + // Second renewal + CTransaction tx4 {}; + tx4.nTime = int64_t {4}; + uint256 tx4_hash = tx4.GetHash(); + CBlockIndex index4 = {}; + index4.nVersion = 13; + index4.nHeight = 2; + index4.nTime = tx4.nTime; + + GRC::Beacon beacon4 {TestKey::Public(), tx4.nTime, tx4_hash}; + beacon4.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload4 {2, TestKey::Cpid(), beacon4}; + beacon_payload4.m_signature = TestKey::Signature(beacon_payload4); + + GRC::Contract contract4 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload4); + GRC::ContractContext ctx4 {contract4, tx4, &index4}; + registry.Add(ctx4); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + BOOST_CHECK(chainlet_head->m_hash == tx4_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 3); + } + + // Let's corrupt the activation beacon to have a previous beacon hash that refers circularly back to the chain head... + bool original_activated_beacon_found = true; + bool circular_corruption_detected = false; + + if (GRC::Beacon_ptr first_active = registry.FindHistorical(activated_beacon_hash)) { + // The original activated beacon m_previous_hash should be the pending beacon hash (beacon1). + BOOST_CHECK(first_active->m_previous_hash == beacon1.m_hash); + BOOST_CHECK(first_active->m_status == GRC::BeaconStatusForStorage::ACTIVE); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + // This creates a circular chainlet. + first_active->m_previous_hash = chainlet_head->m_hash; + + beacon_chain_out_ptr->clear(); + + try { + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + circular_corruption_detected = true; + } + } else { + original_activated_beacon_found = false; + } + + BOOST_CHECK_EQUAL(original_activated_beacon_found, true); + + if (original_activated_beacon_found) { + BOOST_CHECK_EQUAL(circular_corruption_detected, true); + } +} + +BOOST_AUTO_TEST_CASE(beacon_registry_GetBeaconChainletRoot_test_2) +{ + // For right now we will just cut and paste from above, given that the circularity detection causes the registry + // to get reset. + + LogInstance().EnableCategory(BCLog::LogFlags::BEACON); + LogInstance().EnableCategory(BCLog::LogFlags::ACCRUAL); + + FastRandomContext rng(uint256 {0}); + + GRC::BeaconRegistry& registry = GRC::GetBeaconRegistry(); + + // Make sure the registry is reset. + registry.Reset(); + + // This is a trivial initial pending beacon, activation, and two renewals. The typical type of beacon chainlet. + + // Pending beacon + CTransaction tx1 {}; + tx1.nTime = int64_t {1}; + uint256 tx1_hash = tx1.GetHash(); + + CBlockIndex* pindex1 = new CBlockIndex; + pindex1->nVersion = 13; + pindex1->nHeight = 1; + pindex1->nTime = tx1.nTime; + + GRC::Beacon beacon1 {TestKey::Public(), tx1.nTime, tx1_hash}; + beacon1.m_cpid = TestKey::Cpid(); + beacon1.m_status = GRC::Beacon::Status {GRC::BeaconStatusForStorage::PENDING}; + GRC::BeaconPayload beacon_payload1 {2, TestKey::Cpid(), beacon1}; + beacon_payload1.m_signature = TestKey::Signature(beacon_payload1); + + GRC::Contract contract1 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload1); + GRC::ContractContext ctx1 {contract1, tx1, pindex1}; + + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_cpid == TestKey::Cpid()); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(ctx1.m_contract.CopyPayloadAs().m_beacon.m_hash == tx1_hash); + BOOST_CHECK(ctx1.m_tx.GetHash() == tx1_hash); + + registry.Add(ctx1); + + BOOST_CHECK(registry.GetBeaconDB().size() == 1); + + std::vector pending_beacons = registry.FindPending(TestKey::Cpid()); + + BOOST_CHECK(pending_beacons.size() == 1); + + if (pending_beacons.size() == 1) { + BOOST_CHECK(pending_beacons[0]->m_cpid == TestKey::Cpid()); + BOOST_CHECK(pending_beacons[0]->m_hash == tx1_hash); + BOOST_CHECK(pending_beacons[0]->m_previous_hash == uint256 {}); + BOOST_CHECK(pending_beacons[0]->m_public_key == TestKey::Public()); + BOOST_CHECK(pending_beacons[0]->m_status == GRC::BeaconStatusForStorage::PENDING); + BOOST_CHECK(pending_beacons[0]->m_timestamp == tx1.nTime); + } + + // Activation + CBlockIndex* pindex2 = new CBlockIndex; + pindex2->nVersion = 13; + pindex2->nHeight = 2; + pindex2->nTime = int64_t {2}; + uint256* block2_phash = new uint256 {rng.rand256()}; + pindex2->phashBlock = block2_phash; + + std::vector beacon_ids {TestKey::Public().GetID()}; + + registry.ActivatePending(beacon_ids, pindex2->nTime, *pindex2->phashBlock, pindex2->nHeight); + + uint256 activated_beacon_hash = Hash(*block2_phash, pending_beacons[0]->m_hash); + + BOOST_CHECK(registry.GetBeaconDB().size() == 2); + + GRC::Beacon_ptr chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == activated_beacon_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::ACTIVE); + // Note that the activated beacon's timestamp is actually the same as the timestamp of the PENDING beacon. (Here + // t = 1; + BOOST_CHECK(chainlet_head->m_timestamp == 1); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + // There is only one entry in the chainlet.. so the head and root are the same. + BOOST_CHECK(chainlet_root->m_hash == chainlet_head->m_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 1); + } + + // Renewal + CTransaction tx3 {}; + tx3.nTime = int64_t {3}; + uint256 tx3_hash = tx3.GetHash(); + CBlockIndex index3 {}; + index3.nVersion = 13; + index3.nHeight = 3; + index3.nTime = tx3.nTime; + + GRC::Beacon beacon3 {TestKey::Public(), tx3.nTime, tx3_hash}; + beacon3.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload3 {2, TestKey::Cpid(), beacon3}; + beacon_payload3.m_signature = TestKey::Signature(beacon_payload3); + + GRC::Contract contract3 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload3); + GRC::ContractContext ctx3 {contract3, tx3, &index3}; + + registry.Add(ctx3); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + BOOST_CHECK(chainlet_head->m_hash == tx3_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 2); + } + + // Second renewal + CTransaction tx4 {}; + tx4.nTime = int64_t {4}; + uint256 tx4_hash = tx4.GetHash(); + CBlockIndex index4 = {}; + index4.nVersion = 13; + index4.nHeight = 2; + index4.nTime = tx4.nTime; + + GRC::Beacon beacon4 {TestKey::Public(), tx4.nTime, tx4_hash}; + beacon4.m_cpid = TestKey::Cpid(); + GRC::BeaconPayload beacon_payload4 {2, TestKey::Cpid(), beacon4}; + beacon_payload4.m_signature = TestKey::Signature(beacon_payload4); + + GRC::Contract contract4 = GRC::MakeContract(3, GRC::ContractAction::ADD, beacon_payload4); + GRC::ContractContext ctx4 {contract4, tx4, &index4}; + registry.Add(ctx4); + + chainlet_head = registry.Try(TestKey::Cpid()); + + BOOST_CHECK(chainlet_head != nullptr); + + if (chainlet_head != nullptr) { + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + BOOST_CHECK(chainlet_head->m_hash == tx4_hash); + BOOST_CHECK(chainlet_head->m_status == GRC::BeaconStatusForStorage::RENEWAL); + + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + + BOOST_CHECK(chainlet_root->m_hash == activated_beacon_hash); + BOOST_CHECK_EQUAL(beacon_chain_out_ptr->size(), 3); + } + + // Let's corrupt the activation beacon to have a previous beacon hash that is the same as its hash... + bool original_activated_beacon_found = true; + bool circular_corruption_detected = false; + + if (GRC::Beacon_ptr first_active = registry.FindHistorical(activated_beacon_hash)) { + // The original activated beacon m_previous_hash should be the pending beacon hash (beacon1). + BOOST_CHECK(first_active->m_previous_hash == beacon1.m_hash); + BOOST_CHECK(first_active->m_status == GRC::BeaconStatusForStorage::ACTIVE); + + std::vector> beacon_chain_out {}; + + std::shared_ptr>> beacon_chain_out_ptr + = std::make_shared>>(beacon_chain_out); + + // This creates a immediately circular chainlet of one. + first_active->m_previous_hash = first_active->m_hash; + + beacon_chain_out_ptr->clear(); + + try { + GRC::Beacon_ptr chainlet_root = registry.GetBeaconChainletRoot(chainlet_head, beacon_chain_out_ptr); + } catch (std::runtime_error& e) { + circular_corruption_detected = true; + } + } else { + original_activated_beacon_found = false; + } + + BOOST_CHECK_EQUAL(original_activated_beacon_found, true); + + if (original_activated_beacon_found) { + BOOST_CHECK_EQUAL(circular_corruption_detected, true); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/claim_tests.cpp b/src/test/gridcoin/claim_tests.cpp index 0a9b5f1af4..33d1e204ad 100644 --- a/src/test/gridcoin/claim_tests.cpp +++ b/src/test/gridcoin/claim_tests.cpp @@ -97,6 +97,26 @@ static CKey GetTestPrivateKey() return key; } + +// Unfortunately, GCC 13 on openSUSE i386 is misbehaving and exhibiting weird errors in the last decimal places for things +// even as straightforward as +// +// double foo = 0.0; +// text >> foo. +// +// This comparison function works around that by allowing a small error band to pass the tests, but not enough to invalidate +// the tests on compilers that work. +bool comp_double(double lhs, double rhs) +{ + // Require exact match if 0=0. + if (std::min(lhs, rhs) == 0.0) { + return (lhs == rhs); + } else { + double unsigned_rel_error = std::abs(lhs - rhs) / std::min(lhs, rhs); + + return (unsigned_rel_error <= double {1e-8}); + } +} } // anonymous namespace // ----------------------------------------------------------------------------- @@ -118,7 +138,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_to_an_empty_claim) BOOST_CHECK(claim.m_magnitude == 0); BOOST_CHECK(claim.m_research_subsidy == 0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); @@ -141,7 +161,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_to_the_specified_version) BOOST_CHECK(claim.m_magnitude == 0); BOOST_CHECK(claim.m_research_subsidy == 0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); @@ -220,7 +240,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_legacy_boincblock_string_for_researcher) BOOST_CHECK(claim.m_magnitude == 123); BOOST_CHECK(claim.m_research_subsidy == 47.25 * COIN); - BOOST_CHECK(claim.m_magnitude_unit == 0.123456); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.123456)); BOOST_CHECK(claim.m_signature == signature); @@ -503,7 +523,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor) BOOST_CHECK(claim.m_research_subsidy == 0); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); BOOST_CHECK(claim.m_quorum_address.empty() == true); BOOST_CHECK(claim.m_superblock->WellFormed() == false); @@ -545,7 +565,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor_with_superblock) BOOST_CHECK(claim.m_research_subsidy == 0); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature.empty() == true); } @@ -630,7 +650,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher) BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature == expected.m_signature); BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); @@ -670,7 +690,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher_with_superbloc BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); BOOST_CHECK(claim.m_magnitude == 0.0); - BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(comp_double(claim.m_magnitude_unit, 0.0)); BOOST_CHECK(claim.m_signature == expected.m_signature); BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); diff --git a/src/test/gridcoin/contract_tests.cpp b/src/test/gridcoin/contract_tests.cpp index b5c02e2db5..2a0f300763 100644 --- a/src/test/gridcoin/contract_tests.cpp +++ b/src/test/gridcoin/contract_tests.cpp @@ -113,6 +113,22 @@ struct TestMessage GRC::ContractPayload::Make("test", "test", 123, 1)); } + //! + //! \brief Create a complete, signed contract object for the latest contract + //! version. + //! + //! \return Contains the default content used to create the v2 signature + //! above and includes that signature. + //! + static GRC::Contract V2() + { + return GRC::Contract( + 2, + GRC::ContractType::PROJECT, + GRC::ContractAction::ADD, + GRC::ContractPayload::Make("test", "test", 123, 1)); + } + //! //! \brief Create a complete, signed, legacy, version 1 contract object. //! @@ -173,6 +189,31 @@ struct TestMessage return serialized; } + //! + //! \brief Get a serialized representation of a valid version 3 contract + //! message. + //! + //! \return As bytes. Matches the contract message string above (without + //! tags) and includes version 2 components. + //! + //! TODO: Do a better v3 contract example + //! + static std::vector V3Serialized() + { + std::vector serialized { + 0x03, 0x00, 0x00, 0x00, // Version: 32-bit int (little-endian) + 0x05, // Type: PROJECT + 0x01, // Action: ADD + 0x01, 0x00, 0x00, 0x00, // Project contract version + 0x04, // Length of the project name + 0x74, 0x65, 0x73, 0x74, // "test" as bytes + 0x04, // Length of the project URL + 0x74, 0x65, 0x73, 0x74, // "test" as bytes + }; + + return serialized; + } + //! //! \brief Create a legacy, version 1 contract message string for tests. //! @@ -440,7 +481,7 @@ BOOST_AUTO_TEST_CASE(it_converts_a_legacy_payload_into_a_specific_contract_type) "https://example.com/@"); const GRC::ContractPayload payload = contract.m_body.ConvertFromLegacy( - GRC::ContractType::PROJECT); + GRC::ContractType::PROJECT, 1); BOOST_CHECK(payload->ContractType() == GRC::ContractType::PROJECT); BOOST_CHECK(payload->WellFormed(contract.m_action.Value()) == true); @@ -665,6 +706,25 @@ BOOST_AUTO_TEST_CASE(it_moves_a_cast_or_converted_payload) BOOST_CHECK(contract.WellFormed() == false); } +BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_v2) +{ + GRC::Contract contract = TestMessage::V2(); + + // 20 bytes = 4 bytes for the serialization protocol version + // + 1 byte each for the type and action + // + 14 bytes for the project payload + // + 1 byte for the empty public key size + // + BOOST_CHECK(GetSerializeSize(contract, SER_NETWORK, 1) == 20); + + CDataStream stream(SER_NETWORK, 1); + + stream << contract; + std::vector output((unsigned char*)&stream.begin()[0], (unsigned char*)&stream.end()[0]); + + BOOST_CHECK(output == TestMessage::V2Serialized()); +} + BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream) { GRC::Contract contract = TestMessage::Current(); @@ -681,10 +741,10 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream) stream << contract; std::vector output((unsigned char*)&stream.begin()[0], (unsigned char*)&stream.end()[0]); - BOOST_CHECK(output == TestMessage::V2Serialized()); + BOOST_CHECK(output == TestMessage::V3Serialized()); } -BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream) +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_v2) { GRC::Contract contract; @@ -694,6 +754,23 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream) GRC::ContractPayload payload = contract.SharePayload(); + BOOST_CHECK(contract.WellFormed() == true); + BOOST_CHECK(contract.m_version == 2); + BOOST_CHECK(contract.m_type == GRC::ContractType::PROJECT); + BOOST_CHECK(contract.m_action == GRC::ContractAction::ADD); + BOOST_CHECK(payload->LegacyKeyString() == "test"); +} + +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream) +{ + GRC::Contract contract; + + CDataStream stream(TestMessage::V3Serialized(), SER_NETWORK, 1); + + stream >> contract; + + GRC::ContractPayload payload = contract.SharePayload(); + BOOST_CHECK(contract.WellFormed() == true); BOOST_CHECK(contract.m_version == GRC::Contract::CURRENT_VERSION); BOOST_CHECK(contract.m_type == GRC::ContractType::PROJECT); diff --git a/src/test/gridcoin/mrc_tests.cpp b/src/test/gridcoin/mrc_tests.cpp index f1635ffa93..4a427fd3be 100644 --- a/src/test/gridcoin/mrc_tests.cpp +++ b/src/test/gridcoin/mrc_tests.cpp @@ -62,6 +62,9 @@ struct Setup { tx.nTime = pindexGenesisBlock->nTime; key.MakeNewKey(false); + + LOCK(wallet->cs_wallet); + wallet->AddKey(key); GRC::Contract contract = GRC::MakeContract( @@ -155,6 +158,8 @@ BOOST_AUTO_TEST_CASE(it_rejects_invalid_claims) { GRC::MRC mrc; + LOCK(cs_main); + mrc.m_mining_id = cpid; mrc.m_client_version = "6.0.0.0"; mrc.m_last_block_hash = pindex->GetBlockHash(); @@ -194,6 +199,9 @@ BOOST_AUTO_TEST_CASE(createmrc_creates_valid_mrcs) account.m_accrual = 72; GRC::MRC mrc; CAmount reward{0}, fee{0}; + + LOCK2(cs_main, wallet->cs_wallet); + GRC::CreateMRC(pindex->pprev, mrc, reward, fee, wallet); BOOST_CHECK_EQUAL(reward, 72); @@ -208,6 +216,9 @@ BOOST_AUTO_TEST_CASE(it_accepts_valid_fees) account.m_accrual = 72; GRC::MRC mrc; CAmount reward{0}, fee{0}; + + LOCK2(cs_main, wallet->cs_wallet); + GRC::CreateMRC(pindex->pprev, mrc, reward, fee, wallet); mrc.m_fee = 14; @@ -234,6 +245,9 @@ BOOST_AUTO_TEST_CASE(it_creates_valid_mrc_claims) std::map mrc_tx_map; account.m_accrual = 72; + + LOCK2(cs_main, wallet->cs_wallet); + pindex->pprev->AddMRCResearcherContext(cpid, 72, 0.0); BOOST_CHECK(CreateRestOfTheBlock(block, pindex->pprev, mrc_map)); @@ -245,11 +259,11 @@ BOOST_AUTO_TEST_CASE(it_creates_valid_mrc_claims) GRC::Claim claim; - LOCK(cs_main); + uint32_t claim_contract_version = 2; BOOST_CHECK(CreateGridcoinReward(block, pindex->pprev, reward, claim)); - BOOST_CHECK(CreateMRCRewards(block, mrc_map, mrc_tx_map, claim, wallet)); + BOOST_CHECK(CreateMRCRewards(block, mrc_map, mrc_tx_map, claim_contract_version, claim, wallet)); // TODO(div72): Separate this test into pieces and actually have it do // some useful testing by testing the validation logic against it. diff --git a/src/test/gridcoin/project_tests.cpp b/src/test/gridcoin/project_tests.cpp index 7cc82d4196..900ae1babe 100644 --- a/src/test/gridcoin/project_tests.cpp +++ b/src/test/gridcoin/project_tests.cpp @@ -9,23 +9,93 @@ #include namespace { -//! -//! \brief Generate a mock project contract. -//! -//! \param key A fake project name as it might appear in a contract. -//! \param value A fake project URL as it might appear in a contract. -//! -//! \return A mock project contract. -//! -GRC::Contract contract(std::string key, std::string value) +void AddProjectEntry(const uint32_t& payload_version, const std::string& name, const std::string& url, + const bool& gdpr_status, const int& height, const uint64_t time, const bool& reset_registry = false) { - return GRC::MakeContract( - // Add or delete checked before passing to handler, so we don't need - // to give a specific value here: - GRC::ContractAction::UNKNOWN, - std::move(key), - std::move(value), - 1234567); // timestamp + GRC::Whitelist& registry = GRC::GetWhitelist(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index = CBlockIndex {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + if (payload_version == 1) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + name, + url); + + } else if (payload_version == 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + payload_version, + name, + url, + gdpr_status); + } else if (payload_version == 3){ + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::ADD, + payload_version, + name, + url, + gdpr_status); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +void DeleteProjectEntry(const uint32_t& payload_version, const std::string& name, + const int& height, const uint64_t time, const bool& reset_registry = false) +{ + GRC::Whitelist& registry = GRC::GetWhitelist(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index = CBlockIndex {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + if (payload_version == 1) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::REMOVE, + name, + std::string{}); + } else if (payload_version == 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::REMOVE, + payload_version, + name, + std::string{}); + } else if (payload_version == 3){ + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::REMOVE, + payload_version, + name, + std::string{}); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); } //! @@ -54,6 +124,16 @@ BOOST_AUTO_TEST_CASE(it_initializes_to_a_new_project_contract) { const GRC::Project project("Enigma", "http://enigma.test/@"); + BOOST_CHECK_EQUAL(project.m_version, 1); + BOOST_CHECK_EQUAL(project.m_name, "Enigma"); + BOOST_CHECK_EQUAL(project.m_url, "http://enigma.test/@"); + BOOST_CHECK_EQUAL(project.m_timestamp, 0); +} + +BOOST_AUTO_TEST_CASE(it_initializes_to_a_new_project_contract_current_version) +{ + const GRC::Project project("Enigma", "http://enigma.test/@", 0); + BOOST_CHECK_EQUAL(project.m_version, GRC::Project::CURRENT_VERSION); BOOST_CHECK_EQUAL(project.m_name, "Enigma"); BOOST_CHECK_EQUAL(project.m_url, "http://enigma.test/@"); @@ -171,10 +251,10 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_add) expectedv1.begin(), expectedv1.end())); - const GRC::Project projectv2("Enigma", "http://enigma.test/@", 1234567, GRC::Project::CURRENT_VERSION, true); + const GRC::Project projectv2("Enigma", "http://enigma.test/@", 1234567, 2, true); const CDataStream expectedv2 = CDataStream(SER_NETWORK, PROTOCOL_VERSION) - << GRC::Project::CURRENT_VERSION + << uint32_t{2} << std::string("Enigma") << std::string("http://enigma.test/@") << true @@ -213,7 +293,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_add) "111111111111111111111111111111111111111111111111111111111111111111")); CDataStream streamv2 = CDataStream(SER_NETWORK, PROTOCOL_VERSION) - << GRC::Project::CURRENT_VERSION + << uint32_t{2} << std::string("Enigma") << std::string("http://enigma.test/@") << true @@ -222,7 +302,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_add) GRC::Project projectv2; projectv2.Unserialize(streamv2, GRC::ContractAction::ADD); - BOOST_CHECK_EQUAL(projectv2.m_version, GRC::Project::CURRENT_VERSION); + BOOST_CHECK_EQUAL(projectv2.m_version, uint32_t{2}); BOOST_CHECK_EQUAL(projectv2.m_name, "Enigma"); BOOST_CHECK_EQUAL(projectv2.m_url, "http://enigma.test/@"); BOOST_CHECK_EQUAL(projectv2.m_timestamp, 0); @@ -250,10 +330,10 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_delete) expectedv1.begin(), expectedv1.end())); - const GRC::Project projectv2("Enigma", "", 1234567, GRC::Project::CURRENT_VERSION, true); + const GRC::Project projectv2("Enigma", "", 1234567, uint32_t{2}, true); const CDataStream expectedv2 = CDataStream(SER_NETWORK, PROTOCOL_VERSION) - << GRC::Project::CURRENT_VERSION + << uint32_t{2} << std::string("Enigma"); CDataStream streamv2(SER_NETWORK, PROTOCOL_VERSION); @@ -286,13 +366,13 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_delete) BOOST_CHECK(projectv1.WellFormed(GRC::ContractAction::REMOVE) == true); CDataStream streamv2 = CDataStream(SER_NETWORK, PROTOCOL_VERSION) - << GRC::Project::CURRENT_VERSION - << std::string("Enigma"); + << uint32_t{2} + << std::string("Enigma"); GRC::Project projectv2; projectv2.Unserialize(streamv2, GRC::ContractAction::REMOVE); - BOOST_CHECK_EQUAL(projectv2.m_version, GRC::Project::CURRENT_VERSION); + BOOST_CHECK_EQUAL(projectv2.m_version, uint32_t {2}); BOOST_CHECK_EQUAL(projectv2.m_name, "Enigma"); BOOST_CHECK_EQUAL(projectv2.m_url, ""); BOOST_CHECK_EQUAL(projectv2.m_timestamp, 0); @@ -392,29 +472,41 @@ BOOST_AUTO_TEST_SUITE(Whitelist) BOOST_AUTO_TEST_CASE(it_adds_whitelisted_projects_from_contract_data) { - GRC::Whitelist whitelist; + GRC::Whitelist& whitelist = GRC::GetWhitelist(); + + whitelist.Reset(); + + int height = 0; + int64_t time = 0; BOOST_CHECK(whitelist.Snapshot().size() == 0); BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == false); - const GRC::Contract project = contract("Enigma", "http://enigma.test"); - whitelist.Add({ project, g_tx, nullptr }); + AddProjectEntry(1, "Enigma", "http://enigma.test", false, height, time, true); BOOST_CHECK(whitelist.Snapshot().size() == 1); BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == true); + + AddProjectEntry(2, "Foo", "http://foo.test", false, height++, time++, false); + + BOOST_CHECK(whitelist.Snapshot().size() == 2); + BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == true); + BOOST_CHECK(whitelist.Snapshot().Contains("Foo") == true); } BOOST_AUTO_TEST_CASE(it_removes_whitelisted_projects_from_contract_data) { - GRC::Whitelist whitelist; - const GRC::Contract project = contract("Enigma", "http://enigma.test"); + GRC::Whitelist& whitelist = GRC::GetWhitelist(); - whitelist.Add({ project, g_tx, nullptr }); + int height = 0; + int64_t time = 0; + + AddProjectEntry(1, "Enigma", "http://enigma.test", false, height, time, true); BOOST_CHECK(whitelist.Snapshot().size() == 1); BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == true); - whitelist.Delete({ project, g_tx, nullptr }); + DeleteProjectEntry(1, "Enigma", height++, time++, false); BOOST_CHECK(whitelist.Snapshot().size() == 0); BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == false); @@ -422,25 +514,33 @@ BOOST_AUTO_TEST_CASE(it_removes_whitelisted_projects_from_contract_data) BOOST_AUTO_TEST_CASE(it_does_not_mutate_existing_snapshots) { - GRC::Whitelist whitelist; - const GRC::Contract project = contract("Enigma", "http://enigma.test"); + GRC::Whitelist& whitelist = GRC::GetWhitelist(); + + int height = 0; + int64_t time = 0; - whitelist.Add({ project, g_tx, nullptr }); + AddProjectEntry(1, "Enigma", "http://enigma.test", false, height, time, true); + AddProjectEntry(2, "Foo", "http://foo.test", true, height++, time++, false); auto snapshot = whitelist.Snapshot(); - whitelist.Delete({ project, g_tx, nullptr }); + + DeleteProjectEntry(1, "Enigma", height, time, false); BOOST_CHECK(snapshot.Contains("Enigma") == true); + + BOOST_CHECK(whitelist.Snapshot().Contains("Enigma") == false); + BOOST_CHECK(whitelist.Snapshot().Contains("Foo") == true); } BOOST_AUTO_TEST_CASE(it_overwrites_projects_with_the_same_name) { - GRC::Whitelist whitelist; - const GRC::Contract project1 = contract("Enigma", "http://enigma.test"); - const GRC::Contract project2 = contract("Enigma", "http://new.enigma.test"); + GRC::Whitelist& whitelist = GRC::GetWhitelist(); + + int height = 0; + int64_t time = 0; - whitelist.Add({ project1, g_tx, nullptr }); - whitelist.Add({ project2, g_tx, nullptr }); + AddProjectEntry(1, "Enigma", "http://enigma.test", false, height, time, true); + AddProjectEntry(2, "Enigma", "http://new.enigma.test", true, height++, time++, false); auto snapshot = whitelist.Snapshot(); BOOST_CHECK(snapshot.size() == 1); diff --git a/src/test/gridcoin/protocol_tests.cpp b/src/test/gridcoin/protocol_tests.cpp new file mode 100644 index 0000000000..47f3a2ffaf --- /dev/null +++ b/src/test/gridcoin/protocol_tests.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2014-2021 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "gridcoin/protocol.h" + +#include + +// anonymous namespace +namespace { +void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, + const int& height, const uint64_t time, const bool& reset_registry = false) +{ + GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index = CBlockIndex {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + if (payload_version < 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + key, + value); + } else { + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::ADD, + payload_version, // Protocol payload version (post v13) + key, + value, + GRC::ProtocolEntryStatus::ACTIVE); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +void DeleteProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, + const int& height, const uint64_t time, const bool& reset_registry = false) +{ + GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index = CBlockIndex {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + if (payload_version < 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::REMOVE, + key, + value); + } else { + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::REMOVE, + payload_version, // Protocol payload version (post v13) + key, + value, + GRC::ProtocolEntryStatus::DELETED); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +} // anonymous namespace + +BOOST_AUTO_TEST_SUITE(protocol_tests) + +// Note for these tests we are going to mix payload version 1 and 2 entries to make +// sure they act equivalently. + +BOOST_AUTO_TEST_CASE(protocol_WrittenCacheShouldBeReadable) +{ + AddProtocolEntry(1, "key", "hello", 1, 123456789, true); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key").value == "hello"); + + AddProtocolEntry(2, "key", "no hello", 2, 123456790, false); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key").value == "no hello"); +} + +BOOST_AUTO_TEST_CASE(protocol_ClearCacheShouldClearEntireSection) +{ + AddProtocolEntry(1, "key1", "hello", 1, 123456789, true); + AddProtocolEntry(2, "key2", "no hello", 2, 123456790, false); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key1").value == "hello"); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value == "no hello"); + + GRC::GetProtocolRegistry().Reset(); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key1").value.empty() == true); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value.empty() == true); + +} + +BOOST_AUTO_TEST_CASE(protocol_DeletingEntriesShouldSuppressActiveKeyValues) +{ + AddProtocolEntry(1, "key1", "hello", 1, 123456789, true); + AddProtocolEntry(2, "key2", "no hello", 2, 123456790, false); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key1").value == "hello"); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value == "no hello"); + + DeleteProtocolEntry(1, "key1", "hello", 1, 123456791, false); + DeleteProtocolEntry(2, "key2", "no hello", 2, 123456792, false); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key1").value.empty() == true); + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value.empty() == true); +} + +BOOST_AUTO_TEST_CASE(protocol_DeletingEntryShouldSuppressReversionShouldRestore) +{ + AddProtocolEntry(1, "key1", "foo", 1, 123456789, true); + AddProtocolEntry(2, "key2", "fi", 2, 123456790, false); + + // Delete the protocol entry manually to retain the ctx. + + CTransaction dummy_tx; + CBlockIndex dummy_index {}; + dummy_index.nHeight = 123456791; + dummy_tx.nTime = 123456791; + dummy_index.nTime = 123456791; + + GRC::Contract contract; + + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::REMOVE, + uint32_t {2}, // Protocol payload version (post v13) + "key2", + "fi", // The value is actually irrelevant here. + GRC::ProtocolEntryStatus::DELETED); + + GRC::ContractContext ctx(contract, dummy_tx, &dummy_index); + + GRC::GetProtocolRegistry().Add(ctx); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value.empty() == true); + + GRC::ProtocolEntryOption active_entry = GRC::GetProtocolRegistry().TryActive("key2"); + + BOOST_CHECK(!active_entry); + + GRC::ProtocolEntryOption entry = GRC::GetProtocolRegistry().Try("key2"); + + BOOST_CHECK(entry && entry->m_status == GRC::ProtocolEntryStatus::DELETED); + + // Revert the deletion... the record should be resurrected to the state prior to deletion. + + int db_height = GRC::GetProtocolRegistry().GetDBHeight() - 1; + + GRC::GetProtocolRegistry().Revert(ctx); + GRC::GetProtocolRegistry().SetDBHeight(db_height); + + BOOST_CHECK(GRC::GetProtocolRegistry().GetProtocolEntryByKeyLegacy("key2").value == "fi"); + + GRC::ProtocolEntryOption resurrected_entry = GRC::GetProtocolRegistry().Try("key2"); + + BOOST_CHECK(resurrected_entry + && resurrected_entry->m_value == "fi" + && resurrected_entry->m_status == GRC::ProtocolEntryStatus::ACTIVE); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/researcher_tests.cpp b/src/test/gridcoin/researcher_tests.cpp index b632176d09..e163fb5862 100644 --- a/src/test/gridcoin/researcher_tests.cpp +++ b/src/test/gridcoin/researcher_tests.cpp @@ -2,8 +2,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. +#include "gridcoin/protocol.h" #include "main.h" -#include "gridcoin/appcache.h" #include "gridcoin/beacon.h" #include "gridcoin/contract/contract.h" #include "gridcoin/project.h" @@ -15,6 +15,8 @@ #include #include +extern leveldb::DB *txdb; + namespace { //! @@ -142,6 +144,65 @@ void RemoveTestBeacon(const GRC::Cpid cpid) GRC::GetBeaconRegistry().Deactivate(mock_superblock_hash); GRC::GetBeaconRegistry().Delete({ contract, tx, nullptr }); } + +void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, + const int& height, const bool& reset_registry = false) +{ + GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index = CBlockIndex {}; + dummy_index.nHeight = height; + + //use time = height for these tests + dummy_index.nTime = height; + dummy_tx.nTime = height; + + GRC::Contract contract; + + if (payload_version < 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + key, + value); + } else { + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::ADD, + payload_version, // Protocol payload version (post v13) + key, + value, + GRC::ProtocolEntryStatus::ACTIVE); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +// Unfortunately, GCC 13 on openSUSE i386 is misbehaving and exhibiting weird errors in the last decimal places for things +// even as straightforward as +// +// double foo = 0.0; +// text >> foo. +// +// This comparison function works around that by allowing a small error band to pass the tests, but not enough to invalidate +// the tests on compilers that work. +bool comp_double(double lhs, double rhs) +{ + // Require exact match if 0=0. + if (std::min(lhs, rhs) == 0.0) { + return (lhs == rhs); + } else { + double unsigned_rel_error = std::abs(lhs - rhs) / std::min(lhs, rhs); + + return (unsigned_rel_error <= double {1e-8}); + } +} } // anonymous namespace // ----------------------------------------------------------------------------- @@ -164,7 +225,7 @@ BOOST_AUTO_TEST_CASE(it_initializes_with_project_data) BOOST_CHECK(project.m_cpid == expected); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "url"); - BOOST_CHECK(project.m_rac == 0.0); + BOOST_CHECK(comp_double(project.m_rac, 0.0)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); } @@ -193,7 +254,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_project_xml_string) BOOST_CHECK(project.m_cpid == cpid); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "https://example.com/"); - BOOST_CHECK(project.m_rac == 123.45); + BOOST_CHECK(comp_double(project.m_rac, 123.45)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); // Clean up: @@ -228,7 +289,7 @@ BOOST_AUTO_TEST_CASE(it_falls_back_to_compute_a_missing_external_cpid) BOOST_CHECK(project.m_cpid == cpid); BOOST_CHECK(project.m_team == "team name"); BOOST_CHECK(project.m_url == "https://example.com/"); - BOOST_CHECK(project.m_rac == 123.45); + BOOST_CHECK(comp_double(project.m_rac, 123.45)); BOOST_CHECK(project.m_error == GRC::MiningProject::Error::NONE); // Clean up: @@ -314,8 +375,8 @@ BOOST_AUTO_TEST_CASE(it_determines_whether_a_project_is_whitelisted) 0.0); GRC::WhitelistSnapshot s(std::make_shared(GRC::ProjectList { - GRC::Project("Enigma", "http://enigma.test/@", 1234567), - GRC::Project("Einstein@home", "http://einsteinathome.org/@", 1234567), + GRC::Project("Enigma", "http://enigma.test/@"), + GRC::Project("Einstein@home", "http://einsteinathome.org/@"), })); BOOST_CHECK(project.Whitelisted(s) == false); @@ -447,7 +508,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_set_of_project_xml_sections) BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); BOOST_CHECK(project1->m_url == "https://example.com/1"); - BOOST_CHECK(project1->m_rac == 123.45); + BOOST_CHECK(comp_double(project1->m_rac, 123.45)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -459,7 +520,7 @@ BOOST_AUTO_TEST_CASE(it_parses_a_set_of_project_xml_sections) BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); BOOST_CHECK(project2->m_url == "https://example.com/2"); - BOOST_CHECK(project2->m_rac == 567.89); + BOOST_CHECK(comp_double(project2->m_rac, 567.89)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -770,6 +831,10 @@ BOOST_AUTO_TEST_CASE(it_provides_an_overall_status_of_the_researcher_context) BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) { + // Reset registry to go back to default values, including team requirement + // and default team whitelist requirement. + GRC::GetProtocolRegistry().Reset(); + // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); @@ -810,7 +875,7 @@ BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); BOOST_CHECK(project1->m_url == "https://example.com/1"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -822,7 +887,7 @@ BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); BOOST_CHECK(project2->m_url == "https://example.com/2"); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -836,6 +901,9 @@ BOOST_AUTO_TEST_CASE(it_parses_project_xml_to_a_global_researcher_singleton) BOOST_AUTO_TEST_CASE(it_looks_up_loaded_boinc_projects_by_name) { + // Simulate a protocol control directive that disables the team requirement: + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1, true); + // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); @@ -861,7 +929,7 @@ BOOST_AUTO_TEST_CASE(it_looks_up_loaded_boinc_projects_by_name) BOOST_CHECK(project->m_cpid == cpid); BOOST_CHECK(project->m_team == "gridcoin"); BOOST_CHECK(project->m_url == "https://example.com/"); - BOOST_CHECK(project->m_rac == 1.1); + BOOST_CHECK(comp_double(project->m_rac, 1.1)); BOOST_CHECK(project->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project->Eligible() == true); } else { @@ -883,6 +951,10 @@ BOOST_AUTO_TEST_CASE(it_resets_to_investor_mode_when_parsing_no_projects) BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) { + // Simulate a protocol control directive that enables the team requirement. Resetting + // the protocol registry defaults the team whitelist to just Gridcoin. + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 1, true); + // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); @@ -996,7 +1068,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "not gridcoin"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project1->Eligible() == false); } else { @@ -1007,7 +1079,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project2->m_name == "project name 2"); BOOST_CHECK(project2->m_cpid == cpid); BOOST_CHECK(project2->m_team.empty() == true); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project2->Eligible() == false); } else { @@ -1018,7 +1090,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project3->m_name == "project name 3"); BOOST_CHECK(project3->m_cpid == GRC::Cpid()); BOOST_CHECK(project3->m_team == "gridcoin"); - BOOST_CHECK(project3->m_rac == 3.3); + BOOST_CHECK(comp_double(project3->m_rac, 3.3)); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::MALFORMED_CPID); BOOST_CHECK(project3->Eligible() == false); } else { @@ -1029,7 +1101,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project4->m_name == "project name 4"); BOOST_CHECK(project4->m_cpid == GRC::Cpid()); BOOST_CHECK(project4->m_team == "gridcoin"); - BOOST_CHECK(project4->m_rac == 4.4); + BOOST_CHECK(comp_double(project4->m_rac, 4.4)); BOOST_CHECK(project4->m_error == GRC::MiningProject::Error::MALFORMED_CPID); BOOST_CHECK(project4->Eligible() == false); } else { @@ -1040,7 +1112,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project5->m_name == "project name 5"); BOOST_CHECK(project5->m_cpid == cpid); BOOST_CHECK(project5->m_team == "gridcoin"); - BOOST_CHECK(project5->m_rac == 5.5); + BOOST_CHECK(comp_double(project5->m_rac, 5.5)); BOOST_CHECK(project5->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project5->Eligible() == false); } else { @@ -1051,7 +1123,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project6->m_name == "project name 6"); BOOST_CHECK(project6->m_cpid == cpid); BOOST_CHECK(project6->m_team == "gridcoin"); - BOOST_CHECK(project6->m_rac == 6.6); + BOOST_CHECK(comp_double(project6->m_rac, 6.6)); BOOST_CHECK(project6->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project6->Eligible() == false); } else { @@ -1060,7 +1132,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) if (const GRC::ProjectOption project7 = projects.Try("project name 7")) { BOOST_CHECK(project7->m_name == "project name 7"); - BOOST_CHECK(project7->m_rac == 7.7); + BOOST_CHECK(comp_double(project7->m_rac, 7.7)); BOOST_CHECK(project7->m_error == GRC::MiningProject::Error::POOL); BOOST_CHECK(project7->Eligible() == false); } else { @@ -1070,7 +1142,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) if (const GRC::ProjectOption project8 = projects.Try("project name 8")) { BOOST_CHECK(project8->m_name == "project name 8"); BOOST_CHECK(project8->m_cpid.IsZero() == true); - BOOST_CHECK(project8->m_rac == 8.8); + BOOST_CHECK(comp_double(project8->m_rac, 8.8)); BOOST_CHECK(project8->m_error == GRC::MiningProject::Error::POOL); BOOST_CHECK(project8->Eligible() == false); } else { @@ -1081,7 +1153,7 @@ BOOST_AUTO_TEST_CASE(it_tags_invalid_projects_with_errors_when_parsing_xml) BOOST_CHECK(project9->m_name == "project name 9"); BOOST_CHECK(project9->m_cpid == cpid); BOOST_CHECK(project9->m_team == "not gridcoin"); - BOOST_CHECK(project9->m_rac == 0.0); + BOOST_CHECK(comp_double(project9->m_rac, 0.0)); BOOST_CHECK(project9->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project9->Eligible() == false); } else { @@ -1133,8 +1205,7 @@ BOOST_AUTO_TEST_CASE(it_skips_the_team_requirement_when_set_by_protocol) // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); - // Simulate a protocol control directive that disables the team requirement: - WriteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1); + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1, true); GRC::Researcher::Reload(GRC::MiningProjectMap::Parse({ R"XML( @@ -1161,7 +1232,7 @@ BOOST_AUTO_TEST_CASE(it_skips_the_team_requirement_when_set_by_protocol) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "! not gridcoin !"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); } else { @@ -1173,7 +1244,7 @@ BOOST_AUTO_TEST_CASE(it_skips_the_team_requirement_when_set_by_protocol) // Clean up: gArgs.ForceSetArg("email", ""); - DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); + //DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); GRC::Researcher::Reload(GRC::MiningProjectMap()); } @@ -1182,8 +1253,9 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); - // Simulate a protocol control directive with whitelisted teams: - WriteCache(Section::PROTOCOL, "TEAM_WHITELIST", "team 1|Team 2", 1); + // Resetting the protocol registry goes back to the default value of requiring the + // team whitelist. + AddProtocolEntry(2, "TEAM_WHITELIST", "team 1|Team 2", 1, true); GRC::Researcher::Reload(GRC::MiningProjectMap::Parse({ R"XML( @@ -1230,7 +1302,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project1->m_name == "project name 1"); BOOST_CHECK(project1->m_cpid == cpid); BOOST_CHECK(project1->m_team == "! not gridcoin !"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::INVALID_TEAM); BOOST_CHECK(project1->Eligible() == false); } else { @@ -1241,7 +1313,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project2->m_name == "project name 2"); BOOST_CHECK(project2->m_cpid == cpid); BOOST_CHECK(project2->m_team == "team 1"); - BOOST_CHECK(project2->m_rac == 0); + BOOST_CHECK(comp_double(project2->m_rac, 0)); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); } else { @@ -1252,7 +1324,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) BOOST_CHECK(project3->m_name == "project name 3"); BOOST_CHECK(project3->m_cpid == cpid); BOOST_CHECK(project3->m_team == "team 2"); - BOOST_CHECK(project3->m_rac == 0); + BOOST_CHECK(comp_double(project3->m_rac, 0)); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project3->Eligible() == true); } else { @@ -1264,12 +1336,15 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_when_set_by_the_protocol) // Clean up: gArgs.ForceSetArg("email", ""); - DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); + //DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); GRC::Researcher::Reload(GRC::MiningProjectMap()); } BOOST_AUTO_TEST_CASE(it_applies_the_team_requirement_dynamically) { + // Simulate a protocol control directive that enables the team requirement: + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 1, true); + // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); @@ -1296,7 +1371,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_requirement_dynamically) } // Simulate a protocol control directive that disables the team requirement: - WriteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1); + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 2, false); // Rescan in-memory projects for previously-ineligible teams: GRC::Researcher::MarkDirty(); @@ -1313,7 +1388,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_requirement_dynamically) } // Simulate a protocol control directive that enables the team requirement: - WriteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 1); + AddProtocolEntry(2, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 3, false); // Rescan in-memory projects for previously-eligible teams: GRC::Researcher::MarkDirty(); @@ -1331,12 +1406,16 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_requirement_dynamically) // Clean up: gArgs.ForceSetArg("email", ""); - DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); + //DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); GRC::Researcher::Reload(GRC::MiningProjectMap()); } BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_dynamically) { + // Simulate a protocol control directive that enables the team requirement. Resetting + // the protocol registry defaults the team whitelist to just Gridcoin. + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 1, true); + // External CPIDs generated with this email address: gArgs.ForceSetArg("email", "researcher@example.com"); @@ -1394,8 +1473,8 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_dynamically) BOOST_FAIL("Project 3 does not exist in the mining project map."); } - // Simulate a protocol control directive that enables the team whitelist: - WriteCache(Section::PROTOCOL, "TEAM_WHITELIST", "Team 1|Team 2", 1); + // Simulate a protocol control directive that changes the team whitelist from the default. + AddProtocolEntry(1, "TEAM_WHITELIST", "Team 1|Team 2", 2, false); // Rescan in-memory projects for previously-ineligible teams: GRC::Researcher::MarkDirty(); @@ -1426,7 +1505,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_dynamically) } // Simulate a protocol control directive that disables the team whitelist: - WriteCache(Section::PROTOCOL, "TEAM_WHITELIST", "", 1); + AddProtocolEntry(2, "TEAM_WHITELIST", "", 3, false); // Rescan in-memory projects for previously-eligible teams: GRC::Researcher::MarkDirty(); @@ -1458,7 +1537,7 @@ BOOST_AUTO_TEST_CASE(it_applies_the_team_whitelist_dynamically) // Clean up: gArgs.ForceSetArg("email", ""); - DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); + //DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); GRC::Researcher::Reload(GRC::MiningProjectMap()); } @@ -1468,7 +1547,7 @@ BOOST_AUTO_TEST_CASE(it_ignores_the_team_whitelist_without_the_team_requirement) gArgs.ForceSetArg("email", "researcher@example.com"); // Simulate a protocol control directive that disables the team requirement: - WriteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1); + AddProtocolEntry(1, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1, true); AddTestBeacon(GRC::Cpid::Parse("f5d8234352e5a5ae3915debba7258294")); @@ -1495,7 +1574,7 @@ BOOST_AUTO_TEST_CASE(it_ignores_the_team_whitelist_without_the_team_requirement) } // Simulate a protocol control directive that enables the team whitelist: - WriteCache(Section::PROTOCOL, "TEAM_WHITELIST", "Team 1|Team 2", 1); + AddProtocolEntry(2, "TEAM_WHITELIST", "Team 1|Team 2", 2, false); // Rescan in-memory projects for previously-eligible teams: GRC::Researcher::MarkDirty(); @@ -1513,7 +1592,7 @@ BOOST_AUTO_TEST_CASE(it_ignores_the_team_whitelist_without_the_team_requirement) // Simulate a protocol control directive that enables the team requirement // (and thus, the whitelist): - WriteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 1); + AddProtocolEntry(2, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 3, false); // Rescan in-memory projects for previously-eligible teams: GRC::Researcher::MarkDirty(); @@ -1531,8 +1610,8 @@ BOOST_AUTO_TEST_CASE(it_ignores_the_team_whitelist_without_the_team_requirement) // Clean up: gArgs.ForceSetArg("email", ""); - DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); - DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); + //DeleteCache(Section::PROTOCOL, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP"); + //DeleteCache(Section::PROTOCOL, "TEAM_WHITELIST"); RemoveTestBeacon(GRC::Cpid::Parse("f5d8234352e5a5ae3915debba7258294")); GRC::Researcher::Reload(GRC::MiningProjectMap()); } @@ -1570,7 +1649,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project1->m_name == "valid project 1"); BOOST_CHECK(project1->m_cpid == cpid_1); BOOST_CHECK(project1->m_team == "gridcoin"); - BOOST_CHECK(project1->m_rac == 1.1); + BOOST_CHECK(comp_double(project1->m_rac, 1.1)); BOOST_CHECK(project1->m_url == "https://project1.example.com/boinc/"); BOOST_CHECK(project1->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project1->Eligible() == true); @@ -1582,7 +1661,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project2->m_name == "valid project 2"); BOOST_CHECK(project2->m_cpid == cpid_2); BOOST_CHECK(project2->m_team == "gridcoin"); - BOOST_CHECK(project2->m_rac == 2.2); + BOOST_CHECK(comp_double(project2->m_rac, 2.2)); BOOST_CHECK(project2->m_url == "https://project2.example.com/boinc/"); BOOST_CHECK(project2->m_error == GRC::MiningProject::Error::NONE); BOOST_CHECK(project2->Eligible() == true); @@ -1595,7 +1674,7 @@ void it_parses_project_xml_from_a_client_state_xml_file() BOOST_CHECK(project3->m_name == "invalid project 3"); BOOST_CHECK(project3->m_cpid == cpid_2); BOOST_CHECK(project3->m_team == "gridcoin"); - BOOST_CHECK(project3->m_rac == 3.3); + BOOST_CHECK(comp_double(project3->m_rac, 3.3)); BOOST_CHECK(project3->m_url == "https://project3.example.com/boinc/"); BOOST_CHECK(project3->m_error == GRC::MiningProject::Error::MISMATCHED_CPID); BOOST_CHECK(project3->Eligible() == false); @@ -1675,6 +1754,10 @@ BOOST_AUTO_TEST_CASE(it_resets_to_investor_when_it_only_finds_pool_projects) BOOST_CHECK(GRC::Researcher::Get()->Eligible() == false); BOOST_CHECK(GRC::Researcher::Get()->Status() == GRC::ResearcherStatus::POOL); + // If whitelist membership rule is false, then the second project, which is + // a non-pool CPID should match the original cpid at the top. + AddProtocolEntry(2, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "false", 1, true); + GRC::Researcher::Reload(GRC::MiningProjectMap::Parse({ R"XML( @@ -1700,6 +1783,36 @@ BOOST_AUTO_TEST_CASE(it_resets_to_investor_when_it_only_finds_pool_projects) BOOST_CHECK(GRC::Researcher::Get()->Eligible() == true); BOOST_CHECK(GRC::Researcher::Get()->Status() != GRC::ResearcherStatus::POOL); + // If whitelist membership rule is true and the non-pool project does not match the whitelist, + // then researcher should be investor status. + AddProtocolEntry(2, "REQUIRE_TEAM_WHITELIST_MEMBERSHIP", "true", 2, false); + AddProtocolEntry(2, "TEAM_WHITELIST", "Team 1|Team 2", 3, false); + + GRC::Researcher::Reload(GRC::MiningProjectMap::Parse({ + R"XML( + + https://example.com/ + My Project + Gridcoin + XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + 7d0d73fe026d66fd4ab8d5d8da32a611 + + )XML", + R"XML( + + https://example.com/ + Pool Project + Gridcoin + XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + f5d8234352e5a5ae3915debba7258294 + + )XML", + })); + + BOOST_CHECK(GRC::Researcher::Get()->Id() == GRC::MiningId::ForInvestor()); + BOOST_CHECK(GRC::Researcher::Get()->Eligible() == false); + BOOST_CHECK(GRC::Researcher::Get()->Status() == GRC::ResearcherStatus::POOL); + // Clean up: gArgs.ForceSetArg("email", ""); RemoveTestBeacon(cpid); diff --git a/src/test/gridcoin/scraper_registry_tests.cpp b/src/test/gridcoin/scraper_registry_tests.cpp new file mode 100644 index 0000000000..1164ae068e --- /dev/null +++ b/src/test/gridcoin/scraper_registry_tests.cpp @@ -0,0 +1,551 @@ +// Copyright (c) 2014-2023 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "gridcoin/scraper/scraper_registry.h" + +#include + +// anonymous namespace +namespace { +void AddRemoveScraperEntryV1(const std::string& address, const std::string& value, + const GRC::ContractAction& action, const int& height, + const uint64_t& time, const bool& reset_registry = false) +{ + GRC::ScraperRegistry& registry = GRC::GetScraperRegistry(); + + std::string status_string = ToLower(value); + CBitcoinAddress scraper_address; + + // Assert if not a valid address. + assert(scraper_address.SetString(address)); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + assert(!(action == GRC::ContractAction::ADD && !(status_string == "false" || status_string == "true"))); + + if (action == GRC::ContractAction::REMOVE) { + status_string = "false"; + } + + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) - Scraper payload version will be 1. + action, + address, + status_string); + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +void AddRemoveScraperEntryV2(const std::string& address, const GRC::ScraperEntryStatus& status, + const GRC::ContractAction& action, const int& height, + const uint64_t& time, const bool& reset_registry = false) +{ + GRC::ScraperRegistry& registry = GRC::GetScraperRegistry(); + + CBitcoinAddress scraper_address; + CKeyID key_id; + + scraper_address.SetString(address); + + scraper_address.GetKeyID(key_id); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + CBlockIndex dummy_index {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + action, + uint32_t {2}, // Protocol payload version (post v13) + key_id, + status); + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &dummy_index}); +} + +}// anonymous namespace + +BOOST_AUTO_TEST_SUITE(scraper_registry_tests) + +BOOST_AUTO_TEST_CASE(scraper_entries_added_to_scraper_work_correctly_legacy) +{ + int height = 0; + uint64_t time = 0; + + auto& registry = GRC::GetScraperRegistry(); + const auto& scraper_map = registry.Scrapers(); + + std::vector scraper_entries { {"RxKVQ1SGpgyUfMv1zdygmiLi24mxG34k6f", + "S2wGoFavFzTnpaxn6XqLmJDE9FppHiMrCn", + "SA48uv72G9nWcdUG4cnd6EsuqKsyfW6EEN", + "SFxUqNdhdrkhzfjTqALSpCvJbz7JTrvhP3", + "SGhoGYzuHtDgy3NNp6cUx5jaMZJQ8osVaZ", + "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL", + "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"} }; + + registry.Reset(); + + for (const auto& entry : scraper_entries) { + AddRemoveScraperEntryV1(entry, + "true", + GRC::ContractAction::ADD, + height++, + time++, + false); + } + + BOOST_CHECK(scraper_map.size() == 7); + + // Native format from legacy adds. + for (const auto& entry : scraper_entries) { + CBitcoinAddress address; + address.SetString(entry); + + CKeyID key_id; + address.GetKeyID(key_id); + + auto registry_entry = scraper_map.find(key_id); + + BOOST_CHECK(registry_entry != scraper_map.end()); + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::AUTHORIZED); + } + + // Legacy format from legacy adds. + for (const auto& entry : scraper_entries) { + auto& legacy_scraper_entries = registry.GetScrapersLegacy(); + + auto legacy_appcache_entry = legacy_scraper_entries.find(entry); + + BOOST_CHECK(legacy_appcache_entry != legacy_scraper_entries.end()); + + BOOST_CHECK(legacy_appcache_entry->second.value == "true"); + } +} + +BOOST_AUTO_TEST_CASE(scraper_entry_deauthorize_and_delete_works_correctly_legacy) +{ + int height = 0; + uint64_t time = 0; + + auto& registry = GRC::GetScraperRegistry(); + const auto& scraper_map = registry.Scrapers(); + + std::vector scraper_entries { {"RxKVQ1SGpgyUfMv1zdygmiLi24mxG34k6f", + "S2wGoFavFzTnpaxn6XqLmJDE9FppHiMrCn", + "SA48uv72G9nWcdUG4cnd6EsuqKsyfW6EEN", + "SFxUqNdhdrkhzfjTqALSpCvJbz7JTrvhP3", + "SGhoGYzuHtDgy3NNp6cUx5jaMZJQ8osVaZ", + "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL", + "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"} }; + + registry.Reset(); + + for (const auto& entry : scraper_entries) { + AddRemoveScraperEntryV1(entry, + "true", + GRC::ContractAction::ADD, + height++, + time++, + false); + } + + BOOST_CHECK(scraper_map.size() == 7); + + // Deauthorize a scraper + AddRemoveScraperEntryV1("SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL", + "false", + GRC::ContractAction::ADD, + height++, + time++, + false); + + // Still should be 7 current elements. + BOOST_CHECK(scraper_map.size() == 7); + + // Native format from legacy adds. + for (const auto& entry : scraper_entries) { + CBitcoinAddress address; + address.SetString(entry); + + CKeyID key_id; + address.GetKeyID(key_id); + + auto registry_entry = scraper_map.find(key_id); + + BOOST_CHECK(registry_entry != scraper_map.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::NOT_AUTHORIZED); + } else { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::AUTHORIZED); + } + } + + // Legacy format from legacy adds. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries = registry.GetScrapersLegacy(); + + auto legacy_appcache_entry = legacy_scraper_entries.find(entry); + + BOOST_CHECK(legacy_appcache_entry != legacy_scraper_entries.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(legacy_appcache_entry->second.value == "false"); + } else { + BOOST_CHECK(legacy_appcache_entry->second.value == "true"); + } + } + + // Remove a scraper. Here we do it manually because we need the ctx to survive to do the + // reversion later. + height++; + time++; + + CTransaction dummy_tx; + CBlockIndex dummy_index {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) - Scraper payload version will be 1. + GRC::ContractAction::REMOVE, + "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV", + "false"); + + dummy_tx.vContracts.push_back(contract); + + GRC::ContractContext ctx(contract, dummy_tx, &dummy_index); + + registry.Add(ctx); + + // Native map should still be 7 elements + BOOST_CHECK(scraper_map.size() == 7); + + // Legacy extended scrapers map should still be 7 elements + BOOST_CHECK(registry.GetScrapersLegacyExt().size() == 7); + + // Legacy scrapers map should be 6 elements. + BOOST_CHECK(registry.GetScrapersLegacy().size() == 6); + + + // Native format from legacy adds, deauthorize, and delete for active scraper map.. + for (const auto& entry : scraper_entries) { + CBitcoinAddress address; + address.SetString(entry); + + CKeyID key_id; + address.GetKeyID(key_id); + + auto registry_entry = scraper_map.find(key_id); + + BOOST_CHECK(registry_entry != scraper_map.end()); + + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::DELETED); + } else if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::NOT_AUTHORIZED); + } else { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::AUTHORIZED); + } + } + + + // Legacy format from legacy adds, deauthorize, and delete for active scraper map. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries = registry.GetScrapersLegacy(); + + auto legacy_appcache_entry = legacy_scraper_entries.find(entry); + + // deleted entry should not be in active map + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_entry == legacy_scraper_entries.end()); + } else if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(legacy_appcache_entry->second.value == "false"); + } else { + BOOST_CHECK(legacy_appcache_entry->second.value == "true"); + } + } + + + // Legacy format from legacy adds, deauthorize, and delete for extended scraper map. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries_ext = registry.GetScrapersLegacyExt(); + + auto legacy_appcache_ext_entry = legacy_scraper_entries_ext.find(entry); + + + // All entries SHOULD be in extended map. + BOOST_CHECK(legacy_appcache_ext_entry != legacy_scraper_entries_ext.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL" || entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_ext_entry->second.value == "false"); + } + + // Deleted scraper should be marked deleted. + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_ext_entry->second.deleted == true); + } + } + + BOOST_CHECK(registry.GetDBHeight() == 9); + + // Revert the scraper removal using the ctx from the last add. This simulates at a low level what heppens + // when a reorg happens. + registry.Revert(ctx); + + int post_revert_height = 8; + registry.SetDBHeight(post_revert_height); + + // After reversion... + // Native map should be back to 7 elements. + BOOST_CHECK(scraper_map.size() == 7); + + // Legacy extended scrapers map should be 7 elements. + BOOST_CHECK(registry.GetScrapersLegacyExt().size() == 7); + + // Legacy scrapers map should be back to 7 elements. + BOOST_CHECK(registry.GetScrapersLegacy().size() == 7); + + // The specific scraper entry that was removed should now be resurrected after the reversion. + auto resurrected_scraper_entries = registry.GetScrapersLegacy(); + + auto resurrected_scraper = resurrected_scraper_entries.find("SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"); + + BOOST_CHECK(resurrected_scraper != registry.GetScrapersLegacy().end()); + + BOOST_CHECK(resurrected_scraper->first == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"); + BOOST_CHECK(resurrected_scraper->second.value == "true"); + BOOST_CHECK(resurrected_scraper->second.timestamp = 6); +} + + +BOOST_AUTO_TEST_CASE(scraper_entry_deauthorize_and_delete_works_correctly_native) +{ + int height = 0; + uint64_t time = 0; + + auto& registry = GRC::GetScraperRegistry(); + const auto& scraper_map = registry.Scrapers(); + + std::vector scraper_entries { {"RxKVQ1SGpgyUfMv1zdygmiLi24mxG34k6f", + "S2wGoFavFzTnpaxn6XqLmJDE9FppHiMrCn", + "SA48uv72G9nWcdUG4cnd6EsuqKsyfW6EEN", + "SFxUqNdhdrkhzfjTqALSpCvJbz7JTrvhP3", + "SGhoGYzuHtDgy3NNp6cUx5jaMZJQ8osVaZ", + "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL", + "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"} }; + + registry.Reset(); + + for (const auto& entry : scraper_entries) { + AddRemoveScraperEntryV2(entry, + GRC::ScraperEntryStatus::AUTHORIZED, + GRC::ContractAction::ADD, + height++, + time++, + false); + } + + BOOST_CHECK(scraper_map.size() == 7); + + // Deauthorize a scraper + AddRemoveScraperEntryV2("SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL", + GRC::ScraperEntryStatus::NOT_AUTHORIZED, + GRC::ContractAction::ADD, + height++, + time++, + false); + + // Still should be 7 current elements. + BOOST_CHECK(scraper_map.size() == 7); + + // Native format from native adds. + for (const auto& entry : scraper_entries) { + CBitcoinAddress address; + address.SetString(entry); + + CKeyID key_id; + address.GetKeyID(key_id); + + auto registry_entry = scraper_map.find(key_id); + + BOOST_CHECK(registry_entry != scraper_map.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::NOT_AUTHORIZED); + } else { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::AUTHORIZED); + } + } + + // Legacy format from native adds. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries = registry.GetScrapersLegacy(); + + auto legacy_appcache_entry = legacy_scraper_entries.find(entry); + + BOOST_CHECK(legacy_appcache_entry != legacy_scraper_entries.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(legacy_appcache_entry->second.value == "false"); + } else { + BOOST_CHECK(legacy_appcache_entry->second.value == "true"); + } + } + + // Remove a scraper. Here we do it manually because we need the ctx to survive to do the + // reversion later. + height++; + time++; + + CTransaction dummy_tx; + CBlockIndex dummy_index {}; + dummy_index.nHeight = height; + dummy_tx.nTime = time; + dummy_index.nTime = time; + + GRC::Contract contract; + + CBitcoinAddress address; + address.SetString("SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"); + + CKeyID key_id; + address.GetKeyID(key_id) +; + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (pre v13) + GRC::ContractAction::REMOVE, + uint32_t {2}, + key_id, + GRC::ScraperEntryStatus::NOT_AUTHORIZED); + + dummy_tx.vContracts.push_back(contract); + + GRC::ContractContext ctx(contract, dummy_tx, &dummy_index); + + registry.Add(ctx); + + // Native map should still be 7 elements + BOOST_CHECK(scraper_map.size() == 7); + + // Legacy extended scrapers map should still be 7 elements + BOOST_CHECK(registry.GetScrapersLegacyExt().size() == 7); + + // Legacy scrapers map should be 6 elements. + BOOST_CHECK(registry.GetScrapersLegacy().size() == 6); + + // Native format from legacy adds, deauthorize, and delete for active scraper map.. + for (const auto& entry : scraper_entries) { + CBitcoinAddress address; + address.SetString(entry); + + CKeyID key_id; + address.GetKeyID(key_id); + + auto registry_entry = scraper_map.find(key_id); + + BOOST_CHECK(registry_entry != scraper_map.end()); + + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::DELETED); + } else if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::NOT_AUTHORIZED); + } else { + BOOST_CHECK(registry_entry->second->m_status == GRC::ScraperEntryStatus::AUTHORIZED); + } + } + + + // Legacy format from legacy adds, deauthorize, and delete for active scraper map. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries = registry.GetScrapersLegacy(); + + auto legacy_appcache_entry = legacy_scraper_entries.find(entry); + + // deleted entry should not be in active map + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_entry == legacy_scraper_entries.end()); + } else if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL") { + BOOST_CHECK(legacy_appcache_entry->second.value == "false"); + } else { + BOOST_CHECK(legacy_appcache_entry->second.value == "true"); + } + } + + + // Legacy format from legacy adds, deauthorize, and delete for extended scraper map. + for (const auto& entry : scraper_entries) { + auto legacy_scraper_entries_ext = registry.GetScrapersLegacyExt(); + + auto legacy_appcache_ext_entry = legacy_scraper_entries_ext.find(entry); + + + // All entries SHOULD be in extended map. + BOOST_CHECK(legacy_appcache_ext_entry != legacy_scraper_entries_ext.end()); + + if (entry == "SL7PuciT2GVRjjP4MiMLKAxMhfPnBZrsUL" || entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_ext_entry->second.value == "false"); + } + + // Deleted scraper should be marked deleted. + if (entry == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV") { + BOOST_CHECK(legacy_appcache_ext_entry->second.deleted == true); + } + } + + BOOST_CHECK(registry.GetDBHeight() == 9); + + // Revert the scraper removal using the ctx from the last add. This simulates at a low level what heppens + // when a reorg happens. + registry.Revert(ctx); + + int post_revert_height = 8; + registry.SetDBHeight(post_revert_height); + + // After reversion... + // Native map should be back to 7 elements. + BOOST_CHECK(scraper_map.size() == 7); + + // Legacy extended scrapers map should be 7 elements. + BOOST_CHECK(registry.GetScrapersLegacyExt().size() == 7); + + // Legacy scrapers map should be back to 7 elements. + BOOST_CHECK(registry.GetScrapersLegacy().size() == 7); + + // The specific scraper entry that was removed should now be resurrected after the reversion. + auto resurrected_scraper_entries = registry.GetScrapersLegacy(); + + auto resurrected_scraper = resurrected_scraper_entries.find("SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"); + + BOOST_CHECK(resurrected_scraper != registry.GetScrapersLegacy().end()); + + BOOST_CHECK(resurrected_scraper->first == "SLbdvKZHmtu49VUWm88rbcCo9DaC8Z2urV"); + BOOST_CHECK(resurrected_scraper->second.value == "true"); + BOOST_CHECK(resurrected_scraper->second.timestamp = 6); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/sidestake_tests.cpp b/src/test/gridcoin/sidestake_tests.cpp new file mode 100644 index 0000000000..c0caa711f5 --- /dev/null +++ b/src/test/gridcoin/sidestake_tests.cpp @@ -0,0 +1,147 @@ +// Copyright (c) 2024 The Gridcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE(sidestake_tests) + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_trivial) +{ + GRC::Allocation allocation; + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 0); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 1); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), true); + BOOST_CHECK_EQUAL(allocation.IsPositive(), false); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double_below_minimum) +{ + GRC::Allocation allocation((double) 0.0000499999); + + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), true); + BOOST_CHECK_EQUAL(allocation.IsPositive(), false); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double_minimum) +{ + GRC::Allocation allocation((double) 0.0001); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 1); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 10000); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double) +{ + GRC::Allocation allocation((double) 0.0005); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 1); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 2000); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double_one_percent) +{ + GRC::Allocation allocation((double) 0.01); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 1); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 100); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double_just_below_unity) +{ + GRC::Allocation allocation((double) 0.9999); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 9999); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 10000); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_double_maximum_before_multiplication) +{ + GRC::Allocation allocation((double) 1.0); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 1); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 1); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 1); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_Initialization_from_fraction) +{ + GRC::Allocation allocation(Fraction(2500, 10000)); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 2500); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 10000); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), false); + BOOST_CHECK_EQUAL(allocation.IsZero(), false); + BOOST_CHECK_EQUAL(allocation.IsPositive(), true); + BOOST_CHECK_EQUAL(allocation.IsNonNegative(), true); + BOOST_CHECK_EQUAL(allocation.ToCAmount(), (CAmount) 0); + + allocation.Simplify(); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 1); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 4); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_ToPercent) +{ + GRC::Allocation allocation((double) 0.0005); + + BOOST_CHECK(std::abs(allocation.ToPercent() - (double) 0.05) < 1e-08); +} + +BOOST_AUTO_TEST_CASE(sidestake_Allocation_multiplication_and_derivation_of_allocation) +{ + // Multiplication is a very common operation with Allocations, because + // the general pattern is to multiply the allocation times a CAmount rewards + // to determine the rewards in Halfords (CAmount) to put on the output. + + // Allocations that are initialized from doubles are rounded to the nearest 1/10000. This is the worst case + // therefore, in terms of numerator and denominator. + GRC::Allocation allocation(0.9999); + + BOOST_CHECK_EQUAL(allocation.GetNumerator(), 9999); + BOOST_CHECK_EQUAL(allocation.GetDenominator(), 10000); + BOOST_CHECK_EQUAL(allocation.IsSimplified(), true); + + CAmount max_accrual = 16384 * COIN; + + CAmount actual_output = (allocation * max_accrual).ToCAmount(); + BOOST_CHECK_EQUAL(actual_output, int64_t {1638236160000}); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gridcoin/superblock_tests.cpp b/src/test/gridcoin/superblock_tests.cpp index 7f32329c35..61a8a7642c 100644 --- a/src/test/gridcoin/superblock_tests.cpp +++ b/src/test/gridcoin/superblock_tests.cpp @@ -4,6 +4,7 @@ #include "base58.h" #include "compat/endian.h" +#include #include "gridcoin/scraper/scraper_net.h" #include "gridcoin/superblock.h" #include "gridcoin/support/xml.h" @@ -13,7 +14,6 @@ #include #include #include -#include #include #include "test/data/superblock.txt.h" @@ -141,7 +141,7 @@ struct Legacy { const char* chIn = s1.c_str(); unsigned char digest2[16]; - MD5((unsigned char*)chIn, strlen(chIn), (unsigned char*)&digest2); + GRC__MD5((unsigned char*)chIn, strlen(chIn), (unsigned char*)&digest2); const std::vector digest_vector(digest2, digest2 + sizeof(digest2)); diff --git a/src/test/gridcoin_tests.cpp b/src/test/gridcoin_tests.cpp index 87f09cbf10..ff9ae50222 100755 --- a/src/test/gridcoin_tests.cpp +++ b/src/test/gridcoin_tests.cpp @@ -4,9 +4,9 @@ #include "chainparams.h" #include "uint256.h" +#include "gridcoin/protocol.h" #include "util.h" #include "main.h" -#include "gridcoin/appcache.h" #include "gridcoin/staking/reward.h" #include @@ -15,9 +15,9 @@ #include extern bool fTestNet; +extern leveldb::DB *txdb; -namespace -{ +namespace { // Arbitrary random characters generated with Python UUID. const std::string TEST_CPID("17c65330c0924259b2f93c31d25b03ac"); @@ -31,7 +31,42 @@ namespace { } }; -} + + void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, + const CBlockIndex index, const bool& reset_registry = false) + { + GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + dummy_tx.nTime = index.nTime; + + GRC::Contract contract; + + if (payload_version < 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + key, + value); + } else { + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::ADD, + payload_version, // Protocol payload version (post v13) + key, + value, + GRC::ProtocolEntryStatus::ACTIVE); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &index}); + } + +} // anonymous namespace BOOST_GLOBAL_FIXTURE(GridcoinTestsConfig); @@ -67,17 +102,8 @@ BOOST_AUTO_TEST_SUITE_END() // // CBR tests // -struct GridcoinCBRTestConfig -{ - GridcoinCBRTestConfig() - { - // Clear out previous CBR settings. - DeleteCache(Section::PROTOCOL, "blockreward1"); - } -}; BOOST_AUTO_TEST_SUITE(gridcoin_cbr_tests) -BOOST_GLOBAL_FIXTURE(GridcoinCBRTestConfig); BOOST_AUTO_TEST_CASE(gridcoin_DefaultCBRShouldBe10) { @@ -87,26 +113,93 @@ BOOST_AUTO_TEST_CASE(gridcoin_DefaultCBRShouldBe10) BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), DEFAULT_CBR); } +// Note that payload versions 1 and 2 alternate here. This is to test the constructors and +// the machinery to add to the registry. This alternating approach is not what would +// happen on the real blockchain, but is tolerated by the Registry independent of +// block acceptance rules. + BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldOverrideDefault) { const int64_t time = 123456; - const int64_t cbr = 14.9 * COIN; + int64_t cbr = 14.9 * COIN; - CBlockIndex index; - index.nVersion = 10; - index.nTime = time; + CBlockIndex index_1; + index_1.nVersion = 10; + index_1.nTime = time; + index_1.nHeight = 1; + + auto& registry = GRC::GetProtocolRegistry(); + + // Protocol payload version 1 + + AddProtocolEntry(1, "blockreward1", ToString(cbr), index_1, true); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_1), cbr); - WriteCache(Section::PROTOCOL, "blockreward1", ToString(cbr), time); - BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), cbr); + cbr = 16.0 * COIN; + + CBlockIndex index_2; + index_2.nVersion = 13; + index_2.nTime = time + 1; + index_2.nHeight = 2; + + // Protocol payload version 2 + + AddProtocolEntry(2, "blockreward1", ToString(cbr), index_2, false); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_2), cbr); } BOOST_AUTO_TEST_CASE(gridcoin_NegativeCBRShouldClampTo0) { const int64_t time = 123456; CBlockIndex index; - index.nTime = time; + index.nTime = time + 2; + index.nHeight = 3; + + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(1, "blockreward1", ToString(-1 * COIN), index, false); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } - WriteCache(Section::PROTOCOL, "blockreward1", ToString(-1 * COIN), time); BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), 0); } @@ -114,23 +207,60 @@ BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldClampTo2xDefault) { const int64_t time = 123456; CBlockIndex index; - index.nTime = time; + index.nTime = time + 3; + index.nHeight = 4; + + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(2, "blockreward1", ToString(DEFAULT_CBR * 3), index, false); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } - WriteCache(Section::PROTOCOL, "blockreward1", ToString(DEFAULT_CBR * 3), time); BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), DEFAULT_CBR * 2); } +// TODO: when the 180 day lookback is removed, this should be removed as a test as it will +// be irrelevant and invalid. BOOST_AUTO_TEST_CASE(gridcoin_ObsoleteConfigurableCBRShouldResortToDefault) { - CBlockIndex index; - index.nTime = 1538066417; - const int64_t max_message_age = 60 * 24 * 30 * 6 * 60; + CBlockIndex index_check; + index_check.nTime = 1538066417; + index_check.nHeight = 6; + const int64_t max_message_age = 60 * 60 * 24 * 180; - // Make the block reward message 1 second older than the max age - // relative to the block. - WriteCache(Section::PROTOCOL, "blockreward1", ToString(3 * COIN), index.nTime - max_message_age - 1); + CBlockIndex index_add; + index_add.nTime = index_check.nTime - max_message_age - 1; + index_add.nHeight = 5; - BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), DEFAULT_CBR); + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(2, "blockreward1", ToString(3 * COIN), index_add, false); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_check), DEFAULT_CBR); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 452a6b7202..289b063445 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -28,8 +28,8 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) FastRandomContext ctx2(true); for (int i = 10; i > 0; --i) { - BOOST_CHECK_EQUAL(GetRand(std::numeric_limits::max()), uint64_t{10393729187455219830U}); - BOOST_CHECK_EQUAL(GetRandInt(std::numeric_limits::max()), int{769702006}); + BOOST_CHECK_EQUAL(GetRand(), uint64_t{10393729187455219830U}); + BOOST_CHECK_EQUAL(GetRand(), int{769702006}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); } @@ -49,8 +49,8 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) // Check that a nondeterministic ones are not g_mock_deterministic_tests = false; for (int i = 10; i > 0; --i) { - BOOST_CHECK(GetRand(std::numeric_limits::max()) != uint64_t{10393729187455219830U}); - BOOST_CHECK(GetRandInt(std::numeric_limits::max()) != int{769702006}); + BOOST_CHECK(GetRand() != uint64_t{10393729187455219830U}); + BOOST_CHECK(GetRand() != int{769702006}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374}); } diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index 4d3ae87108..fb0054facb 100755 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -48,6 +48,8 @@ BOOST_AUTO_TEST_SUITE(script_P2SH_tests) BOOST_AUTO_TEST_CASE(sign) { + LOCK(cs_main); + // Pay-to-script-hash looks like this: // scriptSig: // scriptPubKey: HASH160 EQUAL @@ -142,6 +144,8 @@ BOOST_AUTO_TEST_CASE(norecurse) BOOST_AUTO_TEST_CASE(set) { + LOCK(cs_main); + // Test the CScript::Set* methods CBasicKeyStore keystore; CKey key[4]; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 2a35324da8..9711966abd 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -282,7 +282,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, 0)); keys.clear(); - keys.push_back(key2); keys.push_back(key2); // Can't re-use sig + keys.push_back(key2); keys.push_back(key2); // Can't reuse sig CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, 0)); diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index f21936cef2..e191133de6 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -330,4 +330,36 @@ BOOST_AUTO_TEST_CASE(class_methods) BOOST_CHECK(methodtest3 == methodtest4); } +BOOST_AUTO_TEST_CASE(variants) +{ + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + using p_t = std::pair; + std::variant v; + CTransaction txval; + const char charstrval[16] = "testing charstr"; + CSerializeMethodsTestSingle csmts(-3, false, "testing", charstrval, txval); + + v = 42; + ss << v; + v = "sel"; + ss << v; + v = 3.1415; + ss << v; + v = std::make_pair(14, 48); + ss << v; + v = csmts; + ss << v; + + ss >> v; + BOOST_CHECK_EQUAL(std::get(v), 42); + ss >> v; + BOOST_CHECK_EQUAL(std::get(v), "sel"); + ss >> v; + BOOST_CHECK_EQUAL(std::get(v), 3.1415); + ss >> v; + BOOST_CHECK(std::get(v) == std::make_pair(14, 48)); + ss >> v; + BOOST_CHECK(std::get(v) == csmts); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sync_tests.cpp b/src/test/sync_tests.cpp index 9549a835de..15e23f9611 100644 --- a/src/test/sync_tests.cpp +++ b/src/test/sync_tests.cpp @@ -14,8 +14,12 @@ BOOST_AUTO_TEST_SUITE(sync_tests) BOOST_AUTO_TEST_CASE(potential_deadlock_detected) { #ifdef DEBUG_LOCKORDER - bool prev = g_debug_lockorder_abort; + bool prev_lockorder_abort = g_debug_lockorder_abort; + bool prev_lockorder_throw_exception = g_debug_lockorder_throw_exception; + g_debug_lockorder_abort = false; + g_debug_lockorder_throw_exception = true; + #endif CCriticalSection mutex1, mutex2; @@ -38,7 +42,8 @@ BOOST_AUTO_TEST_CASE(potential_deadlock_detected) #endif #ifdef DEBUG_LOCKORDER - g_debug_lockorder_abort = prev; + g_debug_lockorder_abort = prev_lockorder_abort; + g_debug_lockorder_throw_exception = prev_lockorder_throw_exception; #endif } diff --git a/src/test/test_gridcoin.cpp b/src/test/test_gridcoin.cpp index 1b8c4bc269..cacb3e12ec 100644 --- a/src/test/test_gridcoin.cpp +++ b/src/test/test_gridcoin.cpp @@ -24,14 +24,15 @@ extern bool g_mock_deterministic_tests; FastRandomContext g_insecure_rand_ctx; +extern void SetupEnvironment(); extern void noui_connect(); extern leveldb::Options GetOptions(); extern void InitLogging(); struct TestingSetup { - ECCVerifyHandle globalVerifyHandle; - TestingSetup() { + SetupEnvironment(); + fs::path m_path_root = fs::temp_directory_path() / "test_common_" PACKAGE_NAME / InsecureRand256().ToString(); fUseFastIndex = true; // Don't verify block hashes when loading gArgs.ForceSetArg("-datadir", m_path_root.string()); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index c7da69d74a..8f8bd204f2 100755 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1,4 +1,5 @@ // Copyright (c) 2011-2020 The Bitcoin Core developers +// Copyright (c) 2024 The Gridcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. @@ -14,6 +15,58 @@ #include +namespace { +// This version, which is recommended by some resources on the web, is actually slower, and has several issues. See +// the unit tests below. +int msb(const int64_t& n) +{ + // Can't take the log of 0. + if (n == 0) { + return 0; + } + + // Log2 is O(1) both time and space-wise. + return (static_cast(floor(log2(std::abs(n)))) + 1); +} + +int msb2(const int64_t& n_in) +{ + int64_t n = std::abs(n_in); + + int index = 0; + + if (n == 0) { + return 0; + } + + for (int i = 0; i <= 63; ++i) { + if (n % 2 == 1) { + index = i; + } + + n /= 2; + } + + return index + 1; +} + +// This is the one currently used in the Fraction class +int msb3(const int64_t& n_in) +{ + int64_t n = std::abs(n_in); + + int index = 0; + + for (; index <= 63; ++index) { + if (n >> index == 0) { + break; + } + } + + return index; +} +} //anonymous namespace + BOOST_AUTO_TEST_SUITE(util_tests) BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -1024,4 +1077,662 @@ BOOST_AUTO_TEST_CASE(util_TrimString) BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); } +BOOST_AUTO_TEST_CASE(Fraction_msb_algorithm_equivalence) +{ + for (unsigned int i = 0; i <= 63; ++i) { + int64_t n = 0; + + if (i > 0) { + n = (int64_t {1} << (i - 1)); + } + + BOOST_CHECK_EQUAL(msb(n), i); + } + + int bias_for_msb_result_63 = 0; + + // msb ugly, ugly, ugly. Log2 looses resolution near the top of the range... + for (int i = 0; i < 16; ++i) { + bias_for_msb_result_63 = (int64_t {1} << i); + + int msb_result = msb(std::numeric_limits::max() - bias_for_msb_result_63); + + if (msb_result == 63) { + LogPrintf("INFO: %s: bias_for_msb_result_63 = %i, msb_result = %i", __func__, bias_for_msb_result_63, msb_result); + break; + } else { + } + } + + // bias_for_msb_result_63 is currently 32768! It should be zero! This disqualifies the log2 based approach based on + // a correctness check. + BOOST_CHECK_EQUAL(msb(std::numeric_limits::max() - bias_for_msb_result_63), 63); + + BOOST_CHECK_EQUAL(msb2(std::numeric_limits::max()), 63); + BOOST_CHECK_EQUAL(msb3(std::numeric_limits::max()), 63); + + std::vector> msb_results, msb2_results, msb3_results; + + unsigned int iterations = 1000; + + FastRandomContext rand(uint256 {0}); + + for (unsigned int i = 0; i < iterations; ++i) { + int64_t n = rand.rand32(); + + msb_results.push_back(std::make_pair(n, msb(n))); + } + + FastRandomContext rand2(uint256 {0}); + + for (unsigned int i = 0; i < iterations; ++i) { + int64_t n = rand2.rand32(); + + msb2_results.push_back(std::make_pair(n, msb2(n))); + } + + FastRandomContext rand3(uint256 {0}); + + for (unsigned int i = 0; i < iterations; ++i) { + int64_t n = rand3.rand32(); + + msb3_results.push_back(std::make_pair(n, msb3(n))); + } + + bool success = true; + + for (unsigned int i = 0; i < iterations; ++i) { + if (msb_results[i] != msb2_results[i] || msb_results[i] != msb3_results[i]) { + success = false; + error("%s: iteration %u: mismatch: %" PRId64 ", msb = %i, %" PRId64 " msb2 = %i, %" PRId64 " msb3 = %i", + __func__, + i, + msb_results[i].first, + msb_results[i].second, + msb2_results[i].first, + msb2_results[i].second, + msb3_results[i].first, + msb3_results[i].second + ); + } + } + + BOOST_CHECK(success); +} + +BOOST_AUTO_TEST_CASE(Fraction_msb_performance_test) +{ + // This is a test to bracket the three different algorithms above in anonymous namespace for doing msb calcs. The first is O(1), + // the second and third are O(log n), but the O(1) straight from the C++ library is pretty heavyweight and highly dependent on CPU + // architecture. + + FastRandomContext rand(uint256 {0}); + + unsigned int iterations = 10000000; + + g_timer.InitTimer("msb_test", true); + + for (unsigned int i = 0; i < iterations; ++i) { + msb(rand.rand64()); + } + + int64_t msb_test_time = g_timer.GetTimes(strprintf("msb %u iterations", iterations), "msb_test").time_since_last_check; + + FastRandomContext rand2(uint256 {0}); + + for (unsigned int i = 0; i < iterations; ++i) { + msb2(rand2.rand64()); + } + + int64_t msb2_test_time = g_timer.GetTimes(strprintf("msb2 %u iterations", iterations), "msb_test").time_since_last_check; + + FastRandomContext rand3(uint256 {0}); + + for (unsigned int i = 0; i < iterations; ++i) { + msb3(rand3.rand64()); + } + + int64_t msb3_test_time = g_timer.GetTimes(strprintf("msb3 %u iterations", iterations), "msb_test").time_since_last_check; + + // The execution time of the above on a 13900K is + + // INFO: GetTimes: timer msb_test: msb 10000000 iterations: elapsed time: 86 ms, time since last check: 86 ms. + // INFO: GetTimes: timer msb_test: msb2 10000000 iterations: elapsed time: 166 ms, time since last check: 80 ms. + // INFO: GetTimes: timer msb_test: msb3 10000000 iterations: elapsed time: 246 ms, time since last check: 80 ms. + + // Which is almost identical. msb appears to be much slower on 32 bit architectures. + + // One can easily have T1 = k1 * O(1) and T2 = k2 * O(n) = k2 * n * O(1) where T1 > T2 for n < q if k1 > q * k2, so the O(1) + // algorithm is by no means the best choice. + + // This test makes sure that the three algorithms are within 20x of the one with the minimum execution time. If not, it will + // fail to prompt us to look at this again. + + double minimum_time = std::min(std::min(msb_test_time, msb2_test_time), msb3_test_time); + + BOOST_CHECK((double) msb_test_time / minimum_time < 20.0); + BOOST_CHECK((double) msb2_test_time / minimum_time < 20.0); + BOOST_CHECK((double) msb3_test_time / minimum_time < 20.0); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_trivial) +{ + Fraction fraction; + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 0); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 1); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), true); + BOOST_CHECK_EQUAL(fraction.IsPositive(), false); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_already_simplified) +{ + Fraction fraction(2, 3); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_not_simplified) +{ + Fraction fraction(4, 6); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 4); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 6); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), false); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_with_simplification) +{ + Fraction fraction(4, 6, true); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_with_simplification_neg_pos) +{ + Fraction fraction(-4, 6, true); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), false); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_with_simplification_pos_neg) +{ + Fraction fraction(4, -6, true); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), false); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_num_denom_with_simplification_neg_neg) +{ + Fraction fraction(-4, -6, true); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Copy_Constructor) +{ + Fraction fraction(4, 6); + + Fraction fraction2(fraction); + + BOOST_CHECK_EQUAL(fraction2.GetNumerator(), 4); + BOOST_CHECK_EQUAL(fraction2.GetDenominator(), 6); + BOOST_CHECK_EQUAL(fraction2.IsSimplified(), false); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_int64_t) +{ + Fraction fraction((int64_t) -2); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 1); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), false); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_Simplify) +{ + Fraction fraction(-4, -6); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), false); + + fraction.Simplify(); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 2); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 3); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), true); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_ToDouble) +{ + Fraction fraction (1, 4); + + BOOST_CHECK_EQUAL(fraction.ToDouble(), 0.25); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition) +{ + Fraction lhs(2, 3); + Fraction rhs(3, 4); + + Fraction sum = lhs + rhs; + + BOOST_CHECK_EQUAL(sum.GetNumerator(), 17); + BOOST_CHECK_EQUAL(sum.GetDenominator(), 12); + BOOST_CHECK_EQUAL(sum.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_with_internal_simplification_common_denominator) +{ + Fraction lhs(3, 10); + Fraction rhs(2, 10); + + Fraction sum = lhs + rhs; + + BOOST_CHECK_EQUAL(sum.GetNumerator(), 1); + BOOST_CHECK_EQUAL(sum.GetDenominator(), 2); + BOOST_CHECK_EQUAL(sum.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_with_internal_simplification) +{ + Fraction lhs(3, 10); + Fraction rhs(1, 5); + + Fraction sum = lhs + rhs; + + BOOST_CHECK_EQUAL(sum.GetNumerator(), 1); + BOOST_CHECK_EQUAL(sum.GetDenominator(), 2); + BOOST_CHECK_EQUAL(sum.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_with_internal_gcd_simplification) +{ + Fraction lhs(1, 6); + Fraction rhs(2, 15); + + // gcd(6, 15) = 3, so this really is + // + // 1 * (15/3) + 2 * (6/3) 1 * 5 + 2 * 2 3 + // ---------------------- = ------------- = -- + // 3 * (6/3) * (15/3) 3 * 2 * 5 10 + + Fraction sum = lhs + rhs; + + BOOST_CHECK_EQUAL(sum.GetNumerator(), 3); + BOOST_CHECK_EQUAL(sum.GetDenominator(), 10); + BOOST_CHECK_EQUAL(sum.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_subtraction) +{ + Fraction lhs(2, 3); + Fraction rhs(3, 4); + + Fraction difference = lhs - rhs; + + BOOST_CHECK_EQUAL(difference.GetNumerator(), -1); + BOOST_CHECK_EQUAL(difference.GetDenominator(), 12); + BOOST_CHECK_EQUAL(difference.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_subtraction_with_internal_simplification) +{ + Fraction lhs(2, 10); + Fraction rhs(7, 10); + + Fraction difference = lhs - rhs; + + BOOST_CHECK_EQUAL(difference.GetNumerator(), -1); + BOOST_CHECK_EQUAL(difference.GetDenominator(), 2); + BOOST_CHECK_EQUAL(difference.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_with_internal_simplification) +{ + Fraction lhs(-2, 3); + Fraction rhs(3, 4); + + Fraction product = lhs * rhs; + + BOOST_CHECK_EQUAL(product.GetNumerator(), -1); + BOOST_CHECK_EQUAL(product.GetDenominator(), 2); + BOOST_CHECK_EQUAL(product.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_with_cross_simplification_overflow_resistance) +{ + + Fraction lhs(std::numeric_limits::max() - 3, std::numeric_limits::max() - 1, false); + Fraction rhs((std::numeric_limits::max() - 1) / (int64_t) 2, (std::numeric_limits::max() - 3) / (int64_t) 2); + + Fraction product; + + // This should NOT overflow + bool overflow = false; + try { + product = lhs * rhs; + } catch (std::overflow_error& e) { + overflow = true; + } + + BOOST_CHECK_EQUAL(overflow, false); + + if (!overflow) { + BOOST_CHECK(product == Fraction(1)); + } +} + +BOOST_AUTO_TEST_CASE(util_Fraction_division_with_internal_simplification) +{ + Fraction lhs(-2, 3); + Fraction rhs(4, 3); + + Fraction quotient = lhs / rhs; + + BOOST_CHECK_EQUAL(quotient.GetNumerator(), -1); + BOOST_CHECK_EQUAL(quotient.GetDenominator(), 2); + BOOST_CHECK_EQUAL(quotient.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_self_addition_with_internal_simplification) +{ + Fraction fraction(3, 10); + + fraction += Fraction(2, 10); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 1); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 2); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_self_subtraction_with_internal_simplification) +{ + Fraction fraction(7, 10); + + fraction -= Fraction(2, 10); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), 1); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 2); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_self_multiplication_with_internal_simplification) +{ + Fraction fraction(-2, 3); + + fraction *= Fraction(3, 4); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -1); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 2); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_self_division_with_internal_simplification) +{ + Fraction fraction(-2, 3); + + fraction /= Fraction(4, 3); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -1); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 2); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_by_zero_Fraction) +{ + Fraction lhs(-2, 3); + Fraction rhs(0); + + Fraction product = lhs * rhs; + + BOOST_CHECK_EQUAL(product.GetNumerator(), 0); + BOOST_CHECK_EQUAL(product.GetDenominator(), 1); + BOOST_CHECK_EQUAL(product.IsSimplified(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_division_by_zero_Fraction) +{ + Fraction lhs(-2, 3); + Fraction rhs(0); + + std::string err; + + try { + Fraction quotient = lhs / rhs; + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, "denominator specified is zero"); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_division_by_zero_int64_t) +{ + Fraction lhs(-2, 3); + int64_t rhs = 0; + + std::string err; + + try { + Fraction quotient = lhs / rhs; + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string{"denominator specified is zero"}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_overflow_1) +{ + Fraction lhs((int64_t) 1 << 30, 1); + Fraction rhs((int64_t) 1 << 31, 1); + + LogPrintf("INFO: %s: msb((int64_t) 1 << 30) = %i", __func__, msb3((int64_t) 1 << 30)); + LogPrintf("INFO: %s: msb((int64_t) 1 << 31) = %i", __func__, msb3((int64_t) 1 << 31)); + + std::string err; + + try { + Fraction product = lhs * rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_overflow_2) +{ + Fraction lhs(((int64_t) 1 << 31) - 1, 1); + Fraction rhs(((int64_t) 1 << 31) - 1, 1); + + std::string err; + + try { + Fraction product = lhs * rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {""}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_multiplication_overflow_3) +{ + Fraction lhs((int64_t) 1 << 31, 1); + Fraction rhs((int64_t) 1 << 31, 1); + + std::string err; + + try { + Fraction product = lhs * rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {"fraction multiplication results in an overflow"}); +} + + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_overflow_1) +{ + Fraction lhs(std::numeric_limits::max() / 2, 1); + Fraction rhs(std::numeric_limits::max() / 2 + 1, 1); + + std::string err; + + try { + Fraction addition = lhs + rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_overflow_2) +{ + Fraction lhs(std::numeric_limits::max() / 2 + 1, 1); + Fraction rhs(std::numeric_limits::max() / 2 + 1, 1); + + std::string err; + + try { + Fraction addition = lhs + rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {"fraction addition of a + b where a > 0 and b > 0 results in an overflow"}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_overflow_3) +{ + Fraction lhs(-(std::numeric_limits::max() / 2 + 1), 1); + Fraction rhs(-(std::numeric_limits::max() / 2 + 1), 1); + + std::string err; + + try { + Fraction addition = lhs + rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_addition_overflow_4) +{ + Fraction lhs(-(std::numeric_limits::max() / 2 + 1), 1); + Fraction rhs(-(std::numeric_limits::max() / 2 + 2), 1); + + std::string err; + + try { + Fraction addition = lhs + rhs; + } catch (std::overflow_error& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, std::string {"fraction addition of a + b where a < 0 and b < 0 results in an overflow"}); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_equal) +{ + BOOST_CHECK_EQUAL(Fraction(1, 2) == Fraction(2, 4), true); + BOOST_CHECK_EQUAL(Fraction(-1, 2) == Fraction(1, -2), true); + BOOST_CHECK_EQUAL(Fraction(-1, 2) == Fraction(1, 2), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_not_equal) +{ + BOOST_CHECK_EQUAL(Fraction(1, 2) != Fraction(2, 4), false); + BOOST_CHECK_EQUAL(Fraction(-1, 2) != Fraction(1, -2), false); + BOOST_CHECK_EQUAL(Fraction(-1, 2) != Fraction(1, 2), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_less_than_or_equal) +{ + BOOST_CHECK_EQUAL(Fraction(3, 4) <= Fraction(4, 5), true); + BOOST_CHECK_EQUAL(Fraction(3, 4) <= Fraction(6, 8), true); + BOOST_CHECK_EQUAL(Fraction(3, 4) <= Fraction(2, 3), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_greater_than_or_equal) +{ + BOOST_CHECK_EQUAL(Fraction(4, 5) >= Fraction(3, 4), true); + BOOST_CHECK_EQUAL(Fraction(6, 8) >= Fraction(3, 4), true); + BOOST_CHECK_EQUAL(Fraction(2, 3) >= Fraction(3, 4), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_less_than) +{ + BOOST_CHECK_EQUAL(Fraction(3, 4) < Fraction(4, 5), true); + BOOST_CHECK_EQUAL(Fraction(3, 4) < Fraction(6, 8), false); + BOOST_CHECK_EQUAL(Fraction(3, 4) < Fraction(2, 3), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_greater_than) +{ + BOOST_CHECK_EQUAL(Fraction(4, 5) > Fraction(3, 4), true); + BOOST_CHECK_EQUAL(Fraction(6, 8) > Fraction(3, 4), false); + BOOST_CHECK_EQUAL(Fraction(2, 3) > Fraction(3, 4), false); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_logic_negation) +{ + BOOST_CHECK_EQUAL(!Fraction(1, 2), false); + BOOST_CHECK_EQUAL(!Fraction(-1, 2), false); + BOOST_CHECK_EQUAL(!Fraction(), true); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_ToString) +{ + Fraction fraction(123, 10000); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.ToString(),"123/10000"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp index 9135984c56..c5e390e531 100755 --- a/src/test/wallet_tests.cpp +++ b/src/test/wallet_tests.cpp @@ -1,5 +1,6 @@ #include +#include "gridcoin/sidestake.h" #include "main.h" #include "wallet/wallet.h" @@ -66,29 +67,29 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) empty_wallet(); // with an empty wallet we can't even pay one cent - BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); - add_coin(1*CENT, 4); // add a new 1 cent coin + add_coin(CENT, 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent - BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); // but we can find a new 1 cent - BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); - add_coin(2*CENT); // add a mature 2 cent coin + add_coin(2 * CENT); // add a mature 2 cent coin // we can't make 3 cents of mature coins - BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(!wallet.SelectCoinsMinConf(3 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); // we can make 3 cents of new coins - BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(3 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); - add_coin(5*CENT); // add a mature 5 cent coin, - add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses - add_coin(20*CENT); // and a mature 20 cent coin + add_coin(5 * CENT); // add a mature 5 cent coin, + add_coin(10 * CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(20 * CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 @@ -97,109 +98,109 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // we can't even make 37 cents if we don't allow new coins even if they're from us BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, spendTime, 6, 6, vCoins, setCoinsRet, nValueRet)); // but we can make 37 cents if we accept new coins from ourself - BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(37 * CENT, spendTime, 1, 6, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); // and we can make 38 cents if we accept all new coins - BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(38 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents - BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + BOOST_CHECK(wallet.SelectCoinsMinConf(34 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents + // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) + BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 - BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(7 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. - BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(8 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(nValueRet == 8 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) - BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(9 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin empty_wallet(); - add_coin( 6*CENT); - add_coin( 7*CENT); - add_coin( 8*CENT); - add_coin(20*CENT); - add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total + add_coin(6 * CENT); + add_coin(7 * CENT); + add_coin(8 * CENT); + add_coin(20 * CENT); + add_coin(30 * CENT); // now we have 6+7+8+20+30 = 71 cents total // check that we have 71 and not 72 - BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(71 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); - add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + add_coin(5 * CENT); // now we have 5+6+7+8+20+30 = 75 cents total // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); - add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 + add_coin(18 * CENT); // now we have 5+6+7+8+18+20+30 // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 - BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(16 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // because in the event of a tie, the biggest coin wins // now try making 11 cents. we should get 5+6 - BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(11 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // check that the smallest bigger coin is used - add_coin( 1*COIN); - add_coin( 2*COIN); - add_coin( 3*COIN); - add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + add_coin(1 * COIN); + add_coin(2 * COIN); + add_coin(3 * COIN); + add_coin(4 * COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + BOOST_CHECK(wallet.SelectCoinsMinConf(95 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); - BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(195 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance empty_wallet(); - add_coin(0.1*CENT); - add_coin(0.2*CENT); - add_coin(0.3*CENT); - add_coin(0.4*CENT); - add_coin(0.5*CENT); + add_coin((GRC::Allocation(1, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(2, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(3, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(4, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(5, 10, true) * CENT).ToCAmount()); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents // we'll get sub-cent change whatever happens, so can expect 1.0 exactly - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // but if we add a bigger coin, making it possible to avoid sub-cent change, things change: - add_coin(1111*CENT); + add_coin(1111 * CENT); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount - + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount // if we add more sub-cent coins: - add_coin(0.6*CENT); - add_coin(0.7*CENT); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(7, 10, true) * CENT).ToCAmount()); // and try again to make 1.0 cents, we can still make 1.0 cents - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change @@ -207,7 +208,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) for (int i = 0; i < 20; i++) add_coin(50000 * COIN); - BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(500000 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 10); // in ten coins @@ -216,38 +217,43 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // sometimes it will fail, and so we use the next biggest coin: empty_wallet(); - add_coin(0.5 * CENT); - add_coin(0.6 * CENT); - add_coin(0.7 * CENT); + add_coin((GRC::Allocation(5, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(7, 10, true) * CENT).ToCAmount()); add_coin(1111 * CENT); - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 1); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) empty_wallet(); - add_coin(0.4 * CENT); - add_coin(0.6 * CENT); - add_coin(0.8 * CENT); + add_coin((GRC::Allocation(4, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(6, 10, true) * CENT).ToCAmount()); + add_coin((GRC::Allocation(8, 10, true) * CENT).ToCAmount()); add_coin(1111 * CENT); - BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount + BOOST_CHECK(wallet.SelectCoinsMinConf(CENT, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + BOOST_CHECK_EQUAL(nValueRet, CENT); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // in two coins 0.4+0.6 // test avoiding sub-cent change empty_wallet(); - add_coin(0.0005 * COIN); - add_coin(0.01 * COIN); - add_coin(1 * COIN); + // Use rational arithmetic because the floating point has problems with GCC13 on 32 bit architecture x86. + add_coin((GRC::Allocation(5, 10000, true) * COIN).ToCAmount()); + add_coin((GRC::Allocation(1, 100, true) * COIN).ToCAmount()); + add_coin(COIN); // trying to make 1.0001 from these three coins - BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins + BOOST_CHECK(wallet.SelectCoinsMinConf((GRC::Allocation(10001, 10000, true) * COIN).ToCAmount(), + spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + // we should get all coins + BOOST_CHECK(nValueRet == (GRC::Allocation(10105, 10000, true) * COIN).ToCAmount()); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 3); // but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change - BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); - BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01 + BOOST_CHECK(wallet.SelectCoinsMinConf((GRC::Allocation(999, 1000, true) * COIN).ToCAmount(), + spendTime, 1, 1, vCoins, setCoinsRet, nValueRet)); + // we should get 1 + 0.01 + BOOST_CHECK(nValueRet == (GRC::Allocation(101, 100, true) * COIN).ToCAmount()); BOOST_CHECK_EQUAL(setCoinsRet.size(), (size_t) 2); // test randomness @@ -277,15 +283,15 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // add 75 cents in small change. not enough to make 90 cents, // then try making 90 cents. there are multiple competing "smallest bigger" coins, // one of which should be picked at random - add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT); + add_coin(5 * CENT); add_coin(10 * CENT); add_coin(15 * CENT); add_coin(20 * CENT); add_coin(25 * CENT); fails = 0; for (int i = 0; i < RANDOM_REPEATS; i++) { // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time // run the test RANDOM_REPEATS times and only complain if all of them fail - BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, spendTime, 1, 6, vCoins, setCoinsRet , nValueRet)); - BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, spendTime, 1, 6, vCoins, setCoinsRet2, nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(90 * CENT, spendTime, 1, 6, vCoins, setCoinsRet , nValueRet)); + BOOST_CHECK(wallet.SelectCoinsMinConf(90 * CENT, spendTime, 1, 6, vCoins, setCoinsRet2, nValueRet)); if (equal_sets(setCoinsRet, setCoinsRet2)) fails++; } diff --git a/src/test/xxd/xxd.c b/src/test/xxd/xxd.c new file mode 100644 index 0000000000..ae25feed2b --- /dev/null +++ b/src/test/xxd/xxd.c @@ -0,0 +1,897 @@ +/* xxd: my hexdump facility. jw + * + * 2.10.90 changed to word output + * 3.03.93 new indent style, dumb bug inserted and fixed. + * -c option, mls + * 26.04.94 better option parser, -ps, -l, -s added. + * 1.07.94 -r badly needs - as input file. Per default autoskip over + * consecutive lines of zeroes, as unix od does. + * -a shows them too. + * -i dump as c-style #include "file.h" + * 1.11.95 if "xxd -i" knows the filename, an 'unsigned char filename_bits[]' + * array is written in correct c-syntax. + * -s improved, now defaults to absolute seek, relative requires a '+'. + * -r improved, now -r -s -0x... is supported. + * change/suppress leading '\0' bytes. + * -l n improved: stops exactly after n bytes. + * -r improved, better handling of partial lines with trailing garbage. + * -r improved, now -r -p works again! + * -r improved, less flushing, much faster now! (that was silly) + * 3.04.96 Per repeated request of a single person: autoskip defaults to off. + * 15.05.96 -v added. They want to know the version. + * -a fixed, to show last line inf file ends in all zeros. + * -u added: Print upper case hex-letters, as preferred by unix bc. + * -h added to usage message. Usage message extended. + * Now using outfile if specified even in normal mode, aehem. + * No longer mixing of ints and longs. May help doze people. + * Added binify ioctl for same reason. (Enough Doze stress for 1996!) + * 16.05.96 -p improved, removed occasional superfluous linefeed. + * 20.05.96 -l 0 fixed. tried to read anyway. + * 21.05.96 -i fixed. now honours -u, and prepends __ to numeric filenames. + * compile -DWIN32 for NT or W95. George V. Reilly, * -v improved :-) + * support --gnuish-longhorn-options + * 25.05.96 MAC support added: CodeWarrior already uses ``outline'' in Types.h + * which is included by MacHeaders (Axel Kielhorn). Renamed to + * xxdline(). + * 7.06.96 -i printed 'int' instead of 'char'. *blush* + * added Bram's OS2 ifdefs... + * 18.07.96 gcc -Wall @ SunOS4 is now silent. + * Added osver for MS-DOS/DJGPP/WIN32. + * 29.08.96 Added size_t to strncmp() for Amiga. + * 24.03.97 Windows NT support (Phil Hanna). Clean exit for Amiga WB (Bram) + * 02.04.97 Added -E option, to have EBCDIC translation instead of ASCII + * (azc10@yahoo.com) + * 22.05.97 added -g (group octets) option (jcook@namerica.kla.com). + * 23.09.98 nasty -p -r misfeature fixed: slightly wrong output, when -c was + * missing or wrong. + * 26.09.98 Fixed: 'xxd -i infile outfile' did not truncate outfile. + * 27.10.98 Fixed: -g option parser required blank. + * option -b added: 01000101 binary output in normal format. + * 16.05.00 Added VAXC changes by Stephen P. Wall + * 16.05.00 Improved MMS file and merge for VMS by Zoltan Arpadffy + * 2011 March Better error handling by Florian Zumbiehl. + * 2011 April Formatting by Bram Moolenaar + * 08.06.2013 Little-endian hexdump (-e) and offset (-o) by Vadim Vygonets. + * 11.01.2019 Add full 64/32 bit range to -o and output by Christer Jensen. + * 04.02.2020 Add -d for decimal offsets by Aapo Rantalainen + * 14.01.2022 Disable extra newlines with -c0 -p by Erik Auerswald. + * 20.06.2022 Permit setting the variable names used by -i by David Gow + * + * (c) 1990-1998 by Juergen Weigert (jnweiger@gmail.com) + * + * I hereby grant permission to distribute and use xxd + * under X11-MIT or GPL-2.0 (at the user's choice). + * + * Contributions by Bram Moolenaar et al. + */ + +/* Visual Studio 2005 has 'deprecated' many of the standard CRT functions */ +#if _MSC_VER >= 1400 +# define _CRT_SECURE_NO_DEPRECATE +# define _CRT_NONSTDC_NO_DEPRECATE +#endif +#if !defined(CYGWIN) && defined(__CYGWIN__) +# define CYGWIN +#endif + +#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__CYGWIN__) +# define _XOPEN_SOURCE 700 /* for fdopen() */ +#endif + +#include +#ifdef VAXC +# include +#else +# include +#endif +#if defined(WIN32) || defined(CYGWIN) +# include /* for setmode() */ +#else +# ifdef UNIX +# include +# endif +#endif +#include +#include /* for strncmp() */ +#include /* for isalnum() */ +#include +#if __MWERKS__ && !defined(BEBOX) +# include /* for fdopen() on MAC */ +#endif + + +/* This corrects the problem of missing prototypes for certain functions + * in some GNU installations (e.g. SunOS 4.1.x). + * Darren Hiebert (sparc-sun-sunos4.1.3_U1/2.7.2.2) + */ +#if defined(__GNUC__) && defined(__STDC__) +# ifndef __USE_FIXED_PROTOTYPES__ +# define __USE_FIXED_PROTOTYPES__ +# endif +#endif + +#ifndef __USE_FIXED_PROTOTYPES__ +/* + * This is historic and works only if the compiler really has no prototypes: + * + * Include prototypes for Sun OS 4.x, when using an ANSI compiler. + * FILE is defined on OS 4.x, not on 5.x (Solaris). + * if __SVR4 is defined (some Solaris versions), don't include this. + */ +#if defined(sun) && defined(FILE) && !defined(__SVR4) && defined(__STDC__) +# define __P(a) a +/* excerpt from my sun_stdlib.h */ +extern int fprintf __P((FILE *, char *, ...)); +extern int fputs __P((char *, FILE *)); +extern int _flsbuf __P((unsigned char, FILE *)); +extern int _filbuf __P((FILE *)); +extern int fflush __P((FILE *)); +extern int fclose __P((FILE *)); +extern int fseek __P((FILE *, long, int)); +extern int rewind __P((FILE *)); + +extern void perror __P((char *)); +# endif +#endif + +char version[] = "xxd 2022-01-14 by Juergen Weigert et al."; +#ifdef WIN32 +char osver[] = " (Win32)"; +#else +char osver[] = ""; +#endif + +#if defined(WIN32) +# define BIN_READ(yes) ((yes) ? "rb" : "rt") +# define BIN_WRITE(yes) ((yes) ? "wb" : "wt") +# define BIN_CREAT(yes) ((yes) ? (O_CREAT|O_BINARY) : O_CREAT) +# define BIN_ASSIGN(fp, yes) setmode(fileno(fp), (yes) ? O_BINARY : O_TEXT) +# define PATH_SEP '\\' +#elif defined(CYGWIN) +# define BIN_READ(yes) ((yes) ? "rb" : "rt") +# define BIN_WRITE(yes) ((yes) ? "wb" : "w") +# define BIN_CREAT(yes) ((yes) ? (O_CREAT|O_BINARY) : O_CREAT) +# define BIN_ASSIGN(fp, yes) ((yes) ? (void) setmode(fileno(fp), O_BINARY) : (void) (fp)) +# define PATH_SEP '/' +#else +# ifdef VMS +# define BIN_READ(dummy) "r" +# define BIN_WRITE(dummy) "w" +# define BIN_CREAT(dummy) O_CREAT +# define BIN_ASSIGN(fp, dummy) fp +# define PATH_SEP ']' +# define FILE_SEP '.' +# else +# define BIN_READ(dummy) "r" +# define BIN_WRITE(dummy) "w" +# define BIN_CREAT(dummy) O_CREAT +# define BIN_ASSIGN(fp, dummy) fp +# define PATH_SEP '/' +# endif +#endif + +/* open has only to arguments on the Mac */ +#if __MWERKS__ +# define OPEN(name, mode, umask) open(name, mode) +#else +# define OPEN(name, mode, umask) open(name, mode, umask) +#endif + +#ifdef AMIGA +# define STRNCMP(s1, s2, l) strncmp(s1, s2, (size_t)l) +#else +# define STRNCMP(s1, s2, l) strncmp(s1, s2, l) +#endif + +#ifndef __P +# if defined(__STDC__) || defined(WIN32) +# define __P(a) a +# else +# define __P(a) () +# endif +#endif + +#define TRY_SEEK /* attempt to use lseek, or skip forward by reading */ +#define COLS 256 /* change here, if you ever need more columns */ +#define LLEN ((2*(int)sizeof(unsigned long)) + 4 + (9*COLS-1) + COLS + 2) + +char hexxa[] = "0123456789abcdef0123456789ABCDEF", *hexx = hexxa; + +/* the different hextypes known by this program: */ +#define HEX_NORMAL 0 +#define HEX_POSTSCRIPT 1 +#define HEX_CINCLUDE 2 +#define HEX_BITS 3 /* not hex a dump, but bits: 01111001 */ +#define HEX_LITTLEENDIAN 4 + +#define CONDITIONAL_CAPITALIZE(c) (capitalize ? toupper((int)c) : c) + +static char *pname; + + static void +exit_with_usage(void) +{ + fprintf(stderr, "Usage:\n %s [options] [infile [outfile]]\n", pname); + fprintf(stderr, " or\n %s -r [-s [-]offset] [-c cols] [-ps] [infile [outfile]]\n", pname); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -a toggle autoskip: A single '*' replaces nul-lines. Default off.\n"); + fprintf(stderr, " -b binary digit dump (incompatible with -ps,-i,-r). Default hex.\n"); + fprintf(stderr, " -C capitalize variable names in C include file style (-i).\n"); + fprintf(stderr, " -c cols format octets per line. Default 16 (-i: 12, -ps: 30).\n"); + fprintf(stderr, " -E show characters in EBCDIC. Default ASCII.\n"); + fprintf(stderr, " -e little-endian dump (incompatible with -ps,-i,-r).\n"); + fprintf(stderr, " -g bytes number of octets per group in normal output. Default 2 (-e: 4).\n"); + fprintf(stderr, " -h print this summary.\n"); + fprintf(stderr, " -i output in C include file style.\n"); + fprintf(stderr, " -l len stop after octets.\n"); + fprintf(stderr, " -n name set the variable name used in C include output (-i).\n"); + fprintf(stderr, " -o off add to the displayed file position.\n"); + fprintf(stderr, " -ps output in postscript plain hexdump style.\n"); + fprintf(stderr, " -r reverse operation: convert (or patch) hexdump into binary.\n"); + fprintf(stderr, " -r -s off revert with added to file positions found in hexdump.\n"); + fprintf(stderr, " -d show offset in decimal instead of hex.\n"); + fprintf(stderr, " -s %sseek start at bytes abs. %sinfile offset.\n", +#ifdef TRY_SEEK + "[+][-]", "(or +: rel.) "); +#else + "", ""); +#endif + fprintf(stderr, " -u use upper case hex letters.\n"); + fprintf(stderr, " -v show version: \"%s%s\".\n", version, osver); + exit(1); +} + + static void +perror_exit(int ret) +{ + fprintf(stderr, "%s: ", pname); + perror(NULL); + exit(ret); +} + + static void +error_exit(int ret, char *msg) +{ + fprintf(stderr, "%s: %s\n", pname, msg); + exit(ret); +} + + static int +getc_or_die(FILE *fpi) +{ + int c = getc(fpi); + if (c == EOF && ferror(fpi)) + perror_exit(2); + return c; +} + + static void +putc_or_die(int c, FILE *fpo) +{ + if (putc(c, fpo) == EOF) + perror_exit(3); +} + + static void +fputs_or_die(char *s, FILE *fpo) +{ + if (fputs(s, fpo) == EOF) + perror_exit(3); +} + +/* Use a macro to allow for different arguments. */ +#define FPRINTF_OR_DIE(args) if (fprintf args < 0) perror_exit(3) + + static void +fclose_or_die(FILE *fpi, FILE *fpo) +{ + if (fclose(fpo) != 0) + perror_exit(3); + if (fclose(fpi) != 0) + perror_exit(2); +} + +/* + * If "c" is a hex digit, return the value. + * Otherwise return -1. + */ + static int +parse_hex_digit(int c) +{ + return (c >= '0' && c <= '9') ? c - '0' + : (c >= 'a' && c <= 'f') ? c - 'a' + 10 + : (c >= 'A' && c <= 'F') ? c - 'A' + 10 + : -1; +} + +/* + * Ignore text on "fpi" until end-of-line or end-of-file. + * Return the '\n' or EOF character. + * When an error is encountered exit with an error message. + */ + static int +skip_to_eol(FILE *fpi, int c) +{ + while (c != '\n' && c != EOF) + c = getc_or_die(fpi); + return c; +} + +/* + * Max. cols binary characters are decoded from the input stream per line. + * Two adjacent garbage characters after evaluated data delimit valid data. + * Everything up to the next newline is discarded. + * + * The name is historic and came from 'undo type opt h'. + */ + static int +huntype( + FILE *fpi, + FILE *fpo, + int cols, + int hextype, + long base_off) +{ + int c, ign_garb = 1, n1 = -1, n2 = 0, n3, p = cols; + long have_off = 0, want_off = 0; + + rewind(fpi); + + while ((c = getc(fpi)) != EOF) + { + if (c == '\r') /* Doze style input file? */ + continue; + + /* Allow multiple spaces. This doesn't work when there is normal text + * after the hex codes in the last line that looks like hex, thus only + * use it for PostScript format. */ + if (hextype == HEX_POSTSCRIPT && (c == ' ' || c == '\n' || c == '\t')) + continue; + + n3 = n2; + n2 = n1; + + n1 = parse_hex_digit(c); + if (n1 == -1 && ign_garb) + continue; + + ign_garb = 0; + + if (!hextype && (p >= cols)) + { + if (n1 < 0) + { + p = 0; + continue; + } + want_off = (want_off << 4) | n1; + continue; + } + + if (base_off + want_off != have_off) + { + if (fflush(fpo) != 0) + perror_exit(3); +#ifdef TRY_SEEK + if (fseek(fpo, base_off + want_off - have_off, SEEK_CUR) >= 0) + have_off = base_off + want_off; +#endif + if (base_off + want_off < have_off) + error_exit(5, "Sorry, cannot seek backwards."); + for (; have_off < base_off + want_off; have_off++) + putc_or_die(0, fpo); + } + + if (n2 >= 0 && n1 >= 0) + { + putc_or_die((n2 << 4) | n1, fpo); + have_off++; + want_off++; + n1 = -1; + if (!hextype && (++p >= cols)) + /* skip the rest of the line as garbage */ + c = skip_to_eol(fpi, c); + } + else if (n1 < 0 && n2 < 0 && n3 < 0) + /* already stumbled into garbage, skip line, wait and see */ + c = skip_to_eol(fpi, c); + + if (c == '\n') + { + if (!hextype) + want_off = 0; + p = cols; + ign_garb = 1; + } + } + if (fflush(fpo) != 0) + perror_exit(3); +#ifdef TRY_SEEK + fseek(fpo, 0L, SEEK_END); +#endif + fclose_or_die(fpi, fpo); + return 0; +} + +/* + * Print line l. If nz is false, xxdline regards the line a line of + * zeroes. If there are three or more consecutive lines of zeroes, + * they are replaced by a single '*' character. + * + * If the output ends with more than two lines of zeroes, you + * should call xxdline again with l being the last line and nz + * negative. This ensures that the last line is shown even when + * it is all zeroes. + * + * If nz is always positive, lines are never suppressed. + */ + static void +xxdline(FILE *fp, char *l, int nz) +{ + static char z[LLEN+1]; + static int zero_seen = 0; + + if (!nz && zero_seen == 1) + strcpy(z, l); + + if (nz || !zero_seen++) + { + if (nz) + { + if (nz < 0) + zero_seen--; + if (zero_seen == 2) + fputs_or_die(z, fp); + if (zero_seen > 2) + fputs_or_die("*\n", fp); + } + if (nz >= 0 || zero_seen > 0) + fputs_or_die(l, fp); + if (nz) + zero_seen = 0; + } +} + +/* This is an EBCDIC to ASCII conversion table */ +/* from a proposed BTL standard April 16, 1979 */ +static unsigned char etoa64[] = +{ + 0040,0240,0241,0242,0243,0244,0245,0246, + 0247,0250,0325,0056,0074,0050,0053,0174, + 0046,0251,0252,0253,0254,0255,0256,0257, + 0260,0261,0041,0044,0052,0051,0073,0176, + 0055,0057,0262,0263,0264,0265,0266,0267, + 0270,0271,0313,0054,0045,0137,0076,0077, + 0272,0273,0274,0275,0276,0277,0300,0301, + 0302,0140,0072,0043,0100,0047,0075,0042, + 0303,0141,0142,0143,0144,0145,0146,0147, + 0150,0151,0304,0305,0306,0307,0310,0311, + 0312,0152,0153,0154,0155,0156,0157,0160, + 0161,0162,0136,0314,0315,0316,0317,0320, + 0321,0345,0163,0164,0165,0166,0167,0170, + 0171,0172,0322,0323,0324,0133,0326,0327, + 0330,0331,0332,0333,0334,0335,0336,0337, + 0340,0341,0342,0343,0344,0135,0346,0347, + 0173,0101,0102,0103,0104,0105,0106,0107, + 0110,0111,0350,0351,0352,0353,0354,0355, + 0175,0112,0113,0114,0115,0116,0117,0120, + 0121,0122,0356,0357,0360,0361,0362,0363, + 0134,0237,0123,0124,0125,0126,0127,0130, + 0131,0132,0364,0365,0366,0367,0370,0371, + 0060,0061,0062,0063,0064,0065,0066,0067, + 0070,0071,0372,0373,0374,0375,0376,0377 +}; + + int +main(int argc, char *argv[]) +{ + FILE *fp, *fpo; + int c, e, p = 0, relseek = 1, negseek = 0, revert = 0; + int cols = 0, colsgiven = 0, nonzero = 0, autoskip = 0, hextype = HEX_NORMAL; + int capitalize = 0, decimal_offset = 0; + int ebcdic = 0; + int octspergrp = -1; /* number of octets grouped in output */ + int grplen; /* total chars per octet group */ + long length = -1, n = 0, seekoff = 0; + unsigned long displayoff = 0; + static char l[LLEN+1]; /* static because it may be too big for stack */ + char *pp; + char *varname = NULL; + int addrlen = 9; + +#ifdef AMIGA + /* This program doesn't work when started from the Workbench */ + if (argc == 0) + exit(1); +#endif + + pname = argv[0]; + for (pp = pname; *pp; ) + if (*pp++ == PATH_SEP) + pname = pp; +#ifdef FILE_SEP + for (pp = pname; *pp; pp++) + if (*pp == FILE_SEP) + { + *pp = '\0'; + break; + } +#endif + + while (argc >= 2) + { + pp = argv[1] + (!STRNCMP(argv[1], "--", 2) && argv[1][2]); + if (!STRNCMP(pp, "-a", 2)) autoskip = 1 - autoskip; + else if (!STRNCMP(pp, "-b", 2)) hextype = HEX_BITS; + else if (!STRNCMP(pp, "-e", 2)) hextype = HEX_LITTLEENDIAN; + else if (!STRNCMP(pp, "-u", 2)) hexx = hexxa + 16; + else if (!STRNCMP(pp, "-p", 2)) hextype = HEX_POSTSCRIPT; + else if (!STRNCMP(pp, "-i", 2)) hextype = HEX_CINCLUDE; + else if (!STRNCMP(pp, "-C", 2)) capitalize = 1; + else if (!STRNCMP(pp, "-d", 2)) decimal_offset = 1; + else if (!STRNCMP(pp, "-r", 2)) revert++; + else if (!STRNCMP(pp, "-E", 2)) ebcdic++; + else if (!STRNCMP(pp, "-v", 2)) + { + fprintf(stderr, "%s%s\n", version, osver); + exit(0); + } + else if (!STRNCMP(pp, "-c", 2)) + { + if (pp[2] && !STRNCMP("apitalize", pp + 2, 9)) + capitalize = 1; + else if (pp[2] && STRNCMP("ols", pp + 2, 3)) + { + colsgiven = 1; + cols = (int)strtol(pp + 2, NULL, 0); + } + else + { + if (!argv[2]) + exit_with_usage(); + colsgiven = 1; + cols = (int)strtol(argv[2], NULL, 0); + argv++; + argc--; + } + } + else if (!STRNCMP(pp, "-g", 2)) + { + if (pp[2] && STRNCMP("roup", pp + 2, 4)) + octspergrp = (int)strtol(pp + 2, NULL, 0); + else + { + if (!argv[2]) + exit_with_usage(); + octspergrp = (int)strtol(argv[2], NULL, 0); + argv++; + argc--; + } + } + else if (!STRNCMP(pp, "-o", 2)) + { + int reloffset = 0; + int negoffset = 0; + if (pp[2] && STRNCMP("ffset", pp + 2, 5)) + displayoff = strtoul(pp + 2, NULL, 0); + else + { + if (!argv[2]) + exit_with_usage(); + + if (argv[2][0] == '+') + reloffset++; + if (argv[2][reloffset] == '-') + negoffset++; + + if (negoffset) + displayoff = ULONG_MAX - strtoul(argv[2] + reloffset+negoffset, NULL, 0) + 1; + else + displayoff = strtoul(argv[2] + reloffset+negoffset, NULL, 0); + + argv++; + argc--; + } + } + else if (!STRNCMP(pp, "-s", 2)) + { + relseek = 0; + negseek = 0; + if (pp[2] && STRNCMP("kip", pp+2, 3) && STRNCMP("eek", pp+2, 3)) + { +#ifdef TRY_SEEK + if (pp[2] == '+') + relseek++; + if (pp[2+relseek] == '-') + negseek++; +#endif + seekoff = strtol(pp + 2+relseek+negseek, (char **)NULL, 0); + } + else + { + if (!argv[2]) + exit_with_usage(); +#ifdef TRY_SEEK + if (argv[2][0] == '+') + relseek++; + if (argv[2][relseek] == '-') + negseek++; +#endif + seekoff = strtol(argv[2] + relseek+negseek, (char **)NULL, 0); + argv++; + argc--; + } + } + else if (!STRNCMP(pp, "-l", 2)) + { + if (pp[2] && STRNCMP("en", pp + 2, 2)) + length = strtol(pp + 2, (char **)NULL, 0); + else + { + if (!argv[2]) + exit_with_usage(); + length = strtol(argv[2], (char **)NULL, 0); + argv++; + argc--; + } + } + else if (!STRNCMP(pp, "-n", 2)) + { + if (pp[2] && STRNCMP("ame", pp + 2, 3)) + varname = pp + 2; + else + { + if (!argv[2]) + exit_with_usage(); + varname = argv[2]; + argv++; + argc--; + } + } + else if (!strcmp(pp, "--")) /* end of options */ + { + argv++; + argc--; + break; + } + else if (pp[0] == '-' && pp[1]) /* unknown option */ + exit_with_usage(); + else + break; /* not an option */ + + argv++; /* advance to next argument */ + argc--; + } + + if (!colsgiven || (!cols && hextype != HEX_POSTSCRIPT)) + switch (hextype) + { + case HEX_POSTSCRIPT: cols = 30; break; + case HEX_CINCLUDE: cols = 12; break; + case HEX_BITS: cols = 6; break; + case HEX_NORMAL: + case HEX_LITTLEENDIAN: + default: cols = 16; break; + } + + if (octspergrp < 0) + switch (hextype) + { + case HEX_BITS: octspergrp = 1; break; + case HEX_NORMAL: octspergrp = 2; break; + case HEX_LITTLEENDIAN: octspergrp = 4; break; + case HEX_POSTSCRIPT: + case HEX_CINCLUDE: + default: octspergrp = 0; break; + } + + if ((hextype == HEX_POSTSCRIPT && cols < 0) || + (hextype != HEX_POSTSCRIPT && cols < 1) || + ((hextype == HEX_NORMAL || hextype == HEX_BITS || hextype == HEX_LITTLEENDIAN) + && (cols > COLS))) + { + fprintf(stderr, "%s: invalid number of columns (max. %d).\n", pname, COLS); + exit(1); + } + + if (octspergrp < 1 || octspergrp > cols) + octspergrp = cols; + else if (hextype == HEX_LITTLEENDIAN && (octspergrp & (octspergrp-1))) + error_exit(1, "number of octets per group must be a power of 2 with -e."); + + if (argc > 3) + exit_with_usage(); + + if (argc == 1 || (argv[1][0] == '-' && !argv[1][1])) + BIN_ASSIGN(fp = stdin, !revert); + else + { + if ((fp = fopen(argv[1], BIN_READ(!revert))) == NULL) + { + fprintf(stderr,"%s: ", pname); + perror(argv[1]); + return 2; + } + } + + if (argc < 3 || (argv[2][0] == '-' && !argv[2][1])) + BIN_ASSIGN(fpo = stdout, revert); + else + { + int fd; + int mode = revert ? O_WRONLY : (O_TRUNC|O_WRONLY); + + if (((fd = OPEN(argv[2], mode | BIN_CREAT(revert), 0666)) < 0) || + (fpo = fdopen(fd, BIN_WRITE(revert))) == NULL) + { + fprintf(stderr, "%s: ", pname); + perror(argv[2]); + return 3; + } + rewind(fpo); + } + + if (revert) + { + if (hextype && (hextype != HEX_POSTSCRIPT)) + error_exit(-1, "Sorry, cannot revert this type of hexdump"); + return huntype(fp, fpo, cols, hextype, + negseek ? -seekoff : seekoff); + } + + if (seekoff || negseek || !relseek) + { +#ifdef TRY_SEEK + if (relseek) + e = fseek(fp, negseek ? -seekoff : seekoff, SEEK_CUR); + else + e = fseek(fp, negseek ? -seekoff : seekoff, + negseek ? SEEK_END : SEEK_SET); + if (e < 0 && negseek) + error_exit(4, "Sorry, cannot seek."); + if (e >= 0) + seekoff = ftell(fp); + else +#endif + { + long s = seekoff; + + while (s--) + if (getc_or_die(fp) == EOF) + { + error_exit(4, "Sorry, cannot seek."); + } + } + } + + if (hextype == HEX_CINCLUDE) + { + /* A user-set variable name overrides fp == stdin */ + if (varname == NULL && fp != stdin) + varname = argv[1]; + + if (varname != NULL) + { + FPRINTF_OR_DIE((fpo, "unsigned char %s", isdigit((int)varname[0]) ? "__" : "")); + for (e = 0; (c = varname[e]) != 0; e++) + putc_or_die(isalnum(c) ? CONDITIONAL_CAPITALIZE(c) : '_', fpo); + fputs_or_die("[] = {\n", fpo); + } + + p = 0; + while ((length < 0 || p < length) && (c = getc_or_die(fp)) != EOF) + { + FPRINTF_OR_DIE((fpo, (hexx == hexxa) ? "%s0x%02x" : "%s0X%02X", + (p % cols) ? ", " : (!p ? " " : ",\n "), c)); + p++; + } + + if (p) + fputs_or_die("\n", fpo); + + if (varname != NULL) + { + fputs_or_die("};\n", fpo); + FPRINTF_OR_DIE((fpo, "unsigned int %s", isdigit((int)varname[0]) ? "__" : "")); + for (e = 0; (c = varname[e]) != 0; e++) + putc_or_die(isalnum(c) ? CONDITIONAL_CAPITALIZE(c) : '_', fpo); + FPRINTF_OR_DIE((fpo, "_%s = %d;\n", capitalize ? "LEN" : "len", p)); + } + + fclose_or_die(fp, fpo); + return 0; + } + + if (hextype == HEX_POSTSCRIPT) + { + p = cols; + while ((length < 0 || n < length) && (e = getc_or_die(fp)) != EOF) + { + putc_or_die(hexx[(e >> 4) & 0xf], fpo); + putc_or_die(hexx[e & 0xf], fpo); + n++; + if (cols > 0 && !--p) + { + putc_or_die('\n', fpo); + p = cols; + } + } + if (cols == 0 || p < cols) + putc_or_die('\n', fpo); + fclose_or_die(fp, fpo); + return 0; + } + + /* hextype: HEX_NORMAL or HEX_BITS or HEX_LITTLEENDIAN */ + + if (hextype != HEX_BITS) + grplen = octspergrp + octspergrp + 1; /* chars per octet group */ + else /* hextype == HEX_BITS */ + grplen = 8 * octspergrp + 1; + + while ((length < 0 || n < length) && (e = getc_or_die(fp)) != EOF) + { + int x; + + if (p == 0) + { + addrlen = sprintf(l, decimal_offset ? "%08ld:" : "%08lx:", + ((unsigned long)(n + seekoff + displayoff))); + for (c = addrlen; c < LLEN; l[c++] = ' ') + ; + } + x = hextype == HEX_LITTLEENDIAN ? p ^ (octspergrp-1) : p; + c = addrlen + 1 + (grplen * x) / octspergrp; + if (hextype == HEX_NORMAL || hextype == HEX_LITTLEENDIAN) + { + l[c] = hexx[(e >> 4) & 0xf]; + l[++c] = hexx[e & 0xf]; + } + else /* hextype == HEX_BITS */ + { + int i; + for (i = 7; i >= 0; i--) + l[c++] = (e & (1 << i)) ? '1' : '0'; + } + if (e) + nonzero++; + if (ebcdic) + e = (e < 64) ? '.' : etoa64[e-64]; + /* When changing this update definition of LLEN above. */ + if (hextype == HEX_LITTLEENDIAN) + /* last group will be fully used, round up */ + c = grplen * ((cols + octspergrp - 1) / octspergrp); + else + c = (grplen * cols - 1) / octspergrp; + c += addrlen + 3 + p; + l[c++] = +#ifdef __MVS__ + (e >= 64) +#else + (e > 31 && e < 127) +#endif + ? e : '.'; + n++; + if (++p == cols) + { + l[c] = '\n'; + l[++c] = '\0'; + xxdline(fpo, l, autoskip ? nonzero : 1); + nonzero = 0; + p = 0; + } + } + if (p) + { + l[c] = '\n'; + l[++c] = '\0'; + xxdline(fpo, l, 1); + } + else if (autoskip) + xxdline(fpo, l, -1); /* last chance to flush out suppressed lines */ + + fclose_or_die(fp, fpo); + return 0; +} + +/* vi:set ts=8 sw=4 sts=2 cino+={2 cino+=n-2 : */ diff --git a/src/univalue/CMakeLists.txt b/src/univalue/CMakeLists.txt new file mode 100644 index 0000000000..c16a48a7ea --- /dev/null +++ b/src/univalue/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(univalue STATIC + lib/univalue.cpp + lib/univalue_get.cpp + lib/univalue_read.cpp + lib/univalue_write.cpp +) + +target_include_directories(univalue PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/src/util.h b/src/util.h index c21fc77d29..511c828155 100644 --- a/src/util.h +++ b/src/util.h @@ -6,11 +6,13 @@ #ifndef BITCOIN_UTIL_H #define BITCOIN_UTIL_H +#include "arith_uint256.h" #include "uint256.h" #include "fwd.h" #include "hash.h" #include +#include #include #include #include @@ -160,27 +162,118 @@ inline int64_t abs64(int64_t n) return (n >= 0 ? n : -n); } -// Small class to represent fractions. We could do more sophisticated things like reduction using GCD, and overloaded -// multiplication, but we don't need it, because this is used in very limited places, and we actually in many of the -// algorithms where this needs to be used need to carefully control the order of multiplication and division using the -// numerator and denominator. +//! +//! \brief Class to represent fractions and common fraction operations with built in simplification. This supports integer operations +//! for consensus critical code where floating point would cause problems across different architectures and/or compiler +//! implementations. +//! +//! In particular this class is used for sidestake allocations, both the allocation "percentage", and the CAmount allocations +//! resulting from multiplying the allocation (fraction) times the CAmount rewards. +//! class Fraction { public: - Fraction() {} - + //! + //! \brief Trivial zero fraction constructor + //! + Fraction() + : m_numerator(0) + , m_denominator(1) + , m_simplified(true) + {} + + //! + //! \brief Copy constructor + //! + //! \param Fraction f + //! + Fraction(const Fraction& f) + : Fraction(f.GetNumerator(), f.GetDenominator()) + {} + + //! + //! \brief Constructor with simplification boolean directive + //! + //! \param Fraction f + //! \param boolean simplify + //! + Fraction(const Fraction& f, const bool& simplify) + : Fraction(f.GetNumerator(), f.GetDenominator(), simplify) + {} + + //! + //! \brief Constructor from numerator and denominator + //! + //! \param in64t_t numerator + //! \param int64_t denominator + //! Fraction(const int64_t& numerator, const int64_t& denominator) : m_numerator(numerator) , m_denominator(denominator) + , m_simplified(false) { if (m_denominator == 0) { throw std::out_of_range("denominator specified is zero"); } + + if (std::gcd(m_numerator, m_denominator) == 1 && m_denominator > 0) { + m_simplified = true; + } + } + + //! + //! \brief Constructor from numerator and denominator with simplification boolean directive + //! + //! \param int64_t numerator + //! \param int64_t denominator + //! \param boolean simplify + //! + Fraction(const int64_t& numerator, + const int64_t& denominator, + const bool& simplify) + : Fraction(numerator, denominator) + { + if (!m_simplified && simplify) { + Simplify(); + } + } + + ~Fraction() + {} + + //! + //! \brief Constructor from input int64_t integer (i.e. denominator = 1). + //! + //! \param numerator + //! + Fraction(const int64_t& numerator) + : Fraction(numerator, 1) + {} + + bool IsZero() const + { + // The denominator cannot be zero by construction rules. + return m_numerator == 0; + } + + bool IsNonZero() const + { + return !IsZero(); + } + + bool IsPositive() const + { + return (m_denominator > 0 && m_numerator > 0) || (m_denominator < 0 && m_numerator < 0); + } + + bool IsNonNegative() const + { + return IsPositive() || IsZero(); } - bool isNonZero() + bool IsNegative() const { - return m_denominator != 0 && m_numerator != 0; + return !IsNonNegative(); } constexpr int64_t GetNumerator() const @@ -193,9 +286,418 @@ class Fraction { return m_denominator; } + bool IsSimplified() const + { + return m_simplified; + } + + void Simplify() + { + // Check whether already simplified, if so, nothing to do. + if (m_simplified) { + return; + } + + // Nice that we are at C++17! :) + int64_t gcd = std::gcd(m_numerator, m_denominator); + + // If both numerator and denominator are negative, + // change the sign of gcd to flip both to positive. + if (m_numerator < 0 && m_denominator < 0) { + gcd = -gcd; + } + + m_numerator = m_numerator / gcd; + m_denominator = m_denominator / gcd; + + // Since the case where both are less than zero has already been changed to +/+, + // If we have m_denominator < 0, we must have m_numerator >= 0. So move the negative + // sign to the numerator and make the denominator positive. This simplifies the equality + // comparison. + if (m_denominator < 0) { + m_denominator = -m_denominator; + m_numerator = -m_numerator; + } + + m_simplified = true; + } + + double ToDouble() const + { + return (double) m_numerator / (double) m_denominator; + } + + Fraction operator=(const Fraction& rhs) + { + m_numerator = rhs.GetNumerator(); + m_denominator = rhs.GetDenominator(); + + return *this; + } + + std::string ToString() const + { + return strprintf("%" PRId64 "/" "%" PRId64, m_numerator, m_denominator); + } + + bool operator!() + { + return IsZero(); + } + + Fraction operator+(const Fraction& rhs) const + { + Fraction slhs(*this, true); + Fraction srhs(rhs, true); + + // If the same denominator (and remember these are already reduced to simplest form) just add the numerators and put + // over the common denominator... + if (slhs.GetDenominator() == srhs.GetDenominator()) { + return Fraction(overflow_add(slhs.GetNumerator(), srhs.GetNumerator()), slhs.GetDenominator(), true); + } + + // Now the more complex case. In general, fraction addition follows this pattern: + // + // a c a * (d/g) + c * (b/g) + // - + - , g = gcd(b, d) => --------------------- where {(b/g), (d/g)} will be elements of the counting numbers. + // b d g * (b/g) * (d/g) + // + // (b/g) and (d/g) are divisible with no remainders precisely because of the definition of gcd. + // + // We have already covered the trivial common denominator case above before bothering to compute the gcd of the + // denominator. + int64_t denom_gcd = std::gcd(slhs.GetDenominator(), srhs.GetDenominator()); + + // We have two special cases. One is where g = b (i.e. d is actually a multiple of b). In this case, + // the expression simplifies to + // + // a * (d/b) + c + // ------------- + // d + if (denom_gcd == slhs.GetDenominator()) { + return Fraction(overflow_add(overflow_mult(slhs.GetNumerator(), srhs.GetDenominator() / slhs.GetDenominator()), + srhs.GetNumerator()), + srhs.GetDenominator(), + true); + } + + // The other is where g = d (i.e. b is actually a multiple of d). In this case, + // the expression simplifies to + // + // a + c * (b/d) + // ------------- + // b + if (denom_gcd == srhs.GetDenominator()) { + return Fraction(overflow_add(overflow_mult(srhs.GetNumerator(), slhs.GetDenominator() / srhs.GetDenominator()), + slhs.GetNumerator()), + slhs.GetDenominator(), + true); + } + + // Otherwise do the full pattern of getting a common denominator (pulling out the gcd of the denominators), + // and adding, then simplify... + // + // This approach is more complex than + // + // a * d + c * b + // ------------- + // b * d + // + // but has the advantage of being more resistant to overflows, especially when the two denominators are related by a large + // gcd. In particular in Gridcoin's application with Allocations, the largest denominator of the allocations is 10000, so + // every allocation denominator in reduced form must be divisible evenly into 10000. This means the majority of fraction + // additions will be the two simpler cases above. + return Fraction(overflow_add(overflow_mult(slhs.GetNumerator(), srhs.GetDenominator() / denom_gcd), + overflow_mult(srhs.GetNumerator(), slhs.GetDenominator() / denom_gcd)), + overflow_mult(denom_gcd, overflow_mult(slhs.GetDenominator() / denom_gcd, srhs.GetDenominator() / denom_gcd)), + true); + } + + Fraction operator+(const int64_t& rhs) const + { + Fraction slhs(*this, true); + + return Fraction(overflow_add(slhs.GetNumerator(), overflow_mult(slhs.GetDenominator(), rhs)), slhs.GetDenominator(), true); + } + + Fraction operator-(const Fraction& rhs) const + { + return (*this + Fraction(-rhs.GetNumerator(), rhs.GetDenominator())); + } + + Fraction operator-(const int64_t& rhs) const + { + return (*this + -rhs); + } + + Fraction operator*(const Fraction& rhs) const + { + Fraction slhs(*this, true); + Fraction srhs(rhs, true); + + // Gcd's can be used in multiplication for better overflow resistance as well. + // + // Consider + // a c + // - * -, where a/b and c/d are already simplified (i.e. gcd(a, b) = gcd(c, d) = 1. + // b d + // + // We can have g = gcd(a, d) and h = gcd(c, b), which is with the numerators reversed, since multiplication is + // commutative. This means we have + // + // (c / h) (a / g) + // ------- * ------- . + // (b / h) (d / g) + // + // If we form Fraction(c, b, true) and Fraction(a, d, true), the simplification will determine and divide the numerator and + // denominator by h and g respectively. + // + // A specific example is instructive. + // + // 1998 1000 999 1000 1000 999 1 1 + // ---- * ---- = ---- * ---- = ---- * --- = - * - + // 2000 999 1000 999 1000 999 1 1 + // + // This is a formal form of what grade school teachers called factor cancellation. :). + + Fraction sxlhs(srhs.GetNumerator(), slhs.GetDenominator(), true); + Fraction sxrhs(slhs.GetNumerator(), srhs.GetDenominator(), true); + + return Fraction(overflow_mult(sxlhs.GetNumerator(), sxrhs.GetNumerator()), + overflow_mult(sxlhs.GetDenominator(), sxrhs.GetDenominator()), + true); + } + + Fraction operator*(const int64_t& rhs) const + { + Fraction slhs(*this, true); + + return Fraction(overflow_mult(slhs.GetNumerator(), rhs), slhs.GetDenominator(), true); + } + + Fraction operator/(const Fraction& rhs) const + { + return (*this * Fraction(rhs.GetDenominator(), rhs.GetNumerator())); + } + + Fraction operator/(const int64_t& rhs) const + { + Fraction slhs(*this, true); + + return Fraction(slhs.GetNumerator(), overflow_mult(slhs.GetDenominator(), rhs), true); + } + + Fraction operator+=(const Fraction& rhs) + { + Simplify(); + + *this = *this + rhs; + + return *this; + } + + Fraction operator+=(const int64_t& rhs) + { + Simplify(); + + *this = *this + rhs; + + return *this; + } + + Fraction operator-=(const Fraction& rhs) + { + Simplify(); + + *this = *this - rhs; + + return *this; + } + + Fraction operator-=(const int64_t& rhs) + { + Simplify(); + + *this = *this - rhs; + + return *this; + } + + Fraction operator*=(const Fraction& rhs) + { + Simplify(); + + *this = *this * rhs; + + return *this; + } + + Fraction operator*=(const int64_t& rhs) + { + Simplify(); + + *this = *this * rhs; + + return *this; + } + + Fraction operator/=(const Fraction& rhs) + { + Simplify(); + + *this = *this / rhs; + + return *this; + } + + Fraction operator/=(const int64_t& rhs) + { + Simplify(); + + *this = *this / rhs; + + return *this; + } + + bool operator==(const Fraction& rhs) const + { + Fraction slhs(*this, true); + Fraction srhs(rhs, true); + + return (slhs.GetNumerator() == srhs.GetNumerator() && slhs.GetDenominator() == slhs.GetDenominator()); + } + + bool operator!=(const Fraction& rhs) const + { + return !(*this == rhs); + } + + bool operator<=(const Fraction& rhs) const + { + return (rhs - *this).IsNonNegative(); + } + + bool operator>=(const Fraction& rhs) const + { + return (*this - rhs).IsNonNegative(); + } + + bool operator<(const Fraction& rhs) const + { + return (rhs - *this).IsPositive(); + } + + bool operator>(const Fraction& rhs) const + { + return (*this - rhs).IsPositive(); + } + + bool operator==(const int64_t& rhs) const + { + return (*this == Fraction(rhs)); + } + + bool operator!=(const int64_t& rhs) const + { + return !(*this == rhs); + } + + bool operator<=(const int64_t& rhs) const + { + return *this <= Fraction(rhs); + } + + bool operator>=(const int64_t& rhs) const + { + return *this >= Fraction(rhs); + } + + bool operator<(const int64_t& rhs) const + { + return *this < Fraction(rhs); + } + + bool operator>(const int64_t& rhs) const + { + return *this > Fraction(rhs); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_numerator); + READWRITE(m_denominator); + READWRITE(m_simplified); + } + private: - int64_t m_numerator = 0; - int64_t m_denominator = 1; + int msb(const int64_t& n) const + { + int64_t abs_n = std::abs(n); + + int index = 0; + + for (; index <= 63; ++index) { + if (abs_n >> index == 0) { + break; + } + } + + return index; + } + + int64_t overflow_mult(const int64_t& a, const int64_t& b) const + { + if (a == 0 || b == 0) { + return 0; + } + + // A 64-bit integer with the lower 32 bits filled has value 2^32 - 1. Multiplying two of these, a * b, together + // is (2^32 - 1) * (2^32 - 1) = 2^64 - 2^33 + 1 > 2^63. Log2(2^63) = msb(a) + msb(b) - 1. So a quick overflow limit... + + if (msb(a) + msb(b) > 63) { + throw std::overflow_error("fraction multiplication results in an overflow"); + } + + return a * b; + } + + int64_t overflow_add(const int64_t& a, const int64_t& b) const + { + if (a == 0) { + return b; + } + + if (b == 0) { + return a; + } + + if (a > 0 && b > 0) { + if (a <= std::numeric_limits::max() - b) { + return a + b; + } else { + throw std::overflow_error("fraction addition of a + b where a > 0 and b > 0 results in an overflow"); + } + } + + if (a < 0 && b < 0) { + // Remember b is negative here, so the difference below is GREATER than std::numeric_limits::min(). + if (a >= std::numeric_limits::min() - b) { + return a + b; + } else { + throw std::overflow_error("fraction addition of a + b where a < 0 and b < 0 results in an overflow"); + } + } + + // The only thing left is that a and b are opposite in sign, so addition cannot overflow. + return a + b; + } + + int64_t m_numerator; + int64_t m_denominator; + bool m_simplified; }; inline std::string leftTrim(std::string src, char chr) @@ -208,19 +710,6 @@ inline std::string leftTrim(std::string src, char chr) return src; } -inline int64_t GetPerformanceCounter() -{ - int64_t nCounter = 0; -#ifdef WIN32 - QueryPerformanceCounter((LARGE_INTEGER*)&nCounter); -#else - timeval t; - gettimeofday(&t, nullptr); - nCounter = (int64_t) t.tv_sec * 1000000 + t.tv_usec; -#endif - return nCounter; -} - /** Median filter over a stream of values. * Returns the median of the last N numbers */ diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 34fe5f2aba..db89881f26 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -21,6 +21,7 @@ static const std::string SAFE_CHARS[] = CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI + CHARS_ALPHA_NUM + " .-_" // SAFE_CHARS_CSV }; std::string SanitizeString(const std::string& str, int rule) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1671bd1f9a..1e8834d8e5 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -28,6 +28,7 @@ enum SafeChars SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset SAFE_CHARS_FILENAME, //!< Chars allowed in filenames SAFE_CHARS_URI, //!< Chars allowed in URIs (RFC 3986) + SAFE_CHARS_CSV //!< Chars allowed in fields stored as comma separated values }; /** diff --git a/src/util/system.cpp b/src/util/system.cpp index 8832e5e493..e31bc297b7 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. +#include "node/ui_interface.h" #include #include #include @@ -1154,6 +1155,9 @@ bool updateRwSetting(const std::string& name, const util::SettingsValue& value) settings.rw_settings[name] = value; } }); + + uiInterface.RwSettingsUpdated(); + return gArgs.WriteSettingsFile(); } @@ -1169,6 +1173,9 @@ bool updateRwSettings(const std::vector= 13) ? 4 : 0; +} + Fraction FoundationSideStakeAllocation() { // Note that the 4/5 (80%) for mainnet was approved by a validated poll, @@ -650,7 +657,7 @@ unsigned int GetMRCOutputLimit(const int& block_version, bool include_foundation // in the returned limit) AND the foundation sidestake allocation is greater than zero, then reduce the reported // output limit by 1. If the foundation sidestake allocation is zero, then there will be no foundation sidestake // output, so the output_limit should be as above. If the output limit was already zero then it remains zero. - if (!include_foundation_sidestake && FoundationSideStakeAllocation().isNonZero() && output_limit) { + if (!include_foundation_sidestake && FoundationSideStakeAllocation().IsNonZero() && output_limit) { --output_limit; } @@ -765,7 +772,8 @@ class ClaimValidator bool CheckReward(const CAmount& research_owed, CAmount& out_stake_owed, const CAmount& mrc_staker_fees_owed, const CAmount& mrc_fees, - const CAmount& mrc_rewards, const unsigned int& mrc_non_zero_outputs) const + const CAmount& mrc_rewards, const unsigned int& mrc_non_zero_outputs, + std::string& error_out) const { out_stake_owed = GRC::GetProofOfStakeReward(m_coin_age, m_block.nTime, m_pindex); @@ -773,6 +781,8 @@ class ClaimValidator // For block version 11, mrc_fees_owed and mrc_rewards are both zero, and there are no MRC outputs, so this is // the only check necessary. if (m_total_claimed > research_owed + out_stake_owed + m_fees + mrc_fees + mrc_rewards) { + error_out = "Claim too high"; + return error("%s: CheckReward FAILED: m_total_claimed of %s > %s = research_owed %s + out_stake_owed %s + m_fees %s + " "mrc_fees %s + mrc_rewards = %s", __func__, @@ -798,7 +808,7 @@ class ClaimValidator // sidestake even though there will not be a corresponding mrc rewards output. (Zero value outputs are // suppressed because that is wasteful. bool foundation_mrc_sidestake_present = (m_claim.m_mrc_tx_map.size() - && FoundationSideStakeAllocation().isNonZero()) ? true : false; + && FoundationSideStakeAllocation().IsNonZero()) ? true : false; // If there is no mrc, then this is coinstake.vout.size() - 0 - 0, which is one beyond the last coinstake // element. @@ -822,6 +832,8 @@ class ClaimValidator } if (total_owed_to_staker > research_owed + out_stake_owed + m_fees + mrc_staker_fees_owed) { + error_out = "Total owed to staker too high"; + return error("%s: FAILED: total_owed_to_staker of %s > %s = research_owed %s + out_stake_owed %s + " "mrc_fees %s + mrc_rewards = %s", __func__, @@ -834,11 +846,120 @@ class ClaimValidator ); } + // For block version 13 and higher, check to ensure that mandatory sidestakes appear as outputs with the correct + // allocations. + if (m_block.nVersion >= 13) { + // Record the base coinstake destination. + CTxDestination coinstake_destination; + ExtractDestination(m_block.vtx[1].vout[1].scriptPubKey, coinstake_destination); + + // Get mandatory sidestakes + std::vector mandatory_sidestakes + = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(GRC::SideStake::FilterFlag::MANDATORY, false); + + // This is exactly the same as the dust elimination in the SplitCoinStakeOutput function in the miner, with + // the addition that a mandatory sidestake that is degenerate, i.e. eliminated by the miner because it is + // to an address that staked the coinstake (i.e. local to the staker's wallet), in favor of simply returning + // the funds back to the staker on the coinstake return, is also removed from the vector here. + for (auto iter = mandatory_sidestakes.begin(); iter != mandatory_sidestakes.end();) { + if (iter->get()->GetAllocation() * total_owed_to_staker < CENT + || iter->get()->GetDestination() == coinstake_destination) { + iter = mandatory_sidestakes.erase(iter); + } else { + ++iter; + } + } + + unsigned int validated_mandatory_sidestakes = 0; + + // Skip the empty output at index 0, stop at the index before the start of MRC's. + for (unsigned int i = 1; i < mrc_start_index; ++i) { + CTxDestination output_destination; + + if (!ExtractDestination(coinstake.vout[i].scriptPubKey, output_destination)) { + return error("%s: FAILED: coinstake output has invalid destination."); + } + + std::vector mandatory_sidestake + = GRC::GetSideStakeRegistry().TryActive(output_destination, + GRC::SideStake::FilterFlag::MANDATORY);; + + // The output is deemed to match if the destination matches AND the computed allocation matches or exceeds + // what is required by the mandatory sidestake. Note that the test uses the GRC::Allocation class, which + // extends the Fraction class, and provides comparison operators. This is now a precise calculation as it + // is integer arithmetic. + if (!mandatory_sidestake.empty()) { + CAmount actual_output = coinstake.vout[i].nValue; + + CAmount required_output = (mandatory_sidestake[0]->GetAllocation() + * total_owed_to_staker).ToCAmount(); + + if (actual_output >= required_output) { + + ++validated_mandatory_sidestakes; + } else { + error_out = "Mandatory sidestake failed validation"; + + error("%s: vout[%u] is mandatory sidestake destination %s, but failed validation: " + "actual_output = %" PRId64 ", required_output = %" PRId64, + __func__, + i, + CBitcoinAddress(output_destination).ToString(), + actual_output, + required_output); + } + } + + // This should not happen, but include the check for thoroughness. + if (validated_mandatory_sidestakes > GetMandatorySideStakeOutputLimit(m_block.nVersion)) { + error_out = "Number of mandatory sidestakes in the coinstake exceeds the protocol limit."; + + return error("%s: FAILED: The number of mandatory sidestakes in the coinstake is %u, which is above " + "the limit of %u", + __func__, + validated_mandatory_sidestakes, + GetMandatorySideStakeOutputLimit(m_block.nVersion)); + } + } + + // See the comments in SplitCoinStakeOutput regarding dust elimination in mandatory sidestake selection. Note + // that in the miner for mandatory sidestakes, the shuffle is done AFTER the dust elimination, if the number of + // residual elements is greater than the maximum allowed number of mandatory sidestakes. This leads to the + // following check. + // + // If the residual number of mandatory sidestakes after dust elimination is GREATER than or equal + // GetMandatorySideStakeOutputLimit, then number of outputs matched to mandatory sidestakes should be equal + // to GetMandatorySideStakeOutputLimit, because the shuffle in combination with the allocation lambda operating + // on non-dust outputs will result in exactly GetMandatorySideStakeOutputLimit mandatory sidestakes, which means + // it will pass above, and also pass the check below. We do not have to worry about a cutoff above + // MaxMandatorySideStakeTotalAlloc because that is handled IN ActiveSideStakeEntries, which is used as the + // starting point in the miner (and of course here). + // + // If the residual number of mandatory sidestakes after dust elimination is less than + // GetMandatorySideStakeOutputLimit, it should be equal in number to the mandatory_sidestakes size from above + // after the elimination of outputs less than 1 CENT. + // + // The combination of these constraints means that the number of validated mandatory sidestakes MUST match + // the minimum of GetMandatorySideStakeOutputLimit and mandatory_sidestakes. + if (validated_mandatory_sidestakes < std::min(GetMandatorySideStakeOutputLimit(m_block.nVersion), + mandatory_sidestakes.size())) { + error_out = "Number of mandatory sidestakes is less than required."; + + return error("%s: FAILED: The number of validated sidestakes, %u, is less than required, %u.", + __func__, + validated_mandatory_sidestakes, + std::min(GetMandatorySideStakeOutputLimit(m_block.nVersion), + mandatory_sidestakes.size())); + } + } // v13+ + // If the foundation mrc sidestake is present, we check the foundation sidestake specifically. The MRC // outputs were already checked by CheckMRCRewards. if (foundation_mrc_sidestake_present) { // The fee amount to the foundation must be correct. if (coinstake.vout[mrc_start_index].nValue != mrc_fees - mrc_staker_fees_owed) { + error_out = "MRC Foundation sidestake amount is incorrect"; + return error("%s: FAILED: foundation output value of %s != mrc_fees %s - " "mrc_staker_fees_owed %s", __func__, @@ -853,11 +974,15 @@ class ClaimValidator // The foundation sidestake destination must be able to be extracted. if (!ExtractDestination(coinstake.vout[mrc_start_index].scriptPubKey, foundation_sidestake_destination)) { + error_out = "MRC Foundation sidestake destination is invalid"; + return error("%s: FAILED: foundation MRC sidestake destination not valid", __func__); } // The sidestake destination must match that specified by FoundationSideStakeAddress(). if (foundation_sidestake_destination != FoundationSideStakeAddress().Get()) { + error_out = "MRC Foundation sidestake destination is incorrect."; + return error("%s: FAILED: foundation MRC sidestake destination does not match protocol", __func__); } @@ -865,7 +990,7 @@ class ClaimValidator } } // v12+ - // If we get here, we are done with v11 and v12 validation so return true. + // If we get here, we are done with v11, v12, and v13 validation so return true. return true; } //v11+ @@ -893,6 +1018,7 @@ class ClaimValidator CAmount mrc_fees = 0; CAmount out_stake_owed; unsigned int mrc_non_zero_outputs = 0; + std::string error_out; // Even if the block is staked by an investor, the claim can include MRC payments to researchers... // @@ -904,7 +1030,7 @@ class ClaimValidator return false; } - if (CheckReward(0, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs)) { + if (CheckReward(0, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs, error_out)) { LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: CheckReward passed: m_total_claimed = %s, research_owed = %s, " "out_stake_owed = %s, m_fees = %s, mrc_staker_fees = %s, mrc_fees = %s, " "mrc_rewards = %s", @@ -932,12 +1058,12 @@ class ClaimValidator } return m_block.DoS(10, error( - "ConnectBlock[%s]: investor claim %s exceeds %s. Expected %s, fees %s", - __func__, - FormatMoney(m_total_claimed), - FormatMoney(out_stake_owed + m_fees), - FormatMoney(out_stake_owed), - FormatMoney(m_fees))); + "ConnectBlock[%s]: investor claim %s, expected %s, fees %: %s", + __func__, + FormatMoney(m_total_claimed), + FormatMoney(out_stake_owed), + FormatMoney(m_fees), + error_out)); } bool CheckResearcherClaim() const @@ -1092,6 +1218,7 @@ class ClaimValidator CAmount mrc_staker_fees = 0; CAmount mrc_fees = 0; unsigned int mrc_non_zero_outputs = 0; + std::string error_out; const GRC::CpidOption cpid = m_claim.m_mining_id.TryCpid(); @@ -1108,7 +1235,7 @@ class ClaimValidator } CAmount out_stake_owed; - if (CheckReward(research_owed, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs)) { + if (CheckReward(research_owed, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs, error_out)) { LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: Post CheckReward: m_total_claimed = %s, research_owed = %s, " "out_stake_owed = %s, mrc_staker_fees = %s, mrc_fees = %s, mrc_rewards = %s", __func__, @@ -1129,7 +1256,7 @@ class ClaimValidator GRC::Quorum::CurrentSuperblock()); research_owed += newbie_correction; - if (CheckReward(research_owed, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs)) { + if (CheckReward(research_owed, out_stake_owed, mrc_staker_fees, mrc_fees, mrc_rewards, mrc_non_zero_outputs, error_out)) { LogPrintf("WARNING: ConnectBlock[%s]: Added newbie_correction of %s to calculated research owed. " "Total calculated research with correction matches claim of %s in %s.", __func__, @@ -1145,7 +1272,7 @@ class ClaimValidator // by research age short 10-block-span pending accrual: if (fTestNet && m_block.nVersion <= 9 - && !CheckReward(0, out_stake_owed, 0, 0, 0, 0)) + && !CheckReward(0, out_stake_owed, 0, 0, 0, 0, error_out)) { LogPrintf( "WARNING: ConnectBlock[%s]: ignored bad testnet claim in %s", @@ -1165,18 +1292,19 @@ class ClaimValidator } return m_block.DoS(10, error( - "ConnectBlock[%s]: researcher claim %s exceeds %s for CPID %s. " - "Expected research %s, stake %s, fees %s. " - "Claimed research %s, stake %s", - __func__, - FormatMoney(m_total_claimed), - FormatMoney(research_owed + out_stake_owed + m_fees), - m_claim.m_mining_id.ToString(), - FormatMoney(research_owed), - FormatMoney(out_stake_owed), - FormatMoney(m_fees), - FormatMoney(m_claim.m_research_subsidy), - FormatMoney(m_claim.m_block_subsidy))); + "ConnectBlock[%s]: researcher claim %s compared to expected %s for CPID %s. " + "Expected research %s, stake %s, fees %s. " + "Claimed research %s, stake %s: %s", + __func__, + FormatMoney(m_total_claimed), + FormatMoney(research_owed + out_stake_owed + m_fees), + m_claim.m_mining_id.ToString(), + FormatMoney(research_owed), + FormatMoney(out_stake_owed), + FormatMoney(m_fees), + FormatMoney(m_claim.m_research_subsidy), + FormatMoney(m_claim.m_block_subsidy), + error_out)); } // Cf. CreateMRCRewards which is this method's conjugate. Note the parameters are out parameters. @@ -1424,13 +1552,11 @@ bool GridcoinConnectBlock( bool found_contract; - GRC::BeaconRegistry& beacons = GRC::GetBeaconRegistry(); - - int beacon_db_height = beacons.GetDBHeight(); + GRC::RegistryBookmarks db_heights; // Note this does NOT handle mrc's. The recording of MRC's is a block level event controlled by the claim. // See below. - GRC::ApplyContracts(block, pindex, beacon_db_height, found_contract); + GRC::ApplyContracts(block, pindex, db_heights, found_contract); if (found_contract) { pindex->MarkAsContract(); @@ -1753,7 +1879,10 @@ bool AddToBlockIndex(CBlock& block, unsigned int nFile, unsigned int nBlockPos, } bool CheckBlock(const CBlock& block, int height1, bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig, bool fLoadingIndex) + EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + AssertLockHeld(cs_main); + // Allow the genesis block to pass. if(block.hashPrevBlock.IsNull() && block.GetHash(true) == (fTestNet ? hashGenesisBlockTestNet : hashGenesisBlock)) @@ -1932,6 +2061,7 @@ bool AcceptBlock(CBlock& block, bool generated_by_me) EXCLUSIVE_LOCKS_REQUIRED(c || (IsV10Enabled(nHeight) && block.nVersion < 10) || (IsV11Enabled(nHeight) && block.nVersion < 11) || (IsV12Enabled(nHeight) && block.nVersion < 12) + || (IsV13Enabled(nHeight) && block.nVersion < 13) ) { return block.DoS(20, error("%s: reject too old nVersion = %d", __func__, block.nVersion)); } else if ((!IsProtocolV2(nHeight) && block.nVersion >= 7) @@ -1940,6 +2070,7 @@ bool AcceptBlock(CBlock& block, bool generated_by_me) EXCLUSIVE_LOCKS_REQUIRED(c || (!IsV10Enabled(nHeight) && block.nVersion >= 10) || (!IsV11Enabled(nHeight) && block.nVersion >= 11) || (!IsV12Enabled(nHeight) && block.nVersion >= 12) + || (!IsV13Enabled(nHeight) && block.nVersion >= 13) ) { return block.DoS(100, error("%s: reject too new nVersion = %d", __func__, block.nVersion)); } diff --git a/src/validation.h b/src/validation.h index 86ff2421ce..3564409a2e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -109,6 +109,7 @@ bool AcceptBlock(CBlock& block, bool generated_by_me); bool CheckBlockSignature(const CBlock& block); unsigned int GetCoinstakeOutputLimit(const int& block_version); +unsigned int GetMandatorySideStakeOutputLimit(const int& block_version); Fraction FoundationSideStakeAllocation(); CBitcoinAddress FoundationSideStakeAddress(); unsigned int GetMRCOutputLimit(const int& block_version, bool include_foundation_sidestake); diff --git a/src/wallet/diagnose.h b/src/wallet/diagnose.h index 88f134d732..606b0205ec 100644 --- a/src/wallet/diagnose.h +++ b/src/wallet/diagnose.h @@ -14,6 +14,7 @@ #include "net.h" #include "util.h" #include +#include #include #include #include @@ -147,7 +148,7 @@ class Diagnose m_hasEligibleProjects = researcher->Id().Which() == GRC::MiningId::Kind::CPID; m_hasPoolProjects = researcher->Projects().ContainsPool(); - m_researcher_mode = !(configured_for_investor_mode || (!m_hasEligibleProjects && m_hasPoolProjects)); + m_researcher_mode = !(configured_for_investor_mode || (!m_hasEligibleProjects && !m_hasPoolProjects)); } /** @@ -257,7 +258,7 @@ class CheckOutboundConnectionCount : public Diagnose "ensure your addnode entries are up-to-date. If you recently started the wallet, you may " "want to wait another few minutes for connections to build up and then test again. Please see " "https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/."); - m_results_string = _("Failed: Count =") + " %1"; + m_results_string = _("Failed: Count = %1"); m_results = Diagnose::FAIL; std::string ss = ToString(outbound_connections); @@ -271,7 +272,7 @@ class CheckOutboundConnectionCount : public Diagnose m_results = Diagnose::WARNING; } else { m_results_tip = ""; - m_results_string = _("Passed: Count =") + " %1"; + m_results_string = _("Passed: Count = %1"); std::string ss = ToString(outbound_connections); m_results_string_arg.push_back(ss); m_results = Diagnose::PASS; @@ -310,11 +311,11 @@ class CheckConnectionCount : public Diagnose "minutes for connections to build up and test again. Please see " "https://gridcoin.us/wiki/config-file.html and https://addnodes.cycy.me/."); m_results = Diagnose::WARNING; - m_results_string = _("Warning: Count =") + " %1 (Pass = 8+)"; + m_results_string = _("Warning: Count = %1 (Pass = 8+)"); m_results_string_arg.push_back(s_connections); } else if (m_connections >= 8) { m_results_tip = ""; - m_results_string = _("Passed: Count =") + " %1"; + m_results_string = _("Passed: Count = %1"); m_results_string_arg.push_back(s_connections); m_results = Diagnose::PASS; @@ -325,7 +326,7 @@ class CheckConnectionCount : public Diagnose "to build up and then test again. Please see https://gridcoin.us/wiki/config-file.html and " "https://addnodes.cycy.me/."); m_results = Diagnose::FAIL; - m_results_string = _("Warning: Count =") + " %1"; + m_results_string = _("Warning: Count = %1"); m_results_string_arg.push_back(s_connections); m_results_tip_arg.push_back(ToString(minimum_connections_to_stake)); } @@ -402,14 +403,16 @@ class CheckClientVersion : public Diagnose m_results_tip_arg.clear(); std::string client_message; + std::string change_log; + GRC::Upgrade::UpgradeType upgrade_type {GRC::Upgrade::UpgradeType::Unknown}; - if (g_UpdateChecker->CheckForLatestUpdate(client_message, false) + if (g_UpdateChecker->CheckForLatestUpdate(client_message, change_log, upgrade_type, false) && client_message.find("mandatory") != std::string::npos) { m_results_tip = _("There is a new mandatory version available and you should upgrade as soon as possible to " "ensure your wallet remains in consensus with the network."); m_results = Diagnose::FAIL; - } else if (g_UpdateChecker->CheckForLatestUpdate(client_message, false) + } else if (g_UpdateChecker->CheckForLatestUpdate(client_message, change_log, upgrade_type, false) && client_message.find("mandatory") == std::string::npos) { m_results_tip = _("There is a new leisure version available and you should upgrade as soon as practical."); m_results = Diagnose::WARNING; @@ -532,15 +535,16 @@ class VerifyCPIDHasRAC : public Diagnose */ const GRC::BeaconRegistry& beacons = GRC::GetBeaconRegistry(); - const GRC::CpidOption cpid = GRC::Researcher::Get()->Id().TryCpid(); - if (const GRC::BeaconOption beacon = beacons.Try(*cpid)) { - if (!beacon->Expired(GetAdjustedTime())) { - return true; - } - for (const auto& beacon_ptr : beacons.FindPending(*cpid)) { - if (!beacon_ptr->Expired(GetAdjustedTime())) { + if (const GRC::CpidOption cpid = GRC::Researcher::Get()->Id().TryCpid()) { + if (const GRC::BeaconOption beacon = beacons.Try(*cpid)) { + if (!beacon->Expired(GetAdjustedTime())) { return true; } + for (const auto& beacon_ptr : beacons.FindPending(*cpid)) { + if (!beacon_ptr->Expired(GetAdjustedTime())) { + return true; + } + } } } return false; @@ -670,7 +674,7 @@ class VerifyTCPPort : public Diagnose if (endpoint_iterator == boost::asio::ip::tcp::resolver::iterator()) { m_tcpSocket.close(); m_results = WARNING; - m_results_tip = _("Outbound communication to TCP port") + " %1 " + _("appears to be blocked."); + m_results_tip = _("Outbound communication to TCP port %1 appears to be blocked."); std::string ss = ToString(GetListenPort()); m_results_string_arg.push_back(ss); } else { @@ -720,7 +724,7 @@ class CheckDifficulty : public Diagnose // If g_nTimeBestReceived == 0, the wallet is still in the initial sync process. In that case use the failure // standard and just warn, with a different explanation. if (g_nTimeBestReceived == 0 && OutOfSyncByAge() && diff < fail_diff) { - m_results_string = _("Warning: 80 block difficulty is less than") + " %1."; + m_results_string = _("Warning: 80 block difficulty is less than %1."); std::string ss = ToString(fail_diff); m_results_string_arg.push_back(ss); @@ -731,8 +735,7 @@ class CheckDifficulty : public Diagnose // If the wallet has been in sync in the past in this run, then apply the normal standards, whether the wallet is // in sync or not right now. else if (g_nTimeBestReceived > 0 && diff < fail_diff) { - m_results_string = _("Failed: 80 block difficulty is less than") + " %1. " + _("This wallet is almost certainly " - "forked."); + m_results_string = _("Failed: 80 block difficulty is less than %1. This wallet is almost certainly forked."); std::string ss = ToString(fail_diff); m_results_string_arg.push_back(ss); @@ -742,8 +745,7 @@ class CheckDifficulty : public Diagnose "from genesis using the menu option. (Note this will take 2-4 hours.)"); m_results = Diagnose::FAIL; } else if (g_nTimeBestReceived > 0 && diff < warn_diff) { - m_results_string = _("Warning: 80 block difficulty is less than") + "%1. " + _("This wallet is probably " - "forked."); + m_results_string = _("Warning: 80 block difficulty is less than %1. This wallet is probably forked."); std::string ss = ToString(warn_diff); m_results_string_arg.push_back(ss); @@ -752,7 +754,7 @@ class CheckDifficulty : public Diagnose "genesis using the menu option. (Note this will take 2-4 hours.)"); m_results = Diagnose::WARNING; } else { - m_results_string = _("Passed: 80 block difficulty is") + " %1."; + m_results_string = _("Passed: 80 block difficulty is %1."); std::string ss = ToString(diff); m_results_string_arg.push_back(ss); m_results = Diagnose::PASS; @@ -827,10 +829,11 @@ class CheckETTS : public Diagnose m_results_tip = _("Your balance is low given the current network difficulty to stake in a reasonable " "period of time to retrieve your research rewards when solo crunching. You should consider " "acquiring more GRC to stake more often, or else use MRC to retrieve your rewards."); - m_results_string = _("Warning: 45 days < ETTS =") + " %1 <= 90 days"; + m_results_string = _("Warning: 45 days < ETTS = %1 <= 90 days"); + m_results_string_arg.push_back(rounded_ETTS); m_results = Diagnose::WARNING; } else { - m_results_string = _("Passed: ETTS =") + " %1 <= 45 days"; + m_results_string = _("Passed: ETTS = %1 <= 45 days"); m_results_string_arg.push_back(rounded_ETTS); m_results = Diagnose::PASS; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d5e9a01a8c..17b5b96fe4 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2186,21 +2186,29 @@ UniValue walletpassphrase(const UniValue& params, bool fHelp) // Note that the walletpassphrase is stored in params[0] which is not mlock()ed SecureString strWalletPass; strWalletPass.reserve(100); - // Get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. - strWalletPass = params[0].get_str().c_str(); + strWalletPass = std::string_view{params[0].get_str()}; - if (strWalletPass.length() > 0) - { + if (strWalletPass.length() > 0) { LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->Unlock(strWalletPass)) - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); - } - else + if (!pwalletMain->Unlock(strWalletPass)) { + // Check if the passphrase has a null character + if (strWalletPass.find('\0') == std::string::npos) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } else { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 5.4.6, " + "please try again with only the characters up to — but not including — " + "the first null character. If this is successful, please set a new " + "passphrase to avoid this issue in the future."); + } + } + } else { throw runtime_error( "walletpassphrase \n" "Stores the wallet decryption key in memory for seconds."); + } NewThread(ThreadTopUpKeyPool, nullptr); int64_t* pnSleepTime = new int64_t(nSleepTime); @@ -2229,15 +2237,13 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp) if (!pwalletMain->IsCrypted()) throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); - // Get rid of these .c_str() calls by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. SecureString strOldWalletPass; strOldWalletPass.reserve(100); - strOldWalletPass = params[0].get_str().c_str(); + strOldWalletPass = std::string_view{params[0].get_str()}; SecureString strNewWalletPass; strNewWalletPass.reserve(100); - strNewWalletPass = params[1].get_str().c_str(); + strNewWalletPass = std::string_view{params[1].get_str()}; if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) throw runtime_error( @@ -2246,14 +2252,24 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { + // Check if the old passphrase had a null character + if (strOldWalletPass.find('\0') == std::string::npos) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } else { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The old wallet passphrase entered is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the old passphrase was set with a version of this software prior to 5.4.6, " + "please try again with only the characters up to — but not including — " + "the first null character."); + } + } return NullUniValue; } /** - * Run the walled diagnose checks + * Run the wallet diagnose checks */ UniValue walletdiagnose(const UniValue& params, bool fHelp) { @@ -2373,11 +2389,9 @@ UniValue encryptwallet(const UniValue& params, bool fHelp) if (pwalletMain->IsCrypted()) throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); - // Get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make params[0] mlock()'d to begin with. SecureString strWalletPass; strWalletPass.reserve(100); - strWalletPass = params[0].get_str().c_str(); + strWalletPass = std::string_view{params[0].get_str()}; if (strWalletPass.length() < 1) throw runtime_error( diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c12db1915e..df01975f35 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -322,12 +322,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) CKeyingMaterial vMasterKey; vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); - GetStrongRandBytes(vMasterKey.data(), WALLET_CRYPTO_KEY_SIZE); + GetStrongRandBytes(vMasterKey); CMasterKey kMasterKey(nDerivationMethodIndex); kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); - GetStrongRandBytes(kMasterKey.vchSalt.data(), WALLET_CRYPTO_SALT_SIZE); + GetStrongRandBytes(kMasterKey.vchSalt); CCrypter crypter; int64_t nStartTime = GetTimeMillis(); @@ -2178,7 +2178,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, } // Insert change output at random position in the transaction: - vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); + vector::iterator position = wtxNew.vout.begin() + GetRand(wtxNew.vout.size()); wtxNew.vout.insert(position, CTxOut(nChange, scriptChange)); } else diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 83300f9641..3e35ccf163 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -99,7 +99,8 @@ class CKeyMetadata class CWalletDB : public CDB { public: - CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose) + CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool flush_on_close = true) + : CDB(strFilename, pszMode, flush_on_close) { } private: diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh index 88919940e0..12035971d0 100755 --- a/test/lint/lint-format-strings.sh +++ b/test/lint/lint-format-strings.sh @@ -35,7 +35,7 @@ if ! python3 -m doctest test/lint/lint-format-strings.py; then fi for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do IFS="," read -r FUNCTION_NAME SKIP_ARGUMENTS <<< "${S}" - for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|tinyformat|univalue|bdb53|test/fuzz/strprintf.cpp)"); do + for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|tinyformat|univalue|bdb53|test/fuzz/strprintf.cpp|test/xxd/xxd.c)"); do MATCHING_FILES+=("${MATCHING_FILE}") done if ! test/lint/lint-format-strings.py --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index efcb72f57a..2c7a56f5bc 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -57,6 +57,7 @@ EXPECTED_BOOST_INCLUDES=( boost/algorithm/string/predicate.hpp boost/algorithm/string/replace.hpp boost/algorithm/string/split.hpp + boost/array.hpp boost/asio.hpp boost/asio/ip/udp.hpp boost/asio/ip/v6_only.hpp diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index efdbcc0d46..4529488a77 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -1,3 +1,4 @@ +MSDOS hights mor mut @@ -26,3 +27,4 @@ smoe sur clen siz +anull